I have an XMl file that looks like:
2
Using @Jorn Horstmann's (http://stackoverflow.com/users/139595/jorn-horstmann) answer from above you can easily add the missing elements. But to write the results to an XML file you should use the TransformerHandler.
Just create a very basic ContentHandler and use it instead of the DefaultHandler. In the ContentHandler you can implement all the basic functions (startDocument, startElement etc.) and add every element to a new Transformer. e.g. In your startDocument function:
Transformer t = hd.getTransformer();
t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
t.setOutputProperty(OutputKeys.METHOD, "xml");
t.setOutputProperty(OutputKeys.INDENT, "yes");
hd.startDocument();
And then (in each other function) add this: e.g. for startElement:
hd.startElement(uri,localName,name,attributes);
Finally you can write all this to a file in the endDocument method.
Correct me if i'm wrong but i think XMLReader and XMLFilter are not really supposed to change a document. I can provide a different approach which with you can change the content of your document too:
public class ExtXMLConfig {
private JAXBContext context;
private Marshaller m;
private Unmarshaller um;
private Schema schema = null;
/**
* Creates an ExtXMLConfig-object, which uses rootClass as object to parse
* and save XML-files.
*
* @param rootClass
* the class use create/parse xml-files from
* @throws JAXBException
*/
public ExtXMLConfig(Class<?> rootClass) throws JAXBException {
context = JAXBContext.newInstance(rootClass);
init();
}
/**
* Creates an ExtXMLConfig, which uses a classPath like javax.xml.bin to use
* all classes in that path to parse and write xml-files
*
* @param classPath
* the class path containing all needed java-objects
* @throws JAXBException
*/
public ExtXMLConfig(String classPath) throws JAXBException {
context = JAXBContext.newInstance(classPath);
init();
}
/**
* Parses a xml-file into a JavaObject.
*
* @param file
* path to the xml-file
* @return a java-Object
*/
public Object load(String file) {
return load(new File(file));
}
/**
* Parses a xml-file into a JavaObject.
*
* @param xml
* File-object representing the xml-file
* @return a java-Object
*/
public Object load(File xml) {
um.setSchema(schema);
if (xml.exists() && xml.isFile()) {
try {
return um.unmarshal(xml);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
System.out.println("Failed to open file: " + xml.getAbsolutePath());
}
return null;
}
/**
* Saves a object into a xml-file.
*
* @param xml
* the object to save
* @param file
* path to the file to save to
*/
public void save(Object xml, String file) {
save(xml, new File(file));
}
/**
* Saves a object into a xml-file.
*
* @param xml
* the object to save
* @param file
* File-object representing the file to save to
*/
public void save(Object xml, File file) {
if (xml != null) {
m.setSchema(schema);
if (!file.isDirectory()) {
try {
if (!file.exists()) {
file.createNewFile();
}
m.marshal(xml, file);
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* Returns a formatted string representation of a xml-file given as a
* java-Object.
*
* @param xml
* the java-object to parse the xml from.
* @return a formatted string representation of the given object
*/
public String toString(Object xml) {
StringWriter out = new StringWriter();
try {
m.setSchema(schema);
m.marshal(xml, out);
return out.toString();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
private void init() throws JAXBException {
m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
m.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
um = context.createUnmarshaller();
}
Using this class to parse your xml-Files you would only need a Class like this:
@XmlRootElement // used to parse this class as xml-Root
public class Game {
private Move moves;
public Game() {};
public void setMove(Move moves) {
this.moves = moves;
}
public Moves getMoves() {
return this.moves;
}
}
with Move being an instance of another class which has the fields you need and also has a annotation for XmlRootElement.
I hope this helps.
Your XMLFilter
should delegate to another ContentHandler
that serializes the document based on the sax events.
SAXTransformerFactory factory = (SAXTransformerFactory)TransformerFactory.newInstance();
TransformerHandler serializer = factory.newTransformerHandler();
Result result = new StreamResult(...);
serializer.setResult(result);
XMLFilterImpl filter = new MyFilter();
filter.setContentHandler(serializer);
XMLReader xmlreader = XMLReaderFactory.createXMLReader();
xmlreader.setFeature("http://xml.org/sax/features/namespaces", true);
xmlreader.setFeature("http://xml.org/sax/features/namespace-prefixes", true);
xmlreader.setContentHandler(filter);
xmlreader.parse(new InputSource(...));
Your callback should delegate to the super
implementation, which forwards the events to the serializing ContentHandler
.
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
super.startElement(namespaceURI, localName, qName, atts);
...
}
In your endElement
callback you can check if your are at the final closing tag and add additional sax events.
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
super.endElement(namespaceURI, localName, qName);
if ("game".equals(localName)) {
super.startElement("", "statistics", "statistics", new AttributesImpl());
char[] chars = String.valueOf(num).toCharArray();
super.characters(chars, 0, chars.length);
super.endElement("", "statistics", "statistics");
}
...
}