如何获取 XML 文件的特定信息

How to get a specifc information for an XML file

我有一个很大的 XML 文件,下面是其中的摘录:

...
<LexicalEntry id="Ait~ifAq_1">
  <Lemma partOfSpeech="n" writtenForm="اِتِّفاق"/>
  <Sense id="Ait~ifAq_1_tawaAfuq_n1AR" synset="tawaAfuq_n1AR"/>
  <WordForm formType="root" writtenForm="وفق"/>
</LexicalEntry>
<LexicalEntry id="tawaA&amp;um__1">
  <Lemma partOfSpeech="n" writtenForm="تَوَاؤُم"/>
  <Sense id="tawaA&amp;um__1_AinosijaAm_n1AR" synset="AinosijaAm_n1AR"/>
  <WordForm formType="root" writtenForm="وأم"/>
</LexicalEntry>    
<LexicalEntry id="tanaAgum_2">
  <Lemma partOfSpeech="n" writtenForm="تناغُم"/>
  <Sense id="tanaAgum_2_AinosijaAm_n1AR" synset="AinosijaAm_n1AR"/>
  <WordForm formType="root" writtenForm="نغم"/>
</LexicalEntry>


<Synset baseConcept="3" id="tawaAfuq_n1AR">
  <SynsetRelations>
    <SynsetRelation relType="hyponym" targets="AinosijaAm_n1AR"/>
    <SynsetRelation relType="hyponym" targets="AinosijaAm_n1AR"/>
    <SynsetRelation relType="hypernym" targets="ext_noun_NP_420"/>
  </SynsetRelations>
  <MonolingualExternalRefs>
    <MonolingualExternalRef externalReference="13971065-n" externalSystem="PWN30"/>
  </MonolingualExternalRefs>
</Synset>
...

我想从中提取特定信息。对于给定的 writtenForm,无论是来自 <Lemma> 还是 <WordForm>,程序都从 writtenForm<Sense> 中获取 synset 的值(相同的 <LexicalEntry>) 并搜索 <Synset> 中与 <Sense> 中的 synset 具有相同值的所有值 id。之后,程序给出了 Synset 的所有关系,即显示 relType 和 returns 到 <LexicalEntry> 的值,并查找值 synset<Sense> 具有与 targets 相同的值然后显示其 writtenForm.

我觉得有点复杂但是结果应该是这样的:

اِتِّفاق hyponym تَوَاؤُم, اِنْسِجام

解决方案之一是使用 Stream reader 因为内存消耗。但我不知道我应该如何着手获得我想要的东西。请帮助我。

如果此 XML 文件太大而无法在内存中表示,请使用 SAX。

您将需要编写 SAX 解析器来维护一个位置。为此,我通常使用 StringBuffer,但字符串堆栈也同样有效。这部分很重要,因为它将允许您跟踪返回文档根目录的路径,这将允许您了解您在给定时间点在文档中的位置(在尝试仅提取一个文档时很有用)信息很少)。

主要逻辑流程如下:

 1. When entering a node, add the node's name to the stack.
 2. When exiting a node, pop the node's name (top element) off the stack.
 3. To know your location, read your current branch of the XML from the bottom of the stack to the top of the stack.
 4. When entering a region you care about, clear the buffer you will capture the characters into
 5. When exiting a region you care about, flush the buffer into the data structure you will return back as your output.

这样您就可以有效地跳过 XML 树中您不关心的所有分支。

SAX 解析器不同于 DOM Parser.It 只查看当前的 item 它无法查看未来的项目,直到它们成为当前的 item 。当 XML 文件非常大时,它是您可以使用的众多工具之一。取而代之的是那里有很多。仅举几例:

  • SAX解析器
  • DOM解析器
  • JDOM 解析器
  • DOM4J解析器
  • STAX解析器

您可以找到所有教程 here

我认为学习后直接使用 DOM4JJDOM 用于商业产品。

SAX 解析器的逻辑是你有一个 MyHandler class 扩展了 DefaultHandler@Overrides 它的一些方法:

XML 文件:

