จะพิมพ์ XML จาก Java ได้อย่างไร?


443

ฉันมี Java String ที่มี XML โดยไม่มีการป้อนบรรทัดหรือการเยื้อง ฉันต้องการที่จะแปลงเป็นสตริงด้วย XML ที่จัดรูปแบบได้ ฉันจะทำสิ่งนี้ได้อย่างไร

String unformattedXml = "<tag><nested>hello</nested></tag>";
String formattedXml = new [UnknownClass]().format(unformattedXml);

หมายเหตุ: การป้อนข้อมูลของฉันเป็นสตริง การส่งออกของฉันเป็นสตริง

(พื้นฐาน) ผลการจำลอง:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <tag>
    <nested>hello</nested>
  </tag>
</root>

ตรวจสอบคำถามนี้: stackoverflow.com/questions/1264849/…
dfa

10
แค่อยากรู้อยากเห็นคุณกำลังส่งออกนี้ไปยังไฟล์ XML หรืออย่างอื่นที่เยื้องสำคัญจริงๆ? เมื่อก่อนฉันกังวลมากเกี่ยวกับการจัดรูปแบบ XML ของฉันเพื่อให้แสดงอย่างถูกต้อง ... แต่หลังจากใช้เวลาไปกับเรื่องนี้ฉันก็รู้ว่าฉันต้องส่งผลงานของฉันไปที่เว็บเบราว์เซอร์และเว็บเบราว์เซอร์ที่ค่อนข้างทันสมัย จะแสดง XML ในโครงสร้างแบบต้นไม้ที่ดีดังนั้นฉันสามารถลืมเกี่ยวกับปัญหานี้และดำเนินการต่อไป ฉันกำลังพูดถึงเรื่องนี้ในกรณีที่คุณ (หรือผู้ใช้รายอื่นที่มีปัญหาเดียวกัน) อาจมองข้ามรายละเอียดเดียวกัน
Abel Morelos

3
@Abel บันทึกไปยังไฟล์ข้อความแทรกเข้าไปใน textareas HTML และทิ้งไปยังคอนโซลเพื่อการดีบัก
Steve McLeod

2
"พักไว้กว้างเกินไป" - ยากที่จะแม่นยำกว่าคำถามที่เป็นอยู่ในปัจจุบัน!
Steve McLeod

คำตอบ:


266
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
//initialize StreamResult with File object to save to file
StreamResult result = new StreamResult(new StringWriter());
DOMSource source = new DOMSource(doc);
transformer.transform(source, result);
String xmlString = result.getWriter().toString();
System.out.println(xmlString);

หมายเหตุ: ผลลัพธ์อาจแตกต่างกันไปขึ้นอยู่กับรุ่น Java ค้นหาวิธีแก้ปัญหาเฉพาะสำหรับแพลตฟอร์มของคุณ


1
วิธีทำเพื่อให้ได้ผลลัพธ์ที่ไม่ได้มี<?xml version="1.0" encoding="UTF-8"?>?
Thang Pham

19
หากต้องการละเว้น<?xml ...>การประกาศให้เพิ่มtransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
rustyx

4
ผู้อ่านทั่วไปอาจพบว่ามีประโยชน์รุ่นปรับปรุงของโซลูชันที่อธิบายไว้ที่นี่ ( stackoverflow.com/a/33541820/363573 )
เตฟาน

5
ที่ถูกdocกำหนดไว้?
Florian F

6
สิ่งนี้ไม่ตอบคำถามของฉัน: ฉันจะจัดรูปแบบสตริงที่มี XML ได้อย่างไร คำตอบนี้ถือว่าคุณได้แปลงวัตถุ String เป็นวัตถุอื่นแล้ว
Steve McLeod

135

นี่คือคำตอบสำหรับคำถามของฉันเอง ฉันรวมคำตอบจากผลลัพธ์ต่าง ๆ เพื่อเขียนคลาสที่พิมพ์ XML สวย

ไม่มีการรับประกันว่าจะตอบสนองอย่างไรกับ XML ที่ไม่ถูกต้องหรือเอกสารขนาดใหญ่

package ecb.sdw.pretty;

import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;

/**
 * Pretty-prints xml, supplied as a string.
 * <p/>
 * eg.
 * <code>
 * String formattedXml = new XmlFormatter().format("<tag><nested>hello</nested></tag>");
 * </code>
 */
public class XmlFormatter {

    public XmlFormatter() {
    }

