2012-04-09 1 views
0

Ich versuche, gültige Java-Anmerkungen in einem Text zu erkennen. Hier ist mein Testprogramm (ich bin derzeit alle Leerzeichen der Einfachheit halber ignorieren, werde ich diese später hinzufügen):Python Regex für Java-Annotationen

txts = ['@SomeName2',     # match 
     '@SomeName2(',     # no match 
     '@SomeName2)',     # no match 
     '@SomeName2()',     # match 
     '@SomeName2()()',    # no match 
     '@SomeName2(value)',   # no match 
     '@SomeName2(=)',    # no match 
     '@SomeName2("")',    # match 
     '@SomeName2(".")',    # no match 
     '@SomeName2(",")',    # match 
     '@SomeName2(value=)',   # no match 
     '@SomeName2(value=")',   # no match 
     '@SomeName2(=3)',    # no match 
     '@SomeName2(="")',    # no match 
     '@SomeName2(value=3)',   # match 
     '@SomeName2(value=3L)',   # match 
     '@SomeName2(value="")',   # match 
     '@SomeName2(value=true)',  # match 
     '@SomeName2(value=false)',  # match 
     '@SomeName2(value=".")',  # no match 
     '@SomeName2(value=",")',  # match 
     '@SomeName2(x="o_nbr ASC, a")', # match 

     # multiple params: 
     '@SomeName2(,value="ord_nbr ASC, name")',       # no match 
     '@SomeName2(value="ord_nbr ASC, name",)',       # no match 
     '@SomeName2(value="ord_nbr ASC, name"insertable=false)',    # no match 
     '@SomeName2(value="ord_nbr ASC, name",insertable=false)',   # match 
     '@SomeName2(value="ord_nbr ASC, name",insertable=false,length=10L)', # match 

     '@SomeName2 ("ord_nbr ASC, name", insertable = false, length = 10L)',  # match 
     ] 


#regex = '((?:@[a-z][a-z0-9_]*))(\((((?:[a-z][a-z0-9_]*))(=)(\d+l?|"(?:[a-z0-9_, ]*)"|true|false))?\))?$' 
#regex = '((?:@[a-z][a-z0-9_]*))(\((((?:[a-z][a-z0-9_]*))(=)(\d+l?|"(?:[a-z0-9_, ]*)"|true|false))?(,((?:[a-z][a-z0-9_]*))(=)(\d+l?|"(?:[a-z0-9_, ]*)"|true|false))*\))?$' 

regex = r""" 
    (?:@[a-z]\w*)        # @ + identifier (class name) 
    (
     \(          # opening parenthesis 
     (
      (?:[a-z]\w*)       # identifier (var name) 
      =          # assigment operator 
      (\d+l?|"(?:[a-z0-9_, ]*)"|true|false) # either a numeric | a quoted string containing only alphanumeric chars, _, space | true | false 
     )?          # optional assignment group 
     \)          # closing parenthesis 
    )?$           # optional parentheses group (zero or one) 
    """ 


rg = re.compile(regex, re.VERBOSE + re.IGNORECASE) 

for txt in txts: 
    m = rg.search(txt) 
    #m = rg.match(txt) 
    if m: 
     print "MATCH: ", 
     output = '' 
     for i in xrange(2): 
      output = output + '[' + str(m.group(i+1)) + ']' 
     print output 
    else: 
     print "NO MATCH: " + txt 

Also im Grunde, was ich habe scheint für Null oder Eins Parameter zu arbeiten. Jetzt versuche ich, die Syntax auf Null oder mehr Parameter zu erweitern, wie im letzten Beispiel.

ich kopiert dann die Regex Teil, der die Zuordnung und prepend es durch ein Komma für die zweite bis n-te Gruppe darstellt: aber

regex = '((?:@[a-z][a-z0-9_]*))(\((((?:[a-z][a-z0-9_]*))(=)(\d+l?|"(?:[a-z0-9_, ]*)"|true|false))?(,((?:[a-z][a-z0-9_]*))(=)(\d+l?|"(?:[a-z0-9_, ]*)"|true|false))*\))?$' 

Das kann nicht Arbeit (diese Gruppe jetzt mit * statt?) . Das Problem scheint zu sein, wie man mit dem ersten Element umgeht, denn das muss optional sein, dann werden Strings wie das erste Extension-Beispiel '@SomeName2(,value="ord_nbr ASC, name")' akzeptiert, was falsch ist. Ich habe keine Ahnung, wie man die zweite bis n-te Zuweisung nur vom Vorhandensein des ersten (optionalen) Elements abhängig macht.