<?xml version="1.0"?>
<class>
   <student rollno="393">
      <firstname>dinkar</firstname>
      <lastname>kad</lastname>
      <nickname>dinkar</nickname>
      <marks>85</marks>
   </student>
   <student rollno="493">
      <firstname>Vaneet</firstname>
      <lastname>Gupta</lastname>
      <nickname>vinni</nickname>
      <marks>95</marks>
   </student>
   <student rollno="593">
      <firstname>jasvir</firstname>
      <lastname>singn</lastname>
      <nickname>jazz</nickname>
      <marks>90</marks>
   </student>
</class>

处理程序 class:

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class UserHandler extends DefaultHandler {

   boolean bFirstName = false;
   boolean bLastName = false;
   boolean bNickName = false;
   boolean bMarks = false;

   @Override
   public void startElement(String uri, 
   String localName, String qName, Attributes attributes)
      throws SAXException {
      if (qName.equalsIgnoreCase("student")) {
         String rollNo = attributes.getValue("rollno");
         System.out.println("Roll No : " + rollNo);
      } else if (qName.equalsIgnoreCase("firstname")) {
         bFirstName = true;
      } else if (qName.equalsIgnoreCase("lastname")) {
         bLastName = true;
      } else if (qName.equalsIgnoreCase("nickname")) {
         bNickName = true;
      }
      else if (qName.equalsIgnoreCase("marks")) {
         bMarks = true;
      }
   }

   @Override
   public void endElement(String uri, 
   String localName, String qName) throws SAXException {
      if (qName.equalsIgnoreCase("student")) {
         System.out.println("End Element :" + qName);
      }
   }

   @Override
   public void characters(char ch[], 
      int start, int length) throws SAXException {
      if (bFirstName) {
         System.out.println("First Name: " 
            + new String(ch, start, length));
         bFirstName = false;
      } else if (bLastName) {
         System.out.println("Last Name: " 
            + new String(ch, start, length));
         bLastName = false;
      } else if (bNickName) {
         System.out.println("Nick Name: " 
            + new String(ch, start, length));
         bNickName = false;
      } else if (bMarks) {
         System.out.println("Marks: " 
            + new String(ch, start, length));
         bMarks = false;
      }
   }
}

主要 Class :

import java.io.File;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXParserDemo {
   public static void main(String[] args){

      try { 
         File inputFile = new File("input.txt");
         SAXParserFactory factory = SAXParserFactory.newInstance();
         SAXParser saxParser = factory.newSAXParser();
         UserHandler userhandler = new UserHandler();
         saxParser.parse(inputFile, userhandler);     
      } catch (Exception e) {
         e.printStackTrace();
      }
   }   
}

XPath was designed for exactly this. Java provides support for it in the javax.xml.xpath包。

做你想做的事,代码看起来像这样:

List<String> findRelations(String word,
                           Path xmlFile)
throws XPathException {

    String xmlLocation = xmlFile.toUri().toASCIIString();

    XPath xpath = XPathFactory.newInstance().newXPath();

    xpath.setXPathVariableResolver(
        name -> (name.getLocalPart().equals("word") ? word : null));
    String id = xpath.evaluate(
        "//LexicalEntry[WordForm/@writtenForm=$word or Lemma/@writtenForm=$word]/Sense/@synset",
        new InputSource(xmlLocation));

    xpath.setXPathVariableResolver(
        name -> (name.getLocalPart().equals("id") ? id : null));
    NodeList matches = (NodeList) xpath.evaluate(
        "//Synset[@id=$id]/SynsetRelations/SynsetRelation",
        new InputSource(xmlLocation),
        XPathConstants.NODESET);

    List<String> relations = new ArrayList<>();

    int matchCount = matches.getLength();
    for (int i = 0; i < matchCount; i++) {
        Element match = (Element) matches.item(i);

        String relType = match.getAttribute("relType");
        String synset = match.getAttribute("targets");

        xpath.setXPathVariableResolver(
            name -> (name.getLocalPart().equals("synset") ? synset : null));
        NodeList formNodes = (NodeList) xpath.evaluate(
            "//LexicalEntry[Sense/@synset=$synset]/WordForm/@writtenForm",
            new InputSource(xmlLocation),
            XPathConstants.NODESET);

        int formCount = formNodes.getLength();
        StringJoiner forms = new StringJoiner(",");
        for (int j = 0; j < formCount; j++) {
            forms.add(
                formNodes.item(j).getNodeValue());
        }

        relations.add(
            String.format("%s %s %s", word, relType, forms));
    }

    return relations;
}

