Worldcreator
Worldcreator

Reputation: 3

java xml Output of an object, special characters are not being escaped correctly

So for a project I have to write a class that takes multiple objects "Page" with parameters nameSpaceID, articleID, title, a string set of categories and then outputs them into an xml file. I tried to solve it by using an XMLOutputFactory with a XMLStreamWriter, to write the xml into StringWriter, then I transform the StringWriter with a transformerFactory to the right format (indent and stuff) and lastly output that into a .xml file. Everything works so far, but I need help with escaping of special characters, if i put a > for example in my fileName, it wont get escaped. I tried escaping it with StringEscapeUtils.escapeXml10(String) but that does only make my output worse.

import java.io.FileOutputStream;
import org.apache.commons.lang3.StringEscapeUtils;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

/**
 * 
 */

/**
 * @author Paul
 *
 */
public class PageExport {
    /**
     * @param args
     */
    public void printPagestoXML(Page[] pages, String fileName, String filePath){
        try {
            StringWriter xmlRAW = new StringWriter();
            XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
            xmlOutputFactory.setProperty("escapeCharacters", false);
            XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(xmlRAW);

            xmlStreamWriter.writeStartDocument("UTF-8", "1.0");

            xmlStreamWriter.writeStartElement("pages");

            for(int i = 0; i < pages.length; i++){
                xmlStreamWriter.writeStartElement("page");
                xmlStreamWriter.writeAttribute("pageID", pages[i].getArticleID() + "");
                xmlStreamWriter.writeAttribute("namespaceID", pages[i].getNamespaceID() + "");
                xmlStreamWriter.writeAttribute("title", StringEscapeUtils.escapeXml10(pages[i].getTitle()));

                if (pages[i].getCategories() != null){
                    xmlStreamWriter.writeStartElement("categories");

                    for(int j = 0; j < pages[i].getCategories().size(); j++) {
                        xmlStreamWriter.writeEmptyElement("category");
                        xmlStreamWriter.writeAttribute("name", pages[i].getCategories().toArray()[j].toString());
                    }

                    xmlStreamWriter.writeEndElement(); //end of categories
                }

                xmlStreamWriter.writeEndElement(); //end of page i
            }
            xmlStreamWriter.writeEndElement(); //end of pages

            xmlStreamWriter.writeEndDocument(); // end of document

            xmlStreamWriter.flush();
            xmlStreamWriter.close();

            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            StreamResult streamResult = new StreamResult(new FileOutputStream(filePath + fileName));
            transformer.transform(new StreamSource(new StringReader(xmlRAW.getBuffer().toString())), streamResult);
        }
        catch (Exception e){
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) {
        String goodFilePath = System.getProperty("user.dir") + "/src/data/";
        String goodFileName = "test.xml";
        Set<String> testCategories = new HashSet<String>();
        testCategories.add("this");
        testCategories.add("is");
        testCategories.add("sparta");
        Page[] testPages = {new Page(0, 1337, "l33t", testCategories), new Page(0, 1338, "l33t>", testCategories)};
        PageExport pe = new PageExport();
        pe.printPagestoXML(testPages, goodFileName, goodFilePath);
    }

}

output of this code (the second pages title is the important one):

<?xml version="1.0" encoding="UTF-8"?>
<pages>
  <page pageID="1337" namespaceID="0" title="l33t">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
  <page pageID="1338" namespaceID="0" title="l33t&amp;gt;">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
</pages>

without StringEscapeUtils.escapeXml10(title) :

<?xml version="1.0" encoding="UTF-8"?>
<pages>
  <page pageID="1337" namespaceID="0" title="l33t">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
  <page pageID="1338" namespaceID="0" title="l33t&gt;">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
</pages>

What I want:

<?xml version="1.0" encoding="UTF-8"?>
<pages>
  <page pageID="1337" namespaceID="0" title="l33t">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
  <page pageID="1338" namespaceID="0" title="l33t>">
    <categories>
      <category name="this"/>
      <category name="is"/>
      <category name="sparta"/>
    </categories>
  </page>
</pages>

EDIT: I fixed the issue by setting the DOCTYPE_PUBLIC to "yes", new code:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

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

import org.apache.log4j.Logger;

/**
 * @author Paul
 *
 */

public class PageExport {

    Logger log = Logger.getLogger(PageExport.class);

    /**
     * Converts a collection of Pages into a XML String and then into a XML file.
     * 
     * @param   pages The collection or Pages, that shall be written into the file.
     * @param   filepath The full path of the XML file.
     * @see     #printPagestoXML(Page[], String, String)
     * @see     Page
     * 
     */

