【问题标题】:Java partial parse of XML file and attributesXML文件和属性的Java部分解析
【发布时间】:2022-10-03 05:02:13
【问题描述】:

我需要解析一个我不知道整个结构的 xml 文件,但我知道一些我希望解析的节点和属性,假设我有这个文件结构:

<Root>
//lots and lots of irrelevant info here
               <Name>
                    <EffectiveName Value=\"My-Name\" />
                    <UserName Value=\"\" />
                    <Annotation Value=\"\" />
                    <MemorizedFirstClipName Value=\"\" />
                </Name>
//lots and lots of irrelevant info here
<NotNeeded1>
    <NotNeeded2>
       <NotNeeded13>
               <KeyTracks>
                                                <KeyTrack Id=\"43\">
                                                    <Notes>
                                                        <MidiNoteEvent Time=\"0\" Duration=\"0.5\" Velocity=\"120\" VelocityDeviation=\"0\" OffVelocity=\"64\" Probability=\"1\" IsEnabled=\"true\" NoteId=\"73\" />
                                                    </Notes>
                                                    <MidiKey Value=\"0\" />
                                                </KeyTrack>
                                                <KeyTrack Id=\"44\">
                                                    <Notes>
                                                        <MidiNoteEvent Time=\"0.5\" Duration=\"0.5\" Velocity=\"120\" VelocityDeviation=\"0\" OffVelocity=\"64\" Probability=\"1\" IsEnabled=\"true\" NoteId=\"75\" />
                                                    </Notes>
                                                    <MidiKey Value=\"1\" />
                                                </KeyTrack>
                                                <KeyTrack Id=\"45\">
                                                    <Notes>
                                                        <MidiNoteEvent Time=\"1\" Duration=\"0.5\" Velocity=\"120\" VelocityDeviation=\"0\" OffVelocity=\"64\" Probability=\"1\" IsEnabled=\"true\" NoteId=\"77\" />
                                                    </Notes>
                                                    <MidiKey Value=\"2\" />
                                                </KeyTrack>
                                                <KeyTrack Id=\"46\">
                                                    <Notes>
                                                        <MidiNoteEvent Time=\"1.5\" Duration=\"0.5\" Velocity=\"120\" VelocityDeviation=\"0\" OffVelocity=\"64\" Probability=\"1\" IsEnabled=\"true\" NoteId=\"79\" />
                                                    </Notes>
                                                    <MidiKey Value=\"3\" />
                                                </KeyTrack>
                                            </KeyTracks>
            </NotNeeded1>
      </NotNeeded2>
</NotNeeded13>
//lots and lots of irrelevant info here
</Root>

它比那要大得多,但只是为了举例。

我希望将文件提取并解析为以下对象:

CustomObject.class

public class CustomObject{
   private String effectiveName;
   private List<KeyTrack> keyTracks;
}

KeyTracks.class

public class KeyTrack {
    private Integer keyTrackId;
    private MidiNoteEvent midiNoteEvent;
    private Integer midiKey;
}

MidiNoteEvent .class

public class MidiNoteEvent {
    private Double time;
    private Integer velocity;
    private Double duration;
    private Boolean isEnabled;
}

我正在尝试尽可能通用,因此如果我需要添加另一个节点或属性,我将不必更改我的解析器,因此 switch/case 或 if/else 不适合我的情况可能有数百个节点和添加,如果需要解析额外的信息,我不想更改多个类。

我试图为我需要的节点创建一个枚举,但我找不到能够正确抓取节点的最佳位置。

这些是我现在的解析器功能

private final Map<String, Object> map;

 @Override
    public ProjectTrack parse(Node node) {
        parseToMap(node);
        return mapper.convertValue(map, CustomObject.class);
    }

    private void parseToMap(Node node){
        if(Arrays.stream(RelevantMidiTrackNodes.values()).anyMatch(
                e -> e.getNodeName().equals(node.getNodeName()))
        ){
            System.out.print(node.getNodeName()  + \": \");
            for (int i = 0; i < node.getAttributes().getLength(); i++) {
                Attr attribute = (Attr)node.getAttributes().item(i);
                System.out.print(attribute.getNodeName()  + \" - \" + attribute.getValue() + \", \");
                map.put(attribute.getNodeName(), attribute.getValue());
            }
            System.out.println();
        }
        NodeList nodeList = node.getChildNodes();
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node currentNode = nodeList.item(i);
            if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
                //calls this method for all the children which is Element
                parseToMap(currentNode);
            }
        }
    }

更新:

更新了文件本身,我需要的节点可以更深 10 个节点,并且父节点无关紧要,所以我正在寻找一种方法来提取第 1、第 2 级和第 10 级的节点。

在我的示例中,将 EffectiveName 提取到字符串并将 KeyTracks 提取到列表

  • 使用 XPath 获取所需元素然后解组它们可能会更好。此外,该问题应提供一个minimal reproducible example,包括最小完整的 xml 结构。
  • 我上传了我需要的 xml 的结构以及我需要将其解析为的对象。如果您能详细说明,不确定到底缺少什么,谢谢

标签: java xml parsing


