Win32 / C: converte le terminazioni di linea in formato DOS / Windows

Ho la seguente funzione C in un progetto API Windows che legge un file e in base alle terminazioni di riga (UNIX, MAC, DOS) sostituisce le terminazioni di riga con le terminazioni di riga giuste per Windows ( \r\n ):

 // Standard C header needed for string functions #include  // Defines for line-ending conversion function #define LESTATUS INT #define LE_NO_CHANGES_NEEDED (0) #define LE_CHANGES_SUCCEEDED (1) #define LE_CHANGES_FAILED (-1) ///  /// If the line endings in a block of data loaded from a file contain UNIX (\n) or MAC (\r) line endings, this function replaces it with DOS (\r\n) endings. ///  /// An array of bytes of input data. /// The size, in bytes, of inData. /// An array of bytes to be populated with output data. This array must already be allocated /// The maximum number of bytes that can be stored in outData. /// A pointer to an integer that receives the number of bytes written into outData. ///  /// If no changes were necessary (the file already contains \r\n line endings), then the return value is LE_NO_CHANGES_NEEDED.
/// If changes were necessary, and it was possible to store the entire output buffer, the return value is LE_CHANGES_SUCCEEDED.
/// If changes were necessary but the output buffer was too small, the return value is LE_CHANGES_FAILED.
/// LESTATUS ConvertLineEndings(BYTE* inData, INT inLen, BYTE* outData, INT outLen, INT* bytesWritten) { char *posR = strstr(inData, "\r"); char *posN = strstr(inData, "\n"); // Case 1: the file already contains DOS/Windows line endings. // So, copy the input array into the output array as-is (if we can) // Report an error if the output array is too small to hold the input array; report success otherwise. if (posN != NULL && posR != NULL) { if (outLen >= inLen) { strcpy(outData, inData); return LE_NO_CHANGES_NEEDED; } return LE_CHANGES_FAILED; } // Case 2: the file contains UNIX line endings. else if (posN != NULL && posR == NULL) { int i = 0; int track = 0; for (i = 0; i outLen) return LE_CHANGES_FAILED; } else { outData[track] = '\r'; track++; if (track > outLen) return LE_CHANGES_FAILED; outData[track] = '\n'; track++; if (track > outLen) return LE_CHANGES_FAILED; } *bytesWritten = track; } } // Case 3: the file contains Mac-style line endings. else if (posN == NULL && posR != NULL) { int i = 0; int track = 0; for (i = 0; i outLen) return LE_CHANGES_FAILED; } else { outData[track] = '\r'; track++; if (track > outLen) return LE_CHANGES_FAILED; outData[track] = '\n'; track++; if (track > outLen) return LE_CHANGES_FAILED; } *bytesWritten = track; } } return LE_CHANGES_SUCCEEDED; }

Tuttavia, mi sembra che questa funzione sia molto lunga (quasi 70 righe) e potrebbe essere ridotta in qualche modo. Ho cercato su Google ma non ho trovato nulla di utile; c’è qualche funzione nella libreria C o nell’API di Windows che mi consentirà di eseguire una sostituzione di stringa invece di cercare manualmente la stringa byte per byte in tempo O (n)?

Ogni personaggio ha bisogno di guardare esattamente una volta, non di più e non di meno. La prima riga del tuo codice effettua già confronti ripetuti, poiché entrambe strstr chiamate strstr avviano nella stessa posizione. Potresti aver usato qualcosa del genere

 char *posR = strstr(inData, "\r"); if (posR && posR[1] == '\n') // Case 1: the file already contains DOS/Windows line endings. 

e se questo fallisce, continua da dove sei finito se hai trovato un \r o, se posR == NULL , iniziando di nuovo dall’alto. Ma poi hai fatto strstr lo strstr “guardasse” ogni personaggio fino alla fine!

Due note aggiuntive:

  1. non c’era bisogno di strstr perché stai cercando un singolo personaggio; usa strchr prossima volta;
  2. le funzioni strXXX presuppongono che il tuo input sia una stringa C formata correttamente: dovrebbe terminare con uno 0 terminante. Tuttavia, fornisci già la lunghezza in inLen , quindi non devi controllare gli zeri. Se può esserci o meno un 0 nel proprio input prima di inLen bytes, è necessario intraprendere le azioni appropriate. In base allo scopo di questa funzione, presumo che non sia necessario controllare gli zero.

