题
我有一个扁平结构的xml文件。我们不控制这个xml文件的格式,只需要处理它。我已经重命名了这些字段,因为它们具有高度特定的域名,并没有对问题产生任何影响。
<attribute name="Title">Book A</attribute>
<attribute name="Code">1</attribute>
<attribute name="Author">
<value>James Berry</value>
<value>John Smith</value>
</attribute>
<attribute name="Title">Book B</attribute>
<attribute name="Code">2</attribute>
<attribute name="Title">Book C</attribute>
<attribute name="Code">3</attribute>
<attribute name="Author">
<value>James Berry</value>
</attribute>
需要注意的关键事项:文件不是特别分层的。书籍由name ='Title'的属性元素的出现界定。但name ='Author'属性节点是可选的。
是否有一个简单的xpath语句可以用来查找“n”的作者?很容易识别书籍'n'的标题,但作者的价值是可选的。而且你不能只考虑下面的作者,因为在第2册的情况下,这将为作者提供第3册。
我已经写了一个状态机来解析它作为一系列元素,但我不禁想到会有一种直接获得我想要的结果的方法。
解决方案
我们想要<!>“属性<!>”; @name“作者”的元素,跟在<!>“属性<!>后面; @name'Title'的元素,其值为'Book n',没有任何其他<!> quot;属性<!> quot; @name'Title'的元素在他们之间(因为如果有,那么作者撰写了其他一些书)。
不同地说,这意味着我们想要一个 第一个 前面标题的作者(<!>“属于<!>”;)的作者是我们正在寻找的那个:
//attribute[@name='Author']
[preceding-sibling::attribute[@name='Title'][1][contains(.,'Book N')]]
N = C = <!> gt;找到<attribute name="Author"><value>James Berry</value></attribute>
N = B = <!> gt;找不到任何东西
使用XSLT 2.0中提供的键和/或分组功能可以使这更容易(如果文件很大,也会更快)。
(SO代码解析器似乎认为'//'代表'评论',但在XPath中它不是!!!叹息。)
其他提示
好吧,我使用 Elementtree 从上面的XML中提取数据。 我已将此XML保存在名为foo.xml
的文件中from xml.etree.ElementTree import fromstring
def extract_data():
"""Returns list of dict of book and
its authors."""
f = open('foo.xml', 'r+')
xml = f.read()
elem = fromstring(xml)
attribute_list = elem.findall('attribute')
dic = {}
lst = []
for attribute in attribute_list:
if attribute.attrib['name'] == 'Title':
key = attribute.text
if attribute.attrib['name'] == 'Author':
for v in attribute.findall('value'):
lst.append(v.text)
value = lst
lst = []
dic[key] = value
return dic
当你运行这个功能时,你会得到这个:
{'Book A': ['James Berry', 'John Smith'], 'Book C': ['James Berry']}
我希望这就是你要找的东西。如果没有,那么只需指定一点。 :)
正如 bambax 在他的回答中所提到的,使用XSLT密钥的解决方案更有效,尤其是对于大型XML文档:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes"/>
<!-- -->
<xsl:key name="kAuthByTitle"
match="attribute[@name='Author']"
use="preceding-sibling::attribute[@name='Title'][1]"/>
<!-- -->
<xsl:template match="/">
Book C Author:
<xsl:copy-of select=
"key('kAuthByTitle', 'Book C')"/>
<!-- -->
====================
Book B Author:
<xsl:copy-of select=
"key('kAuthByTitle', 'Book B')"/>
</xsl:template>
</xsl:stylesheet>
对此XML文档应用上述转换时:
<t>
<attribute name="Title">Book A</attribute>
<attribute name="Code">1</attribute>
<attribute name="Author">
<value>James Berry</value>
<value>John Smith</value>
</attribute>
<attribute name="Title">Book B</attribute>
<attribute name="Code">2</attribute>
<attribute name="Title">Book C</attribute>
<attribute name="Code">3</attribute>
<attribute name="Author">
<value>James Berry</value>
</attribute>
</t>
产生正确的输出:
Book C Author:
<attribute name="Author">
<value>James Berry</value>
</attribute>
====================
Book B Author:
请注意,应尽可能避免使用"//"
XPath缩写,因为它通常会导致在每次评估XPath表达式时扫描整个XML文档。
选择所有标题并应用模板
<xsl:template match="/">
<xsl:apply-templates select="//attribute[@name='Title']"/>
</xsl:template>
在模板输出标题中,检查是否存在下一个标题。如果没有,请输出以下作者。如果确实存在,请检查以下书籍的以下作者节点是否与当前书籍的以下作者节点相同。如果是,则表示当前的书没有作者:
<xsl:template match="*">
<book>
<title><xsl:value-of select="."/></title>
<author>
<xsl:if test="not(following::attribute[@name='Title']) or following::attribute[@name='Author'] != following::attribute[@name='Title']/following::attribute[@name='Author']">
<xsl:value-of select="following::attribute[@name='Author']"/>
</xsl:if>
</author>
</book>
</xsl:template>
我不确定你真的想去那里:我发现最简单的是从作者那里得到以前的标题,然后检查下面的第一作者或标题确实是一个标题。难看!
/books/attribute[@name="Author"]
[preceding-sibling::attribute[@name="Title" and string()="Book B"]
[following-sibling::attribute[ @name="Author"
or @name="Title"
]
[1]
[@name="Author"]
]
][1]
(我添加了 books 标签来包装文件)。
我用libxml2 BTW测试了这个,使用 xml_grep2 ,但仅限于您提供的样本数据,所以欢迎更多测试)。