11

我正在编写一个读取 XML 文件、进行一些修改并写回 XML 的 Java 程序。

使用标准 Java XML DOM API,不保留属性的顺序。

也就是说,如果我有一个输入文件,例如:

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

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

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

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

但是,我的程序需要保留属性的顺序,以便人们可以使用 diff 工具轻松比较输入和输出文档。

一种解决方案是使用 SAX(而不是 DOM)处理文档: DOM 处理后 XML 属性的顺序

但是,这不适用于我的情况,因为我需要在一个节点中进行的转换可能取决于整个文档上的XPath表达式。

因此,最简单的事情是拥有一个与标准 Java DOM 库非常相似的 XML 库,不同之处在于它保留了属性顺序。

有这样的图书馆吗?

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

4

7 回答 7

3

如今, Saxon提供了一个序列化选项[1]来控制属性输出的顺序。它不保留输入顺序(因为 Saxon 不知道输入顺序),但它允许您控制,例如,ID 属性总是首先出现。

如果要手动编辑 XML,这将非常有用;属性以“错误”顺序出现的 XML 可能会让人类阅读者或编辑者迷失方向。

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

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

于 2016-03-10T09:19:51.047 回答
2

做两次:

使用 DOM 解析器阅读文档,以便您拥有参考资料和存储库,如果您愿意的话。

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

于 2013-07-19T12:23:29.543 回答
2

您可能还想尝试DecentXML,因为它可以保留属性顺序、注释甚至缩进。

如果您需要以编程方式更新一个也应该是人工可编辑的 XML 文件,那就太好了。我们将它用于我们的配置工具之一。

- 编辑 -

它似乎不再在其原始位置可用;试试这些:

于 2019-12-03T12:36:42.500 回答
0

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

于 2013-07-19T12:34:23.840 回答
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>
于 2019-04-09T18:10:32.483 回答
0

根据Dave 的描述,我们有类似的要求。一个有效的解决方案是基于 Java 反射。

这个想法是在运行时为属性设置 propOrder。在我们的例子中,APP_DATA 元素包含三个属性:appkeyvalue。生成的 AppData 类在 propOrder 中包含“内容”,并且没有其他属性:

@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;
}
于 2017-11-15T18:18:44.483 回答
-1

您不能使用 DOM,但可以使用SAX或使用 XPath 查询子级。

访问答案Order of XML attributes after DOM processing

于 2013-07-19T09:23:13.963 回答