如何使用DOM来转义特殊字符

时间:2016-07-20 08:23:03

标签: java xml dom xml-parsing domdocument

这个问题最近一直困扰着我,我似乎无法找到可能的解决方案。

我正在处理一个Web服务器,它接收一个XML文档来进行一些处理。服务器的解析器与&,&#;;,#34;,<,>有问题。我知道这很糟糕,我没有在该服务器上实现xml解析器。但在等待补丁之前我需要规避。

现在,在将我的XML文档上传到此服务器之前,我需要解析它并转义xml特殊字符。我目前正在使用DOM。问题是,如果我遍历TEXT_NODES并用转义版本替换所有特殊字符,当我保存此文档时,

d'ex我得到d'ex但我需要d'ex

这是有道理的,因为,DOM逃脱"&"。但显然这不是我需要的。

因此,如果DOM已经能够将"&"转发到"&",我怎样才能让它从"转换为"等其他字符?

如果它不能,我怎样才能在已经解析和转义的文本中保存它们,而不必在保存时重新转义它们?

这就是我如何逃避使用apache StringEscapeUtils class:

的特殊字符
public String xMLTransform() throws Exception
      {

         String xmlfile = FileUtils.readFileToString(new File(filepath));

         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
         DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
         Document doc = docBuilder.parse(new InputSource(new StringReader(xmlfile.trim().replaceFirst("^([\\W]+)<", "<"))));

       NodeList nodeList = doc.getElementsByTagName("*");

       for (int i = 0; i < nodeList.getLength(); i++) {
          Node currentNode = nodeList.item(i);
          if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
              Node child = currentNode.getFirstChild();
              while(child != null) {
                  if (child.getNodeType() == Node.TEXT_NODE) {                   
                    child.setNodeValue(StringEscapeUtils.escapeXml10(child.getNodeValue()));
//Escaping works here. But when saving the final document, the "&" used in escaping gets escaped as well by DOM.


                  }
                  child = child.getNextSibling();
              }
          }
      }

         TransformerFactory transformerFactory = TransformerFactory.newInstance();

       Transformer transformer = transformerFactory.newTransformer();
         DOMSource source = new DOMSource(doc);
         StringWriter writer = new StringWriter();
         StreamResult result = new StreamResult(writer);
         transformer.transform(source, result);


         FileOutputStream fop = null;
         File file;

         file = File.createTempFile("escapedXML"+UUID.randomUUID(), ".xml");

         fop = new FileOutputStream(file);

         String xmlString = writer.toString();
         byte[] contentInBytes = xmlString.getBytes();

         fop.write(contentInBytes);
         fop.flush();
         fop.close();

      return file.getPath();


      }

4 个答案:

答案 0 :(得分:3)

我认为您正在寻找的解决方案是一个自定义的XSLT解析器,您可以为其他HTML转义进行配置。

我无法说某些如何配置xslt文件以执行您想要的操作,但我相信它可以完成。我已经删除了下面的基本Java设置:

@Test
    public void testXSLTTransforms () throws Exception {
        DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
        DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
        Document doc = docBuilder.newDocument();
        Element el = doc.createElement("Container");
        doc.appendChild(el);


        Text e = doc.createTextNode("Character");
        el.appendChild(e);
        //e.setNodeValue("\'");
        //e.setNodeValue("\"");

        e.setNodeValue("&");



        TransformerFactory transformerFactory = TransformerFactory.newInstance();       
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");        
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");


        DOMSource source = new DOMSource(doc);
        StreamResult result = new StreamResult(System.out);
        //This prints the original document to the command line.
        transformer.transform(source, result);

        InputStream xsltStream =  getClass().getResourceAsStream("/characterswap.xslt");
            Source xslt = new StreamSource(xsltStream);
            transformer = transformerFactory.newTransformer(xslt);
            //This one is the one you'd pipe to a file
            transformer.transform(source, result);
    }

我有一个简单的XSLT,用于概念验证,显示您提到的默认字符编码:

  

characterswap.xslt

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

 <xsl:template match="node()|@*">
 <xsl:text> &#xa;  Original VALUE :  </xsl:text>
     <xsl:copy-of select="."/>
     <xsl:text> &#xa;  OUTPUT ESCAPING DISABLED :  </xsl:text>
      <xsl:value-of select="." disable-output-escaping="yes"/>
      <xsl:text> &#xa;  OUTPUT ESCAPING ENABLED :  </xsl:text>
      <xsl:value-of select="." disable-output-escaping="no"/>
 </xsl:template>

