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.
eine erstaunliche Antwort – cerberos