使用lxml解析奇怪的结构化XML

时间:2011-10-25 18:54:33

标签: python xml parsing lxml

我需要解析许多XML文件。我写了一些有用的代码,但很难看,我想从比我更有经验的人那里得到一些建议。

首先,我可能在错误的上下文中使用了一些术语,因为我对XML的经验有限。按元素,除非另有说明,我的意思是这样的:

 <root>
  <element>
   ...
  </element>
  <element>
   ...
  </element>
 </root>  

无论如何,每个文件都包含许多元素,并带有许多子元素(显然)。让我感到困惑的是,需要以四种不同的方式获取相关值;

1)节点文字:

<tag>value</tag>

2)属性:

<tag attribute="value"></tag>

3)标签内“隐藏”的值(本例中为“true”):

<tag><boolean.true/></tag>

4)同名的标签内的值(“tagA”),但带有不同名称的“祖父母”标签(“tag1”和“tag2”),都在同一个元素内。 “tagA”对我没用,相反我会寻找“tag1”和“tag2”。

<element>
   <tag1><tagA>value</tagA><tag1>
   <tag2><tagA>value</tagA></tag2>
</element>

目前我有一个字典,每个文件都作为一个键。值是带有“属性”,“节点文本”,“标记”和“父元素”键的字典。

示例:

{'file1.xml' : 'attributes' : {'Person': 'Id', 'Car' : 'Color'},
               'node text': ['Name', 'Address'],
}

“人”和“汽车”是标签,“Id”和“颜色”是属性名称。

这样可以轻松迭代所有元素并检查每个标记,如果字典中存在匹配项(如果dict ['file1.xml'] ['attributes']中的elem.tag),则提取值

正如我所说,代码有效,但我不喜欢我的解决方案。此外,并非所有元素都具有所有子元素(例如,Person可能没有汽车,那么该标签将完全丢失),我需要将这些值分配给“None”。现在我得到每个文件中每个元素应该存在的所有标记,将它们变成一个集合,然后检查它们与我实际从该元素中提取值的标记集之间的差异。再一次,代码非常难看。

希望这个混乱有道理。

编辑:

我使用J.F.Sebastian建议将xpath存储到字典中的每个值,字段名称为键,xpath为值。

3 个答案:

答案 0 :(得分:3)

您可以使用相对于您的元素的xpath表达式来简化您的输入代码,而不是复杂的数据结构,例如#1-4个案例:

  1. 标记/文本()
  2. 标记/ @属性
  3. 名(DTBoolean / * [1])
  4. (TAG1 | TAG2)/ * /文本()
  5. 要使用的输出数据结构取决于您希望以后如何在代码中使用它。您可以从对当前代码最方便的结构开始。当您更好地理解需求时,将其演变为更通用的解决方案。

      

    我将它输出到csv,其中每个元素都是csv文件中的一行。   ...   我使用defaultdict存储元素,然后在将它们输出到csv之前将它们存储在列表中。

    你可以使用普通的dict和csv.DictWriter(fieldnames = xpathdict.keys()):

    # for each element
    row_dict = dict.fromkeys(xpathdict.keys())
    ...
    # for each key 
    row_dict[key] = element.xpath(xpathdict[key]) or None
    ...
    dictwriter.writerow(row_dict)
    

    其中xpathdict是字段名称和相应的xpath表达式之间的映射。一般而言,除了xpath exprs之外,您还可以存储函数对象f(element) -> csv field,而不是/。

答案 1 :(得分:2)

我不认为#3是合法的XML,因为没有关联的开放标记,即使它在其他地方,也不会在该示例中正确嵌套。由于<字符,表达式将被解释为结束标记。

答案 2 :(得分:1)

我假设你想要采取这样的事情:

<root>
  <element>
    <text_attribute>Some Text</text_attribute>
    <attribute var="blah"/>
    <bool_attribute><boolean.true/></bool_attribute>
  </element>
  <element>
    <text_attribute>Some more Text</text_attribute>
    <attribute var="blah again"/>
    <bool_attribute><boolean.false/></bool_attribute>
  </element>
</root>

得到这样的东西:

[
   { "text_attribute":"Some Text", "attribute":"blah", "bool_attribute":True },
   { "text_attribute":"Some more Text", "attribute":"blah again", "bool_attribute":False }
]

要做到这一点,我会做这样的事情(未经测试):

# Helper function so we can extract a default from an xpath result if empty
def get_first(x, default_value):
  if(len(x)>0) return x[0]
  return default_value

# Parse one element
def process_element( e ):
  retval = {}
  retval['text_attribute'] = get_first(e.xpath("text_attribute/text()"), "default text")
  retval['attribute'] = get_first( e.xpath("attribute/@var"), "default attribute")
  retval['bool_attribute'] = get_first( e.xpath("bool_attribute/boolean.true"), False )
  return retval

# Parse all the elements
elements = []
elements_xml = xml.xpath('/root/element')
for e in elements_xml:
  elements.push( process_element(e) )