Java xml library that keeps attribute order

I am writing a java program that reads an xml file, makes some changes and writes back xml.

Using the standard java xml DOM api, the order of the attributes is not preserved. that is, if I have an input file, for example:

<person first_name="john" last_name="lederrey"/> 

I can get the output file as:

 <person last_name="lederrey" first_name="john"/> 

This is correct because the XML specification says that the order attribute does not matter.

However, my program must keep the order of the attributes so that a person can easily compare the input and output document with the diff tool.

One solution to this is to process the document using SAX (instead of the DOM): XML attribute order after DOM processing

however, this does not work for my case, because the conversion that I need to do in one node may depend on the XPATH expression throughout the document. therefore, the easiest way would be to have an xml library very similar to the standard java DOM lib, except that it preserves the order of the attributes.

Is there such a library?

ps: please avoid discussing whether to keep attribute order or not. This is a very interesting discussion, but that’s not the point.

+6
source share
6 answers

Do it twice:

Read the document using the DOM parser so you have a link, a repository if you want.

Then read it again using SAX. At that moment when you need to do the conversion, refer to the DOM version to determine what you need, and then output what you need in the middle of the SAX stream.

+1
source

The answer for those late for the party: Saxon these days offers a serialization option [1] to control the order in which attributes are displayed. It does not preserve the input order (since Saxon does not know the input order), but it allows you to control, for example, that the identifier attribute always appears first. And this can be very useful if the XML is edited manually; XML, in which attributes appear in the “wrong” order, can be very disoriented by the human reader or editor.

If you use this as part of a diff process, you need to put both files in a process that normalizes the order of the attributes before comparing them. However, to compare files, my preferred approach is to parse both of them and use the deep equality () XPath function; or use a specialized tool, such as DeltaXML.

[1] saxon: attribute-order - see http://www.saxonica.com/documentation/index.html#!extensions/output-extras/serialization-parameters

+1
source

It is best to use STAX instead of the DOM to create the source document. StAX gives you excellent control over these things and allows you to consistently output a stream to an output stream rather than keeping it in memory.

0
source

We had the same description requirements for Dave. The solution that worked was based on Java reflection.

The idea is to set propOrder for attributes at runtime. In our case, there is an APP_DATA element containing 3 attributes: app, key, value. The generated AppData class includes “content” in propOrder and none of the other attributes:

 @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "AppData", propOrder = { "content" }) public class AppData { @XmlValue protected String content; @XmlAttribute(name = "Value", required = true) protected String value; @XmlAttribute(name = "Name", required = true) protected String name; @XmlAttribute(name = "App", required = true) protected String app; ... } 

So, to determine the order at runtime, Java reflection was used:

  final String[] propOrder = { "app", "name", "value" }; ReflectionUtil.changeAnnotationValue( AppData.class.getAnnotation(XmlType.class), "propOrder", propOrder); final JAXBContext jaxbContext = JAXBContext .newInstance(ADI.class); final Marshaller adimarshaller = jaxbContext.createMarshaller(); adimarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); adimarshaller.marshal(new JAXBElement<ADI>(new QName("ADI"), ADI.class, adi), new StreamResult(fileOutputStream)); 

The change AnnotationValue () was borrowed from this post: Change the class definition annotation string parameter at run time

Here's a method for your convenience (credit applies to @assylias and @Balder):

 /** * Changes the annotation value for the given key of the given annotation to newValue and returns * the previous value. */ @SuppressWarnings("unchecked") public static Object changeAnnotationValue(Annotation annotation, String key, Object newValue){ Object handler = Proxy.getInvocationHandler(annotation); Field f; try { f = handler.getClass().getDeclaredField("memberValues"); } catch (NoSuchFieldException | SecurityException e) { throw new IllegalStateException(e); } f.setAccessible(true); Map<String, Object> memberValues; try { memberValues = (Map<String, Object>) f.get(handler); } catch (IllegalArgumentException | IllegalAccessException e) { throw new IllegalStateException(e); } Object oldValue = memberValues.get(key); if (oldValue == null || oldValue.getClass() != newValue.getClass()) { throw new IllegalArgumentException(); } memberValues.put(key,newValue); return oldValue; } 

