2012-04-11 15 views
4

Ich bin ratlos. Ich habe seit Tagen versucht, dies zur Arbeit zu bringen. Aber damit komme ich nicht weiter, also dachte ich mir, ich würde euch hier beraten und sehen, ob jemand mir helfen kann!pysparsing ein Abfrageformat zu einem anderen

Ich verwende Pyparssing in einem Versuch, ein Abfrageformat zu einem anderen zu analysieren. Dies ist keine einfache Transformation aber tatsächlich dauert einige Gehirn :)

Die aktuelle Abfrage ist die folgende:

("breast neoplasms"[MeSH Terms] OR breast cancer[Acknowledgments] 
OR breast cancer[Figure/Table Caption] OR breast cancer[Section Title] 
OR breast cancer[Body - All Words] OR breast cancer[Title] 
OR breast cancer[Abstract] OR breast cancer[Journal]) 
AND (prevention[Acknowledgments] OR prevention[Figure/Table Caption] 
OR prevention[Section Title] OR prevention[Body - All Words] 
OR prevention[Title] OR prevention[Abstract]) 

Und mit pyparsing ich konnte die folgende Struktur erhalten:

[[[['"', 'breast', 'neoplasms', '"'], ['MeSH', 'Terms']], 'or', 
[['breast', 'cancer'], ['Acknowledgments']], 'or', [['breast', 'cancer'], 
['Figure/Table', 'Caption']], 'or', [['breast', 'cancer'], ['Section', 
'Title']], 'or', [['breast', 'cancer'], ['Body', '-', 'All', 'Words']], 
'or', [['breast', 'cancer'], ['Title']], 'or', [['breast', 'cancer'], 
['Abstract']], 'or', [['breast', 'cancer'], ['Journal']]], 'and', 
[[['prevention'], ['Acknowledgments']], 'or', [['prevention'], 
['Figure/Table', 'Caption']], 'or', [['prevention'], ['Section', 'Title']], 
'or', [['prevention'], ['Body', '-', 'All', 'Words']], 'or', 
[['prevention'], ['Title']], 'or', [['prevention'], ['Abstract']]]] 

Aber jetzt bin ich ratlos. Ich muss die obige Ausgabe zu einer Lucene Suchabfrage formatieren. Hier ist ein kurzes Beispiel für die Transformationen erforderlich:

"breast neoplasms"[MeSH Terms] --> [['"', 'breast', 'neoplasms', '"'], 
['MeSH', 'Terms']] --> mesh terms: "breast neoplasms" 

Aber ich bin genau dort stecken. Ich muss auch die speziellen Wörter AND und OR verwenden können.

so eine abschließende Abfrage könnte: Begriffe Netz: „Brust-Neoplasmen“ und Prävention

Wer kann mir helfen und geben Sie mir einige Hinweise, wie dieses Problem zu lösen? Jede Art von Hilfe wäre willkommen.

Da ich pyparsing verwende, bin ich Python zu bount. Ich habe den folgenden Code eingefügt, damit du damit spielen kannst und nicht bei 0 anfangen musst!

Vielen Dank für die Hilfe!

def PubMedQueryParser(): 
    word = Word(alphanums +".-/&§") 
    complex_structure = Group(Literal('"') + OneOrMore(word) + Literal('"')) + Suppress('[') + Group(OneOrMore(word)) + Suppress(']') 
    medium_structure = Group(OneOrMore(word)) + Suppress('[') + Group(OneOrMore(word)) + Suppress(']') 
    easy_structure = Group(OneOrMore(word)) 
    parse_structure = complex_structure | medium_structure | easy_structure 
    operators = oneOf("and or", caseless=True) 
    expr = Forward() 
    atom = Group(parse_structure) + ZeroOrMore(operators + expr) 
    atom2 = Group(Suppress('(') + atom + Suppress(')')) + ZeroOrMore(operators + expr) | atom 
    expr << atom2 
    return expr 

Antwort

5