    public String format(String unformattedXml) {
        try {
            final Document document = parseXmlFile(unformattedXml);

            OutputFormat format = new OutputFormat(document);
            format.setLineWidth(65);
            format.setIndenting(true);
            format.setIndent(2);
            Writer out = new StringWriter();
            XMLSerializer serializer = new XMLSerializer(out, format);
            serializer.serialize(document);

            return out.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private Document parseXmlFile(String in) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            InputSource is = new InputSource(new StringReader(in));
            return db.parse(is);
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String unformattedXml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?><QueryMessage\n" +
                        "        xmlns=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message\"\n" +
                        "        xmlns:query=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query\">\n" +
                        "    <Query>\n" +
                        "        <query:CategorySchemeWhere>\n" +
                        "   \t\t\t\t\t         <query:AgencyID>ECB\n\n\n\n</query:AgencyID>\n" +
                        "        </query:CategorySchemeWhere>\n" +
                        "    </Query>\n\n\n\n\n" +
                        "</QueryMessage>";

        System.out.println(new XmlFormatter().format(unformattedXml));
    }

}

13
เพียงเพื่อให้ทราบว่าคำตอบนี้ต้องใช้ Xerces หากคุณไม่ต้องการเพิ่มการพึ่งพานี้คุณสามารถใช้ไลบรารี jdk มาตรฐานและ javax.xml.transform.Transformer (ดูคำตอบของฉันด้านล่าง)
khylo

45
ย้อนกลับไปในปี 2008 นี่เป็นคำตอบที่ดี แต่ตอนนี้สามารถทำได้ด้วยคลาส JDK มาตรฐานมากกว่าคลาส Apache ดูxerces.apache.org/xerces2-j/faq-general.html#faq-6 ใช่นี่เป็นคำถามที่พบบ่อย Xerces แต่คำตอบนั้นครอบคลุมคลาส JDK มาตรฐาน การเริ่มต้นใช้งาน 1.5 ของคลาสเหล่านี้มีปัญหามากมาย แต่ทุกอย่างทำงานได้ดีจาก 1.6 บน คัดลอกตัวอย่าง LSSerializer ใน FAQ สับบิต "... " และเพิ่มwriter.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);หลังLSSerializer writer = ...บรรทัด
George Hawkins

2
ฉันได้สร้างชั้นเรียนขนาดเล็กโดยใช้ตัวอย่างที่ Apache มอบให้ซึ่ง @GeorgeHawkins ให้ลิงก์ไป มันขาดวิธีการdocumentเริ่มต้นตัวแปรดังนั้นฉันคิดว่าฉันอาจเพิ่มในการชะลอตัวและทำตัวอย่างจากมันได้อย่างรวดเร็ว แจ้งให้เราทราบหากฉันควรเปลี่ยนแปลงบางอย่างpastebin.com/XL7932aC
samwell

ไม่เป็นความจริงที่คุณสามารถทำได้ด้วย jdk เท่านั้น อย่างน้อยก็ไม่น่าเชื่อถือ ขึ้นอยู่กับการใช้งานรีจิสตรีภายในที่ไม่ได้ทำงานกับ jdk7u72 ของฉันตามค่าเริ่มต้น ดังนั้นคุณยังควรใช้ของ apache โดยตรง
user1050755

นี่คือวิธีการแก้ปัญหาโดยไม่ต้องอ้างอิงใด ๆ : stackoverflow.com/a/33541820/363573
เตฟาน

131

ทางออกที่ง่ายกว่าโดยอิงจากคำตอบนี้ :

public static String prettyFormat(String input, int indent) {
    try {
        Source xmlInput = new StreamSource(new StringReader(input));
        StringWriter stringWriter = new StringWriter();
        StreamResult xmlOutput = new StreamResult(stringWriter);
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        transformerFactory.setAttribute("indent-number", indent);
        Transformer transformer = transformerFactory.newTransformer(); 
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.transform(xmlInput, xmlOutput);
        return xmlOutput.getWriter().toString();
    } catch (Exception e) {
        throw new RuntimeException(e); // simple exception handling, please review it
    }
}

public static String prettyFormat(String input) {
    return prettyFormat(input, 2);
}

testcase:

prettyFormat("<root><child>aaa</child><child/></root>");

ผลตอบแทน:

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <child>aaa</child>
  <child/>
</root>

1
นี่คือรหัสที่ฉันใช้เสมอ แต่ที่ บริษัท นี้ใช้ไม่ได้ฉันคิดว่าพวกเขากำลังใช้ไลบรารีการแปลง XML อื่น ฉันสร้างโรงงานเป็นบรรทัดแยกต่างหากจากนั้นก็ทำfactory.setAttribute("indent-number", 4);และตอนนี้ก็ใช้งานได้
Adrian Smith

วิธีทำเพื่อให้ได้ผลลัพธ์ที่ไม่ได้มี<?xml version="1.0" encoding="UTF-8"?>?
Thang Pham

4
@Harry:transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
jjmontes

5
สวัสดีฉันกำลังใช้รหัสที่แน่นอนนี้และรูปแบบการขุดของฉันถูกต้องยกเว้นองค์ประกอบแรกดังนั้นนี่<?xml version="1.0" encoding="UTF-8"?><root>คือทั้งหมดในบรรทัดเดียว ความคิดใดทำไม
CodyK

2
@Codemiester: ดูเหมือนว่าจะเป็นข้อบกพร่อง (ดูstackoverflow.com/a/18251901/3375325 ) การเพิ่มการtransformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");ทำงานสำหรับฉัน
jansohn

100

ตอนนี้เป็นปี 2012 และ Java สามารถทำได้มากกว่าที่เคยใช้กับ XML ฉันต้องการเพิ่มทางเลือกในคำตอบที่ฉันยอมรับ สิ่งนี้ไม่มีการพึ่งพาภายนอก Java 6

import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;

import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;

/**
 * Pretty-prints xml, supplied as a string.
 * <p/>
 * eg.
 * <code>
 * String formattedXml = new XmlFormatter().format("<tag><nested>hello</nested></tag>");
 * </code>
 */
public class XmlFormatter {

    public String format(String xml) {

        try {
            final InputSource src = new InputSource(new StringReader(xml));
            final Node document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src).getDocumentElement();
            final Boolean keepDeclaration = Boolean.valueOf(xml.startsWith("<?xml"));

        //May need this: System.setProperty(DOMImplementationRegistry.PROPERTY,"com.sun.org.apache.xerces.internal.dom.DOMImplementationSourceImpl");


            final DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
            final DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
            final LSSerializer writer = impl.createLSSerializer();

            writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); // Set this to true if the output needs to be beautified.
            writer.getDomConfig().setParameter("xml-declaration", keepDeclaration); // Set this to true if the declaration is needed to be outputted.

            return writer.writeToString(document);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String unformattedXml =
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?><QueryMessage\n" +
                        "        xmlns=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/message\"\n" +
                        "        xmlns:query=\"http://www.SDMX.org/resources/SDMXML/schemas/v2_0/query\">\n" +
                        "    <Query>\n" +
                        "        <query:CategorySchemeWhere>\n" +
                        "   \t\t\t\t\t         <query:AgencyID>ECB\n\n\n\n</query:AgencyID>\n" +
                        "        </query:CategorySchemeWhere>\n" +
                        "    </Query>\n\n\n\n\n" +
                        "</QueryMessage>";

        System.out.println(new XmlFormatter().format(unformattedXml));
    }
}

ไม่มีการเยื้อง แต่ทำงานได้กับสิ่งนี้: System.setProperty (DOMImplementationRegistry.PROPERTY, "com.sun.org.apache.xerces.internal.dom.DOMImplementationSourceImpl");
ggb667

1
คุณจะเพิ่มการเยื้องในตัวอย่างนี้ได้อย่างไร
ggb667

2
@DanTemple ดูเหมือนว่าคุณจะต้องใช้ LSOutput เพื่อควบคุมการเข้ารหัส ดูchipkillmar.net/2009/03/25/pretty-print-xml-from-a-dom
Joshua Davis

