Como imprimir bastante XML de Java?


442

Eu tenho uma String Java que contém XML, sem feeds de linha ou recuos. Gostaria de transformá-lo em uma String com XML bem formatado. Como eu faço isso?

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

Nota: Minha entrada é uma String . Minha saída é uma String .

Resultado simulado (básico):

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

verifique esta pergunta: stackoverflow.com/questions/1264849/…
dfa

10
Apenas curioso, você está enviando essa saída para um arquivo XML ou outra coisa em que o recuo realmente importa? Algum tempo atrás, eu estava muito preocupado com a formatação do meu XML para exibi-lo corretamente ... mas, depois de passar muito tempo nisso, percebi que tinha que enviar minha saída para um navegador da Web e qualquer navegador da Web relativamente moderno realmente exibirá o XML em uma boa estrutura em árvore, para que eu possa esquecer esse problema e seguir em frente. Estou mencionando isso apenas no caso de você (ou outro usuário com o mesmo problema) ter esquecido os mesmos detalhes.
Abel Morelos

3
@Abel, salvando em arquivos de texto, inserindo em áreas de texto HTML e despejando no console para fins de depuração.
Steve McLeod

2
"colocar em espera como muito amplo" - é difícil ser mais preciso do que a pergunta atualmente é!
Steve McLeod

Respostas:


265
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);

Nota: Os resultados podem variar dependendo da versão do Java. Pesquise soluções alternativas específicas para sua plataforma.


1
Como fazer para que a saída não contenha <?xml version="1.0" encoding="UTF-8"?>?
Thang Pham

19
Para omitir a <?xml ...>declaração, adicionetransformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
rustyx 25/08

4
Os leitores casuais podem achar útil uma versão aprimorada da solução descrita aqui ( stackoverflow.com/a/33541820/363573 ).
Stephan

5
onde é docdefinido?
Florian F

6
Isso não responde à minha pergunta: como formatar uma String que contém XML? Esta resposta já pressupõe que você de alguma forma converteu o objeto String em outro objeto.
Steve McLeod

135

Aqui está uma resposta para minha própria pergunta. Combinei as respostas dos vários resultados para escrever uma classe que imprime bastante XML.

Não há garantias de como responde com XML inválido ou documentos grandes.

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
Apenas observe que esta resposta requer o uso do Xerces. Se você não quiser adicionar essa dependência, em seguida, você pode simplesmente usar as bibliotecas do JDK padrão e javax.xml.transform.Transformer (ver minha resposta abaixo)
khylo

45
Em 2008, essa era uma boa resposta, mas agora tudo isso pode ser feito com classes JDK padrão, e não com classes Apache. Consulte xerces.apache.org/xerces2-j/faq-general.html#faq-6 . Sim, esta é uma FAQ do Xerces, mas a resposta abrange as classes JDK padrão. A implementação inicial 1.5 dessas classes teve muitos problemas, mas tudo funciona bem a partir da 1.6. Copie o exemplo LSSerializer no FAQ, pique o bit "..." e adicione writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE);depois da LSSerializer writer = ...linha.
George Hawkins

2
Eu criei uma classe pequena usando o exemplo que o Apache deu, ao qual @GeorgeHawkins deu um link. Faltava como a variável documentfoi inicializada, então pensei em adicionar a desaceleração e fazer um rápido exemplo disso. Deixe-me saber se eu deveria mudar alguma coisa, pastebin.com/XL7932aC
Samwell

não é verdade que você pode fazer isso apenas com o jdk. pelo menos não de forma confiável. depende de alguma implementação de registro interno que não esteja ativa com meu jdk7u72 por padrão. então é melhor você usar diretamente o material apache.
user1050755

Aqui está uma solução sem nenhuma dependência: stackoverflow.com/a/33541820/363573 .
Stephan

131

uma solução mais simples com base nesta resposta :

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);
}

caso de teste:

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

retorna:

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

1
Este é o código que eu sempre usei, mas nesta empresa não funcionou, presumo que eles estejam usando outra biblioteca de transformação XML. Eu criei a fábrica como uma linha separada e depois criei factory.setAttribute("indent-number", 4);e agora funciona.
Adrian Smith

Como fazer para que a saída não contenha <?xml version="1.0" encoding="UTF-8"?>?
Thang Pham

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

5
Oi, eu estou usando esse código exato, e os meus formatos corretamente, com exceção do primeiro elemento Então, este: <?xml version="1.0" encoding="UTF-8"?><root>é tudo em uma linha. Alguma idéia do porquê?
CodyK

