【问题标题】:innerHTML unencodes &lt; in attributesinnerHTML 在属性中取消编码 <
【发布时间】:2016-01-03 14:31:11
【问题描述】:

我有一个 HTML 文档,它的某些属性中可能包含 &amp;lt;&amp;gt;。我试图提取它并通过 XSLT 运行它,但 XSLT 引擎错误告诉我 &amp;lt; 在属性内无效。

我做了一些挖掘,发现它在源文档中被正确地转义了,但是当它通过innerHTML 加载到 DOM 中时,DOM 是 unencoding 的属性。奇怪的是,它对&amp;lt;&amp;gt; 执行此操作,但对&amp;amp; 之类的其他一些则不执行此操作。

这是一个简单的例子:

var div = document.createElement('DIV');
div.innerHTML = '<div asdf="&lt;50" fdsa="&amp;50"></div>';
console.log(div.innerHTML)

我假设 DOM 实现决定 HTML 属性可以不如 XML 属性严格,并且这是“按预期工作”。我的问题是,我可以在不写一些可怕的正则表达式替换的情况下解决这个问题吗?

【问题讨论】:

  • @Abel 我正在使用 jQuery 的.html(),我只是试图减少到我认为“问题”正在发生的地方。源文档是 XML,我在使用 .html() 插入之前通过浏览器 XSLT 运行它。稍后我通过逆向过程将 XML 取出。我只是觉得奇怪的是 DOM unescaping 这个字符(而不是其他字符)。
  • 我无法修改源 XML,需要在最后的输出中保留相同的内容。我可以在中间运行任何必要的转换,但我正在寻找一种比一些正则表达式替换更好的方法。特别是考虑到字符是&amp;lt;,文档中充满了。
  • @Abel 我唯一的目标是将它从 DOM 中取出,就像它进入时一样(如 &amp;lt;)。我用.text(string) 把它放进去,用.text() 把它拿出来。这次往返的问题是输入不等于输出(仅在这种情况下)。
  • 啊,对不起。嗯,这可能只有使用其他 DOM 方法才有可能,而不是 innerHTML。即,这有效:div.firstChild.attributes['title']。但这需要大量额外的机制来“模仿”innerHTML。

标签: javascript html xml xslt innerhtml


【解决方案1】:

试试XMLSerializer:

var div = document.getElementById('d1');

var pre = document.createElement('pre');
pre.textContent = div.outerHTML;
document.body.appendChild(pre);

pre = document.createElement('pre');
pre.textContent = new XMLSerializer().serializeToString(div);
document.body.appendChild(pre);
&lt;div id="d1" data-foo="a &amp;lt; b &amp;amp;&amp;amp; b &amp;gt; c"&gt;This is a test&lt;/div&gt;

您可能需要调整 XSLT 以考虑 XHTML 命名空间 XMLSerializer 插入(至少在此处使用 Firefox 进行测试)。

【讨论】:

  • 这更接近我想要的,但它并不适用于所有浏览器(IE8没有XMLSerializer
  • @murrayju,see this question on XML Serializer,如果您必须支持用户份额 .xml。我认为 Martin Honnen 的这个解决方案非常好:)。
  • @Abel,我不认为在 IE 或其他地方为 HTML DOM 节点实现了xml 属性,它只存在于 MSXML DOM 节点。
  • 是的,这就是我的观点,您必须为不支持 XMLSerializer 的浏览器破例(如何做到这一点在链接的答案中显示),
  • 虽然我没有使用它来直接解决我的问题,但实现它可能也是一个好主意,因为它似乎比innerHTML 更可靠。感谢您的帮助!
【解决方案2】:

我不确定这是否是您正在寻找的,但请看看。

var div1 = document.createElement('DIV');
var div2  = document.createElement('DIV');
div1.setAttribute('asdf','&lt;50');
div1.setAttribute('fdsa','&amp;50');
div2.appendChild(div1);
console.log(div2.innerHTML.replace(/&amp;/g, '&'));