一些基本的 XPath 信息:

  • XPath 使用单个类似文件路径的字符串来匹配 XML 文档的各个部分。这些部分可以是文档的任何结构部分:文本、元素、属性,甚至是评论之类的东西。
  • 一个 Java XPath 表达式可以尝试精确匹配一个或多个部分,甚至可以将所有匹配的部分连接成一个字符串。
  • 在 XPath 表达式中,名称本身代表一个元素。例如,XPath 中的 WordForm 表示 XML 文档中的任何 <WordForm …> 元素。
  • @开头的名称表示一个属性。例如,@writtenForm 指的是 XML 文档中的任何 writtenForm=… 属性。
  • 斜线表示 XML 文档中的父项和子项。 LexicalEntry/Lemma 表示 <Lemma> 元素是 <LexicalEntry> 元素的直接子元素。 Synset/@id 表示任何 <Synset> 元素的 id=… 属性。
  • 正如以 / 开头的路径表示 Unix 中的绝对(根目录相对)路径一样,以斜杠开头的 XPath 表示相对于 XML 文档的根目录的表达式。
  • 两个斜线表示后代,可以是直系子女、孙子女、曾孙子女等。因此,//LexicalEntry表示文档中的任何LexicalEntry; /LexicalEntry 只匹配作为根元素的 LexicalEntry 元素。
  • 方括号表示匹配限定符。 Synset[@baseConcept='3'] 匹配任何具有值为字符串“3”的 baseConcept 属性的 <Synset> 元素。
  • XPath 可以引用外部定义的变量,使用 Unix-shell-like $ 替换,如 $word。这些变量如何传递给 XPath 表达式取决于引擎。 Java 使用 setXPathVariableResolver 方法。变量名称与节点名称位于完全独立的命名空间中,因此如果变量名称与 XML 文档中的元素名称或属性名称相同,则无关紧要。

因此,代码中的 XPath 表达式表示:

//LexicalEntry[WordForm/@writtenForm=$word or Lemma/@writtenForm=$word]/Sense/@synset

匹配 XML 文档中任何具有

的任何 <LexicalEntry> 元素
  • 具有 writtenForm 属性的 WordForm 子项,其值等于 word 变量
  • 具有 writtenForm 属性的 Lemma child 其值等于 word 变量

并且对于每个这样的 <LexicalEntry> 元素,return 任何 <Sense> 元素的 synset 属性的值,该元素是 [=21= 的直接子元素]元素。

word 变量由 xpath.setXPathVariableResolver 在外部定义,就在对 XPath 表达式求值之前。

//Synset[@id=$id]/SynsetRelations/SynsetRelation

匹配 XML 文档中 id 属性等于 id 变量的任何 <Synset> 元素。对于每个这样的 <Synset> 元素,查找任何直接的 SynsetRelations 子元素,以及 return 它的每个直接 SynsetRelation 子元素。

id 变量由 xpath.setXPathVariableResolver 在外部定义,就在 XPath 表达式被求值之前。

//LexicalEntry[Sense/@synset=$synset]/WordForm/@writtenForm

匹配 XML 文档中任何具有 <Sense> 子元素的任何 <LexicalEntry> 元素,该子元素的 synset 属性值与 [=36] 相同=] 变量。对于每个匹配的元素,找到任何 <WordForm> 子元素和 return 该元素的 writtenForm 属性。

synset 变量由 xpath.setXPathVariableResolver 在外部定义,就在 XPath 表达式被计算之前。


从逻辑上讲,上面的内容应该是:

  • 找到所请求词的同义词集值。
  • 使用 synset 值定位 SynsetRelation 元素。
  • 找到与每个匹配的 SynsetRelation 的目标值相对应的 writtenForm 值。