Nun, Sie haben einen guten Start hingelegt. Aber von hier aus ist es einfach, sich in Einzelheiten des Parser-Tweakings zu verlieren, und Sie könnten tagelang in diesem Modus sein. Lassen Sie uns Ihr Problem mit der ursprünglichen Abfragesyntax durchgehen.

Wenn Sie mit einem Projekt wie diesem beginnen, schreiben Sie eine BNF der Syntax, die Sie analysieren möchten. Es muss nicht super streng sein, in der Tat, hier ist ein Start bei einem auf dem, was ich von Ihrer Probe sehen:

word :: Word('a'-'z', 'A'-'Z', '0'-'9', '.-/&§') 
field_qualifier :: '[' word+ ']' 
search_term :: (word+ | quoted_string) field_qualifier? 
and_op :: 'and' 
or_op :: 'or' 
and_term :: or_term (and_op or_term)* 
or_term :: atom (or_op atom)* 
atom :: search_term | ('(' and_term ')') 

Der ganz in der Nähe ist - wir ein kleines Problem mit einem gewissen Zweideutigkeit haben zwischen word und die and_op und or_op Ausdrücke, da 'und' und 'oder' stimmen mit der Definition eines Wortes überein. Wir müssen dies zum Zeitpunkt der Implementierung strikter machen, um sicherzustellen, dass "Krebs oder Karzinom oder Lymphom oder Melanom" als 4 verschiedene Suchbegriffe gelesen wird, getrennt durch "oder", nicht nur ein großer Begriff (was ich denke, was Sie aktuell sind Parser würde tun). Wir haben auch den Vorteil, die Vorrangstellung von Operatoren zu erkennen - vielleicht nicht unbedingt notwendig, aber lassen Sie uns vorerst damit beginnen.

Umstellung auf pyparsing einfach genug:

LBRACK,RBRACK,LPAREN,RPAREN = map(Suppress,"[]()") 
and_op = CaselessKeyword('and') 
or_op = CaselessKeyword('or') 
word = Word(alphanums + '.-/&') 

field_qualifier = LBRACK + OneOrMore(word) + RBRACK 
search_term = ((Group(OneOrMore(word)) | quoted_string)('search_text') + 
       Optional(field_qualifier)('field')) 
expr = Forward() 
atom = search_term | (LPAREN + expr + RPAREN) 
or_term = atom + ZeroOrMore(or_op + atom) 
and_term = or_term + ZeroOrMore(and_op + or_term) 
expr << and_term 

die Mehrdeutigkeit von ‚oder‘ zu begegnen und ‚und‘ stellten wir einen negativen Look-Ahead am Anfang des Wortes:

word = ~(and_op | or_op) + Word(alphanums + '.-/&') 

zu gibt eine gewisse Struktur zu den Ergebnissen, wickelt in Group Klassen:

field_qualifier = Group(LBRACK + OneOrMore(word) + RBRACK) 
search_term = Group(Group(OneOrMore(word) | quotedString)('search_text') + 
          Optional(field_qualifier)('field')) 
expr = Forward() 
atom = search_term | (LPAREN + expr + RPAREN) 
or_term = Group(atom + ZeroOrMore(or_op + atom)) 
and_term = Group(or_term + ZeroOrMore(and_op + or_term)) 
expr << and_term 

Jetzt par singt Ihren Beispieltext mit:

res = expr.parseString(test) 
from pprint import pprint 
pprint(res.asList()) 

gibt:

[[[[[[['"breast neoplasms"'], ['MeSH', 'Terms']], 
    'or', 
    [['breast', 'cancer'], ['Acknowledgments']], 
    'or', 
    [['breast', 'cancer'], ['Figure/Table', 'Caption']], 
    'or', 
    [['breast', 'cancer'], ['Section', 'Title']], 
    'or', 
    [['breast', 'cancer'], ['Body', '-', 'All', 'Words']], 
    'or', 
    [['breast', 'cancer'], ['Title']], 
    'or', 
    [['breast', 'cancer'], ['Abstract']], 
    'or', 
    [['breast', 'cancer'], ['Journal']]]]], 
    'and', 
    [[[[['prevention'], ['Acknowledgments']], 
    'or', 
    [['prevention'], ['Figure/Table', 'Caption']], 
    'or', 
    [['prevention'], ['Section', 'Title']], 
    'or', 
    [['prevention'], ['Body', '-', 'All', 'Words']], 
    'or', 
    [['prevention'], ['Title']], 
    'or', 
    [['prevention'], ['Abstract']]]]]]] 

Eigentlich ziemlich ähnlich zu den Ergebnissen aus Ihrem Parser. Wir könnten nun durch diese Struktur rekursieren und Ihre neue Abfragezeichenfolge erstellen, aber ich bevorzuge dies mit geparsten Objekten, die zur Parserzeit erstellt werden, indem Klassen als Tokencontainer anstelle von Group definiert werden und dann Verhalten zu den Klassen hinzugefügt wird gewünschte Ausgabe. Der Unterschied besteht darin, dass unsere geparsten Objekt-Token-Container ein Verhalten aufweisen können, das für die Art des analysierten Ausdrucks spezifisch ist.

Wir beginnen mit einer abstrakten Basisklasse ParsedObject, die die analysierten Tokens als Initialisierungsstruktur verwendet. Wir werden auch eine abstrakte Methode hinzufügen, queryString, die wir in allen Berechnungs Klassen implementieren werden Ihre gewünschte Ausgabe zu erstellen:

class ParsedObject(object): 
    def __init__(self, tokens): 
     self.tokens = tokens 
    def queryString(self): 
     '''Abstract method to be overridden in subclasses''' 

Jetzt sind wir von dieser Klasse ableiten kann, und jede Unterklasse kann eingesetzt werden Pars-Aktion beim Definieren der Grammatik.

Wenn wir dies tun, Group s, die Art der für die Struktur hinzugefügt wurden uns in den Weg bekommen, so dass wir den ursprünglichen Parser ohne sie neu zu definieren:

search_term = Group(OneOrMore(word) | quotedString)('search_text') + 
        Optional(field_qualifier)('field') 
atom = search_term | (LPAREN + expr + RPAREN) 
or_term = atom + ZeroOrMore(or_op + atom) 
and_term = or_term + ZeroOrMore(and_op + or_term) 
expr << and_term 

Jetzt implementieren wir die Klasse für search_term, mit self.tokens die analysierten Bits in der Eingabezeichenfolge gefunden zuzugreifen:

class SearchTerm(ParsedObject): 
    def queryString(self): 
     text = ' '.join(self.tokens.search_text) 
     if self.tokens.field: 
      return '%s: %s' % (' '.join(f.lower() 
             for f in self.tokens.field[0]),text) 
     else: 
      return text 
search_term.setParseAction(SearchTerm) 

Als nächstes werden wir die and_term und or_term Ausdrücke implementieren. Beide sind binäre Operatoren unterscheiden sich nur in ihrer resultierenden Operator Zeichenfolge in der Ausgabe-Abfrage, so können wir nur eine Klasse definieren, und lassen Sie sie konstant für ihre jeweiligen Bediener reiht eine Klasse bieten:

class BinaryOperation(ParsedObject): 
    def queryString(self): 
     joinstr = ' %s ' % self.op 
     return joinstr.join(t.queryString() for t in self.tokens[0::2]) 
class OrOperation(BinaryOperation): 
    op = "OR" 
class AndOperation(BinaryOperation): 
    op = "AND" 
or_term.setParseAction(OrOperation) 
and_term.setParseAction(AndOperation) 

Beachten Sie, dass pyparsing ein wenig anders von traditionellen Parsern - unser BinaryOperation passt "a oder b oder c" als einen einzelnen Ausdruck, nicht als die verschachtelten Paare "(a oder b) oder c". Also müssen wir alle Begriffe mit dem Stepping Slice [0::2] wieder zusammenfügen.