Hope this helps someone!

0
source

You can override AttributeSortedMap and sort the attributes as needed. The basic idea is to load a document, recursively copy to elements that support a sorted attributeMap, and serialize using the existing XMLSerializer.

test.xml

 <root> <person first_name="john1" last_name="lederrey1"/> <person first_name="john2" last_name="lederrey2"/> <person first_name="john3" last_name="lederrey3"/> <person first_name="john4" last_name="lederrey4"/> </root> 

AttOrderSorter.java

 import com.sun.org.apache.xerces.internal.dom.AttrImpl; import com.sun.org.apache.xerces.internal.dom.AttributeMap; import com.sun.org.apache.xerces.internal.dom.CoreDocumentImpl; import com.sun.org.apache.xerces.internal.dom.ElementImpl; import com.sun.org.apache.xml.internal.serialize.OutputFormat; import com.sun.org.apache.xml.internal.serialize.XMLSerializer; import org.w3c.dom.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.Writer; import java.util.List; import static java.util.Arrays.asList; public class AttOrderSorter { private List<String> sortAtts = asList("last_name", "first_name"); public void format(String inFile, String outFile) throws Exception { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = dbFactory.newDocumentBuilder(); Document outDocument = builder.newDocument(); try (FileInputStream inputStream = new FileInputStream(inFile)) { Document document = dbFactory.newDocumentBuilder().parse(inputStream); Element sourceRoot = document.getDocumentElement(); Element outRoot = outDocument.createElementNS(sourceRoot.getNamespaceURI(), sourceRoot.getTagName()); outDocument.appendChild(outRoot); copyAtts(sourceRoot.getAttributes(), outRoot); copyElement(sourceRoot.getChildNodes(), outRoot, outDocument); } try (Writer outxml = new FileWriter(new File(outFile))) { OutputFormat format = new OutputFormat(); format.setLineWidth(0); format.setIndenting(false); format.setIndent(2); XMLSerializer serializer = new XMLSerializer(outxml, format); serializer.serialize(outDocument); } } private void copyElement(NodeList nodes, Element parent, Document document) { for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = new ElementImpl((CoreDocumentImpl) document, node.getNodeName()) { @Override public NamedNodeMap getAttributes() { return new AttributeSortedMap(this, (AttributeMap) super.getAttributes()); } }; copyAtts(node.getAttributes(), element); copyElement(node.getChildNodes(), element, document); parent.appendChild(element); } } } private void copyAtts(NamedNodeMap attributes, Element target) { for (int i = 0; i < attributes.getLength(); i++) { Node att = attributes.item(i); target.setAttribute(att.getNodeName(), att.getNodeValue()); } } public class AttributeSortedMap extends AttributeMap { AttributeSortedMap(ElementImpl element, AttributeMap attributes) { super(element, attributes); nodes.sort((o1, o2) -> { AttrImpl att1 = (AttrImpl) o1; AttrImpl att2 = (AttrImpl) o2; Integer pos1 = sortAtts.indexOf(att1.getNodeName()); Integer pos2 = sortAtts.indexOf(att2.getNodeName()); if (pos1 > -1 && pos2 > -1) { return pos1.compareTo(pos2); } else if (pos1 > -1 || pos2 > -1) { return pos1 == -1 ? 1 : -1; } return att1.getNodeName().compareTo(att2.getNodeName()); }); } } public void main(String[] args) throws Exception { new AttOrderSorter().format("src/main/resources/test.xml", "src/main/resources/output.xml"); } } 

Result of output.xml:

 <?xml version="1.0" encoding="UTF-8"?> <root> <person last_name="lederrey1" first_name="john1"/> <person last_name="lederrey2" first_name="john2"/> <person last_name="lederrey3" first_name="john3"/> <person last_name="lederrey4" first_name="john4"/> </root> 
0
source

You cannot use the DOM, but you can use SAX or request children using XPATH Visit this answer fooobar.com/questions/157370 / ...

-1
source

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


All Articles