Словарь синтаксического анализа в python

#python #xml #python-3.x

#python #xml #python-3.x

Вопрос:

У меня есть этот XML, который при синтаксическом анализе выдает словарь с отношением родитель-потомок

  import xml.etree.ElementTree as ET

def remove_value(listOfDicts, key):
    for subVal in listOfDicts:
        if key in subVal:
            del subVal[key]

def get_children(parent):
    for child in parent:
        if "ACTION" in child.tag:
            continue
        if 'BOOLOP' in child.attrib:
            yield child.attrib['BOOLOP']
        else:
            yield child.attrib


def get_parent_children_mapping(tree):
    return { parent: list(get_children(parent)) for parent in tree.iter() if "ACTION" not in parent.tag }

s = """
  <RULE COMMENT="" DEFAULTPRIORITY="50" DESCRIPTION="DefaultShipping" DISABLEFLG="N" DefinedOutsideTree="true" FIRINGPHASE="" NAME="DefaultShipping" PATH="PB.SMB.DefaultShipping" RULECLASSIFICATION="PICK" TRIGGER="1">
            <BOOLOP BOOLOP="and" SEQ="0" TYPE="0">
                <BOOLOP BOOLOP="or" SEQ="1" TYPE="0">
                    <FRAGMENT FUNC1="value" FUNC2="propval" NULLACTION="Fragment is false" OP="=" PROP1="DEFAULT_SKU1" PROP2="_sku" SEQ="1" TYPE="1"/>
                    <FRAGMENT FUNC1="value" FUNC2="propval" NULLACTION="Fragment is false" OP="=" PROP1="DEFAULT_SKU2" PROP2="_sku" SEQ="2" TYPE="1"/>
                    <FRAGMENT FUNC1="value" FUNC2="propval" NULLACTION="Fragment is false" OP="=" PROP1="DEFAULT_SKU3" PROP2="_sku" SEQ="3" TYPE="1"/>
                </BOOLOP>
                <FRAGMENT FUNC1="propval" FUNC2="literal" NULLACTION="Fragment is false" OP="=" PROP1="_amEntitled" PROP2="1" SEQ="2" TYPE="1"/>
                <BOOLOP BOOLOP="and" SEQ="3" TYPE="0">
                    <FRAGMENT FUNC1="value" FUNC2="literal" NULLACTION="Fragment is true" OP="!=" PROP1="IsShipingSelected" PROP2="yes" SEQ="1" TYPE="1"/>
                    <FRAGMENT FUNC1="value" FUNC2="literal" NULLACTION="Fragment is true" OP="!=" PROP1="IsShipingSelected" PROP2="no" SEQ="2" TYPE="1"/>
                </BOOLOP>
            </BOOLOP>
            </RULE>
"""
entries = ['NULLACTION','SEQ','TYPE']
tree = ET.fromstring(s)
for parent, children in get_parent_children_mapping(tree).items():
    if children:
        for vals in entries:
            remove_value(children, vals)
        if 'NAME' in parent.attrib:
            print("{0} : {1}".format(parent.attrib['NAME'], children))


        elif 'BOOLOP' in parent.attrib:
            print("{0} : {1}".format(parent.attrib['BOOLOP'], children))

        else:
            print("{0} : {1}".format(parent.tag, children))
  

Программа выводит вывод, подобный этому, после перебора элементов:

ВЫВОД:

 DefaultShipping : ['and']
    and : ['or', {'FUNC1': 'propval', 'FUNC2': 'literal', 'OP': '=', 'PROP1': '_amEntitled', 'PROP2': '1'}, 'and']
    or : [{'FUNC1': 'value', 'FUNC2': 'propval', 'OP': '=', 'PROP1': 'DEFAULT_SKU1', 'PROP2': '_sku'}, {'FUNC1': 'value', 'FUNC2': 'propval', 'OP': '=', 'PROP1': 'DEFAULT_SKU2', 'PROP2': '_sku'}, {'FUNC1': 'value', 'FUNC2': 'propval', 'OP': '=', 'PROP1': 'DEFAULT_SKU3', 'PROP2': '_sku'}]
    and : [{'FUNC1': 'value', 'FUNC2': 'literal', 'OP': '!=', 'PROP1': 'IsShipingSelected', 'PROP2': 'yes'}, {'FUNC1': 'value', 'FUNC2': 'literal', 'OP': '!=', 'PROP1': 'IsShipingSelected', 'PROP2': 'no'}]
  

