4

私は途方に暮れています。私はこれを何日も機能させようとしています。しかし、私はこれでどこにも行けないので、ここであなたたちに相談して、誰かが私を助けることができるかどうか見てみようと思いました!

あるクエリ形式を別のクエリ形式に解析しようとして、pyparsing を使用しています。これは単純な変換ではありませんが、実際には頭脳が必要です :)

現在のクエリは次のとおりです。

("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])

そして、pyparsing を使用して、次の構造を取得できました。

[[[['"', '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']]]]

しかし今、私は途方に暮れています。上記の出力を lucene 検索クエリにフォーマットする必要があります。必要な変換の簡単な例を次に示します。

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

しかし、私はそこで立ち往生しています。また、AND と OR という特殊な単語を使用できるようにする必要もあります。

最後のクエリは次のようになります: メッシュ用語: 「乳房腫瘍」と予防

誰が私を助け、これを解決するためのヒントを教えてくれますか? どんな種類の助けもいただければ幸いです。

私は pyparsing を使用しているため、python に依存しています。以下のコードを貼り付けたので、0 から始める必要はありません。

助けてくれてどうもありがとう!

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
4

1 に答える 1

5

さて、あなたはまともなスタートを切りました。しかし、ここからは、パーサーの微調整の詳細に行き詰まりやすく、何日もそのモードにいる可能性があります。元のクエリ構文から始めて、問題を順を追って説明しましょう。

このようなプロジェクトを開始するときは、解析したい構文の 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 ')')

これはかなり近いです。「and」と「or」は単語の定義と一致するため、and と andwordの式の間にあいまいさが生じる可能性があるため、わずかな問題があります。実装時にこれを強化する必要があります。「癌または癌腫またはリンパ腫または黒色腫」が 1 つの大きな用語だけでなく、「または」で区切られた 4 つの異なる検索用語として読み取られるようにする必要があります (これが現在の検索用語です)。パーサーが行います)。また、演算子の優先順位を認識するという利点もあります - 厳密には必要ないかもしれませんが、今はそれで行きましょう。and_opor_op

pyparsing への変換は簡単です:

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

「or」と「and」のあいまいさに対処するために、単語の先頭に否定的な先読みを置きます。

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

結果に何らかの構造を与えるには、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']]]]]]]

実際、パーサーの結果とかなり似ています。この構造を再帰して新しいクエリ文字列を作成することもできますが、私は、クラスを s ではなくトークン コンテナーとして定義Groupし、クラスに動作を追加して、解析時に作成された解析済みオブジェクトを使用してこれを行うことを好みます。出力。違いは、解析されたオブジェクト トークン コンテナーが、解析された式の種類に固有の動作を持つことができることです。

ParsedObject解析されたトークンを初期化構造体として受け取る基本抽象クラス から始めます。queryStringまた、必要な出力を作成するためにすべての派生クラスに実装する抽象メソッド を追加します。

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

これで、このクラスから派生できるようになり、文法を定義する際に、任意のサブクラスを解析アクションとして使用できます。

これを行うとGroup、構造体のために追加された s が邪魔になるので、元のパーサーをそれらなしで再定義します。

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_termandor_term式を実装します。両方とも、出力クエリの結果の演算子文字列のみが異なる二項演算子であるため、1 つのクラスを定義するだけで、それぞれの演算子文字列にクラス定数を提供できます。

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)

pyparsing は従来のパーサーとは少し異なることに注意してください -BinaryOperationネストされたペア "(a or b) or c" としてではなく、"a or b or c" を単一の式として照合します。したがって、ステッピング スライスを使用してすべての項を再結合する必要があります[0::2]

最後に、すべての expr を () でラップすることにより、ネストを反映する parse アクションを追加します。

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

便宜上、パーサー全体を 1 つのコピー/貼り付け可能なブロックにまとめました。

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()

以下を出力します。

((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))

この出力の一部を引き締める必要があると思います-これらのluceneタグ名は非常にあいまいに見えます-私は投稿されたサンプルに従っていました. しかし、パーサーをあまり変更する必要はありませんqueryString。添付されたクラスのメソッドを調整するだけです。

ポスターへの追加の演習として、クエリ言語に NOT ブール演算子のサポートを追加します。

于 2012-04-12T04:56:08.093 に答える