Kann es getan werden? Ist es so gemacht? Wie löst man das am besten?

Dank

+0

(Regex-Teil, der die Zuordnung darstellt) + Funktioniert das oben für Sie, "+" zeigt eins oder übereinstimmt. Machen Sie die 2. bis N-ten optional und abhängig von ersten. – subiet

+0

Hinweis: Machen Sie es sich nicht zur Gewohnheit, 're.IGNORECASE' zu verwenden. Es ist langsamer (nicht so eine große Sache) und ist schrecklich, wenn es mit Unicode verwendet wird (eine große Sache.) –

+0

Versuchen Sie, eine gültige Java-Annotation oder eine eingeschränkte Teilmenge zu erkennen? Ich kann mir mehrere perfekt gültige Java-Anmerkungen vorstellen, die von Ihrer Regex nicht behandelt werden. –

Antwort

1

Wenn Sie nur versuchen, gültige Syntax zu erkennen, glaube ich, die Regex unten gibt Ihnen das Spielen Sie wollen. Aber ich bin mir nicht sicher, was du mit den Gruppen machst. Willst du jeden Parameterwert auch in seiner eigenen Gruppe? Das wird schwieriger, und ich bin mir nicht einmal sicher, ob es mit Regex überhaupt möglich ist.

regex = r'((?:@[a-z][a-z0-9_]*))(?:\((?!,)(?:(([a-z][a-z0-9_]*(=)(?:("[a-z0-9_, ]*")|(true|false)|(\d+l?))))(?!,\)),?)*\)(?!\()|$)' 

Wenn Sie die einzelnen Parameter/Werte benötigen, müssen Sie wahrscheinlich einen echten Parser dafür schreiben.

EDIT: Hier ist eine kommentierte Version. Ich habe auch viele der Capturing- und Nicht-Capturing-Gruppen entfernt, um das Verständnis zu erleichtern. Wenn Sie dies mit re.findall() verwenden wird es zwei Gruppen zurück: der Name der Funktion, und alle params in Klammern:

regex = r''' 
(@[a-z][a-z0-9_]*) # function name, captured in group 
(     # open capture group for all parameters 
\(    # opening function parenthesis 
    (?!,)   # negative lookahead for unwanted comma 
    (?:    # open non-capturing group for all params 
    [a-z][a-z0-9_]* # parameter name 
    =    # parameter assignmentoperators 
    (?:"[a-z0-9_, ]*"|true|false|(?:\d+l?)) # possible parameter values 
    (?!,\))   # negative lookahead for unwanted comma and closing parenthesis 
    ,?    # optional comma, separating params 
)*    # close param non-capturing group, make it optional 
\)     # closing function parenthesis 
(?!\(\))   # negative lookahead for empty parentheses 
|$     # OR end-of-line (in case there are no params) 
)     # close capture group for all parameters 
''' 

Ihre Kommentare über die Parameter Nach dem Lesen wird wahrscheinlich die einfachste Sache, die oben Regex zu verwenden sein Ziehen Sie alle Parameter heraus und schreiben Sie eine weitere Regex, um die Namen/Wert-Paare herauszuziehen, die Sie verwenden möchten. Dies wird jedoch auch schwierig, da in den Parameterwerten Kommas stehen. Ich werde das für den Leser als Übung lassen :)

+0

Das scheint zu funktionieren, danke. Obwohl ich Schwierigkeiten habe, es vollständig zu verstehen. Die Gruppen ... Ich habe keine Ahnung, warum ich sie tatsächlich ausgib, sie sind ein Überbleibsel des Beispiels, mit dem ich angefangen habe. Das einzige, was ich tun muss, ist herauszufinden, welche Annotationen korrekt sind und die Zuordnungen (Schlüssel/Wert-Paare) daraus zu extrahieren (instanziieren Sie die Klasse 'KeyValue (" insertable "," true ") pro Paar). Beachten Sie, dass es für den ersten arg/param möglich sein sollte, den Schlüssel überhaupt nicht zu spezifizieren: '@BlaBla (" one_two, three ", insertable = true)' sollte auch in Wirklichkeit übereinstimmen, aber ich habe versucht, meine Frage zu halten so einfach wie möglich. – Kawu

+0

Beachten Sie, dass "@ SomeName2 (Wert =" ord_nbr ASC, Name "insertable = false)" übereinstimmt, aber nicht sollte. (Vergiss das als Beispiel, sorry! Es geht im Grunde um komma-getrennte Listen wie du sie aus jeder Sprache kennst ...) -> Frage aktualisiert – Kawu

+1

're.VERBOSE', Leute! Besonders wichtig für Lehrzwecke. ;) –

