web-dev-qa-db-fra.com

Le format csv peut-il être défini par une expression régulière?

Un collègue et moi avons récemment discuté de la question de savoir si une expression rationnelle pure est capable d'encapsuler entièrement le format csv, de telle sorte qu'elle soit capable d'analyser tous les fichiers avec n'importe quel caractère d'échappement, de guillemet et de séparateur.

La regex n'a pas besoin d'être capable de changer ces caractères après la création, mais elle ne doit pas échouer sur aucun autre cas Edge.

J'ai soutenu que cela est impossible pour un simple tokenizer. Le seul regex qui pourrait être en mesure de le faire est un style PCRE très complexe qui va au-delà de la simple symbolisation.

Je cherche quelque chose dans le sens de:

... le format csv est une grammaire sans contexte et en tant que tel, il est impossible d'analyser avec regex seul ...

Ou ai-je tort? Est-il possible d'analyser csv avec juste une expression régulière POSIX?

Par exemple, si le caractère d'échappement et le caractère de citation sont ", alors ces deux lignes sont valides csv:

"""this is a test.""",""
"and he said,""What will be, will be."", to which I replied, ""Surely not!""","moving on to the next field here..."
20
Spencer Rathbun

Agréable en théorie, terrible en pratique

Par [~ # ~] csv [~ # ~] Je vais supposer que vous entendez la convention telle que décrite dans RFC 418 .

Bien que la correspondance des données CSV de base soit triviale:

"data", "more data"

Remarque: BTW, il est beaucoup plus efficace d'utiliser une fonction .split ('/ n'). Split ('"') pour des données très simples et bien structurées comme celle-ci. Les expressions régulières fonctionnent comme un NDFSM ( Machine à états finis non déterministe) qui gaspille beaucoup de temps à revenir en arrière une fois que vous commencez à ajouter des cas Edge comme des caractères d'échappement.

Par exemple, voici la chaîne de correspondance d'expressions régulières la plus complète que j'ai trouvée:

re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^                                   # Anchor to start of string.
\s*                                 # Allow whitespace before value.
(?:                                 # Group for value alternatives.
  '[^'\\]*(?:\\[\S\s][^'\\]*)*'     # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*"     # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*    # or Non-comma, non-quote stuff.
)                                   # End group of value alternatives.
\s*                                 # Allow whitespace after value.
(?:                                 # Zero or more additional values
  ,                                 # Values separated by a comma.
  \s*                               # Allow whitespace before value.
  (?:                               # Group for value alternatives.
    '[^'\\]*(?:\\[\S\s][^'\\]*)*'   # Either Single quoted string,
  | "[^"\\]*(?:\\[\S\s][^"\\]*)*"   # or Double quoted string,
  | [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*  # or Non-comma, non-quote stuff.
  )                                 # End group of value alternatives.
  \s*                               # Allow whitespace after value.
)*                                  # Zero or more additional values
$                                   # Anchor to end of string.
"""

Il gère raisonnablement les valeurs entre guillemets simples et doubles, mais pas les retours à la ligne des valeurs, les guillemets échappés, etc.

Source: Débordement de pile - Comment puis-je analyser une chaîne avec JavaScript

Cela devient un cauchemar une fois que les étuis Edge courants sont introduits comme ...

"such as ""escaped""","data"
"values that contain /n newline chars",""
"escaped, commas, like",",these"
"un-delimited data like", this
"","empty values"
"empty trailing values",        // <- this is completely valid
                                // <- trailing newline, may or may not be included

Le cas Edge de nouvelle ligne en tant que valeur suffit à lui seul à casser 99,9999% des analyseurs basés sur RegEx trouvés dans la nature. La seule alternative "raisonnable" consiste à utiliser la correspondance RegEx pour la tokenisation de caractère de contrôle/non-contrôle de base (c'est-à-dire terminal vs non-terminal) couplée à une machine d'état utilisée pour une analyse de niveau supérieur.

Source: Expérience autrement connue sous le nom de douleur et de souffrance étendues.

Je suis l'auteur de jquery-CSV , le seul analyseur CSV basé sur javascript, entièrement compatible RFC, dans le monde. J'ai passé des mois à résoudre ce problème, à parler avec de nombreuses personnes intelligentes et à essayer une tonne d'implémentations différentes, y compris 3 réécritures complètes du moteur de l'analyseur de base.

tl; dr - Moral de l'histoire, PCRE est nul à lui seul pour analyser tout sauf les grammaires régulières les plus simples et les plus strictes (Ie Type-III). Bien qu'il soit utile pour la tokenisation des chaînes terminales et non terminales.

20
Evan Plaice

Regex peut analyser n'importe quel langage normal et ne peut pas analyser des choses fantaisistes comme des grammaires récursives. Mais CSV semble être assez régulier, donc analysable avec une expression régulière.

Travaillons à partir de définition : sont autorisés la séquence, les alternatives de forme de choix (|) Et la répétition (étoile de Kleene, *).

  • Une valeur sans guillemets est régulière: [^,]* # Tout caractère mais virgule
  • Une valeur entre guillemets est régulière: "([^\"]|\\\\|\\")*" # séquence de tout sauf guillemet " Ou guillemet échappé \" Ou échappement échappé \\
    • Certains formulaires peuvent inclure des guillemets d'échappement avec des guillemets, ce qui ajoute une variante ("")*" À l'expression ci-dessus.
  • Une valeur autorisée est régulière: <valeur-unquoted> | <quoted-value>
  • Une seule ligne CSV est régulière: <valeur> (, <valeur> )*
  • Une séquence de lignes séparées par \n Est également évidemment régulière.

Je n'ai pas testé méticuleusement chacune de ces expressions et je n'ai jamais défini de groupes de captures. J'ai également passé sous silence certains détails techniques, comme les variantes de caractères qui peuvent être utilisées à la place de ,, ", Ou les séparateurs de lignes: ceux-ci ne cassent pas la régularité, vous obtenez simplement plusieurs langues légèrement différentes .

Si vous pouvez détecter un problème dans cette preuve, veuillez commenter! :)

Mais malgré cela, l'analyse pratique des fichiers CSV par des expressions régulières pures peut être problématique. Vous devez savoir laquelle des variantes est envoyée à l'analyseur, et il n'y a pas de norme pour cela. Vous pouvez essayer plusieurs analyseurs sur chaque ligne jusqu'à ce que l'une d'entre elles réussisse, ou diviser en quelque sorte les commentaires du formulaire. Mais cela peut nécessiter des moyens autres que les expressions régulières pour fonctionner efficacement, voire pas du tout.

20
9000

Réponse simple - probablement pas.

Le premier problème est le manque de norme. Bien que l'on puisse décrire leur csv d'une manière strictement définie, on ne peut pas s'attendre à obtenir des fichiers csv strictement définis. "Soyez conservateur dans ce que vous faites, soyez libéral dans ce que vous acceptez des autres" -Jon Postal

En supposant que l'on ait un standard standard acceptable, il y a la question des caractères d'échappement et si ceux-ci doivent être équilibrés.

Une chaîne dans de nombreux formats csv est définie comme string value 1,string value 2. Cependant, si cette chaîne contient une virgule, c'est maintenant "string, value 1",string value 2. S'il contient une citation, il devient "string, ""value 1""",string value 2.

À ce stade, je pense que c'est impossible. Le problème étant que vous devez déterminer le nombre de guillemets que vous avez lus et si une virgule se trouve à l'intérieur ou à l'extérieur du mode entre guillemets doubles de la valeur. L'équilibrage des parenthèses est un problème d'expression rationnelle impossible. Certains moteurs d'expression régulière étendus (PCRE) peuvent y faire face, mais ce n'est pas une expression régulière alors.

Vous pourriez trouver https://stackoverflow.com/questions/8629763/csv-parsing-with-a-context-free-grammar utile.


Modifié:

J'ai cherché des formats pour les caractères d'échappement et je n'en ai trouvé aucun qui nécessite un comptage arbitraire - ce n'est donc probablement pas le problème.

Cependant, il y a des problèmes de caractère d'échappement et de délimiteur d'enregistrement (pour commencer). http://www.csvreader.com/csv_format.php est une bonne lecture sur les différents formats à l'état sauvage.

  • Les règles pour la chaîne entre guillemets (s'il s'agit d'une chaîne entre guillemets simple ou d'une chaîne entre guillemets doubles) diffèrent.
    • 'This, is a value' contre "This, is a value"
  • Les règles pour les caractères d'échappement
    • "This ""is a value""" contre "This \"is a value\""
  • La gestion du délimiteur d'enregistrement intégré ({rd})
    • (brut intégré) "This {rd}is a value" vs (échappé) "This \{rd}is a value" vs (traduit) "This {0x1C}is a value"

L'essentiel ici est qu'il est possible d'avoir une chaîne qui aura toujours plusieurs interprétations valides.

La question connexe (pour les cas Edge) "est-il possible d'avoir une chaîne non valide qui est acceptée?"

Je doute fortement qu'il existe une expression régulière qui puisse correspondre à chaque CSV valide créé par une application et rejeter chaque csv qui ne peut pas être analysé.

5
user40980

Définissez d'abord la grammaire de votre CSV (les délimiteurs de champ sont-ils échappés ou encodés d'une manière ou d'une autre s'ils apparaissent dans le texte?), Puis il peut être déterminé s'il est analysable avec regex. Grammaire en premier: analyseur en second lieu: http://www.boyet.com/articles/csvparser.html Il convient de noter que cette méthode utilise un tokenizer - mais je ne peux pas constuire une expression rationnelle POSIX qui correspondre à tous les cas Edge. Si votre utilisation des formats CSV est non régulière et sans contexte ... alors votre réponse est dans votre question. Bon aperçu ici: http://nikic.github.com/2012/06/15/The-true-power-of-regular-expressions.html

2
iivel

Cette expression régulière peut symboliser le CSV normal, comme décrit dans le RFC:

/("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/

Explication:

  • ("(?:[^"]|"")*"|[^,"\n\r]*) - un champ CSV, cité ou non
    • "(?:[^"]|"")*" - un champ entre guillemets;
      • [^"]|"" - chaque caractère n'est pas " Ou " Échappé en tant que ""
    • [^,"\n\r]* - un champ sans guillemets, qui ne peut pas contenir ,"\n\r
  • (,|\r?\n|\r) - le séparateur suivant, soit , Ou une nouvelle ligne
    • \r?\n|\r - une nouvelle ligne, l'une des \r\n\n\r

Un fichier CSV entier peut être mis en correspondance et validé à l'aide de cette expression régulière à plusieurs reprises. Il est alors nécessaire de corriger les champs entre guillemets et de les diviser en lignes en fonction des séparateurs.

Voici le code d'un analyseur CSV en Javascript, basé sur l'expression rationnelle:

var csv_tokens_rx = /("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/y;
var csv_unescape_quote_rx = /""/g;
function csv_parse(s) {
    if (s && s.slice(-1) != '\n')
        s += '\n';
    var ok;
    var rows = [];
    var row = [];
    csv_tokens_rx.lastIndex = 0;
    while (true) {
        ok = csv_tokens_rx.lastIndex == s.length;
        var m = s.match(csv_tokens_rx);
        if (!m)
            break;
        var v = m[1], d = m[2];
        if (v[0] == '"') {
            v = v.slice(1, -1);
            v = v.replace(csv_unescape_quote_rx, '"');
        }
        if (d == ',' || v)
            row.Push(v);
        if (d != ',') {
            rows.Push(row)
            row = [];
        }
    }
    return ok ? rows : null;
}

Si cette réponse aide à régler votre argument, c'est à vous de décider; Je suis juste heureux d'avoir un petit analyseur CSV simple et correct.

À mon avis, un programme Lex est plus ou moins une grande expression régulière, et ceux-ci peuvent symboliser des formats beaucoup plus complexes, comme le langage de programmation C.

En référence aux définitions RFC 418 :

  1. saut de ligne (CRLF) - L'expression rationnelle est plus flexible, permettant CRLF, LF ou CR.
  2. Le dernier enregistrement du fichier peut avoir ou non un saut de ligne de fin - L'expression régulière telle qu'elle est nécessite un saut de ligne final, mais l'analyseur s'ajuste pour cela.
  3. Il peut y avoir une ligne d'en-tête facultative - Cela n'a pas d'impact sur l'analyseur.
  4. Chaque ligne doit contenir le même nombre de champs dans le fichier - non appliqué
    Les espaces sont considérés comme faisant partie d'un champ et ne doivent pas être ignorés - d'accord
    Le dernier champ de l'enregistrement ne doit pas être suivi d'une virgule - non appliqué
  5. Chaque champ peut ou non être mis entre guillemets ... - d'accord
  6. Les champs contenant des sauts de ligne (CRLF), des guillemets doubles et des virgules doivent être placés entre guillemets doubles - d'accord
  7. un guillemet double apparaissant à l'intérieur d'un champ doit être échappé en le précédant d'un autre guillemet double - ok

L'expression régulière elle-même satisfait la plupart des exigences RFC 4180. Je ne suis pas d'accord avec les autres, mais il est facile d'ajuster l'analyseur pour les implémenter.

2
Sam Watkins