如何从Java中漂亮地打印XML?

499

我有一个包含XML代码的Java字符串,其中没有换行或缩进。我想将其转换为格式良好的XML字符串。如何做到这一点?

String unformattedXml = "<tag><nested>hello</nested></tag>";
String formattedXml = new [UnknownClass]().format(unformattedXml);
注意:我的输入是一个字符串(String)。我的输出也是一个字符串(String)。
(基础)模拟结果:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <tag>
    <nested>hello</nested>
  </tag>
</root>

请查看这个问题:https://dev59.com/lXM_5IYBdhLWcg3wslfs - dfa
10
只是好奇,你是将这个输出发送到XML文件或其他需要缩进很重要的地方吗?之前我非常关注格式化我的XML以便正确显示它...但在花费了大量时间后,我意识到我必须将输出发送到一个Web浏览器,并且任何相对现代的Web浏览器都可以以漂亮的树形结构显示XML,所以我可以忘记这个问题并继续前进。我提到这个只是为了防止你(或其他有同样问题的用户)可能会忽略同样的细节。 - Abel Morelos
4
@Abel,将数据保存到文本文件中,插入到HTML文本区域中,并将其倒出到控制台以进行调试。 - Steve McLeod
6
“put on hold as too broad” 的意思是“因问题过于广泛而被搁置”,目前很难比问题更加精确明了! - Steve McLeod
34个回答

0
如果您不需要太多缩进,只需要一些换行符,那么使用正则表达式可能就足够了...
String leastPrettifiedXml = uglyXml.replaceAll("><", ">\n<");

代码很好,只是因为缺少缩进而导致结果不够清晰。


(对于带有缩进的解决方案,请参见其他答案。)


1
嗯...只是在大声思考,谁会需要这样的解决方案呢?我能想到的唯一应用领域就是我们从某些网络服务中获取的数据,开发人员或测试人员可能需要这样简单的工具来测试数据及其有效性。否则不是一个好选择... - Sudhakar Chavali
1
@SudhakarChavali 我是一名开发者。有时候我需要用dirty println()和log.debug() hacks来记录日志;即使程序出现问题,有时候我只能在受限的服务器环境中使用日志文件(只能通过Web管理界面访问,而无法使用shell进行逐步调试)。 - comonad
不要用这种方式手动解析XML。如果XML中包含注释内的“<”符号,你的代码将会破坏XML结构。 - Michael Kay

-1

在想出自己的解决方案之前,我应该先查找这个页面!无论如何,我的解决方案使用Java递归来解析xml页面。这段代码完全自包含,不依赖于第三方库。而且..它使用递归!

// you call this method passing in the xml text
public static void prettyPrint(String text){
    prettyPrint(text, 0);
}

// "index" corresponds to the number of levels of nesting and/or the number of tabs to print before printing the tag
public static void prettyPrint(String xmlText, int index){
    boolean foundTagStart = false;
    StringBuilder tagChars = new StringBuilder();
    String startTag = "";
    String endTag = "";
    String[] chars = xmlText.split("");
    // find the next start tag
    for(String ch : chars){
        if(ch.equalsIgnoreCase("<")){
            tagChars.append(ch);
            foundTagStart = true;
        } else if(ch.equalsIgnoreCase(">") && foundTagStart){
            startTag = tagChars.append(ch).toString();
            String tempTag = startTag;
            endTag = (tempTag.contains("\"") ? (tempTag.split(" ")[0] + ">") : tempTag).replace("<", "</"); // <startTag attr1=1 attr2=2> => </startTag>
            break;
        } else if(foundTagStart){
            tagChars.append(ch);
        }
    }
    // once start and end tag are calculated, print start tag, then content, then end tag
    if(foundTagStart){
        int startIndex = xmlText.indexOf(startTag);
        int endIndex = xmlText.indexOf(endTag);
        // handle if matching tags NOT found
        if((startIndex < 0) || (endIndex < 0)){
            if(startIndex < 0) {
                // no start tag found
                return;
            } else {
                // start tag found, no end tag found (handles single tags aka "<mytag/>" or "<?xml ...>")
                printTabs(index);
                System.out.println(startTag);
                // move on to the next tag
                // NOTE: "index" (not index+1) because next tag is on same level as this one
                prettyPrint(xmlText.substring(startIndex+startTag.length(), xmlText.length()), index);
                return;
            }
        // handle when matching tags found
        } else {
            String content = xmlText.substring(startIndex+startTag.length(), endIndex);
            boolean isTagContainsTags = content.contains("<"); // content contains tags
            printTabs(index);
            if(isTagContainsTags){ // ie: <tag1><tag2>stuff</tag2></tag1>
                System.out.println(startTag);
                prettyPrint(content, index+1); // "index+1" because "content" is nested
                printTabs(index);
            } else {
                System.out.print(startTag); // ie: <tag1>stuff</tag1> or <tag1></tag1>
                System.out.print(content);
            }
            System.out.println(endTag);
            int nextIndex = endIndex + endTag.length();
            if(xmlText.length() > nextIndex){ // if there are more tags on this level, continue
                prettyPrint(xmlText.substring(nextIndex, xmlText.length()), index);
            }
        }
    } else {
        System.out.print(xmlText);
    }
}

