javax.xml.transform.Transformer 的漂亮打印输出,仅使用标准 java api(缩进和 Doctype 定位)

Posted

技术标签:

【中文标题】javax.xml.transform.Transformer 的漂亮打印输出,仅使用标准 java api(缩进和 Doctype 定位)【英文标题】:Pretty-printing output from javax.xml.transform.Transformer with only standard java api (Indentation and Doctype positioning) 【发布时间】:2010-11-18 21:18:58 【问题描述】:

使用以下简单代码:

package test;

import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

public class TestOutputKeys 
    public static void main(String[] args) throws TransformerException 

        // Instantiate transformer input
        Source xmlInput = new StreamSource(new StringReader(
                "<!-- Document comment --><aaa><bbb/><ccc/></aaa>"));
        StreamResult xmlOutput = new StreamResult(new StringWriter());

        // Configure transformer
        Transformer transformer = TransformerFactory.newInstance()
                .newTransformer(); // An identity transformer
        transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, "testing.dtd");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(xmlInput, xmlOutput);

        System.out.println(xmlOutput.getWriter().toString());
    


我得到了输出:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Document comment --><!DOCTYPE aaa SYSTEM "testing.dtd">

<aaa>
<bbb/>
<ccc/>
</aaa>

问题 A:doctype 标签出现在文档注释之后。是否可以让它出现在文档注释之前?

问题 B:如何实现缩进,仅使用 JavaSE 5.0 API? 此问题与How to pretty-print xml from java 基本相同,然而该问题中的几乎所有答案都依赖于外部库。仅使用 java api 的唯一适用答案(由名为 Lorenzo Boccaccia 的用户发布)基本上等于上面发布的代码,但对我不起作用(如输出所示,我没有缩进)。

我猜你必须设置用于缩进的空格数量,正如外部库的许多答案所做的那样,但我只是找不到在 java api 中指定的位置。鉴于在 java api 中存在将缩进属性设置为“是”的可能性,因此必须能够以某种方式执行缩进。我就是想不通。

【问题讨论】:

问题 A 没有意义。您是指第二部分中的“之前”吗? 是的。我编辑了问题以更改拼写错误。谢谢。 重复我在***.com/questions/139076/… 中所做的评论 - 您现在可以在没有外部库的情况下进行漂亮的打印。见xerces.apache.org/xerces2-j/faq-general.html#faq-6。是的,这是一个 Xerces 常见问题解答,但答案涵盖了标准 JDK 类。这些类的最初 1.5 实现有很多问题,但从 1.6 开始一切正常。复制 FAQ 中的 LSSerializer 示例,去掉“...”位并在 LSSerializer writer = ... 行之后添加 writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); 此代码 sn-p 易受 XML 外部实体注入 (XXE) 的攻击。见cheatsheetseries.owasp.org/cheatsheets/… 【参考方案1】:

一个小工具类作为例子...

import org.apache.xml.serialize.XMLSerializer;

public class XmlUtil 

public static Document file2Document(File file) throws Exception 
    if (file == null || !file.exists()) 
        throw new IllegalArgumentException("File must exist![" + file == null ? "NULL"
                : ("Could not be found: " + file.getAbsolutePath()) + "]");
    
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    dbFactory.setNamespaceAware(true);
    return dbFactory.newDocumentBuilder().parse(new FileInputStream(file));


public static Document string2Document(String xml) throws Exception 
    InputSource src = new InputSource(new StringReader(xml));
    DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
    dbFactory.setNamespaceAware(true);
    return dbFactory.newDocumentBuilder().parse(src);


public static OutputFormat getPrettyPrintFormat() 
    OutputFormat format = new OutputFormat();
    format.setLineWidth(120);
    format.setIndenting(true);
    format.setIndent(2);
    format.setEncoding("UTF-8");
    return format;


public static String document2String(Document doc, OutputFormat format) throws Exception 
    StringWriter stringOut = new StringWriter();
    XMLSerializer serial = new XMLSerializer(stringOut, format);
    serial.serialize(doc);
    return stringOut.toString();


public static String document2String(Document doc) throws Exception 
    return XmlUtil.document2String(doc, XmlUtil.getPrettyPrintFormat());


public static void document2File(Document doc, File file) throws Exception 
    XmlUtil.document2String(doc, XmlUtil.getPrettyPrintFormat());


public static void document2File(Document doc, File file, OutputFormat format) throws Exception 
    XMLSerializer serializer = new XMLSerializer(new FileOutputStream(file), format);
    serializer.serialize(doc);


XMLserializer 由 Apache Foundation 中的 xercesImpl 提供。这是maven依赖:

<dependency>
    <groupId>xerces</groupId>
    <artifactId>xercesImpl</artifactId>
    <version>2.11.0</version>
</dependency>

您可以在此处找到您最喜欢的构建工具的依赖项:http://mvnrepository.com/artifact/xerces/xercesImpl/2.11.0。

【讨论】:

请添加对外部库的引用。此示例仅适用于 JDK。 XMLSerializer 属于 org.apache.xml.serialize。【参考方案2】:

要使输出成为有效的 XML 文档,NO。有效的 XML 文档必须以处理指令开头。有关详细信息,请参阅 XML 规范 http://www.w3.org/TR/REC-xml/#sec-prolog-dtd。

【讨论】:

这个答案是基于对问题的误解。注释可以在 doctype 声明之前或之后。 IE。您可以使用xmlDeclaration comment doctypeDeclarationxmlDeclaration doctypeDeclaration comment。这个问题从未谈到在 xmlDeclaration 之前放置任何内容。【参考方案3】:

您可以使用XSLT file 美化所有内容。谷歌抛出了一些结果,但我无法评论它们的正确性。

【讨论】:

我喜欢这个主意。我在这类事情(命名空间操作、空白控制等)中使用了相当多的 XSLT。它效率不高,但很容易,而且不依赖于解析器。【参考方案4】:

缺少的部分是缩进量。您可以设置缩进和缩进量如下:

transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("http://xml.apache.org/xsltindent-amount", "2");
transformer.transform(xmlInput, xmlOutput);

【讨论】:

很高兴知道,我认为它失败了,因为我有一个旧版本的 xalan,仔细检查 此解决方案缩进生成的 XML 文档,编译时不会出现错误或警告。 正如你所说,它取决于 Xalan,但这是 jdk 的一部分。据我所知,没有 API 级别设置来设置缩进,所以如果用户使用不同的实现,您需要添加开关处理来设置该实现的缩进。但是您不能控制所使用的实现吗? 我对 api 是什么的看法似乎告诉我 api 应该由执行指定任务的函数/方法组成,并且在使用 api 时,不需要直接处理底层实现.但话又说回来,我只是一个新手程序员,也许事情只会按照我认为在乌托邦世界中应该有的方式工作。我仍然认为 OutputKeys.INDENT 存在于 api 级别的事实应该意味着 api 级别的缩进是可能的,除非 api 有缺陷(或者 Apache 的实现有缺陷,没有按照应有的方式解释属性) 这是我一直这样做的方式,但在这里它不起作用,可能是一个不同的 XML 库。我做了factory.setAttribute("indent-number", 4);,现在可以了。

以上是关于javax.xml.transform.Transformer 的漂亮打印输出,仅使用标准 java api(缩进和 Doctype 定位)的主要内容,如果未能解决你的问题,请参考以下文章