保留属性顺序的java xml库

时间:2013-07-18 14:13:38

标签: java xml

我正在编写一个java程序,它读取xml文件,进行一些修改,然后写回xml。

使用标准java xml DOM api,不保留属性的顺序。 也就是说,如果我有一个输入文件,如:

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

我可能会得到一个输出文件:

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

这是正确的,因为XML规范说订单属性并不重要。

但是,我的程序需要保留属性的顺序, 这样一个人就可以轻松地将输入和输出文档与diff工具进行比较。

一个解决方案是使用SAX(而不是DOM)处理文档: Order of XML attributes after DOM processing

然而,这对我的情况不起作用, 因为我需要在一个节点中进行的转换可能取决于整个文档中的XPATH表达式。 所以,最简单的方法是让一个xml库与标准的java DOM lib非常相似,但它保留了属性顺序。

有没有这样的图书馆?

ps:请避免讨论是否应该保留属性顺序。这是一个非常有趣的讨论,但这不是这个问题的重点。

8 个答案:

答案 0 :(得分:2)

对于那些迟到的人的答案:Saxon这些天提供了一个序列化选项[1]来控制输出属性的顺序。它不保留输入顺序(因为Saxon不知道输入顺序)但它确实允许您控制,例如,ID属性始终首先出现。如果要对XML进行手工编辑,这可能非常有用;属性出现在&#34;错误&#34;对于人类读者或编辑来说,秩序可能会非常迷惑。

如果您将此作为diff过程的一部分使用,那么您可能希望将两个文件放在一个过程中,该过程在比较属性顺序之前规范化属性顺序。但是,为了比较文件,我首选的方法是解析它们并使用XPath deep-equal()函数;或使用像DeltaXML这样的专业工具。

[1]撒克逊人:属性顺序 - 请参阅http://www.saxonica.com/documentation/index.html#!extensions/output-extras/serialization-parameters

答案 1 :(得分:1)

做两次:

使用DOM解析器阅读文档,以便您可以参考,如果您愿意,还可以使用存储库。

然后使用SAX再次阅读。在您需要进行转换时,引用DOM版本以确定您需要的内容,然后在SAX流的中间输出您需要的内容。

答案 2 :(得分:1)

您可能还想尝试DecentXML,因为它可以保留属性顺序,注释甚至标识。如果您需要以编程方式更新也应该可以人工编辑的XML文件,则非常好。我们将其用于我们的一种配置工具。

答案 3 :(得分:0)

您最好的选择是使用StAX而不是DOM来生成原始文档。 StAX为您提供了很多对这些内容的精细控制,并允许您逐步将输出流式传输到输出流,而不是将其全部保存在内存中。

答案 4 :(得分:0)

根据戴夫的描述我们有类似的要求。有效的解决方案基于Java反射。

想法是在运行时为属性设置propOrder。在我们的例子中,APP_DATA元素包含3个属性:app,key,value。生成的AppData类在propOrder中包含“content”,而不包含其他任何属性:

@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;
    ...
}

因此,如下使用Java反射在运行时设置顺序:

        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));

从这篇文章中借用了changeAnnotationValue(): Modify a class definition's annotation string parameter at runtime

以下是为方便起见的方法(归功于@assylias和@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;
}

希望这有助于某人!

答案 5 :(得分:0)

您可以覆盖AttributeSortedMap并根据需要对属性进行排序。 主要思想是:加载文档,以递归方式复制到支持排序的attributeMap的元素,并使用现有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");
    }
}

结果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>

答案 6 :(得分:0)

Underscore-java库在加载xml时保留属性顺序。我是该项目的维护者。

答案 7 :(得分:-1)

您不能使用DOM,但您可以使用SAX,或使用XPATH查询子项 请访问此答案https://stackoverflow.com/a/3728241/2598693