2009-03-17 5 views
1

Compte tenu de ce texte:moissonneuse-batteuse regex en rubis

 
    /* F004 (0309)00 */ 
    /* field 1 */ 
    /* field 2 */ 
    /* F004 (0409)00 */ 
    /* field 1 */ 
    /* field 2 */ 

comment puis-je analyser dans ce tableau:
[
["F004"],["0309"],["/* field 1 */\n/* field 2 */"],
["F004"],["0409"],["/* field 1 */\n/* field 2 */"]
]

Je suis le code de travail pour analyser les deux premiers éléments:

form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m 
text.scan(form) 

[
["F004"],["0309"],
["F004"],["0409"]
]

Et est ici t il code où je tente d'analyser tous les trois et ne parviennent w/une erreur de regex invalide:

form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m 
form_and_fields = /#{form}(.[^#{form}]+)/m 
text.scan(form_and_fields) 


modifier: C'est ce qui a fini par travailler pour moi, grâce à la fois raiponce, & singpolyma:

form =/
    \/\*\s+(\w+)\s+\((\d+)\)\d+\s+\*\/ #formId & edDate 
    (.+?)         #fieldText 
    (?=\/\*\s+\w+\s+\(\d+\)\d+\s+\*\/|\Z) #stop at beginning of next form 
             # or the end of the string 
/mx 
text.scan(form) 

Répondre

6

Vous semblez mal comprendre comment les classes de caractères (par ex. [a-f0-9] ou [^aeiouy]). /[^abcd]/ ne pas nier le modèle abcd, il dit "correspondre à tout caractère qui n'est pas 'a' ou 'b' ou 'c' ou 'd'".

Si vous voulez faire correspondre la négation d'un motif, utilisez la construction /(?!pattern)/. C'est une correspondance de largeur nulle - ce qui signifie qu'elle ne correspond à aucun caractère, elle correspond à une position. Semblable à la façon dont /^/ et /$/ correspondent au début et à la fin d'une chaîne, ou /\b/ correspond à la limite d'un mot. Par exemple: /(?!xx)/ correspond à chaque position où le motif "xx" ne démarre pas.

En général, après avoir utilisé une négation de modèle, vous devez faire correspondre un caractère pour avancer dans la chaîne.

Donc, pour utiliser votre modèle:

form = /\/\*\s+(\w+)\s+\((\d{4})\)[0]{2}\s+\*\//m 
form_and_fields = /#{form}((?:(?!#{form}).)+)/m 
text.scan(form_and_fields) 

De l'intérieur (je vais utiliser (?#comments))

  • (?!#{form}) votre modèle original nie, il correspond à une position où votre modèle original ne peut pas démarrer.
  • (?:(?!#{form}).)+ signifie correspondre à un caractère après cela, et essayez à nouveau, autant de fois que possible, mais au moins une fois. (?:(?#whatever)) est une parenthèse non-capturant - bon pour le groupement.

En RIR, cela donne:

irb> text.scan(form_and_fields) 
=> [["F004", "0309", " \n /* field 1 */ \n /* field 2 */ \n ", nil, nil], ["F004", "0409", " \n /* field 1 */ \n /* field 2 */ \n", nil, nil]] 

Les nil s viennent des groupes de capture supplémentaires en form qui sont utilisés dans le modèle niée (?!#{form}) et donc ne pas saisir quoi que ce soit sur un match réussi.

Cela pourrait être nettoyé quelques-uns:

form_and_fields = /#{form}\s*(.+?)\s*(?:(?=#{form})|\Z)/m 
text.scan(form_and_fields) 

Maintenant, au lieu d'un négatif zéro préanalyse largeur, nous utilisons un zéro avant positive largeur (?=#{form}) pour correspondre à la position de l'occurrence suivante form. Donc, dans cette regex, nous correspondons à tout jusqu'à l'occurrence suivante de form (sans, y compris la prochaine occurrence dans notre match). Cela nous permet de découper des espaces autour des champs. Nous devons également vérifier pour le cas où nous frappons la fin de la chaîne - /\Z/, puisque cela pourrait arriver aussi.

En irb:

irb> text.scan(form_and_fields) 
=> [["F004", "0309", "/* field 1 */ \n /* field 2 */", "F004", "0409"], ["F004", "0409", "/* field 1 */ \n /* field 2 */", nil, nil]] 

Notez maintenant que les deux derniers champs sont peuplés la première fois - b/c la capture parens dans le zéro avant positive largeur trouvé quelque chose, même si elle n'a pas été marquée comme "consommé" pendant le processus - c'est pourquoi ce bit pourrait être réapparu pour la deuxième fois.

+0

bonne explication des raisons pour lesquelles son chemin échoue . Je fais cela que le fait d'être à la fois avec l'ensemble de la chose niée est excessif pour ce (et la plupart) des scénarios, cependant. – singpolyma

+0

D'accord. Mais je pensais que c'était un moment propice à l'apprentissage. – rampion

+0

Merci, vous avez fait un excellent travail en expliquant cela. J'ai besoin d'utiliser la négation parce que le texte entre les formulaires n'est pas uniforme, l'exemple était trop simplifié. –

2
a.scan(/\/\*\s+(\S+)\s+\((\d+)\)\d+\s+\*\/\s+(\/\*.+\*\/\s+\n\s+\/\*.+\*\/)/) 
=> [["F004", "0309", "/* field 1 */ \n /* field 2 */"], ["F004", "0409", "/* field 1 */ \n /* field 2 */"]] 
0

Pour ce que ça vaut, vous pourriez constater que votre code finit par être plus lisible si vous l'avez développé et utilisé plusieurs expressions rationnelles plus simples. Par exemple (non testé):

transformed_lines = [] 

    text.each_line do |line| 
    if line =~ /(\w|\d)+\s\(\d+)\)/ 
     transformed_lines << [ $1, $2, "" ] 
    else 
     transformed_lines.last.last << line.strip 
    end 
    end 

Mieux encore, envisager la création d'une classe ou struct simple pour stocker les résultats il est donc un peu plus clair ce qui se passe où:

transformed_lines << OpenStruct.new :thingy_one => $1, :thingy_two => $2, :fields => "" 
    ... 
    transformed_lines.last.fields << line.strip