我正在编写一个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:请避免讨论是否应该保留属性顺序。这是一个非常有趣的讨论,但这不是这个问题的重点。
答案 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