글쎄, 당신은 괜찮은 출발을 할 수있었습니다. 그러나 여기에서 파서 - 미세 조정의 세부 사항에 빠지기가 쉽습니다. 그리고 며칠 동안이 모드에있을 수 있습니다. 원래 쿼리 구문으로 시작하여 문제를 해결해 보겠습니다.
이와 같은 프로젝트를 시작할 때 구문 분석 할 구문의 BNF를 작성하십시오. - 우리는 몇 가지 가능한 모호함 사이에 약간의 문제가
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 ')')
아주 가까이 : 그것은 매우 엄격한 될 필요가 없습니다, 사실, 여기에 내가 샘플에서 볼 수있는 내용에 따라 하나의 시작이다 word
및 and_op
및 or_op
표현식이 있습니다. '및'과 '또는'는 단어의 정의와 일치하기 때문입니다. 우리는 구현 시간에 이것을 단단히하여 "암 또는 암종 또는 림프종 또는 흑색 종"이 4 개의 다른 검색 용어로 '또는'로 분리되어 있는지를 확인해야합니다. 하나의 큰 용어가 아닙니다. 파서가 할 것이다). 우리는 또한 연산자의 우선 순위를 인식하는 이점을 얻습니다. 아마도 꼭 필요한 것은 아니지만 지금 당장 보겠습니다.대한 파싱로 변환
는 간단하다 :
word = ~(and_op | or_op) + Word(alphanums + '.-/&')
사람 :
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
는 '나'와 '와'의 모호성을 해결하기 위해, 우리는 단어의 시작 부분에 부정적 예측을 넣어 결과에 어떤 구조를주고, Group
클래스로 감싼다 :
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
지금 파 와 샘플 텍스트를 노래 :
res = expr.parseString(test)
from pprint import pprint
pprint(res.asList())
을 제공합니다 파서의 결과에 매우 유사한 사실
[[[[[[['"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']]]]]]]
을. 우리는 이제이 구조체를 통해 반복하여 새 쿼리 문자열을 작성할 수 있습니다. 그러나 구문 분석 된 객체를 사용하여 이것을 수행하는 것이 더 좋으며, 구문 분석시에 클래스를 Group
대신 토큰 컨테이너로 정의한 다음 클래스에 동작을 추가하여 원하는 출력. 구별되는 개체 토큰 컨테이너는 구문 분석 된 식의 종류에 특정한 동작을 가질 수 있습니다.
기본 추상 클래스 인 ParsedObject
으로 시작하여 파싱 된 토큰을 초기화 구조로 사용합니다. 우리는 또한 우리가 원하는 출력을 생성하는 모든 파생하는 클래스에 구현하는 것이다 추상적 인 방법 queryString
, 추가 할 것입니다 :
class ParsedObject(object):
def __init__(self, tokens):
self.tokens = tokens
def queryString(self):
'''Abstract method to be overridden in subclasses'''
이제 우리는이 클래스에서 파생 할 수 있습니다 및 모든 서브 클래스는로 사용할 수 있습니다 문법을 정의 할 때 구문 분석 동작. 우리는이 작업을 수행 할 때
, 구조 추가 된 Group
들 종류의 우리의 방식으로, 그래서 우리는 그들없이 원래의 파서를 다시 정의 할 수 있습니다 :
는
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
이제 우리는 사용 search_term
의 클래스를 구현 self.tokens
는 입력 문자열에있는 구문 분석 비트에 액세스 :
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)
다음으로 우리가 and_term
및 or_term
표현을 구현하는 것입니다. 모두 출력 쿼리에서의 결과 연산자 문자열 만 다른 이항 연산자, 그래서 우리는 단지 하나 개의 클래스를 정의하고 그들의 각각의 운영자 문자열 상수 클래스를 제공하도록 할 수 있습니다 : 대한 파싱이 조금 다르다는 것을
가
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)
주 전통적인 파서의 경우 - BinaryOperation
은 "a 또는 b 또는 c"를 중첩 된 쌍 "(a 또는 b) 또는 c"가 아닌 단일 표현식과 일치시킵니다. 따라서 스테핑 슬라이스 [0::2]
을 사용하여 모든 조건에 다시 가입해야합니다.여기에서 복사/붙여 넣을 수있는 블록의 전체 파서이며, 여러분의 편의를 위해
class Expr(ParsedObject):
def queryString(self):
return '(%s)' % self.tokens[0].queryString()
expr.setParseAction(Expr)
:
마지막으로, 우리는()의 모든 exprs 포장에 의한 중첩을 반영하기 위해 구문 분석 작업을 추가
난 당신이 t의 일부를 강화해야합니다 같은데요
((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))
을 다음 인쇄
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()
그의 출력 - 그 lucene 태그 이름은 매우 모호하게 보입니다 - 나는 단지 당신의 게시 된 샘플을 따르고있었습니다. 하지만 파서를 많이 변경하지 않아도되며 첨부 된 클래스의 queryString
메서드 만 조정하면됩니다.
포스터 추가 연습 : 쿼리 언어에 NOT 부울 연산자에 대한 지원을 추가하십시오.
놀라운 대답 – cerberos