    public void printPagestoXML(Page[] pages, String filepath){
        //Converting a single input filepath into a filepath & filename and
        //then running the method with the arguments
        String newfilepath = "";
        String[] splitpath = filepath.split("/");
        for (int i = 0; i < splitpath.length - 1 ; i++){
            newfilepath += (splitpath[i] + "/");
        }
        printPagestoXML(pages,  newfilepath, splitpath[splitpath.length - 1].split("\\.")[0]);
    }

    /**
     * Converts a collection of Pages into a XML String and then into a XML file.
     * 
     * @param   pages The collection or Pages, that shall be written into the file.
     * @param   filepath The path of the XML file.
     * @param   filename Name of the .xml file (Without .xml)
     * @see     #printPagestoXML(Page[], String, String)
     * @see     Page
     * 
     */

    public void printPagestoXML(Page[] pages, String filepath, String filename){

        try {
            //Method starts of by creating a new outputfactory, that prints to a StringWriter,
            //so that the xml String can still be transformed before getting output.
            StringWriter rawXml = new StringWriter();
            XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory();
            XMLStreamWriter xmlStreamWriter = xmlOutputFactory.createXMLStreamWriter(rawXml);

            xmlStreamWriter.writeStartDocument("UTF-8", "1.0"); //start of the XML stream

            xmlStreamWriter.writeStartElement("pages"); //the first element "pages"

            for(int i = 0; i < pages.length; i++){  
                //loop to create elements for all pages in the collection
                log.info("Creating Page " + i + ": " + pages[i].getTitle());
                xmlStreamWriter.writeStartElement("page");
                xmlStreamWriter.writeAttribute("pageID", pages[i].getArticleID() + "");
                xmlStreamWriter.writeAttribute("namespaceID", pages[i].getNamespaceID() + "");
                xmlStreamWriter.writeAttribute("title", pages[i].getTitle());

                if (pages[i].getCategories() != null){  
                    xmlStreamWriter.writeStartElement("categories");

                    for(int j = 0; j < pages[i].getCategories().size(); j++) {  
                        //loop to create all categories for the currently creating page
                        log.trace("Creating Category " + j + ": " + pages[i].getCategories().toArray()[j].toString());
                        xmlStreamWriter.writeEmptyElement("category");
                        xmlStreamWriter.writeAttribute("name", pages[i].getCategories().toArray()[j].toString());
                    }

                    xmlStreamWriter.writeEndElement(); //end of categories
                }
                else {
                    // in case a page doesn't categories, the element wont be created and a warning is posted
                    log.info("Page " + (i + 1) + " does not have categories (" + pages[i].toString() + ")");
                }

                xmlStreamWriter.writeEndElement(); //end of page i
            }
            log.info("Last page written.");
            xmlStreamWriter.writeEndElement(); //end of pages
            xmlStreamWriter.writeEndDocument(); // end of document

            xmlStreamWriter.flush();
            xmlStreamWriter.close(); //close the streamwriter

            /*
             * The StringWriter variable rawXml now contains the whole XML string, but it still has to be
             * transformed, otherwise it would all be printed in one line.
             */
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, "yes");    //Setting the output properties
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");            //for the transformer
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
            StreamResult streamResult = new StreamResult(new FileOutputStream(filepath + filename + ".xml"));

            //initiation of the output streamresult with the filepath
            transformer.transform(new StreamSource(new StringReader(rawXml.toString())), streamResult);

            log.info(filename + ".xml created.");
            //transformation / formatting of the xml string and output into .xml file
        } catch (Exception e){
            log.warn(e.getMessage());
        }
    }

Upvotes: 0

Views: 2758

Answers (2)

Deepak Sachdeva
Deepak Sachdeva

Reputation: 1010

add below line in dependencies in build.gradle

compile 'commons-lang:commons-lang:2.5'

For unescape use

String title = StringEscapeUtils.unescapeJava(.getTitle());

or

String title = StringEscapeUtils.unescapeJava(userProfile.getScreen_name().replace("\n", "\\n")
                    .replace("&amp;", "&"));

For escape use

String title = StringEscapeUtils.escapeJava(xmlResponse.getTitle());

or

String title = StringEscapeUtils.escapeJava(xmlResponse.getTitle()).replace(Constants.ESCAPED_NEWLINE, Constants.NEWLINE);

Upvotes: -1

zyexal
zyexal

Reputation: 1579

Please read about Character Data and Markup:

Ampersand character & and the left angle bracket < may appear in their literal form only when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. If they are needed elsewhere, they must be escaped using either numeric character references or the strings &amp; and &lt; respectively.

The right angle bracket > may be represented using the string &gt;, and must, for compatibility, be escaped using &gt; or a character reference when it appears in the string ]]> in content, when that string is not marking the end of a CDATA section.

Now it should be clear, why it's not working like you expected.

Upvotes: 4

Related Questions