</xsl:stylesheet>

控制台非常基本:

<?xml version="1.0" encoding="UTF-8"?>
<Container>&amp;</Container>

  Original VALUE :  <Container>&amp;</Container> 
  OUTPUT ESCAPING DISABLED :  & 
  OUTPUT ESCAPING ENABLED :  &amp;

您可以从XSLT执行中获取活动节点并执行特定的字符替换。我可以找到多个例子,但是我很难让他们在我的环境中工作。

XSLT string replace 是一个很好的起点。

这是关于我对XSLT的了解程度,我希望它可以帮助您解决问题。

祝你好运。

我正在考虑进一步,解决方案可能不仅仅是XSLT。根据您的描述,我的印象是,您需要寻找一整套 html编码,而不是 xml10编码

沿着这些方向,如果我们采用您当前的节点文本转换:

if (child.getNodeType() == Node.TEXT_NODE) {
    child.setNodeValue(StringEscapeUtils.escapeXml10(child.getNodeValue()));
}

明确指望我们想要HTML编码:

if (child.getNodeType() == Node.TEXT_NODE) {
    //Capture the current node value
    String nodeValue = child.getNodeValue();
    //Decode for XML10 to remove existing escapes
    String decodedNode = StringEscapeUtils.unescapeXml10(nodeValue);
    //Then Re-encode for HTML (3/4/5)
    String fullyEncodedHTML = StringEscapeUtils.escapeHtml3(decodedNode);
    //String fullyEncodedHTML = StringEscapeUtils.escapeHtml4(decodedNode);
    //String fullyEncodedHTML = StringEscapeUtils.escapeHtml5(decodedNode);

    //Then place the fully-encoded HTML back to the node
    child.setNodeValue(fullyEncodedHTML);
}
  

我认为xml现在将完全用所有的编码   HTML逃脱了你想要的。

现在将它与XSLT结合用于输出转义(从上面),并且当写出文件时文档将不会进行任何进一步的转换。

我喜欢这个解决方案,因为它限制了XSLT文件中的逻辑。您无需管理整个String查找/替换,而只需确保复制整个节点并复制 text()并禁用输出转义。

从理论上讲,这似乎可以实现我对你的目标的理解。

  

再次警告我使用XSLT是弱的,所以示例xslt文件可能   还需要一些调整。该解决方案减少了未知的工作   数量,在我看来。

答案 1 :(得分:1)

我已经看到人们使用正则表达式来做类似的事情

从(Replace special character with an escape preceded special character in Java

复制

String newSearch = search.replaceAll("(?=[]\\[+&|!(){}^\"~*?:\\\\-])", "\\\\");

那个糟糕的正则表达式是&#34;展望未来&#34; - 非捕获断言,以下char匹配某些东西 - 在本例中为字符类。

请注意你不需要在角色类中逃脱角色,除了a](即使是减号也不需要在第一个或最后一个时进行转义)。

\\\\是你编写正则表达式文字的代码\(对java进行一次转义,对正则表达式进行一次转义)

这是对这项工作的考验:

public static void main(String[] args) { String search = "code:xy"; String newSearch = search.replaceAll("(?=[]\\[+&|!(){}^\"~*?:\\\\-])", "\\\\"); System.out.println(newSearch); }

输出:

code\:xy

答案 2 :(得分:1)

这与这个问题密切相关(how to Download a XML file from a URL by Escaping Special Characters like &lt; &gt; $amp; etc?)。

这篇文章有类似的情况,代码下载带解析/转义内容的XML。

据我所知,你读取文件,解析它并转义字符。在保存期间,XML会被转义&#34;再次。虽然您可以使用DOM来检查格式良好的XML或模式,但要转义的基于文件的操作可以帮助您转义XML和HTML特殊字符。帖子中的代码示例指的是使用IOUtils和StringUtils来完成它。希望这可以帮助 !

答案 3 :(得分:0)

我会在这里使用StringEscapeUtils.escapeXml10()...详细信息。 https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringEscapeUtils.html#ESCAPE_XML10