Win32 / C: Conversion des fins de ligne au format DOS / Windows

J’ai la fonction C suivante dans un projet API Windows qui lit un fichier et, en fonction des fins de ligne (UNIX, MAC, DOS), elle remplace les fins de ligne par les fins de ligne correctes pour Windows ( \r\n ):

 // Standard C header needed for ssortingng 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; }

Cependant, j’ai le sentiment que cette fonction est très longue (presque 70 lignes) et pourrait être réduite d’une manière ou d’une autre. J’ai effectué une recherche sur Google mais je n’ai rien trouvé d’utile. Existe-t-il une fonction dans la bibliothèque C ou dans l’API Windows qui me permette d’effectuer un remplacement de chaîne plutôt que de rechercher manuellement la chaîne octet par octet en un temps O (n)?

Chaque personnage a besoin de regarder précisément une fois, ni plus ni moins. La toute première ligne de votre code fait déjà des comparaisons répétées, car les deux appels strstr commencent à la même position. Vous auriez pu utiliser quelque chose comme

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

et si cela échoue, continuez là où vous avez fini si vous avez trouvé un \r ou, si posR == NULL , en partant du haut. Mais ensuite, vous strstr déjà fait “regarder” chaque personnage jusqu’à la fin!

Deux notes supplémentaires:

  1. vous n’avez pas besoin de strstr car vous recherchez un seul personnage; utilisez strchr prochaine fois;
  2. les fonctions strXXX supposent toutes que votre entrée est une chaîne C correctement formée: elle doit se terminer par un 0 final. Cependant, vous fournissez déjà la longueur en inLen , vous n’avez donc pas à vérifier les zéros. S’il peut y avoir ou non un 0 dans votre entrée avant octets inLen , vous devez prendre les mesures appropriées. En fonction de l’objective de cette fonction, je suppose que vous n’avez pas du tout besoin de vérifier les zéros.

Ma proposition: regardez tous les personnages depuis le début une fois et agissez uniquement quand il s’agit d’ un \r ou d’ un \n . Si le premier que vous rencontrez est un \r et que le suivant est un \n , vous avez terminé. (Cela suppose que les fins de ligne ne sont pas “mélangées”.)

Si vous ne revenez pas dans cette première boucle, il y a autre chose que \r\n et vous pouvez continuer à partir de là. Mais vous devez toujours agir sur un \r ou un \n ! Je propose donc ce code plus court (et une enum place de vos définitions):

 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; } 

Il existe quelques anciens et rares formats de «texte brut» qui utilisent d’autres constructions; de la mémoire, quelque chose comme \r\n\n . Si vous voulez pouvoir nettoyer n’importe quoi , vous pouvez append un saut pour tous les \r après un seul \n , et le même pour le cas contraire. Ceci nettoiera également toutes les fins de ligne “mixtes”, car il traitera également correctement \r\n .

Voici ce que je considérerais comme un code un peu plus simple, moitié moins de lignes. Bien sûr, comme Ben Voigt l’a souligné, vous ne pouvez pas battre le temps de jeu (n), alors je n’ai pas essayé de le faire. Je n’ai utilisé aucune fonction de bibliothèque, car cela semble plus simple de cette façon, et je doute que des appels de fonction supplémentaires puissent accélérer le code.

 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; } 

Les plus grandes différences dans mon code sont que

  1. J’ai utilisé l’opérateur d’incrément.
  2. J’ai évité les fonctions de bibliothèque pour plus de simplicité.
  3. Ma fonction gère correctement les fichiers mixtes (dans mon interprétation).
  4. Je préfère les caractères minuscules. C’est évidemment une préférence stylistique.
  5. Je préfère un enum sur #defines. Aussi une préférence stylistique.