Выходные данные отображают отношения родитель-потомок. Теперь я хочу дополнительно манипулировать выводом примерно так:

(FUNC1(PROP1) OP FUNC2(PROP2) and FUNC1(PROP1) OP FUNC2(PROP2)) or (FUNC1(PROP1) OP FUNC2(PROP2))

Есть ли что-то встроенное в синтаксический анализатор или мне нужно написать свой собственный синтаксический анализатор. Приветствуется любая помощь.

ПРИМЕЧАНИЕ

В моем примере вывода с помощью FUNC1, FUNC2, PROP1 и т.д. Я имею в виду их значения.

Спасибо @ Martijn Pieters за руководство по синтаксическим деревьям.

Ответ №1:

Нет, встроенного парсера нет, да он вам и не нужен. Вам нужно построить синтаксическое дерево, что не так уж и сложно:

 from dataclasses import dataclass, field, fields
from typing import List

@dataclass
class Node:
    seq: int
    type: int

    @classmethod
    def tree_from_xml(cls, node):
        name = node.tag
        for cls in cls.__subclasses__():
            if cls.__name__.upper() == name:
                return cls.node_from_xml(node)

    @classmethod
    def node_from_xml(cls, node, **kwargs):
        fieldargs = {f.name: node.attrib.get(f.name.upper()) for f in fields(cls)}
        fieldargs['seq'] = int(fieldargs['seq'])
        fieldargs['type'] = int(fieldargs['type'])
        fieldargs.update(kwargs)
        return cls(**fieldargs)

@dataclass
class BoolOp(Node):
    boolop: str
    operands: List[Node] = field(default_factory=list)

    def __str__(self):
        joined = f' {self.boolop} '.join(map(str, self.operands))
        return f'({joined})'

    @classmethod
    def node_from_xml(cls, node):
        operands = (Node.tree_from_xml(child) for child in node)
        return super().node_from_xml(node, operands=[op for op in operands if op])

@dataclass
class Fragment(Node):
    func1: str
    func2: str
    nullaction: str
    op: str
    prop1: str
    prop2: str

    def __str__(self):
        return f'{self.func1}({self.prop1}) {self.op} {self.func2}({self.prop2})'
  

затем создайте эти объекты из BOOLOP XML-узла верхнего уровня и распечатайте результат:

 rule = Node.tree_from_xml(tree.find('.//BOOLOP'))
print(rule)
  

Обратите внимание, что это rule подкласс Node , поэтому вы все еще можете напрямую проверять дерево или вызывать str() этот объект, чтобы получить преобразование строки.

ДЕМОНСТРАЦИЯ:

 >>> rule = Node.tree_from_xml(tree.find('.//BOOLOP'))
>>> print(rule)
((value(DEFAULT_SKU1) = propval(_sku) or value(DEFAULT_SKU2) = propval(_sku) or value(DEFAULT_SKU3) = propval(_sku)) and propval(_amEntitled) = literal(1) and (value(IsShipingSelected) != literal(yes) and value(IsShipingSelected) != literal(no)))
  

Комментарии:

1. Это именно то, чего я хотел. Я обязательно прочитаю больше о синтаксических деревьях. У меня только один вопрос. Это ссылка на мой полный xml . Я делал это, потому что мне нужно найти итерацию по тегу правила и получить весь набор свойств (т. Е. определение правила), а затем найти, которое запускается на основе того, какое свойство прикреплено к item.

2. Но спасибо за помощь. Я пытался добиться этого с прошлой недели. Сначала я даже пытался преобразовать xml в JSON, но это была болезненная работа. Итак, я переключился обратно на xml

3. @Duck_dragon правильно; сохраняя структуры правил в виде деревьев, вы можете добавлять функциональность к этим узлам по мере необходимости и, возможно, добавлять больше узлов для покрытия большего количества тегов XML, все зависит от ваших конечных целей. Если вам нужно сопоставить правила с определенным набором значений, то, например, имело бы смысл добавить возможность оценивать каждое правило.

4. Я получаю сообщение об ошибке, AttributeError: 'list' object has no attribute 'tag' если я пишу findall вместо find. В принципе, мне нужно получить все правила, основанные на определенных критериях. У меня есть только отредактированный код в моем вопросе

5. @Duck_dragon основная идея заключается в том, что выражения представляют собой иерархические структуры, и если вам нужно рассуждать о них, то использование древовидной структуры узлов, содержащих nodes, упрощает вашу работу, особенно когда эти узлы являются функциональными классами.