Insert a new element into an XML file using SAX Filter

I have an XMl file that looks like this:

<?xml version="1.0" encoding="UTF-8"?> <game > <moves> <turn>2</turn> <piece nr="1" /> <turn>4</turn> <piece nr="1" /> </moves> </game> 

I am writing a Java program that takes an XML file as input, then parses it using a SAX and SAX filter and calculates:

  • the sum of the contents of the rotation element (here = 6)
  • number of piece elements (here = 2)

Then I want to use the SAX filter to generate the output XML file, which matches the input, but with an additional element of the type:

 <s:statistics> <s:turn-total>6</s:turn-total> <s:piece-count>2</s:piece-count> </s:statistics> 

The prefix s is a namespace reference.

My program so far:

  public class test{ public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("error "); System.exit(1); } String xmlInput = args[0]; String filteredXML = args[1]; test test1 = new test(); test1.sax(xmlInput, filteredXML); } private void sax(String gameXML, String filteredGameXML)throws Exception{ FileInputStream fis = new FileInputStream( gameXML); InputSource is = new InputSource(fis); XMLReader xr = XMLReaderFactory.createXMLReader(); XMLFilter xf = new MyFilter(); xf.setParent(xr); xr = xf; xr.parse(is); xr.setFeature("http://xml.org/sax/features/namespaces", true); DefaultHandler handler = new DefaultHandler(); xr.setContentHandler(handler); } private class MyFilter extends XMLFilterImpl{ StringBuffer buffer; int temp=0; int sum=0; String ff; int numof=0; private MyFilter() {} @Override public void startDocument() throws SAXException { System.out.println( "START DOCUMENT" ); numof=0; } public void startElement(String namespaceURI, String localName, String name, Attributes attributes) throws SAXException{ if(localName.equals("turn")){ buffer=new StringBuffer(); } if("piece".equals(name)){ numof++; } } public void characters(char[] ch, int start, int length) throws SAXException { String s=new String(ch, start, length); if(buffer!=null){ buffer.append(s); } } public void endElement(String uri, String localName, String name)throws SAXException { if(buffer!=null ){ ff=buffer.toString(); temp=Integer.valueOf(ff); sum=sum+temp; } buffer=null; } public void endDocument() throws SAXException { System.out.println( "END DOCUMENT" ); System.out.println("sum of turn: "+ sum); System.out.println("sum of piece: "+ numof); } } } 

What should I do next?

+4
source share
3 answers

Your XMLFilter should delegate to another ContentHandler that serializes the document based on 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 a super implementation that translates the events into serialization of the ContentHandler .

 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { super.startElement(namespaceURI, localName, qName, atts); ... } 

In your endElement you can check if you are in the last 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"); } ... } 
+4
source

Correct me if I am wrong, but I think that XMLReader and XMLFilter should not actually change the document. I can suggest a different approach that can also change the contents of your document:

 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 only need a class:

 @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, which is an instance of another class that has the fields you need, and also has an annotation for XmlRootElement.

Hope this helps.

0
source

Using @Jorn Horstmann (http://stackoverflow.com/users/139595/jorn-horstmann), the answer above you can easily add missing elements. But to write the results to an XML file you have to use TransformerHandler.

Just create a very simple ContentHandler and use it instead of the DefaultHandler. In ContentHandler you can implement all the basic functions (startDocument, startElement, etc.) and add each element to a new Transformer. For example 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 every other function) add this: for example for startElement:

 hd.startElement(uri,localName,name,attributes); 

Finally, you can write all this to a file in the endDocument method.

0
source

Source: https://habr.com/ru/post/1412532/


All Articles