private static void printTabs(int counter){
    while(counter-- > 0){ 
        System.out.print("\t");
    }
}

1
Underscore-java,U.formatXml(xml) 也不依赖第三方库。 - Valentyn Kolesnikov
1
永远不要像这样手动解析XML。如果XML中包含注释或CDATA部分中的“<”字符,您的代码将破坏XML。 - Michael Kay

-1

试试这个:

 try
                    {
                        TransformerFactory transFactory = TransformerFactory.newInstance();
                        Transformer transformer = null;
                        transformer = transFactory.newTransformer();
                        StringWriter buffer = new StringWriter();
                        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
                        transformer.transform(new DOMSource(element),
                                  new StreamResult(buffer)); 
                        String str = buffer.toString();
                        System.out.println("XML INSIDE IS #########################################"+str);
                        return element;
                    }
                    catch (TransformerConfigurationException e)
                    {
                        e.printStackTrace();
                    }
                    catch (TransformerException e)
                    {
                        e.printStackTrace();
                    }

看不出与已发布的一些答案有什么不同。 - Olivier Cailloux
在这里构建DOM是不必要且低效的。请使用StreamSource。 - Michael Kay

-1

我曾试图实现类似的功能,但没有任何外部依赖。该应用程序已经使用DOM仅用于记录XML格式!

这是我的示例片段:

public void formatXML(final String unformattedXML) {
    final int length = unformattedXML.length();
    final int indentSpace = 3;
    final StringBuilder newString = new StringBuilder(length + length / 10);
    final char space = ' ';
    int i = 0;
    int indentCount = 0;
    char currentChar = unformattedXML.charAt(i++);
    char previousChar = currentChar;
    boolean nodeStarted = true;
    newString.append(currentChar);
    for (; i < length - 1;) {
        currentChar = unformattedXML.charAt(i++);
        if(((int) currentChar < 33) && !nodeStarted) {
            continue;
        }
        switch (currentChar) {
        case '<':
            if ('>' == previousChar && '/' != unformattedXML.charAt(i - 1) && '/' != unformattedXML.charAt(i) && '!' != unformattedXML.charAt(i)) {
                indentCount++;
            }
            newString.append(System.lineSeparator());
            for (int j = indentCount * indentSpace; j > 0; j--) {
                newString.append(space);
            }
            newString.append(currentChar);
            nodeStarted = true;
            break;
        case '>':
            newString.append(currentChar);
            nodeStarted = false;
            break;
        case '/':
            if ('<' == previousChar || '>' == unformattedXML.charAt(i)) {
                indentCount--;
            }
            newString.append(currentChar);
            break;
        default:
            newString.append(currentChar);
        }
        previousChar = currentChar;
    }
    newString.append(unformattedXML.charAt(length - 1));
    System.out.println(newString.toString());
}

它可以删除文本中的空格。例如:<text>\n some example lol\n<text> 转换后:<text>someexamplelol<test> - Maciej Pulikowski
是的,它还有其他缺陷,比如处理注释、DTD等。然而,在这方面进行更正后,我能够得到一个可接受的(除了像<text>一些复杂的<b>文本</b>再次出现,然后没有别的内容</text>这样的复杂元素)逻辑工作。现在手头没有代码,但会找些空闲时间重新编写。 - Faisal K
请说明这个解决方案如何改进已有的答案,否则它只会增加噪音。 - Olivier Cailloux

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