【讨论】:

  • 我看不出这是如何用属性内的转义小于字符来回答问题的......而且你可能不希望 every & 被替换......跨度>
  • 实际上它转换了 和&到<和&amp;分别。替换功能将其改回原来的格式。
  • 正是我的观点。 &amp;amp; 不应该被替换,&amp;lt; 只有在它是属性值的一部分时才应该被替换,就像字符串被解释为 XML 一样。它不应替换其他出现(文本节点、注释节点、处理指令、cdata 部分,尽管其中一些在 HTML 中很少见)。
  • 我对此感到沮丧的是,setAttribute 的行为与 innerHTML 对于相同的文字 &amp;lt; 的行为不同。我确信这就是@Abel 所说的在一种情况下被解释为“HTML”而不是另一种情况时的意思。
  • @murrayju,是的,innerHTML 是 DOM 的一个非常不幸的部分。几乎所有其他 DOM 属性都在 DOM 上工作并作为 XML,但 innerHTML 不能。在某些情况下很方便,尤其是。作为一个 setter,但它返回 XML(你已经发现了困难的方法)。
【解决方案3】:

最终对我最有效的是在传入文档上使用 XSLT 双重转义这些(并在传出文档上反转)。

所以属性中的&amp;amp;lt; 变为&amp;amp;lt;。感谢@Abel 的建议。

这是我添加的 XSLT,以防其他人发现它有帮助:

First 是在 XSLT 1.0 中进行字符串替换的模板。如果可以使用 XSLT 2.0,则可以使用内置的replace

<xsl:template name="string-replace-all">
    <xsl:param name="text"/>
    <xsl:param name="replace"/>
    <xsl:param name="by"/>
    <xsl:choose>
        <xsl:when test="contains($text, $replace)">
            <xsl:value-of select="substring-before($text,$replace)"/>
            <xsl:value-of select="$by"/>
            <xsl:call-template name="string-replace-all">
                <xsl:with-param name="text" select="substring-after($text,$replace)"/>
                <xsl:with-param name="replace" select="$replace"/>
                <xsl:with-param name="by" select="$by"/>
            </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$text"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

接下来是执行我需要的特定替换的模板:

<!-- xml -> html -->
<xsl:template name="replace-html-codes">
    <xsl:param name="text"/>
    <xsl:variable name="lt">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="$text"/>
            <xsl:with-param name="replace" select="'&lt;'"/>
            <xsl:with-param name="by" select="'&amp;lt;'"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="gt">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="$lt"/>
            <xsl:with-param name="replace" select="'&gt;'"/>
            <xsl:with-param name="by" select="'&amp;gt;'"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$gt"/>
</xsl:template>

<!-- html -> xml -->
<xsl:template name="restore-html-codes">
    <xsl:param name="text"/>
    <xsl:variable name="lt">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="$text"/>
            <xsl:with-param name="replace" select="'&amp;lt;'"/>
            <xsl:with-param name="by" select="'&lt;'"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="gt">
        <xsl:call-template name="string-replace-all">
            <xsl:with-param name="text" select="$lt"/>
            <xsl:with-param name="replace" select="'&amp;gt;'"/>
            <xsl:with-param name="by" select="'&gt;'"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$gt"/>
</xsl:template>

XSLT 主要是一种传递。我只是在复制属性时调用适当的模板:

<xsl:template match="@*">
    <xsl:attribute name="data-{local-name()}">
        <xsl:call-template name="replace-html-codes">
            <xsl:with-param name="text" select="."/>
        </xsl:call-template>
    </xsl:attribute>
</xsl:template>

<!-- copy all nodes -->
<xsl:template match="node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

【讨论】:

    【解决方案4】:

    值得一提的几件事可能会对某人有所帮助:

    • 确保您的 HTML 确实有效,例如我不小心使用了\,而我应该使用/,这导致了这个问题。
    • 正如 OP 在问题中指出的那样,您可以使用 &amp;amp;,因此您可以尝试例如&amp;amp;lt;&amp;amp;gt;
    • alternatives&lt;&gt; 看起来很相似。
    • alternate的方式来表达&lt;&gt;&amp;#60;&amp;#62;

    【讨论】:

      猜你喜欢
      • 2014-12-02
      • 2014-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-04
      • 2018-07-11
      • 2019-08-09
      • 1970-01-01
      相关资源
      最近更新 更多