【发布时间】:2022-12-18 20:46:31
【问题描述】:
我刚刚了解了 xmlstarlet,但不幸的是我在 XML 方面遇到了很多困难,所以我希望我能得到一些帮助......
比如说,我有这个 XML 文件,test.xml:
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<g id="layer3" inkscape:label="hello">
<circle id="circ2" inkscape:label="there"/>
<rect id="rect2" inkscape:label="world"/>
</g>
<g id="layer4">
<circle id="circ3" inkscape:label="more"/>
</g>
</objects>
所以我想做的是:对于存在inkscape:label属性的每个节点,将inkscape:label属性的值复制到id属性;所以上面的预期输出是:
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<g id="hello" inkscape:label="hello">
<circle id="there" inkscape:label="there"/>
<rect id="world" inkscape:label="world"/>
</g>
<g id="layer4">
<circle id="more" inkscape:label="more"/>
</g>
</objects>
我怎样才能用 xmlstarlet 做到这一点?
显然,我可以使用表达式 string("TEST") 将所有 id 属性替换为固定值,如下所示:
$ xmlstarlet edit -N inkscape="http://www.inkscape.org/namespaces/inkscape" --update '//*/@id' --expr 'string("TEST")'
test.xml
test.xml:3.40: Namespace prefix inkscape for label on g is not defined
<g id="layer3" inkscape:label="hello">
^
test.xml:4.46: Namespace prefix inkscape for label on circle is not defined
<circle id="circ2" inkscape:label="there"/>
^
test.xml:5.44: Namespace prefix inkscape for label on rect is not defined
<rect id="rect2" inkscape:label="world"/>
^
test.xml:8.45: Namespace prefix inkscape for label on circle is not defined
<circle id="circ3" inkscape:label="more"/>
^
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<g id="TEST" inkscape:label="hello">
<circle id="TEST" inkscape:label="there"/>
<rect id="TEST" inkscape:label="world"/>
</g>
<g id="TEST">
<circle id="TEST" inkscape:label="more"/>
</g>
</objects>
...我可以像这样用表达式string(../@id)“重新插入”属性 id 的值(所以我基本上得到与输入相同的输出):
$ xmlstarlet edit -N inkscape="http://www.inkscape.org/namespaces/inkscape" --update '//*/@id' --expr 'string(../@id)' test.xml
test.xml:3.40: Namespace prefix inkscape for label on g is not defined
<g id="layer3" inkscape:label="hello">
^
test.xml:4.46: Namespace prefix inkscape for label on circle is not defined
<circle id="circ2" inkscape:label="there"/>
^
test.xml:5.44: Namespace prefix inkscape for label on rect is not defined
<rect id="rect2" inkscape:label="world"/>
^
test.xml:8.45: Namespace prefix inkscape for label on circle is not defined
<circle id="circ3" inkscape:label="more"/>
^
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<g id="layer3" inkscape:label="hello">
<circle id="circ2" inkscape:label="there"/>
<rect id="rect2" inkscape:label="world"/>
</g>
<g id="layer4">
<circle id="circ3" inkscape:label="more"/>
</g>
</objects>
...但我不能使用相同的技巧(表达式string(../@inkscape:label) - 或string(../@*[local-name()='label']) 按照How does local-name find attributes in an xml node?)从属性inkscape:label 读取 - 我无法真正判断这是否是因为“命名空间前缀”。 .“未定义”消息:
$ xmlstarlet edit -N inkscape="http://www.inkscape.org/namespaces/inkscape" --update '//*/@id' --expr 'string(../@inkscape:label)' test.xml
test.xml:3.40: Namespace prefix inkscape for label on g is not defined
<g id="layer3" inkscape:label="hello">
^
test.xml:4.46: Namespace prefix inkscape for label on circle is not defined
<circle id="circ2" inkscape:label="there"/>
^
test.xml:5.44: Namespace prefix inkscape for label on rect is not defined
<rect id="rect2" inkscape:label="world"/>
^
test.xml:8.45: Namespace prefix inkscape for label on circle is not defined
<circle id="circ3" inkscape:label="more"/>
^
<?xml version="1.0" encoding="UTF-8"?>
<objects>
<g id="" inkscape:label="hello">
<circle id="" inkscape:label="there"/>
<rect id="" inkscape:label="world"/>
</g>
<g id="">
<circle id="" inkscape:label="more"/>
</g>
</objects>
通过get attribute value using xmlstarlet or xmllint;我可以确认我可以使用以下方法定位 id 属性:
xmlstarlet select -N inkscape="http://www.inkscape.org/namespaces/inkscape" --template --value-of '//*/@id' test.xml
...但是 inkscape:label 的相应命令不返回任何内容:
xmlstarlet select -N inkscape="http://www.inkscape.org/namespaces/inkscape" --template --value-of '//*/@inkscape:label' test.xml
这可能是命名空间的事情,但我不明白我怎么能忽略命名空间,而只与文档中的属性名称相关......
编辑:终于用 Python 3 解决了这里的问题:
#!/usr/bin/env python3
# https://stackoverflow.com/questions/30097949/elementtree-findall-to-recursively-select-all-child-elements
# https://stackoverflow.com/questions/13372604/python-elementtree-parsing-unbound-prefix-error
# https://stackoverflow.com/questions/2352840/parsing-broken-xml-with-lxml-etree-iterparse
# https://stackoverflow.com/questions/28813876/how-do-i-get-pythons-elementtree-to-pretty-print-to-an-xml-file
import sys
import lxml
import lxml.etree
import xml.etree.ElementTree as ET
def proc_node(node):
target_label = 'inkscape:label' # file without namespace, like `test.xml` here
#target_label = '{http://www.inkscape.org/namespaces/inkscape}label' # file with namespace (like proper Inkscape .svg)
if target_label in node.attrib:
node.attrib['id'] = node.attrib[target_label]
for childel in node.getchildren():
proc_node(childel)
parser1 = lxml.etree.XMLParser(encoding="utf-8", recover=True)
tree1 = ET.parse('test.xml', parser1)
ET.indent(tree1, space=" ", level=0)
proc_node(tree1.getroot())
print(lxml.etree.tostring(tree1.getroot(), xml_declaration=True, pretty_print=True, encoding='UTF-8').decode('utf-8'))
...如果我调用这个xmlproc.py,那么结果是:
$ python3 xmlproc.py
<?xml version='1.0' encoding='UTF-8'?>
<objects>
<g id="hello" inkscape:label="hello">
<circle id="there" inkscape:label="there"/>
<rect id="world" inkscape:label="world"/>
</g>
<g id="layer4">
<circle id="more" inkscape:label="more"/>
</g>
</objects>
...这正是我想要的。
因此,本着如何假设问题的精神来指定 - 我如何使用 xmlstarlet 实现这一点?
【问题讨论】:
-
您确定
test.xml真的看起来像问题中的示例 xml 吗?该示例没有inkspace的命名空间声明,ET 将返回“未绑定前缀”错误。 -
谢谢@JackFleeting - 事实上,我的实际文件是一个
inkspace,但由于我忘记了我过去可能知道的关于 XML(和名称空间)的一切,我很惊讶地发现做一个“最小的例子”不会由于 XML 命名空间前缀,一般工作。所以 Python 代码显示了在这两种情况下如何处理 - 我想知道是否可以使用特殊开关xmlstarlet来做同样的事情(即处理除了属性前缀之外没有命名空间信息的最小 XML 文件,和一个“真实的”“正确命名空间”的 XML 文件)
标签: xml xmlstarlet