使用标准Java API(缩进和文档类型位置)漂亮地打印javax.xml.transform.Transformer的输出

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相同,然而那个问题中几乎所有的答案都依赖于外部库。唯一可行的答案(由名为Lorenzo Boccaccia的用户发布),基本上等同于上面发布的代码,但对我不起作用(如输出所示,我没有得到缩进)。

我猜您必须设置要用于缩进的空格数量,因为许多具有外部库的答案都是这样做的,但我只是找不到在Java API中指定缩进属性的位置。鉴于在Java API中存在设置缩进属性为“是”的可能性,必须有一种方法来执行缩进。我只是想不出怎么做。


4
在 https://dev59.com/qnVC5IYBdhLWcg3w9F89 中,我发表了评论,现在您可以在不使用外部库的情况下对XML进行漂亮打印。请参见 http://xerces.apache.org/xerces2-j/faq-general.html#faq-6。是的,这是一个Xerces FAQ,但答案涵盖了标准JDK类。这些类的1.5初始实现存在许多问题,但从1.6版本开始一切都正常运行。复制FAQ中的LSSerializer示例,删除“...”部分,并在“LSSerializer writer = ...”行之后添加 writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); - George Hawkins
这段代码片段容易受到XML外部实体注入(XXE)攻击。请参阅https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#transformerfactory - fanbondi
4个回答

119

缺失的部分是缩进量。您可以按照以下方式设置缩进和缩进量:

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

1
该解决方案会缩进生成的 XML 文档,编译时不会出现错误或警告。 - Dave Jarvis
2
就像你所说的,这取决于Xalan,但它是jdk的一部分。据我所知,没有API级别的设置来设置缩进,因此,如果用户使用不同的实现,您需要添加开关处理来设置该实现的缩进。但是,您不是控制所使用的实现吗? - Rich Seller
6
我的看法是,API 应该由执行指定任务的函数/方法组成。在使用 API 时,不应该直接涉及底层实现细节。但是我只是一个初学者,也许我的想法只适用于乌托邦世界。尽管如此,我认为 OutputKeys.INDENT 存在于 API 层面,应该意味着可以进行 API 级别的缩进,除非 API 本身存在缺陷(或 Apache 的实现有问题,没有按照应有的方式解释该属性)。 - Alderath
6
这是我一直以来的做法,但在这里它不起作用了,可能是因为使用了不同的XML库。我执行了factory.setAttribute("indent-number", 4);,现在它可以工作了。 - Adrian Smith
谢谢,它很好地完成了工作,但是根节点(在我的情况下为<Nama-e-Amaal>)是这样的:<?xml version="1.0" encoding="UTF-8"?><Nama-e-Amaal>。我怎么才能让它换到下一行? - Bugs Happen
显示剩余6条评论

5
一个小的工具类作为示例...
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。 - Aubin

1
你可以使用XSLT文件来美化所有内容。谷歌会显示一些结果,但我无法评论它们的正确性。

我喜欢这个想法。我经常使用XSLT来进行此类操作(命名空间操作,空格控制等)。虽然它不是很高效,但它非常容易,并且不依赖于解析器。 - skaffman

0

这个回答基于对问题的误解。评论可以放在doctype声明之前或之后。也就是说,你可以有xmlDeclaration comment doctypeDeclaration或者xmlDeclaration doctypeDeclaration comment。问题从未提到在xmlDeclaration之前放置任何内容。 - Alderath

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接