1

Verwenden Sie die re.VERBOSE Flagge

Sie hier ein paar lustige Dinge getan haben. Hier ist Ihre ursprüngliche regex:

regex = '((?:@[a-z][a-z0-9_]*))(\((((?:[a-z][a-z0-9_]*))(=)(\d+l?|" 
(?:[a-z0-9_, ]*)"|true|false))?\))?$' 

Für den Anfang, verwenden Sie die re.VERBOSE Flagge, so dass Sie diese über mehrere Zeilen brechen kann. Auf diese Weise beeinflussen Leerzeichen und Kommentare im regulären Ausdruck seine Bedeutung nicht, sodass Sie dokumentieren können, was der reguläre Ausdruck versucht.

Da Sie nicht dokumentiert haben, was diese Regex versucht zu tun, kann ich es nicht weiter zerlegen. Dokumentieren Sie die Absicht jedes nicht-trivialen regulären Ausdrucks, indem Sie re.VERBOSE verwenden, indem Sie sie über mehrere Zeilen aufteilen und kommentieren.


Brechen Sie das Problem in überschaubare Teile

Ihre regex ziemlich schwer zu verstehen, weil es versucht, zu viel zu tun. Wie es aussieht, ist Ihre regex versucht, zwei Dinge zu tun:

  1. Spiel ein Symbolname der Form @SomeSymbol2, gegebenenfalls durch eine parenthesised Liste von Argumenten gefolgt, (arg1="val1",arg2="val2"...)
  2. Validate den Inhalt der parenthesised Argumentliste, so dass (arg1="val1",arg2="val2") geht aber (232,211) nicht.

Ich würde vorschlagen, das in zwei Teile zu brechen, wie unten:

import re 
import pprint 

txts = [ 
     '@SomeName2',    # match 
     '@SomeName2(',    # no match 
     '@SomeName2)',    # no match 
     '@SomeName2()',   # match 
     '@SomeName2()()',   # no match 
     '@SomeName2(value)',  # no match 
     '@SomeName2(=)',   # no match 
     '@SomeName2("")',   # no match 
     '@SomeName2(value=)',  # no match 
     '@SomeName2(value=")',  # no match 
     '@SomeName2(=3)',   # no match 
     '@SomeName2(="")',   # no match 
     '@SomeName2(value=3)',  # match 
     '@SomeName2(value=3L)', # match 
     '@SomeName2(value="")', # match 
     '@SomeName2(value=true)', # match 
     '@SomeName2(value=false)', # match 
     '@SomeName2(value=".")', # no match 
     '@SomeName2(value=",")', # match 
     '@SomeName2(value="ord_nbr ASC, name")', # match 

     # extension needed!: 
     '@SomeName2(,value="ord_nbr ASC, name")', # no match 
     '@SomeName2(value="ord_nbr ASC, name",)', # no match 
     '@SomeName2(value="ord_nbr ASC, name",insertable=false)' 
     ] # no match YET, but should 

# Regular expression to match overall @symbolname(parenthesised stuff) 
regex_1 = re.compile(r""" 
^     # Start of string 
(@[a-zA-Z]\w*)  # Matches initial token. Token name must start with a letter. 
        # Subsequent characters can be any of those matched by \w, being [a-zA-Z0-9_] 
        # Note behaviour of \w is LOCALE dependent. 
(\([^)]* \))? # Optionally, match parenthesised part containing zero or more characters 
$     # End of string 
""", re.VERBOSE) 

#Regular expression to validate contents of parentheses 
regex_2 = re.compile(r""" 
^ 
(
    ([a-zA-Z]\w*)  # argument key name (i.e. 'value' in the examples above) 
    =     # literal equals symbol 
    (     # acceptable arguments are: 
     true |   # literal "true" 
     false |   # literal "false" 
     \d+L? |   # integer (optionally followed by an 'L') 
     "[^"]*"   # string (may not contain quote marks!) 
    ) 
    \s*,?\s*   # optional comma and whitespace 
)*      # Match this entire regex zero or more times 
$ 
""", re.VERBOSE) 