1
ฉันพยายามใช้สิ่งนี้ใน Andriod แต่ฉันไม่สามารถหาแพ็คเกจ `DOMImplementationRegistry ได้ ฉันใช้จาวา 8
Chintan Soni

2
ขอบคุณที่รวมรายการนำเข้าด้วยเช่นกันมีแพ็คเกจที่ขัดแย้งกันมากมายเพื่อให้เข้าใจถึงชุดค่าผสมที่จำเป็นอย่างอื่น ..
Leon

54

เพียงเพื่อให้ทราบว่าคำตอบที่ติดอันดับยอดนิยมจำเป็นต้องใช้ xerces

หากคุณไม่ต้องการเพิ่มการพึ่งพาภายนอกนี้คุณสามารถใช้ไลบรารี jdk มาตรฐาน (ซึ่งจริงๆแล้วสร้างขึ้นโดยใช้ xerces ภายใน)

NB มีข้อผิดพลาดกับ jdk รุ่น 1.5 ดูhttp://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446แต่ตอนนี้ได้รับการแก้ไขแล้ว,

(หมายเหตุหากเกิดข้อผิดพลาดสิ่งนี้จะส่งคืนข้อความต้นฉบับ)

package com.test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

public class XmlTest {
    public static void main(String[] args) {
        XmlTest t = new XmlTest();
        System.out.println(t.formatXml("<a><b><c/><d>text D</d><e value='0'/></b></a>"));
    }

    public String formatXml(String xml){
        try{
            Transformer serializer= SAXTransformerFactory.newInstance().newTransformer();
            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
            //serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            //serializer.setOutputProperty("{http://xml.customer.org/xslt}indent-amount", "2");
            Source xmlSource=new SAXSource(new InputSource(new ByteArrayInputStream(xml.getBytes())));
            StreamResult res =  new StreamResult(new ByteArrayOutputStream());            
            serializer.transform(xmlSource, res);
            return new String(((ByteArrayOutputStream)res.getOutputStream()).toByteArray());
        }catch(Exception e){
            //TODO log error
            return xml;
        }
    }

}

ในกรณีนี้แท็บด้านซ้ายจะไม่ถูกใช้ แท็กทั้งหมดเริ่มต้นที่สัญลักษณ์แรกของบรรทัดเช่นข้อความปกติ
Ruslan

คุณไม่จำเป็นต้องระบุชุดอักขระเมื่อแปลงไปมาระหว่างไบต์และสตริง?
Will Glass

2
ไม่จำเป็นต้องแปลงจากและเป็นอาร์เรย์ / ไบต์ อย่างน้อยที่สุดคุณจะต้องระบุชุดอักขระเมื่อทำเช่นนั้น ตัวเลือกที่ดีกว่าคือการใช้คลาส StringReader และ StringWriter ที่หุ้มใน InputSource และ StreamResult
maximdim

ไม่ทำงาน. คุณต้องยุ่งกับการใช้รีจิสทรีภายใน
user1050755

นี่คือความแตกต่างที่ง่ายกว่าของโซลูชันนี้: stackoverflow.com/a/33541820/363573
Stephan

32

ผมเคยพิมพ์สวยในอดีตที่ผ่านมาโดยใช้org.dom4j.io.OutputFormat.createPrettyPrint ()วิธีการ

public String prettyPrint(final String xml){  

    if (StringUtils.isBlank(xml)) {
        throw new RuntimeException("xml was null or blank in prettyPrint()");
    }

    final StringWriter sw;

    try {
        final OutputFormat format = OutputFormat.createPrettyPrint();
        final org.dom4j.Document document = DocumentHelper.parseText(xml);
        sw = new StringWriter();
        final XMLWriter writer = new XMLWriter(sw, format);
        writer.write(document);
    }
    catch (Exception e) {
        throw new RuntimeException("Error pretty printing xml:\n" + xml, e);
    }
    return sw.toString();
}

3
โซลูชันที่ยอมรับนั้นไม่ได้เยื้องแท็กที่ซ้อนกันในกรณีของฉันอย่างถูกต้อง
Chase Seibert

3
ฉันใช้สิ่งนี้ร่วมกับการลบช่องว่างท้ายท้ายบรรทัด:prettyPrintedString.replaceAll("\\s+\n", "\n")
jediz

19

นี่คือวิธีการทำโดยใช้dom4j :

การนำเข้า:

import org.dom4j.Document;  
import org.dom4j.DocumentHelper;  
import org.dom4j.io.OutputFormat;  
import org.dom4j.io.XMLWriter;

รหัส:

String xml = "<your xml='here'/>";  
Document doc = DocumentHelper.parseText(xml);  
StringWriter sw = new StringWriter();  
OutputFormat format = OutputFormat.createPrettyPrint();  
XMLWriter xw = new XMLWriter(sw, format);  
xw.write(doc);  
String result = sw.toString();

1
มันไม่ได้ผลสำหรับฉัน มันให้อะไรเช่น: <?xml version...ในบรรทัดเดียวและทุกอย่างอื่นในบรรทัดอื่น
sixtyfootersdude

14

เมื่อคุณเริ่มต้นด้วยStringคุณจะต้องแอบแฝงกับDOMวัตถุ (เช่นNode) Transformerก่อนที่คุณจะสามารถใช้ อย่างไรก็ตามหากคุณรู้ว่าสตริง XML ของคุณนั้นถูกต้องและคุณไม่ต้องการให้หน่วยความจำโอเวอร์เฮดของการแยกสตริงเป็น DOM แล้วเรียกใช้การแปลงผ่าน DOM เพื่อรับสตริงกลับ - คุณสามารถทำแบบเก่า ๆ ได้ ตัวละครโดยการแยกตัวละคร แทรกขึ้นบรรทัดใหม่และช่องว่างหลัง</...>ตัวละครทุกตัวรักษาและเยื้องเคาน์เตอร์ (เพื่อกำหนดจำนวนของช่องว่าง) ที่คุณเพิ่มขึ้นสำหรับทุกคน<...>และลดลงสำหรับทุกคนที่</...>คุณเห็น

ข้อจำกัดความรับผิดชอบ - ฉันแก้ไข / ตัด / ข้อความของฟังก์ชั่นด้านล่างดังนั้นพวกเขาจึงอาจไม่สามารถรวบรวมได้

public static final Element createDOM(String strXML) 
    throws ParserConfigurationException, SAXException, IOException {

    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setValidating(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    InputSource sourceXML = new InputSource(new StringReader(strXML));
    Document xmlDoc = db.parse(sourceXML);
    Element e = xmlDoc.getDocumentElement();
    e.normalize();
    return e;
}

public static final void prettyPrint(Node xml, OutputStream out)
    throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException {
    Transformer tf = TransformerFactory.newInstance().newTransformer();
    tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    tf.setOutputProperty(OutputKeys.INDENT, "yes");
    tf.transform(new DOMSource(xml), new StreamResult(out));
}

1
"อย่างไรก็ตามหากคุณรู้ว่าสตริง XML ของคุณถูกต้อง ... " จุดที่ดี ดูโซลูชันของฉันตามวิธีการด้านล่างนี้
David Easley

12

หากใช้ไลบรารี XML ของบุคคลที่สามก็โอเคคุณสามารถหลีกเลี่ยงสิ่งที่ง่ายกว่าสิ่งที่คำตอบที่ได้รับคะแนนสูงสุด ในปัจจุบันแนะนำ

มีการระบุไว้ว่าทั้งอินพุตและเอาต์พุตควรเป็น Strings ดังนั้นนี่คือวิธีการใช้งานยูทิลิตี้ที่ใช้กับไลบรารี่ XOM :

import nu.xom.*;
import java.io.*;

[...]

public static String format(String xml) throws ParsingException, IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    Serializer serializer = new Serializer(out);
    serializer.setIndent(4);  // or whatever you like
    serializer.write(new Builder().build(xml, ""));
    return out.toString("UTF-8");
}

ฉันทดสอบว่าใช้งานได้และผลลัพธ์ไม่ขึ้นอยู่กับรุ่น JRE ของคุณหรืออะไรทำนองนั้น หากต้องการดูวิธีกำหนดรูปแบบผลลัพธ์ตามที่คุณต้องการให้ดูที่SerializerAPI

อันนี้จริง ๆ แล้วออกมานานกว่าที่ฉันคิด - จำเป็นต้องมีบางบรรทัดพิเศษเพราะSerializerต้องการที่OutputStreamจะเขียนถึง แต่โปรดทราบว่ามีโค้ดน้อยมากสำหรับการ XML ที่ twiddling จริงที่นี่

(คำตอบนี้เป็นส่วนหนึ่งของการประเมินผลของฉัน XOM ซึ่งได้รับการแนะนำให้เป็นหนึ่งในตัวเลือกของฉันในคำถามเกี่ยวกับการที่ดีที่สุดห้องสมุด Java XMLเพื่อแทนที่ DOM4J สำหรับบันทึกที่มี DOM4J คุณสามารถบรรลุเป้าหมายนี้ได้อย่างง่ายดายคล้ายกันโดยใช้. XMLWriterและOutputFormat. แก้ไข : .. . ถูกแสดงในคำตอบของ mlo55 )


2
ขอบคุณนั่นคือสิ่งที่ฉันกำลังมองหา หากคุณมีการแยกวิเคราะห์ XML ด้วย XOM ในวัตถุ "เอกสาร" คุณสามารถส่งโดยตรงไปยัง serializer.write (เอกสาร);
Thibault D.

12

Kevin Hakanson กล่าวว่า: "อย่างไรก็ตามหากคุณรู้ว่าสตริง XML ของคุณถูกต้องและคุณไม่ต้องการให้หน่วยความจำโอเวอร์เฮดของการแยกสตริงเป็น DOM แล้วเรียกใช้การแปลงผ่าน DOM เพื่อรับสตริงกลับ - คุณสามารถ เพียงแค่ทำตัวละครแบบเก่า ๆ โดยการแยกวิเคราะห์อักขระขึ้นบรรทัดใหม่และช่องว่างหลังตัวละครทุกตัวรักษาและเยื้องเคาน์เตอร์ (เพื่อกำหนดจำนวนช่องว่าง) ที่คุณเพิ่มขึ้นสำหรับทุก <... > และลดลงสำหรับทุกคนที่คุณเห็น "

ตกลง วิธีการดังกล่าวเร็วกว่ามากและมีการพึ่งพาน้อยกว่ามาก

ตัวอย่างการแก้ปัญหา:

/**
 * XML utils, including formatting.
 */
public class XmlUtils
{
  private static XmlFormatter formatter = new XmlFormatter(2, 80);

  public static String formatXml(String s)
  {
    return formatter.format(s, 0);
  }

  public static String formatXml(String s, int initialIndent)
  {
    return formatter.format(s, initialIndent);
  }

  private static class XmlFormatter
  {
    private int indentNumChars;
    private int lineLength;
    private boolean singleLine;

    public XmlFormatter(int indentNumChars, int lineLength)
    {
      this.indentNumChars = indentNumChars;
      this.lineLength = lineLength;
    }

    public synchronized String format(String s, int initialIndent)
    {
      int indent = initialIndent;
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < s.length(); i++)
      {
        char currentChar = s.charAt(i);
        if (currentChar == '<')
        {
          char nextChar = s.charAt(i + 1);
          if (nextChar == '/')
            indent -= indentNumChars;
          if (!singleLine)   // Don't indent before closing element if we're creating opening and closing elements on a single line.
            sb.append(buildWhitespace(indent));
          if (nextChar != '?' && nextChar != '!' && nextChar != '/')
            indent += indentNumChars;
          singleLine = false;  // Reset flag.
        }
        sb.append(currentChar);
        if (currentChar == '>')
        {
          if (s.charAt(i - 1) == '/')
          {
            indent -= indentNumChars;
            sb.append("\n");
          }
          else
          {
            int nextStartElementPos = s.indexOf('<', i);
            if (nextStartElementPos > i + 1)
            {
              String textBetweenElements = s.substring(i + 1, nextStartElementPos);

              // If the space between elements is solely newlines, let them through to preserve additional newlines in source document.
              if (textBetweenElements.replaceAll("\n", "").length() == 0)
              {
                sb.append(textBetweenElements + "\n");
              }
              // Put tags and text on a single line if the text is short.
              else if (textBetweenElements.length() <= lineLength * 0.5)
              {
                sb.append(textBetweenElements);
                singleLine = true;
              }
              // For larger amounts of text, wrap lines to a maximum line length.
              else
              {
                sb.append("\n" + lineWrap(textBetweenElements, lineLength, indent, null) + "\n");
              }
              i = nextStartElementPos - 1;
            }
            else
            {
              sb.append("\n");
            }
          }
        }
      }
      return sb.toString();
    }
  }

  private static String buildWhitespace(int numChars)
  {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < numChars; i++)
      sb.append(" ");
    return sb.toString();
  }

  /**
   * Wraps the supplied text to the specified line length.
   * @lineLength the maximum length of each line in the returned string (not including indent if specified).
   * @indent optional number of whitespace characters to prepend to each line before the text.
   * @linePrefix optional string to append to the indent (before the text).
   * @returns the supplied text wrapped so that no line exceeds the specified line length + indent, optionally with
   * indent and prefix applied to each line.
   */
  private static String lineWrap(String s, int lineLength, Integer indent, String linePrefix)
  {
    if (s == null)
      return null;

    StringBuilder sb = new StringBuilder();
    int lineStartPos = 0;
    int lineEndPos;
    boolean firstLine = true;
    while(lineStartPos < s.length())
    {
      if (!firstLine)
        sb.append("\n");
      else
        firstLine = false;

      if (lineStartPos + lineLength > s.length())
        lineEndPos = s.length() - 1;
      else
      {
        lineEndPos = lineStartPos + lineLength - 1;
        while (lineEndPos > lineStartPos && (s.charAt(lineEndPos) != ' ' && s.charAt(lineEndPos) != '\t'))
          lineEndPos--;
      }
      sb.append(buildWhitespace(indent));
      if (linePrefix != null)
        sb.append(linePrefix);

      sb.append(s.substring(lineStartPos, lineEndPos + 1));
      lineStartPos = lineEndPos + 1;
    }
    return sb.toString();
  }

  // other utils removed for brevity
}

2
นี่คือวิธีที่ควรทำ จัดรูปแบบได้ทันทีในระดับสตริง นี่เป็นทางออกเดียวที่จะจัดรูปแบบ XML ที่ไม่ถูกต้องหรือไม่สมบูรณ์
Florian F

11

อืม ... ต้องเผชิญกับบางสิ่งเช่นนี้และเป็นข้อบกพร่องที่รู้จัก ... เพียงแค่เพิ่ม OutputProperty นี้

transformer.setOutputProperty(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "8");

หวังว่านี่จะช่วย ...


2
OutputPropertiesFactory นี้มาจากไหน
helenov

import com.sun.org.apache.xml.internal.serializer. *;
gaurav

9

เกี่ยวกับความคิดเห็นที่ "คุณจะต้องสร้างต้นไม้ DOM": ไม่คุณไม่ต้องการและไม่ควรทำเช่นนั้น

แต่ให้สร้าง StreamSource (StreamSource ใหม่ (StringReader ใหม่ (str))) แทนและป้อนไปยังหม้อแปลงข้อมูลประจำตัวที่กล่าวถึงซึ่งจะใช้ SAX parser และผลลัพธ์จะเร็วขึ้นมากการสร้างต้นไม้ขั้นกลางเป็นค่าใช้จ่ายที่บริสุทธิ์สำหรับกรณีนี้ มิฉะนั้นคำตอบที่ติดอันดับต้น ๆ ก็ดี


1
ฉันเห็นด้วยอย่างสุดใจ: การสร้างต้น DOM กลางนั้นเสียความทรงจำ ขอบคุณสำหรับคำตอบนั้น
Florian F

9

ใช้สกาล่า:

import xml._
val xml = XML.loadString("<tag><nested>hello</nested></tag>")
val formatted = new PrettyPrinter(150, 2).format(xml)
println(formatted)

คุณสามารถทำได้ใน Java เช่นกันหากคุณขึ้นอยู่กับ scala-library.jar ดูเหมือนว่านี้:

import scala.xml.*;

public class FormatXML {
    public static void main(String[] args) {
        String unformattedXml = "<tag><nested>hello</nested></tag>";
        PrettyPrinter pp = new PrettyPrinter(150, 3);
        String formatted = pp.format(XML.loadString(unformattedXml), TopScope$.MODULE$);
        System.out.println(formatted);
    }
}

PrettyPrinterวัตถุถูกสร้างด้วยสอง ints เป็นครั้งแรกความยาวสายสูงสุดและครั้งที่สองเป็นขั้นตอนการเยื้อง


9

รุ่นปรับปรุงเล็กน้อยจากmilosmns ...

public static String getPrettyXml(String xml) {
    if (xml == null || xml.trim().length() == 0) return "";

    int stack = 0;
    StringBuilder pretty = new StringBuilder();
    String[] rows = xml.trim().replaceAll(">", ">\n").replaceAll("<", "\n<").split("\n");

    for (int i = 0; i < rows.length; i++) {
        if (rows[i] == null || rows[i].trim().length() == 0) continue;

        String row = rows[i].trim();
        if (row.startsWith("<?")) {
            pretty.append(row + "\n");
        } else if (row.startsWith("</")) {
            String indent = repeatString(--stack);
            pretty.append(indent + row + "\n");
        } else if (row.startsWith("<") && row.endsWith("/>") == false) {
            String indent = repeatString(stack++);
            pretty.append(indent + row + "\n");
            if (row.endsWith("]]>")) stack--;
        } else {
            String indent = repeatString(stack);
            pretty.append(indent + row + "\n");
        }
    }

    return pretty.toString().trim();
}

private static String repeatString(int stack) {
     StringBuilder indent = new StringBuilder();
     for (int i = 0; i < stack; i++) {
        indent.append(" ");
     }
     return indent.toString();
} 

โดยที่ repeatString (สแต็ค ++); วิธี..?
user1912935

2
String คงที่สแตติกส่วนตัว (สแต็ก int) {StringBuilder เยื้อง = new StringBuilder ใหม่ (); สำหรับ (int i = 0; i <stack; i ++) {indent.append (""); } ส่งคืน indent.toString (); }
codeskraps

การเยื้องไม่ทำงานดีที่แท็กสิ้นสุดคุณต้องเปลี่ยน} else if (row.startsWith("</")) {ส่วนนี้:else if (row.startsWith("</")) { String indent = repeatIdent(--stack); if (pretty.charAt(pretty.length() - 1) == '\n') { pretty.append(indent + row + "\n"); } else { pretty.append(row + "\n"); } }
Csaba Tenkes

8

สำหรับการอ้างอิงในอนาคตนี่เป็นวิธีแก้ปัญหาที่เหมาะกับฉัน (ขอบคุณความคิดเห็นที่ @George Hawkins โพสต์ในหนึ่งในคำตอบ):

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
LSSerializer writer = impl.createLSSerializer();
writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
LSOutput output = impl.createLSOutput();
ByteArrayOutputStream out = new ByteArrayOutputStream();
output.setByteStream(out);
writer.write(document, output);
String xmlStr = new String(out.toByteArray());

6

หากคุณแน่ใจว่าคุณมี XML ที่ถูกต้องอันนี้จะง่ายและหลีกเลี่ยงต้นไม้ XML DOM อาจมีข้อบกพร่องบางอย่างแสดงความคิดเห็นถ้าคุณเห็นอะไร

public String prettyPrint(String xml) {
            if (xml == null || xml.trim().length() == 0) return "";

            int stack = 0;
            StringBuilder pretty = new StringBuilder();
            String[] rows = xml.trim().replaceAll(">", ">\n").replaceAll("<", "\n<").split("\n");

            for (int i = 0; i < rows.length; i++) {
                    if (rows[i] == null || rows[i].trim().length() == 0) continue;

                    String row = rows[i].trim();
                    if (row.startsWith("<?")) {
                            // xml version tag
                            pretty.append(row + "\n");
                    } else if (row.startsWith("</")) {
                            // closing tag
                            String indent = repeatString("    ", --stack);
                            pretty.append(indent + row + "\n");
                    } else if (row.startsWith("<")) {
                            // starting tag
                            String indent = repeatString("    ", stack++);
                            pretty.append(indent + row + "\n");
                    } else {
                            // tag data
                            String indent = repeatString("    ", stack);
                            pretty.append(indent + row + "\n");
                    }
            }

            return pretty.toString().trim();
    }

2
ซึ่งเป็นวิธีการ repeatString .. ?
user1912935

3
String คงที่สแตติกส่วนตัว (สแต็ก int) {StringBuilder เยื้อง = new StringBuilder ใหม่ (); สำหรับ (int i = 0; i <stack; i ++) {indent.append (""); } ส่งคืน indent.toString (); }
codeskraps

ใช่ [user1912935] สิ่งที่ @codeskraps เขียนนั้นควรจะง่ายพอ :)
milosmns

การต่อข้อมูลกับ StringBuilder ภายในลูป: ฝึกไม่ดี
james.garriss

@ james.garriss แต่มันง่ายมากที่จะแยกเป็นบรรทัดใหม่นี่แสดงให้เห็นถึงวิธีการง่ายๆโดยไม่ต้องมีต้นไม้ DOM
milosmns

5

โซลูชันด้านบนทั้งหมดใช้ไม่ได้สำหรับฉันจากนั้นฉันพบhttp://myshittycode.com/2014/02/10/java-properly-indenting-xml-string/

เบาะแสคือการลบช่องว่างด้วย XPath

    String xml = "<root>" +
             "\n   " +
             "\n<name>Coco Puff</name>" +
             "\n        <total>10</total>    </root>";

try {
    Document document = DocumentBuilderFactory.newInstance()
            .newDocumentBuilder()
            .parse(new InputSource(new ByteArrayInputStream(xml.getBytes("utf-8"))));

    XPath xPath = XPathFactory.newInstance().newXPath();
    NodeList nodeList = (NodeList) xPath.evaluate("//text()[normalize-space()='']",
                                                  document,
                                                  XPathConstants.NODESET);

    for (int i = 0; i < nodeList.getLength(); ++i) {
        Node node = nodeList.item(i);
        node.getParentNode().removeChild(node);
    }

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

    StringWriter stringWriter = new StringWriter();
    StreamResult streamResult = new StreamResult(stringWriter);

    transformer.transform(new DOMSource(document), streamResult);

    System.out.println(stringWriter.toString());
}
catch (Exception e) {
    e.printStackTrace();
}

1
โปรดทราบว่าการใช้คุณสมบัติ '{ xml.apache.org/xslt } การเยื้องจำนวน' จะผูกคุณกับการใช้งานหม้อแปลงเฉพาะ
vallismortis

1
จากการแก้ปัญหาทั้งหมดนี้ได้ผลดีที่สุด ฉันมีช่องว่างและบรรทัดใหม่ใน XML ของฉันอยู่แล้วและฉันไม่ต้องการเพิ่มการอ้างอิงเพิ่มเติมในโครงการของฉัน ฉันหวังว่าฉันไม่จำเป็นต้องแยกวิเคราะห์ XML แต่ก็ดี
Fabio

5

รหัสด้านล่างนี้ทำงานได้อย่างสมบูรณ์

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

String formattedXml1 = prettyFormat("<root><child>aaa</child><child/></root>");

public static String prettyFormat(String input) {
    return prettyFormat(input, "2");
}

public static String prettyFormat(String input, String indent) {
    Source xmlInput = new StreamSource(new StringReader(input));
    StringWriter stringWriter = new StringWriter();
    try {
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", indent);
        transformer.transform(xmlInput, new StreamResult(stringWriter));

        String pretty = stringWriter.toString();
        pretty = pretty.replace("\r\n", "\n");
        return pretty;              
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

5

ฉันผสมทั้งหมดและเขียนโปรแกรมเล็ก ๆ กำลังอ่านจากไฟล์ xml และพิมพ์ออกมา Just แทนที่จะเป็น xzy ให้พา ธ ไฟล์ของคุณ

    public static void main(String[] args) throws Exception {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    dbf.setValidating(false);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document doc = db.parse(new FileInputStream(new File("C:/Users/xyz.xml")));
    prettyPrint(doc);

}

private static String prettyPrint(Document document)
        throws TransformerException {
    TransformerFactory transformerFactory = TransformerFactory
            .newInstance();
    Transformer transformer = transformerFactory.newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
    transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
    DOMSource source = new DOMSource(document);
    StringWriter strWriter = new StringWriter();
    StreamResult result = new StreamResult(strWriter);transformer.transform(source, result);
    System.out.println(strWriter.getBuffer().toString());

    return strWriter.getBuffer().toString();

}

4

เป็นอีกทางเลือกที่เหมาะกับเรา

import java.io.StringWriter;
import org.dom4j.DocumentHelper;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;

**
 * Pretty Print XML String
 * 
 * @param inputXmlString
 * @return
 */
public static String prettyPrintXml(String xml) {

    final StringWriter sw;

    try {
        final OutputFormat format = OutputFormat.createPrettyPrint();
        final org.dom4j.Document document = DocumentHelper.parseText(xml);
        sw = new StringWriter();
        final XMLWriter writer = new XMLWriter(sw, format);
        writer.write(document);
    }
    catch (Exception e) {
        throw new RuntimeException("Error pretty printing xml:\n" + xml, e);
    }
    return sw.toString();
}

3

ใช้ jdom2: http://www.jdom.org/

import java.io.StringReader;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

String prettyXml = new XMLOutputter(Format.getPrettyFormat()).
                         outputString(new SAXBuilder().build(new StringReader(uglyXml)));

3

อีกทางเลือกหนึ่งสำหรับคำตอบจากmax , codeskraps , David Easleyและmilosmnsได้ดูไลบรารี่ของเครื่องพิมพ์สวยน้ำหนักเบาและมีประสิทธิภาพสูงของฉัน: xml-formatter

// construct lightweight, threadsafe, instance
PrettyPrinter prettyPrinter = PrettyPrinterBuilder.newPrettyPrinter().build();

StringBuilder buffer = new StringBuilder();
String xml = ..; // also works with char[] or Reader

if(prettyPrinter.process(xml, buffer)) {
     // valid XML, print buffer
} else {
     // invalid XML, print xml
}

บางครั้งเช่นเมื่อเรียกใช้บริการ SOAP ที่เยาะเย้ยโดยตรงจากไฟล์มันเป็นการดีที่จะมีเครื่องพิมพ์ที่สวยซึ่งจัดการ XML ที่พิมพ์แล้วสวย:

PrettyPrinter prettyPrinter = PrettyPrinterBuilder.newPrettyPrinter().ignoreWhitespace().build();

ในขณะที่บางคนแสดงความคิดเห็นการพิมพ์ที่สวยงามนั้นเป็นเพียงวิธีการนำเสนอ XML ในรูปแบบที่มนุษย์อ่านได้มากขึ้นช่องว่างอย่างเคร่งครัดไม่ได้อยู่ในข้อมูล XML ของคุณ

ไลบรารีมีไว้สำหรับการพิมพ์แบบสวยเพื่อจุดประสงค์ในการบันทึกและยังมีฟังก์ชั่นสำหรับการกรอง (การลบทรีย่อย / การลบแบบไม่ระบุชื่อ) และการพิมพ์แบบสวยของ XML ในโหนด CDATA และ Text


2

ฉันมีปัญหาเดียวกันและฉันประสบความสำเร็จอย่างมากกับ JTidy ( http://jtidy.sourceforge.net/index.html )

ตัวอย่าง:

Tidy t = new Tidy();
t.setIndentContent(true);
Document d = t.parseDOM(
    new ByteArrayInputStream("HTML goes here", null);

OutputStream out = new ByteArrayOutputStream();
t.pprint(d, out);
String html = out.toString();

2

ขีด-JavaU.formatXml(string)มีวิธีการคง ฉันเป็นผู้ดูแลโครงการ ตัวอย่างสด

import com.github.underscore.lodash.U;

public class MyClass {
    public static void main(String args[]) {
        String xml = "<tag><nested>hello</nested></tag>";

        System.out.println(U.formatXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>" + xml + "</root>"));
    }
}

เอาท์พุท:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <tag>
      <nested>hello</nested>
   </tag>
</root>

นี่มันเจ๋งมาก!
senyor

1

มีอรรถประโยชน์บรรทัดคำสั่ง xml ที่ดีมากชื่อ xmlstarlet ( http://xmlstar.sourceforge.net/ ) ที่สามารถทำสิ่งต่างๆมากมายซึ่งผู้คนจำนวนมากใช้

คุณสามารถรันโปรแกรมนี้โดยใช้โปรแกรม Runtime.exec แล้วอ่านไฟล์เอาต์พุตที่ฟอร์แมตแล้ว มีตัวเลือกเพิ่มเติมและการรายงานข้อผิดพลาดที่ดีกว่าโค้ด Java สองสามบรรทัดที่สามารถให้ได้

ดาวน์โหลด xmlstarlet: http://sourceforge.net/project/showfiles.php?group_id=66612&package_id=64589


1

ฉันได้พบว่าใน Java 1.6.0_32 วิธีปกติในการพิมพ์สตริง XML (การใช้ Transformer ที่มีค่า null หรือ identity xslt) ไม่ทำงานตามที่ฉันต้องการหากแท็กแยกด้วยช่องว่างโดยไม่ต้องแยก ข้อความ ฉันลองใช้<xsl:strip-space elements="*"/>ในแม่แบบของฉันเพื่อประโยชน์ วิธีแก้ปัญหาที่ง่ายที่สุดที่ฉันพบคือการตัดพื้นที่ตามที่ฉันต้องการโดยใช้ตัวกรอง SAXSource และ XML ตั้งแต่โซลูชันของฉันสำหรับบันทึกฉันยังขยายสิ่งนี้เพื่อทำงานกับแฟรกเมนต์ XML ที่ไม่สมบูรณ์ โปรดสังเกตว่าวิธีการปกติทำงานได้ดีถ้าคุณใช้ DOMSource แต่ฉันไม่ต้องการใช้สิ่งนี้เนื่องจากความไม่สมบูรณ์และค่าใช้จ่ายในหน่วยความจำ

public static class WhitespaceIgnoreFilter extends XMLFilterImpl
{

    @Override
    public void ignorableWhitespace(char[] arg0,
                                    int arg1,
                                    int arg2) throws SAXException
    {
        //Ignore it then...
    }

    @Override
    public void characters( char[] ch,
                            int start,
                            int length) throws SAXException
    {
        if (!new String(ch, start, length).trim().equals("")) 
               super.characters(ch, start, length); 
    }
}

public static String prettyXML(String logMsg, boolean allowBadlyFormedFragments) throws SAXException, IOException, TransformerException
    {
        TransformerFactory transFactory = TransformerFactory.newInstance();
        transFactory.setAttribute("indent-number", new Integer(2));
        Transformer transformer = transFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
        StringWriter out = new StringWriter();
        XMLReader masterParser = SAXHelper.getSAXParser(true);
        XMLFilter parser = new WhitespaceIgnoreFilter();
        parser.setParent(masterParser);

        if(allowBadlyFormedFragments)
        {
            transformer.setErrorListener(new ErrorListener()
            {
                @Override
                public void warning(TransformerException exception) throws TransformerException
                {
                }

                @Override
                public void fatalError(TransformerException exception) throws TransformerException
                {
                }

                @Override
                public void error(TransformerException exception) throws TransformerException
                {
                }
            });
        }

        try
        {
            transformer.transform(new SAXSource(parser, new InputSource(new StringReader(logMsg))), new StreamResult(out));
        }
        catch (TransformerException e)
        {
            if(e.getCause() != null && e.getCause() instanceof SAXParseException)
            {
                if(!allowBadlyFormedFragments || !"XML document structures must start and end within the same entity.".equals(e.getCause().getMessage()))
                {
                    throw e;
                }
            }
            else
            {
                throw e;
            }
        }
        out.flush();
        return out.toString();
    }

1

โซลูชันที่ฉันพบที่นี่สำหรับ Java 1.6+ ไม่ทำการฟอร์แมตโค้ดใหม่หากมีการฟอร์แมตแล้ว สิ่งที่เหมาะกับฉัน (และฟอร์แมตโค้ดใหม่แล้ว) คือสิ่งต่อไปนี้

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.Canonicalizer;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.w3c.dom.Element;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.IOException;
import java.io.StringReader;

public class XmlUtils {
    public static String toCanonicalXml(String xml) throws InvalidCanonicalizerException, ParserConfigurationException, SAXException, CanonicalizationException, IOException {
        Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
        byte canonXmlBytes[] = canon.canonicalize(xml.getBytes());
        return new String(canonXmlBytes);
    }

    public static String prettyFormat(String input) throws TransformerException, ParserConfigurationException, IOException, SAXException, InstantiationException, IllegalAccessException, ClassNotFoundException {
        InputSource src = new InputSource(new StringReader(input));
        Element document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src).getDocumentElement();
        Boolean keepDeclaration = input.startsWith("<?xml");
        DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
        DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
        LSSerializer writer = impl.createLSSerializer();
        writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);
        writer.getDomConfig().setParameter("xml-declaration", keepDeclaration);
        return writer.writeToString(document);
    }
}

มันเป็นเครื่องมือที่ดีที่จะใช้ในการทดสอบหน่วยของคุณสำหรับการเปรียบเทียบ xml แบบเต็มสตริง

private void assertXMLEqual(String expected, String actual) throws ParserConfigurationException, IOException, SAXException, CanonicalizationException, InvalidCanonicalizerException, TransformerException, IllegalAccessException, ClassNotFoundException, InstantiationException {
    String canonicalExpected = prettyFormat(toCanonicalXml(expected));
    String canonicalActual = prettyFormat(toCanonicalXml(actual));
    assertEquals(canonicalExpected, canonicalActual);
}

1

สำหรับผู้ที่ค้นหาวิธีแก้ปัญหาที่รวดเร็วและสกปรก - ซึ่งไม่ต้องการให้ XML ใช้งานได้จริง 100% เช่นในกรณีของการบันทึก REST / SOAP (คุณไม่มีทางรู้ว่าคนอื่นส่งอะไร ;-))

ฉันพบรหัสขั้นสูงแล้วฉันพบว่าออนไลน์ซึ่งฉันคิดว่ายังขาดหายไปที่นี่เนื่องจากวิธีการที่เป็นไปได้:

public static String prettyPrintXMLAsString(String xmlString) {
    /* Remove new lines */
    final String LINE_BREAK = "\n";
    xmlString = xmlString.replaceAll(LINE_BREAK, "");
    StringBuffer prettyPrintXml = new StringBuffer();
    /* Group the xml tags */
    Pattern pattern = Pattern.compile("(<[^/][^>]+>)?([^<]*)(</[^>]+>)?(<[^/][^>]+/>)?");
    Matcher matcher = pattern.matcher(xmlString);
    int tabCount = 0;
    while (matcher.find()) {
        String str1 = (null == matcher.group(1) || "null".equals(matcher.group())) ? "" : matcher.group(1);
        String str2 = (null == matcher.group(2) || "null".equals(matcher.group())) ? "" : matcher.group(2);
        String str3 = (null == matcher.group(3) || "null".equals(matcher.group())) ? "" : matcher.group(3);
        String str4 = (null == matcher.group(4) || "null".equals(matcher.group())) ? "" : matcher.group(4);

        if (matcher.group() != null && !matcher.group().trim().equals("")) {
            printTabs(tabCount, prettyPrintXml);
            if (!str1.equals("") && str3.equals("")) {
                ++tabCount;
            }
            if (str1.equals("") && !str3.equals("")) {
                --tabCount;
                prettyPrintXml.deleteCharAt(prettyPrintXml.length() - 1);
            }

            prettyPrintXml.append(str1);
            prettyPrintXml.append(str2);
            prettyPrintXml.append(str3);
            if (!str4.equals("")) {
                prettyPrintXml.append(LINE_BREAK);
                printTabs(tabCount, prettyPrintXml);
                prettyPrintXml.append(str4);
            }
            prettyPrintXml.append(LINE_BREAK);
        }
    }
    return prettyPrintXml.toString();
}

private static void printTabs(int count, StringBuffer stringBuffer) {
    for (int i = 0; i < count; i++) {
        stringBuffer.append("\t");
    }
}

public static void main(String[] args) {
    String x = new String(
            "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><soap:Fault><faultcode>soap:Client</faultcode><faultstring>INVALID_MESSAGE</faultstring><detail><ns3:XcbSoapFault xmlns=\"\" xmlns:ns3=\"http://www.someapp.eu/xcb/types/xcb/v1\"><CauseCode>20007</CauseCode><CauseText>INVALID_MESSAGE</CauseText><DebugInfo>Problems creating SAAJ object model</DebugInfo></ns3:XcbSoapFault></detail></soap:Fault></soap:Body></soap:Envelope>");
    System.out.println(prettyPrintXMLAsString(x));
}

นี่คือผลลัพธ์:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <soap:Fault>
        <faultcode>soap:Client</faultcode>
        <faultstring>INVALID_MESSAGE</faultstring>
        <detail>
            <ns3:XcbSoapFault xmlns="" xmlns:ns3="http://www.someapp.eu/xcb/types/xcb/v1">
                <CauseCode>20007</CauseCode>
                <CauseText>INVALID_MESSAGE</CauseText>
                <DebugInfo>Problems creating SAAJ object model</DebugInfo>
            </ns3:XcbSoapFault>
        </detail>
    </soap:Fault>
  </soap:Body>
</soap:Envelope>

1

ฉันเห็นคำตอบหนึ่งข้อใช้Scalaดังนั้นนี่คือคำตอบอีกกรณีหนึ่งGroovyในกรณีที่มีคนพบว่ามันน่าสนใจ การเยื้องเริ่มต้นคือ 2 ขั้นตอนตัวXmlNodePrinterสร้างสามารถส่งผ่านค่าอื่นเช่นกัน

def xml = "<tag><nested>hello</nested></tag>"
def stringWriter = new StringWriter()
def node = new XmlParser().parseText(xml);
new XmlNodePrinter(new PrintWriter(stringWriter)).print(node)
println stringWriter.toString()

การใช้งานจาก Java ถ้า jar ของ groovy อยู่ใน classpath

  String xml = "<tag><nested>hello</nested></tag>";
  StringWriter stringWriter = new StringWriter();
  Node node = new XmlParser().parseText(xml);
  new XmlNodePrinter(new PrintWriter(stringWriter)).print(node);
  System.out.println(stringWriter.toString());

1

ในกรณีที่คุณไม่ต้องการการเยื้องที่มาก แต่เพียงไม่กี่บรรทัดการแบ่งก็อาจเพียงพอที่จะเพียงแค่ regex ...

String leastPrettifiedXml = uglyXml.replaceAll("><", ">\n<");

รหัสนี้ดีไม่ใช่ผลลัพธ์เนื่องจากการเยื้องที่ขาดหายไป


(สำหรับวิธีแก้ปัญหาด้วยการเยื้องดูคำตอบอื่น ๆ )


1
อืม ... แค่คิดดังใครจะต้องแก้ปัญหาแบบนั้นเหรอ? สิ่งเดียวที่ฉันเห็นคือข้อมูลที่เราได้รับจากบริการบนเว็บและเพื่อทดสอบข้อมูลและความถูกต้องของผู้พัฒนาหรือผู้ทดสอบอาจต้องการสิ่งที่ง่ายเช่นนั้น มิฉะนั้นจะไม่ใช่ตัวเลือกที่ดี ....
Sudhakar Chavali

1
@SudhakarChavali ฉันเป็นผู้พัฒนา ฉันอาจต้องการสิ่งนั้นสำหรับ println สกปรก () และ log.debug () แฮ็ก; นั่นคือบางครั้งฉันสามารถใช้ไฟล์บันทึกจากภายในสภาพแวดล้อมเซิร์ฟเวอร์ที่ จำกัด (ด้วยส่วนต่อประสานผู้ดูแลเว็บแทนการเข้าถึงเชลล์) แทนโปรแกรมที่มีการแก้ไขข้อบกพร่องทีละขั้นตอน
comonad
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.