La mia proposta: guarda ogni carattere dall’inizio una volta , e agisci solo quando è \r o un \n . Se il primo di questi incontri è \r e il successivo è \n , hai finito. (Ciò presuppone che le terminazioni di riga non siano “miste”.)

Se non si ritorna in questo primo ciclo, c’è qualcos’altro di \r\n , e si può continuare da quel punto in poi. Ma devi ancora agire solo su \r o \n ! Quindi propongo questo codice più breve (e un enum posto del tuo):

 enum LEStatus_e { LE_CHANGES_FAILED=-1, LE_NO_CHANGES_NEEDED, LE_CHANGES_SUCCEEDED }; enum LEStatus_e ConvertLineEndings(BYTE *inData, INT inLen, BYTE *outData, INT outLen, INT *bytesWritten) { INT sourceIndex = 0, destIndex; if (outLen < inLen) return LE_CHANGES_FAILED; /* Find first occurrence of either \r or \n This will return immediately for No Change Needed */ while (sourceIndex < inLen) { if (inData[sourceIndex] == '\r') { if (sourceIndex < inLen-1 && inData[sourceIndex+1] == '\n') { memcpy (outData, inData, inLen); *bytesWritten = inLen; return LE_NO_CHANGES_NEEDED; } break; } if (inData[sourceIndex] == '\n') break; sourceIndex++; } /* We processed this far already: */ memcpy (outData, inData, sourceIndex); if (sourceIndex == inLen) return LE_NO_CHANGES_NEEDED; destIndex = sourceIndex; while (sourceIndex < inLen) { switch (inData[sourceIndex]) { case '\n': case '\r': sourceIndex++; if (destIndex+2 >= outLen) return LE_CHANGES_FAILED; outData[destIndex++] = '\r'; outData[destIndex++] = '\n'; break; default: outData[destIndex++] = inData[sourceIndex++]; } } *bytesWritten = destIndex; return LE_CHANGES_SUCCEEDED; } 

Esistono alcuni vecchi e rari formati di “testo normale” che utilizzano altre costruzioni; dalla memoria, qualcosa come \r\n\n . Se vuoi essere in grado di disinfettare qualcosa , puoi aggiungere un salto per tutti \r s dopo un singolo \n , e lo stesso per il caso opposto. Questo pulirà anche le terminazioni di linea “miste”, dato che tratterà correttamente anche \r\n .

Ecco cosa considererei un codice un po ‘più semplice, metà delle linee. Naturalmente, come ha sottolineato Ben Voigt, non puoi battere il tempo di O (n), quindi non ho fatto alcun tentativo di farlo. Non ho usato alcuna funzione di libreria, perché sembra più semplice in questo modo, e dubito che le chiamate a funzioni extra potrebbero rendere il codice più veloce.

 enum lestatus { le_no_changes_needed = 0, le_changes_succeeded = 1, le_changes_failed = -1 }; enum lestatus ConvertLineEndings(char *indata, int inlen, char *outdata, int outlen) { int outpos = 0, inpos; enum lestatus it_changed = le_no_changes_needed; for (inpos = 0; inpos outlen) return le_changes_failed; if (indata[inpos] != '\r' && indata[inpos] != '\n') { /* it is an ordinary character, just copy it */ outdata[outpos++] = indata[inpos]; } else if (outpos + 2 > outlen) { return le_changes_failed; } else if ((indata[inpos+1] == '\r' || indata[inpos+1] == '\n') && indata[inpos] != indata[inpos+1]) { /* it is \r\n or \n\r, output it in canonical order */ outdata[outpos++] = '\r'; outdata[outpos++] = '\n'; inpos++; /* skip the second character */ } else { /* it is a mac or unix line ending, convert to dos */ outdata[outpos++] = '\r'; outdata[outpos++] = '\n'; it_changed = le_changes_succeeded; } } return it_changed; } 

Le maggiori differenze nel mio codice sono queste

  1. Ho usato l’operatore di incremento.
  2. Ho evitato le funzioni di libreria per semplicità.
  3. La mia funzione gestisce correttamente i file con finale misto (nella mia interpretazione).
  4. Preferisco i caratteri minuscoli. Questa è ovviamente una preferenza stilistica.
  5. Preferisco un enum su #defines. Anche una preferenza stilistica.