How to make newlines between attributes in XML file by using dom4j? - java

I want to generate a xml file in the following format by using java :
each attribute should be in separate line.
<parameters>
<parameter
name="Tom"
city="York"
number="123"
/>
</parameters>
But I can only get all attributes in one line
<parameters>
<parameter name="Tom" city="York" number="123"/>
</parameters>
I'm using dom4j, could anyone tell how I can make it? Does dom4j supports this kind of format?
Thanks.

You cannot do it with the XMLWriter unless you want to substantially rewrite the main logic. However, since XMLWriter is also a SAX ContentHandler it can consume SAX events and serialize them to XML, and in this mode of operation, XMLWriteruses a different code path which is easier to customize. The following sub class will give you almost what you want, except that empty elements will not use the short form <element/>. Maybe that can be fixed by further tweaking.
static class ModifiedXmlWriter extends XMLWriter {
// indentLevel is private, need reflection to read it
Field il;
public ModifiedXmlWriter(OutputStream out, OutputFormat format) throws UnsupportedEncodingException {
super(out, format);
try {
il = XMLWriter.class.getDeclaredField("indentLevel");
il.setAccessible(true);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
int getIndentLevel() {
try {
return il.getInt(this);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
#Override
protected void writeAttributes(Attributes attributes) throws IOException {
int l = getIndentLevel();
setIndentLevel(l+1);
super.writeAttributes(attributes);
setIndentLevel(l);
}
#Override
protected void writeAttribute(Attributes attributes, int index) throws IOException {
writePrintln();
indent();
super.writeAttribute(attributes, index);
}
}
public static void main(String[] args) throws Exception {
String XML = "<parameters>\n" +
" <parameter name=\"Tom\" city=\"York\" number=\"123\"/>\n" +
"</parameters>";
Document doc = DocumentHelper.parseText(XML);
XMLWriter writer = new ModifiedXmlWriter(System.out, OutputFormat.createPrettyPrint());
SAXWriter sw = new SAXWriter(writer);
sw.write(doc);
}
Sample output:
<?xml version="1.0" encoding="UTF-8"?>
<parameters>
<parameter
name="Tom"
city="York"
number="123"></parameter>
</parameters>

Generally speaking, very few XML serializers give you this level of control over the output format.
You can get something close to this with the Saxon serializer if you specify the options method=xml, indent=yes, saxon:line-length=20. The Saxon serializer is capable of taking a DOM4J tree as input. You will need Saxon-PE or -EE because it requires a serialization parameter in the Saxon namespace. It still won't be exactly what you want because the first attribute will be on the same line as the element name and the others will be vertically aligned underneath the first.

Related

SAX parser does not call startDTD

I have followed Obtaining DOCTYPE details using SAX (JDK 7), implementing it like this:
public class MyXmlReader {
public static void parse(InputSource inputSource) {
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
MyContentHandler handler = new MyContentHandler();
xmlReader.setContentHandler(handler);
xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", handler); // Does not work; handler is set, but startDTD/endDTD is not called
xmlReader.setDTDHandler(handler);
xmlReader.setErrorHandler(new MyErrorHandler());
xmlReader.setFeature("http://xml.org/sax/features/validation", false);
xmlReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
xmlReader.parse(inputSource);
}
catch (SAXException e) {
throw new MyImportException("Error while parsing file", e);
}
}
}
MyContentHandler extends DefaultHandler2, but neither startDTD nor endDTD is called (but e.g. startEntity is in fact called, so the lexical handler is set).
I have tried to leave the features out, but this changes nothing.
What goes wrong here?
I am using Java 8 JDK 1.8.0_144.
The XML looks like this:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE MyMessage SYSTEM "http://www.testsite.org/mymessage/5.1/reference/international.dtd">
<MyMessage>
<Header>
...
According to XMLReader API you need to set a DTD Handler, otherwise DTD Events will be silently ignored. A DefaultHandler2 yet implements DTDHandler interface, so you could use xmlReader.setDTDHandler(handler); again;

Insert blank lines in jdom2 pretty printing

I'm trying to simply add some blank lines to my jdom xml output. I've tried the following without luck:
Element root = new Element("root");
root.addContent(new CDATA("\n"));
root.addContent(new Text("\n"));
I figured the all-whitespace entry was being ignored so I tried creating my own XMLOutputProccessor like this:
class TweakedOutputProcessor extends AbstractXMLOutputProcessor {
#Override
public void process(java.io.Writer out, Format format, Text text) throws IOException {
if ("\n".equals(text.getText())) {
out.write("\n");
} else {
super.process(out, format, text);
}
}
}\
... called like this:
public static void printDocument(Document doc) {
XMLOutputter xmlOutput = new XMLOutputter(new TweakedOutputProcessor());
xmlOutput.setFormat(Format.getPrettyFormat());
try {
xmlOutput.output(doc, System.out);
} catch (IOException e) { }
}
The unexpected thing here was that process(..., Text) was never called. After some experimentation I've found that process(..., Document) is being called, but none of the other process(..., *) methods are.
I also tried overriding the printText(...) and printCDATA(...) methods, but neither is being called -- even when the text is non-whitespace! Yet printElement(...) is being called.
So...
What is going on here? What's doing the work if not these methods?
How do I simply insert a blank line?
Use the XML xml:space="preserve" when setting values in the XML. JDOM honours that XML white space handling

Extend a JDOM Document

For a project at university, I need to parse a GML file. GML files are XML based so I use JDOM2 to parse it. To fit my purposes, I extended org.jdom2.Document like so:
package datenbank;
import java.io.File;
// some more imports
public class GMLDatei extends org.jdom2.Document {
public void saveAsFile() {
// ...
}
public GMLKnoten getRootElement(){
return (GMLKnoten) this.getDocument().getRootElement();
}
public void setRootElement(GMLKnoten root){
this.getDocument().setRootElement(root);
}
}
I also extended org.jdom2.Element and named the subclass GMLKnoten but this does not matter too much for my question.
When testing, I try to load a GML file. When using the native document and element classes, it loads fine, but when using my subclasses, I get the following scenario:
I load the file using:
SAXBuilder saxBuilder = new SAXBuilder();
File inputFile = new File("gml/Roads_Munich_Route_Lines.gml");
GMLDatei document = null;
ArrayList<String> types = new ArrayList<String>();
try {
document = (GMLDatei) saxBuilder.build(inputFile);
} catch (JDOMException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
In the line
document = (GMLDatei) saxBuilder.build(inputFile);
I get a Cast-Exception:
Exception in thread "main" java.lang.ClassCastException: org.jdom2.Document cannot be cast to datenbank.GMLDatei
at datenbank.GMLTest.main(GMLTest.java:27)
I thought that casting schould be no problem as I am subclassing org.jdom2.document. What am I missing?
vat
In general I want to "challenge" your requirement to extend Document - what value do you get from your custom classes that are not already part of the native implementation? I ask this for 2 reasons:
as the maintainer of JDOM, should I be adding some new feature?
I am just curious.....
JDOM has a system in place for allowing you to extend it's core classes and have a different implementation of them when parsing a document. It is done by extending the JDOMFactory.
Consider this code here: JDOMFactory interface. When SAXParser parses a document it uses those methods to build the document.
There is a default, overridable implementation in DefaultJDOMFactory that you can extend, and, for example, in your implementation, you must override the non-final "Element" methods like:
#Override
public Element element(final int line, final int col, final String name,
String prefix, String uri) {
return new Element(name, prefix, uri);
}
and instead have:
#Override
public Element element(final int line, final int col, final String name,
String prefix, String uri) {
return new GMLKnoten (name, prefix, uri);
}
Note that you will have to override all methods that are non-final and return content that is to be customised (for example, you will have to override 4 Element methods by my count.
With your own GMLJDOMFactory you can then use SAXBuilder by either using the full constructor new SAXBuilder(null, null, new GMPJDOMFactory()) or by setting the JDOMFactory after you have constructred it with setJDOMFactory(...)

How to use StaX

Hey guys so I am brand new to the world of Java-XML parsing and found that the StaX API is probably my best bet as I need to both read and write XML files. Alright so I have a very short (and should be very simple) program that (should) create an XMLInputFactory and use that to create a XMLStreamReader. The XMLStreamReader is created using a FileInputStream attached to an XML file in the same directory as the source file. However even though the FileInputStream compiled properly, the XMLInputFactory cannot access it and without the FileInputStream it cannot creat the XMLStreamReader. Please help as I have no idea what to and am frustrated to the point of giving up!
import javax.xml.stream.*;
import java.io.*;
public class xml {
static String status;
public static void main(String[] args) {
status = "Program has started";
printStatus();
XMLInputFactory inFactory = XMLInputFactory.newInstance();
status = "XMLInputFactory (inFactory) defined"; printStatus();
try { FileInputStream fIS = new FileInputStream("stax.xml"); }
catch (FileNotFoundException na) { System.out.println("FileNotFound"); }
status = "InputStream (fIS) declared"; printStatus();
try { XMLStreamReader xmlReader = inFactory.createXMLStreamReader(fIS); } catch (XMLStreamException xmle) { System.out.println(xmle); }
status = "XMLStreamReader (xmlReader) created by 'inFactory'"; printStatus();
}
public static void printStatus(){ //this is a little code that send notifications when something has been done
System.out.println("Status: " + status);
}
}
also here is the XML file if you need it:
<?xml version="1.0"?>
<dennis>
<hair>brown</hair>
<pants>blue</pants>
<gender>male</gender>
</dennis>
Your problem has to do w/ basic java programming, nothing to do w/ stax. your FileInputStream is scoped within a try block (some decent code formatting would help) and therefore not visible to the code where you are attempting to create the XMLStreamReader. with formatting:
XMLInputFactory inFactory = XMLInputFactory.newInstance();
try {
// fIS is only visible within this try{} block
FileInputStream fIS = new FileInputStream("stax.xml");
} catch (FileNotFoundException na) {
System.out.println("FileNotFound");
}
try {
// fIS is not visible here
XMLStreamReader xmlReader = inFactory.createXMLStreamReader(fIS);
} catch (XMLStreamException xmle) {
System.out.println(xmle);
}
on a secondary note, StAX is a nice API, and a great one for highly performant XML processing in java. however, it is not the simplest XML api. you would probably be better off starting with the DOM based apis, and only using StAX if you experience performance issues using DOM. if you do stay with StAX, i'd advise using XMLEventReader instead of XMLStreamReader (again, an easier api).
lastly, do not hide exception details (e.g. catch them and print out something which does not include the exception itself) or ignore them (e.g. continue processing after the exception is thrown without attempting to deal with the problem).

How do I edit a XML node in a file object, using Java

There are a lot of examples on the internet of "reading" files but I can't find anything on "editing" a node value and writing it back out to the original file.
I have a non-working xml writer class that looks like this:
import org.w3c.dom.Document;
public class RunIt {
public static Document xmlDocument;
public static void main(String[] args)
throws TransformerException, IOException {
try {
xmlDocument = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse("thor.xml");
} catch (IOException ex) {
ex.printStackTrace();
} catch (SAXException ex) {
ex.printStackTrace();
} catch (ParserConfigurationException ex) {
ex.printStackTrace();
}
addElement("A", "New");
writeDoc();
}
public static void addElement(String path, String val){
Element e = xmlDocument.createElement(path);
e.appendChild(xmlDocument.createTextNode(val));
xmlDocument.getDocumentElement().appendChild(e);
}
public static void writeDoc() throws TransformerException, IOException {
StringWriter writer = new StringWriter();
Transformer tf;
try {
tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(xmlDocument), new StreamResult(writer));
writer.close();
} catch (TransformerConfigurationException e) {
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
e.printStackTrace();
}
}
}
For this example, lets say this is the XML and I want to add a "C" node (inside the A node) that contains the value "New" :
<A>
<B>Original</B>
</A>
You use the Document object to create new nodes. Adding nodes as you suggest involves creating a node, setting its content and then appending it to the root element. In this case your code would look somehting like this:
Element e = xmlDocument.createElement("C");
e.appendChild(xmlDocument.createTextNode("new"));
xmlDocument.getDocumentElement().appendChild(e);
This will add the C node as a new child of A right after the B node.
Additionally, Element has some convenience functions that reduce the amount of required code. The second line above could have been replaced with
e.setTextContent("new");
More complicated efforts involving non root elements will involve you using XPath to fetch the target node to be edited. If you do start to use XPath to target nodes, bear in mind that the JDK XPath performance is abysmal. Avoid using an XPath of "#foo" in favor of constructs like e.getAttribute("foo") whenever you can.
EDIT: Formatting the document back to a string which can be written to a file can be done with the following code.
Document xmlDocument;
StringWriter writer = new StringWriter();
TransformerFactory.newInstance().transform(new DOMSource(xmlDocument), new StreamResult(writer));
writer.close();
String xmlString = writer.toString();
EDIT: Re: updated question with code.
Your code doesn't work because you're conflating 'path' and 'element name'. The parameter to Document.createElement() is the name of the new node, not the location in which to place it. In the example code I wrote I didn't get into locating the appropriate node because you were asking specifically about adding a node to the document parent element. If you want your addElement() to behave the way I think you're expecting it to behave, you'd have to add another parameter for the xpath of the target parent node.
The other problem with your code is that your writeDoc() function doesn't have any output. My example shows writing the XML to a String value. You can write it to any writer you want by adapting the code, but in your example code you use a StringWriter but never extract the written string out of it.
I would suggest rewriting your code something like this
public static void main(String[] args) {
File xmlFile = new File("thor.xml");
Document xmlDocument = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(xmlFile);
// this is effective because we know we're adding to the
// document root element
// if you want to write to an arbitrary node, you must
// include code to find that node
addTextElement(xmlDocument.getDocumentElement(), "C", "New");
writeDoc(new FileWriter(xmlFile);
}
public static Element addTextElement(Node parent, String element, String val){
Element e = addElement(parent, element)
e.appendChild(xmlDocument.createTextNode(val));
return e;
}
public static Element addElement(Node parent, String element){
Element e = xmlDocument.createElement(path);
parent.appendChild(e);
return e;
}
public static void writeDoc(Writer writer) {
try {
Transformer tf = TransformerFactory.newInstance().newTransformer();
tf.transform(new DOMSource(xmlDocument), new StreamResult(writer));
} finally {
writer.close();
}
}
In order to write your document back to a file, you'll need an XML serializer or write your own. If you are using the Xerces library, check out XMLSerializer. For sample usage, you can also check out the DOMWriter samples page.
For more information on Xerces, read this

Categories

Resources