【问题标题】:lxml - Is there any hacky way to keep "?lxml - 有什么办法可以保留“?
【发布时间】:2015-02-25 13:39:21
【问题描述】:

我注意到 xml 实体 &quot 会自动强制转换为它们真正的原始字符:

>>> from lxml import etree as et
>>> parser = et.XMLParser()
>>> xml = et.fromstring("<root><elem>&quot;hello world&quot;</elem></root>", parser)
>>> print et.tostring(xml, pretty_print=1)
<root>
  <elem>"hello world"</elem>
</root>

>>> 

我找到了一个相关的旧(2009-02-07)thread

s = cStringIO.StringIO(""""她是男人!"""") e = etree.parse(s,etree.XMLParser(resolve_entities=False))

注意还有etree.fromstring()。

etree.tostring(e) '“她是男人!”'

我原以为 resolve_entities=False 会阻止 翻译,例如,“到”。

“resolve_entities”选项适用于在 DTD 中定义的实体 您要保留其中的引用而不是解析的值。 您提到的实体是 XML 规范的一部分,而不是 DTD。

是否有另一种方法来防止这种行为(或者,如果没有别的, 事后反转)?

嗯,你得到的是格式良好的 XML。请问你为什么需要 输出中的实体引用?

不过,响应是您想要这样做的原因,这个问题没有直接的答案。我很惊讶,因为 etree 解析器强制转换而没有提供禁用它的选项。

以下示例说明了为什么我需要此解决方案,此 xml 用于 xbmc skinning 解析器:

>>> print open("/tmp/so.xml").read() #the original file
<window id="1234">
        <defaultcontrol>101</defaultcontrol>
        <controls>
                <control type="button" id="101">
                        <onfocus>Dialog.Close(212)</onfocus>
                        <onfocus>SetFocus(11)</onfocus>
                </control>
                <control type="button" id="102">
                        <visible>StringCompare(VideoPlayer.PlotOutline,Stream.IsPlaying) + !Skin.HasSetting(Stream.IsUpdated)</visible>
                        <onfocus>RunScript(script.test)</onfocus>
                        <onfocus>SetFocus(11)</onfocus>
                </control>
                <control type="button" id="103">
                        <visible>SubString(VideoPlayer.PlotOutline,Video.IsPlaying)</visible>
                        <onfocus>Close</onfocus>
                        <onfocus>RunScript(&quot;/.xbmc/addons/script.hello.world/default.py&quot;,&quot;$INFO[VideoPlayer.Album]&quot;,&quot;$INFO[VideoPlayer.Genre]&quot;)</onfocus>
                </control>
        </controls>
</window>

>>> root = et.parse("/tmp/so.xml", parser)
>>> r = root.getroot()
>>> for c in r:
...     for cc in c:
...         if cc.attrib.get('id') == "103":
...             cc.remove(cc[1]) #remove 1 element, it's just a demonstrate
... 
>>> o = open("/tmp/so.xml", "w")
>>> o.write(et.tostring(r, pretty_print=1)) #save it back
>>> o.close()
>>> print open("/tmp/so.xml").read() #the file after implemented 
<window id="1234">
        <defaultcontrol>101</defaultcontrol>
        <controls>
                <control type="button" id="101">
                        <onfocus>Dialog.Close(212)</onfocus>
                        <onfocus>SetFocus(11)</onfocus>
                </control>
                <control type="button" id="102">
                        <visible>StringCompare(VideoPlayer.PlotOutline,Stream.IsPlaying) + !Skin.HasSetting(Stream.IsUpdated)</visible>
                        <onfocus>RunScript(script.test)</onfocus>
                        <onfocus>SetFocus(11)</onfocus>
                </control>
                <control type="button" id="103">
                        <visible>SubString(VideoPlayer.PlotOutline,Video.IsPlaying)</visible>
                        <onfocus>RunScript("/.xbmc/addons/script.hello.world/default.py","$INFO[VideoPlayer.Album]","$INFO[VideoPlayer.Genre]")</onfocus>
                </control>
        </controls>
</window>

>>> 

正如您在最后的 id "103" 下的 onfocus 元素中看到的,&quot 不再是原来的形式,如果"$INFO[VideoPlayer.Album]" 变量包含嵌套引号并变为无效和错误的 ""test""。

那么,我有什么办法可以让 &quot 保持其原始形式吗?

[更新]: 对于感兴趣的人,其他 3 个预定义的 xml 实体,即 gtltamp 只能通过使用 method= 进行转换"html"script 标签。无论是lxml VS xml.etree.ElementTree 还是python2 VS python3 的机制都是一样的,让人摸不着头脑:

>>> from lxml import etree as et
>>> r = et.fromstring("<root><script>&quot;&apos;&amp;&gt;&lt;</script><p>&quot;&apos;&amp;&gt;&lt;</p></root>")
>>> print et.tostring(r, pretty_print=1, method="xml")
<root>
  <script>"'&amp;&gt;&lt;</script>
  <p>"'&amp;&gt;&lt;</p>
</root>

>>> print et.tostring(r, pretty_print=1, method="html")
<root><script>"'&><</script><p>"'&amp;&gt;&lt;</p></root>

>>> 

[更新2]: 以下是所有可能的 html 标签的列表:

#https://github.com/html5lib/html5lib-python/blob/master/html5lib/sanitizer.py
acceptable_elements = ['a', 'abbr', 'acronym', 'address', 'area',
'article', 'aside', 'audio', 'b', 'big', 'blockquote', 'br', 'button',
'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup',
'command', 'datagrid', 'datalist', 'dd', 'del', 'details', 'dfn',
'dialog', 'dir', 'div', 'dl', 'dt', 'em', 'event-source', 'fieldset',
'figcaption', 'figure', 'footer', 'font', 'form', 'header', 'h1',
'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input', 'ins',
'keygen', 'kbd', 'label', 'legend', 'li', 'm', 'map', 'menu', 'meter',
'multicol', 'nav', 'nextid', 'ol', 'output', 'optgroup', 'option',
'p', 'pre', 'progress', 'q', 's', 'samp', 'section', 'select',
'small', 'sound', 'source', 'spacer', 'span', 'strike', 'strong',
'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'time', 'tfoot',
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'video']
from lxml import etree as et
for e in acceptable_elements:
    r = et.fromstring(e.join(["<", ">hello&amp;world</", ">"]))
    s = et.tostring(r, pretty_print=1, method="html")
    closed_tag = "</" + e + ">"
    if closed_tag not in s:
        print s

运行这段代码,你会看到如下输出:

<area>

<br>

<col>

<hr>

<img>

<input>

如您所见,只打印了打开标签,其余的只是进入黑洞。我测试了所有 5 个 xml 实体并且都具有相同的行为。这太令人困惑了。这在使用 HTMLParser 时没有发生,所以我猜 fromstring(method 应该默认为 xml) 和 tostring(method="html") 步骤之间存在错误。而且我发现它与实体无关,因为“hello”(没有实体)也被截断为(而且你好无处可去,如果使用方法=“它可以随时出现xml”打印出来)。

【问题讨论】:

  • 我假设某处有“”,例如id="101",因此很难确定哪些引号需要保留,哪些不需要。所以我不能简单地对整个文件或每个文本执行 .replace。
  • 有道理,但它会在&gt; 之后和&lt; 之前,对吗?对于一种 hacky 方式,我认为您需要找到一种可以安全地执行 string.replace 的模式,或者也许有人可能会建议一种我不知道的更好方法。祝你好运:)
  • 我能想到的是替换所有 2 个 xml 预定义实体,包括 &apos 和 "在解析之前到特定的字符串(例如ANDquot;),然后在解析后将其替换回所有这些字符串。这很丑陋,但它应该可以完成工作。我很惊讶其他 3 个 xml 预定义的 xml 实体(>、<、&)不会由 xml etree 自动转换。如果有人提出更好的想法,我将不胜感激。
  • 从我目前注意到的情况来看,3 个 xml 预定义实体(>、<、&)只有在使用 method="html" 时才会被转换,并且标签必须命名为“脚本”。参考stackoverflow.com/questions/19017253/…
  • 确实需要深入解析器源代码才能找出原因,但祝你好运。

标签: python xml xml-parsing lxml elementtree


【解决方案1】:
from xml.sax.saxutils import escape
from lxml import etree

def to_string(xdoc):
    r = ""
    for action, elem in etree.iterwalk(xdoc, events=("start", "end")):
        if action == 'start':
            text = escape(elem.text, {"'": "&apos;", "\"": "&quot;"}) if elem.text is not None else ""
            attrs = "".join([' %s="%s"' % (k, v) for k, v in elem.attrib.items()])
            r += "<%s%s>%s" % (elem.tag, attrs, text)
        elif action == 'end':
            r += "</%s>%s" % (elem.tag, elem.tail if elem.tail else "\n")
    return r
xdoc = etree.fromstring(xml_text)
s = to_string(xdoc)

【讨论】:

  • 对我来说有点太晚了,因为我不再使用 python 工作了。不过,我对上面的/tmp/so.xml 进行了快速测试,运行代码后type="button" id="101/102/103" 属性已被删除。
  • 确实修复了 attrs。这不是真正的通用解决方案,但在某些情况下可能会有所帮助。
猜你喜欢
  • 2014-05-22
  • 2019-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-20
  • 1970-01-01
  • 1970-01-01
  • 2023-02-06
相关资源
最近更新 更多