Schließlich fügen wir eine Parse-Aktion jede Verschachtelung zu reflektieren, indem sie alle exprs in() Umwickeln 's:

class Expr(ParsedObject): 
    def queryString(self): 
     return '(%s)' % self.tokens[0].queryString() 
expr.setParseAction(Expr) 

Für Ihre Bequemlichkeit ist hier die gesamte Parser in einer Kopie/verpastbarem Block:

from pyparsing import * 

LBRACK,RBRACK,LPAREN,RPAREN = map(Suppress,"[]()") 
and_op = CaselessKeyword('and') 
or_op = CaselessKeyword('or') 
word = ~(and_op | or_op) + Word(alphanums + '.-/&') 
field_qualifier = Group(LBRACK + OneOrMore(word) + RBRACK) 

search_term = (Group(OneOrMore(word) | quotedString)('search_text') + 
       Optional(field_qualifier)('field')) 
expr = Forward() 
atom = search_term | (LPAREN + expr + RPAREN) 
or_term = atom + ZeroOrMore(or_op + atom) 
and_term = or_term + ZeroOrMore(and_op + or_term) 
expr << and_term 

# define classes for parsed structure 
class ParsedObject(object): 
    def __init__(self, tokens): 
     self.tokens = tokens 
    def queryString(self): 
     '''Abstract method to be overridden in subclasses''' 

class SearchTerm(ParsedObject): 
    def queryString(self): 
     text = ' '.join(self.tokens.search_text) 
     if self.tokens.field: 
      return '%s: %s' % (' '.join(f.lower() 
             for f in self.tokens.field[0]),text) 
     else: 
      return text 
search_term.setParseAction(SearchTerm) 

class BinaryOperation(ParsedObject): 
    def queryString(self): 
     joinstr = ' %s ' % self.op 
     return joinstr.join(t.queryString() 
           for t in self.tokens[0::2]) 
class OrOperation(BinaryOperation): 
    op = "OR" 
class AndOperation(BinaryOperation): 
    op = "AND" 
or_term.setParseAction(OrOperation) 
and_term.setParseAction(AndOperation) 

class Expr(ParsedObject): 
    def queryString(self): 
     return '(%s)' % self.tokens[0].queryString() 
expr.setParseAction(Expr) 


test = """("breast neoplasms"[MeSH Terms] OR breast cancer[Acknowledgments] 
OR breast cancer[Figure/Table Caption] OR breast cancer[Section Title] 
OR breast cancer[Body - All Words] OR breast cancer[Title] 
OR breast cancer[Abstract] OR breast cancer[Journal]) 
AND (prevention[Acknowledgments] OR prevention[Figure/Table Caption] 
OR prevention[Section Title] OR prevention[Body - All Words] 
OR prevention[Title] OR prevention[Abstract])""" 

res = expr.parseString(test)[0] 
print res.queryString() 

Welche der folgenden druckt:

((mesh terms: "breast neoplasms" OR acknowledgments: breast cancer OR 
    figure/table caption: breast cancer OR section title: breast cancer OR 
    body - all words: breast cancer OR title: breast cancer OR 
    abstract: breast cancer OR journal: breast cancer) AND 
(acknowledgments: prevention OR figure/table caption: prevention OR 
    section title: prevention OR body - all words: prevention OR 
    title: prevention OR abstract: prevention)) 

einige t ich vermute, Sie müssen verschärfen seine Ausgabe - diese Lucene-Tag-Namen sehen sehr vieldeutig aus - ich folgte nur Ihrer geposteten Probe. Aber Sie sollten den Parser nicht viel ändern müssen, passen Sie einfach die queryString Methoden der angefügten Klassen an.

Als eine zusätzliche Übung für das Poster: Fügen Sie Unterstützung für den Booleschen Operator NOT in Ihrer Abfragesprache hinzu.

+1

eine erstaunliche Antwort – cerberos