2
@ Codemiester: Parece ser um bug (consulte stackoverflow.com/a/18251901/3375325 ). Adicionando transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");funcionou para mim.
jansohn

100

Agora é 2012 e o Java pode fazer mais do que costumava fazer com XML, gostaria de adicionar uma alternativa à minha resposta aceita. Isso não tem dependências fora do 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));
    }
}

Sem indentação, mas funciona com isso: System.setProperty (DOMImplementationRegistry.PROPERTY, "com.sun.org.apache.xerces.internal.dom.DOMImplementationSourceImpl");
ggb667

1
Como você adiciona recuo neste exemplo?
ggb667

2
@ DanTemple Parece que você precisa usar o LSOutput para controlar a codificação. Veja chipkillmar.net/2009/03/25/pretty-print-xml-from-a-dom
Joshua Davis

1
Eu tentei usar isso no Andriod, mas não consigo encontrar o pacote `DOMImplementationRegistry. Eu estou usando java 8.
Chintan Soni

2
obrigado por incluindo a lista de importação, bem como, tantos pacotes conflitantes disponíveis para dar sentido a combinação necessária de outra forma ..
Leon

54

Apenas observe que a resposta mais bem avaliada requer o uso de xerces.

Se você não deseja adicionar essa dependência externa, pode simplesmente usar as bibliotecas jdk padrão (que na verdade são criadas usando xerces internamente).

NB: Houve um erro na versão 1.5 do jdk, consulte http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6296446, mas está resolvido agora.,

(Observe que, se ocorrer um erro, isso retornará o texto original)

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;
        }
    }

}

Nesse caso, as guias esquerdas não são usadas. Todas as tags começam no primeiro símbolo da linha, como texto usual.
Ruslan

você não precisa especificar um conjunto de caracteres ao converter entre bytes e string?
Will Glass

2
Não deve ser necessário converter de e para matrizes de bytes / String. No mínimo, você precisaria especificar charset ao fazer isso. A melhor opção seria usar as classes StringReader e StringWriter agrupadas em InputSource e StreamResult.
usar o seguinte comando

não está funcionando. você precisa mexer com alguma implementação de registro interno.
user1050755

Aqui está uma variante mais simples desta solução: stackoverflow.com/a/33541820/363573
Stephan

32

Imprimi no passado usando o método 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
A solução aceita não recua adequadamente as tags aninhadas no meu caso, essa é a mesma.
Chase Seibert

3
I utilizado este em conjunto com a remoção de todos os espaços à direita no final de linhas:prettyPrintedString.replaceAll("\\s+\n", "\n")
jediz

19

Aqui está uma maneira de fazê-lo usando dom4j :

Importações:

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

Código:

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
Isso não funcionou para mim. Apenas dava algo como: <?xml version...em uma linha e tudo mais em outra linha.
Sixtyfootersdude

14

Desde que você está começando com a String, é necessário ocultar um DOMobjeto (por exemplo Node) antes de poder usar o Transformer. No entanto, se você souber que sua string XML é válida e não desejar incorrer na sobrecarga de memória ao analisar uma string em um DOM, execute uma transformação no DOM para recuperar uma string - você pode fazer algo antiquado caractere por análise de caractere. Insira uma nova linha e espaços após todos os </...>caracteres, mantenha e recue o contador (para determinar o número de espaços) que você incrementa para todos <...>e diminui para todos os </...>que vê.

Isenção de responsabilidade - fiz uma edição de recortar / colar / texto das funções abaixo, para que elas não sejam compiladas como estão.

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
"No entanto, se você souber que sua seqüência de caracteres XML é válida ..." bom ponto. Veja minha solução com base nesta abordagem abaixo.
David Easley 27/05

12

Se o uso de uma biblioteca XML de terceiros estiver correto, você poderá obter algo significativamente mais simples do que o sugerido pelas respostas mais votadas no momento .

Foi declarado que entrada e saída devem ser Strings, então aqui está um método utilitário que faz exatamente isso, implementado com a biblioteca 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");
}

Eu testei que funciona, e os resultados não dependem da sua versão do JRE ou de algo parecido. Para ver como personalizar o formato de saída ao seu gosto, dê uma olhada na SerializerAPI.

Na verdade, isso saiu por mais tempo do que eu pensava - algumas linhas extras eram necessárias porque Serializerquer uma OutputStreamescrita. Mas observe que há muito pouco código para o XML real girando aqui.

(Esta resposta faz parte da minha avaliação do XOM, que foi sugerida como uma opção na minha pergunta sobre a melhor biblioteca Java XML para substituir o dom4j. Para o registro, com o dom4j, você pode conseguir isso com uma facilidade semelhante usando XMLWritere OutputFormat. Edit : .. . como demonstrado na resposta do mlo55 .)


2
Obrigado, era o que eu estava procurando. Se você já possui um XML analisado com o XOM em um objeto "Documento", pode passá-lo diretamente para serializer.write (document);
Thiagoult D.

12

Kevin Hakanson disse: "No entanto, se você sabe que sua string XML é válida e não deseja incorrer na sobrecarga de memória de analisar uma string em um DOM, execute uma transformação no DOM para recuperar uma string - você pode basta fazer algum caractere antiquado pela análise de caracteres. Insira uma nova linha e espaços após todos os caracteres, mantenha e recue o contador (para determinar o número de espaços) que você incrementa a cada <...> e decrementa a cada exibição. "

Acordado. Essa abordagem é muito mais rápida e possui muito menos dependências.

Solução de exemplo:

/**
 * 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
É assim que deve ser feito. Formate em tempo real no nível da string. Essa é a única solução que formatará XML inválido ou incompleto.
Florian F

11

Hmmm ... enfrentou algo assim e é um bug conhecido ... basta adicionar este OutputProperty ..

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

Espero que isto ajude ...


2
De onde vem este OutputPropertiesFactory?
helenov

importar com.sun.org.apache.xml.internal.serializer. *;
gaurav 7/02

9

Sobre o comentário de que "você deve primeiro construir uma árvore DOM": Não, você não precisa e não deve fazer isso.

Em vez disso, crie um StreamSource (novo StreamSource (novo StringReader (str)) e alimente isso ao transformador de identidade mencionado. Isso usará o analisador SAX e o resultado será muito mais rápido. Construir uma árvore intermediária é uma sobrecarga pura para este caso. Caso contrário, a resposta mais bem classificada é boa.


1
Concordo plenamente: construir a árvore DOM intermediária é um desperdício de memória. Agradecer por essa resposta.
Florian F

9

Usando scala:

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

Você também pode fazer isso em Java, se depender do scala-library.jar. Se parece com isso:

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);
    }
}

O PrettyPrinterobjeto é construído com duas entradas, sendo a primeira a extensão máxima da linha e a segunda a etapa de indentação.


9

versão ligeiramente melhorada de 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();
} 

onde é repeatString (pilha ++); método..?
user1912935

2
string estática privada repeatString (pilha int) {StringBuilder indent = new StringBuilder (); for (int i = 0; i <pilha; i ++) {indent.append (""); } retornar indent.toString (); }
codeskraps 25/06

O recuo não está funcionando bem no final tags.You necessidade de mudança de } else if (row.startsWith("</")) {parte a esta: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

Apenas para referência futura, aqui está uma solução que funcionou para mim (graças a um comentário que @George Hawkins postou em uma das respostas):

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

Se você tem certeza de que possui um XML válido, este é simples e evita árvores XML DOM. Talvez tenha alguns bugs, comente se vir alguma coisa

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
Onde está o método repeatString ..?
user1912935

3
string estática privada repeatString (pilha int) {StringBuilder indent = new StringBuilder (); for (int i = 0; i <pilha; i ++) {indent.append (""); } retornar indent.toString (); }
codeskraps 25/06

Sim [user1912935], o que @codeskraps escreveu, deve ser suficiente simples :)
milosmns

Concatenação com um StringBuilder dentro de um loop: Má prática.
Json.garriss

@ james.garriss Mas é super fácil dividir em novas linhas, isso apenas ilustra uma abordagem simples sem nenhuma árvore DOM.
milosmns

5

Todas as soluções acima não funcionaram para mim, então eu achei isso http://myshittycode.com/2014/02/10/java-properly-indenting-xml-string/

A dica é remover espaços em branco com o 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
Observe que o uso da propriedade '{ xml.apache.org/xslt } indent-amount' o vinculará a uma implementação específica do transformador.
precisa saber é o seguinte

1
De todas as soluções, esta funcionou melhor. Eu já tinha espaços e novas linhas no meu XML e não queria adicionar mais dependências ao meu projeto. Eu gostaria de não ter que analisar o XML, mas tudo bem.
Fabio

5

Este código abaixo funcionando perfeitamente

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

Eu misturo todos eles e escrevo um pequeno programa. Ele está lendo o arquivo xml e imprimindo. Apenas em vez de xzy, forneça o caminho do arquivo.

    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

Apenas mais uma solução que funciona para nós

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

Usando 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

Como alternativa às respostas de max , codeskraps , David Easley e milosmns , veja minha biblioteca leve e de alto desempenho de impressoras bonitas: 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
}

Às vezes, como ao executar serviços SOAP simulados diretamente do arquivo, é bom ter uma impressora bonita que também lide com XML já impresso:

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

Como alguns comentaram, a impressão bonita é apenas uma maneira de apresentar XML de uma forma mais legível por humanos - o espaço em branco não pertence estritamente aos seus dados XML.

A biblioteca é destinada à impressão bonita para fins de registro e também inclui funções para filtragem (remoção / anonimização de subárvore) e impressão bonita de XML nos nós CDATA e Texto.


2

Eu tive o mesmo problema e estou tendo grande sucesso com o JTidy ( http://jtidy.sourceforge.net/index.html )

Exemplo:

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

Sublinhado-java tem método estático U.formatXml(string). Eu sou o mantenedor do projeto. Exemplo ao vivo

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>"));
    }
}

Resultado:

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

Isso é incrível!
senyor

1

existe um utilitário xml de linha de comando muito bom chamado xmlstarlet ( http://xmlstar.sourceforge.net/ ) que pode fazer muitas coisas que muitas pessoas usam.

Você pode executar esse programa programaticamente usando Runtime.exec e, em seguida, ler o arquivo de saída formatado. Possui mais opções e melhores relatórios de erros do que algumas linhas de código Java podem fornecer.

faça o download do xmlstarlet: http://sourceforge.net/project/showfiles.php?group_id=66612&package_id=64589


1

Eu descobri que no Java 1.6.0_32 o método normal para imprimir bastante uma string XML (usando um Transformer com um xslt nulo ou de identidade) não se comporta como eu gostaria se as tags fossem apenas separadas por espaços em branco, em vez de não serem separadas. texto. Eu tentei usar <xsl:strip-space elements="*"/>no meu modelo sem sucesso. A solução mais simples que encontrei foi reduzir o espaço da maneira que eu queria usando um filtro SAXSource e XML. Como minha solução foi para registro, eu também estendi isso para trabalhar com fragmentos XML incompletos. Observe que o método normal parece funcionar bem se você usar um DOMSource, mas eu não queria usá-lo devido à incompletude e sobrecarga de memória.

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

As soluções que encontrei aqui para Java 1.6+ não reformatam o código se ele já estiver formatado. O que funcionou para mim (e re-formatou o código já formatado) foi o seguinte.

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);
    }
}

É uma boa ferramenta para usar em seus testes de unidade para comparar xml de cadeia completa.

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

Para quem procura uma solução rápida e suja - que não precisa que o XML seja 100% válido. por exemplo, no caso de registro REST / SOAP (você nunca sabe o que os outros enviam ;-))

Encontrei e avancei um código snippet que encontrei on-line, que acho que ainda falta aqui como uma abordagem possível válida:

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));
}

aqui está a saída:

<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

eu vi uma resposta usando Scala, então aqui está outra Groovy, caso alguém ache interessante. O recuo padrão é 2 etapas, o XmlNodePrinterconstrutor também pode receber outro valor.

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()

Uso do Java se o jar groovy estiver no caminho de classe

  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

Caso você não precise de indentação tanto, mas algumas quebras de linha, isso pode ser suficiente para simplesmente regexar ...

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

O código é bom, não o resultado devido à falta de recuo.


(Para soluções com recuo, consulte outras respostas.)


1
Hmmmm ... Só pensando alto, quem precisaria dessa solução? A única área que eu posso ver são os dados que obtemos de alguns serviços da Web e apenas para testar esses dados e sua validade, o desenvolvedor ou o testador podem precisar dos mais fáceis. Caso contrário, não é uma boa opção ....
Sudhakar Chavali

1
@SudhakarChavali eu sou um desenvolvedor. Talvez eu precise disso para hacks sujos println () e log.debug (); ou seja, algumas vezes eu só posso usar arquivos de log em um ambiente de servidor restrito (com interface de administrador da web em vez de acesso ao shell) em vez de depurar passo a passo o programa.
comonad
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.