for line in txts: 
    print("\n") 
    print(line) 
    m1 = regex_1.search(line)  

    if m1: 
     annotation_name, annotation_args = m1.groups() 

     print "Symbol name : ", annotation_name 
     print "Argument list : ", annotation_args 

     if annotation_args: 
      s2 = annotation_args.strip("()") 
      m2 = regex_2.search(s2) 
      if (m2): 
       pprint.pprint(m2.groups()) 
       print "MATCH" 
      else: 
       print "MATCH FAILED: regex_2 didn't match. Contents of parentheses were invalid." 
     else: 
      print "MATCH" 

    else: 
     print "MATCH FAILED: regex_1 didn't match." 

Diese fast bekommt man zu einer endgültigen Lösung. Der einzige Eckfall, den ich sehen kann, ist, dass dies (inkorrekt) mit einem nachgestellten Komma in der Argumentliste übereinstimmt. (Sie können dies überprüfen, einen einfachen String-Operation verwenden, str.endswith().)


bearbeiten Nachlese: Die Syntax für die Argumentliste ist eigentlich ziemlich nahe an einem realen Datenformat - Sie wahrscheinlich argument_list einen Feed könnte JSON oder YAML Parser und es würde Ihnen sagen, ob es gut war oder nicht. Verwenden Sie das vorhandene Rad (JSON-Parser), anstatt das Rad neu zu erfinden, wenn Sie können.

Dies würde unter anderem ermöglichen -

  • Erkennung aller Argumenttypen, die Javascript, einschließlich Gleitkommazahlen und so weiter
  • Unterstützung für entkam Anführungszeichen innerhalb von Zeichenketten unterstützt. Gerade jetzt wird der reguläre Ausdruck barf und stirbt auf "This is a quote mark: \".", weil er denkt, dass das zweite Zitat die Zeichenfolge beendet. (Das tut es nicht.)

Dies kann in Regex getan werden, aber es ist schrecklich und kompliziert.

+0

Die zulässigen Werte sind ziemlich einfach: 'true | false | | , MyEnum.WHATEVER'. Keine Notwendigkeit für mehr tatsächlich. – Kawu

+0

Ich habe 'MyEnum.WHATEVER' nicht erlaubt - aber es ist ziemlich einfach zu sehen, wo man das in' regex_2' hinzufügen könnte. (Die Freude von 'RE.VERBOSE'!) –

+0

Ja, ich weiß ... Ich habe das Enum aus Gründen der Einfachheit verlassen. – Kawu

0

Hier ist, was ich mit in der Zwischenzeit kam:

#ws = '' 
ws = '\s*' 
className = r'(?:[A-Za-z]\w*)' 
annotationName = r'@' + className 
attrName = r'(?:[A-Za-z]\w*)' 
attrValue = r'(\d+[lL]?|"(?:[A-Za-z0-9_, ]*)"|true|false)' 
firstParam = r'' + ws + '(' + attrName + ws + '=' + ws + ')?' + attrValue + ws 
nthParam = r'' + ws + attrName + ws + '=' + ws + attrValue + ws 

regex = r'' + annotationName + ws + '(\((((' + firstParam + ')?|(' + firstParam + '(,' + nthParam + ')+)))?\))?$' 

Urplötzlich dieser nicht so schrecklich sieht mehr. Was dies bedeutet ist im Grunde:

have a either zero or exactly one first assignment/s OR have a first assignment plus at least an nth assignment with leading comma 

Diese weitere berücksichtigt, dass das erste Attribut muss nicht unbedingt einen Namen (Schlüssel), z.B. @SomeName2("ord_nbr ASC, name",insertable=false,length=10L). Ich weiß nicht genau, ob das wirklich in Java der Fall ist. Zumindest die Zerlegung erlaubt mir, dies leicht zu ändern.

Ich werde Alans Antwort akzeptieren und nicht diese. Dies ist nur als Referenz (oder bis ich von den Lookarounds erfuhr). Danke an beide für die Hilfe. Leider kann nur einer das Akzeptieren bekommen. : -/

Hinweis Ich habe meine ursprüngliche Frage mit mehr Teststrings aktualisiert.