【解决方案1】:

这是一个如何使用 xpath 获取节点并将它们解组到 pojos 的示例。在这种情况下,获取MidiNoteEvent

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;

public class MainJaxbXpath {

    public static void main(String[] args) throws Exception {   
            FileInputStream fileIS;
            fileIS = new FileInputStream(System.getProperty("user.home") + "/tmp/tmp.xml");

            DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder;
            builder = builderFactory.newDocumentBuilder();
            
            JAXBContext jc = JAXBContext.newInstance( MidiNoteEvent.class );
            Unmarshaller u = jc.createUnmarshaller();

            Document xmlDocument;
            xmlDocument = builder.parse(fileIS);

            XPath xPath = XPathFactory.newInstance().newXPath();
            NodeList nodeList =(NodeList) xPath.compile("//MidiNoteEvent").evaluate(xmlDocument, XPathConstants.NODESET);
            
            MidiNoteEvent o = (MidiNoteEvent) u.unmarshal( nodeList.item(0) );
    }
}

带有适当注释的 MidiNoteEvent.class

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="MidiNoteEvent")
public class MidiNoteEvent {
    
    @XmlAttribute(name = "Time")
    private Double time;
    @XmlAttribute(name = "Velocity")
    private Integer velocity;
    @XmlAttribute(name = "Duration")
    private Double duration;
    @XmlAttribute(name = "IsEnabled")
    private Boolean isEnabled;
    // getters/setters
}

可以为 1 个以上的类创建 JAXBContext

JAXBContext jc = JAXBContext.newInstance( KeyTrack.class, MidiNoteEvent.class );

【讨论】:

  • 架构显然会随着时间而改变,在我看来,当架构更改频繁时,数据绑定 (JAXB) 始终是一种糟糕的方法。
  • Xpath 是相对的,因此在这种情况下架构更改并不重要。
  • 典型的问题是模式更改会使现有 Java 代码无效,因此您在实施模式更改时会立即遇到更改控制问题,因为如果不重新构建和重新测试,您不知道它们的影响是什么。
  • 鉴于这种情况,如果架构更改,为什么 xslt 方法不会失效?
  • XSLT 的基于匹配模式的处理模型都是为了让代码尽可能地健壮地抵抗模式变化;并且(通常)处理器在编译时不访问模式;它们是纯粹的解释。
【解决方案2】:

我个人选择的所有 XML 处理工具都是 XSLT。当您混合使用 XSLT 和 Java 时,细节会因一个 XSLT 处理器而异,但这是使用 XSLT 3.0 和 Saxon 的方法。本示例使用开源版本。

首先,编写一个样式表,将您想要的数据提取为 XDM(XPath 数据模型)映射:

<xsl:transform version="3.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
  <xsl:map>
     <xsl:apply-templates select="//EffectiveName, //KeyTrack"/>
  </xsl:map>
</xsl:template>
<xsl:template match="//EffectiveName">
  <xsl:map-item key="'EffectiveName'" select="@Value"/>
</xsl:template>
<xsl:template match="//KeyTrack">
  <xsl:map-item key="@Id" select="map{
      "time":      number(Nodes/@Time),
      "velocity":  number(Notes/@Velocity),
      "duration":  number(Notes/@Duration),
      "isEnabled": string(Notes/@IsEnabled),
      "midiKey":   number(MidiKey/@Value),
     }"/>
</xsl:template>
</xsl:transform>

运行此转换以提供 Saxon XdmMap 对象:

Processor proc = new Processor(false);
DocumentBuilder builder = proc.newDocumentBuilder();
XdmNode source = builder.build("input.xml");
XsltCompiler comp = proc.newXsltCompiler();
Xslt30Transformer trans = comp.compile("stylesheet.xsl").load30();
XdmMap result = (XdmMap)trans.applyTemplates(source);

然后处理 XdmMap 以构造要在应用程序中使用的 Java POJO。

for (Map.Entry<> entry : result.entrySet()) {
   String key = entry.getKey().getStringValue();
   if (key.equals("EffectiveName") {
     ...
   } else {
      ...
   }
}

如果您愿意,只需很少的更改,您就可以让样式表以 JSON 格式输出您想要的数据,然后使用 JSON 库来构建您的 POJO。

实际上,如果我这样做,我会问您是否真的需要在 Java 中处理数据——我可能会尝试找到一种方法来完成整个工作在 XSLT 中。但我不知道“整个工作”是什么,所以这可能是不现实的。

【讨论】:

  • 谢谢你,我不熟悉这种方法,所以我会研究一下。文件本身很大,有很多我不需要的节点,我需要的大多数节点都是内部节点,比如 9-10 个节点深,而父节点无关紧要。此外,我想拥有一个枚举或可以帮助构建文件的东西,而无需在 2 个地方进行更改。在您的情况下,如果我需要额外的节点,我需要在解析为 POJO 时更改 stylesheet.xsl 和 if\else
猜你喜欢
  • 2021-08-29
  • 2014-02-18
  • 2012-06-29
  • 2013-09-29
  • 2016-03-16
  • 2013-06-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多