飙血推荐
  • HTML教程
  • MySQL教程
  • JavaScript基础教程
  • php入门教程
  • JavaScript正则表达式运用
  • Excel函数教程
  • UEditor使用文档
  • AngularJS教程
  • ThinkPHP5.0教程

使用 X 定义从超大 XML 文件中提取数据

时间:2022-01-04  作者:电脑狂魔  

X-definition 是一种开源 Java API,可用于从 XML 文件中提取数据,而不管其大小。它不会迫使 Java 虚拟机抱怨它的堆内存不足,甚至也不会要求您的 Java 代码按照 XML 部分的出现顺序逐步执行这些部分,直到到达您需要的数据位置. 它只需要一个 XML 文档的标记模型,并且每 GB 的 XML 数据需要大约 90 到 120 秒的处理时间。

在本文中,我们将下载一个2.5 GB文件,并使用最少的代码从中提取数据。我们的 X 定义说明如下:

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther; occurs *; finally outln();forget;'>
<main_release>
onTrue out(getText());
</main_release>
<artists>
<artist xd:script="options ignoreOther;occurs *;">
<id>
onTrue out("\t" + getText());
</id>
<name>
onTrue out("\t" + getText());
</name>
</artist>
</artists>
</master>
</masters>
</xd:def>

为了调用 X 定义 API,我们只需要以下java代码: 

import 域名.NullReportWriter;
import 域名cument;
import 域名ctory;
import 域名ol;
public class Xdefinition {
public static void main(String[] argv) throws Exception
{
Xdefinition xdefinition = new Xdefinition();
}
{
XDPool xpool = 域名ileXD(null,"域名");
XDDocument xdoc = 域名teXDDocument();
域名se("域名",new NullReportWriter(false));
}
}

没有其他东西需要编写、编译或运行。 

下载 XML 文档

从下载站点,我使用了从2021目录中获取的文件:存档的名称是域名. 

它是最大的文件(2.5 GB,正如我之前提到的),但是是有关月份的文件。域名解压时最大的 ( ) 超过 60 GB。 

下载 X 定义 

您可以在此处下载 X 定义。 类路径中唯一需要的文件是xdef-41.域名,位于存档xdef目录中。 

该档案还包含 API 文档、源代码(包括 API 接口和 Syntea 实现的代码;提供的 JavaDoc 目前仅适用于前者)和各种 PDF 格式的用户手册。我将在整篇文章中引用的用户手册是xdef-域名(语言描述),也可以在GitHub上找到(并且会在机会来临时链接到全文)。 

准备 .xdef 文件 

.xdef刚才我们完整地看到了这个文件:它带有一个 XML 声明,并由用 XML 编写的标记组成。在手册中,它或xd:def元素经常被称为“X 定义”。

这或多或少表明它是 XML 文档的 [可能缩写] 模型。“模型”是手册至少使用一次的术语。总体思路是提供一种表示现有 XML 文档的方式,该方式与人类可读的文档平行,比 XML 模式定义可能更明显一些。 

事实上,当我们开始编写我们的.xdef文件时,我们可以像制作我们下载的文档的空副本一样继续。当文档太大而无法在文本编辑器中打开时,这并不容易,但这并非不可能。在 Linux 系统上,我们可以使用less命令打开 XML 文件并发现文档元素,该元素称为masters. 完成此操作后,我们就有足够的信息来编写.xdef文件的封闭元素: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
</xd:def>

除了通过root属性识别文档根之外,我们不需要做更多的事情(并且还声明 X-definition 命名空间以便在xd整个过程中使用前缀)。name我们可以使用一个可选属性来标识xdef元素本身,但只有在项目中包含多个元素时才需要它。当我们稍后调用 API 时,我们需要的只是.xdef文件的路径。 

手册的附录 A 包含对xd:def元素以及.xdef文件中允许的其他元素和属性的标准 (Backus-Naur) 符号描述。

当我们查看我们的 XML 文档时,很明显masters元素包含master元素。以下在某种程度上代表了一个master元素: 

<master id="2407459">
<main_release>6201234</main_release>
<images>
<image type="primary" uri="" uri150="" width="600" height="600"/>
<image type="secondary" uri="" uri150="" width="600" height="597"/>
</images>
<artists>
<artist>
<id>4054418</id>
<name>Lee Caron</name>
<anv></anv>
<join></join>
<role></role>
<tracks></tracks>
</artist>
</artists>
<genres>
<genre>Rock</genre>
<genre>Pop</genre>
</genres>
<styles>
<style>Rhythm & Blues</style>
<style>Rock & Roll</style>
</styles>
<year>1955</year>
<title>Back To An Empty Room</title>
<data_quality>Correct</data_quality>
<videos>
<video src="https://域名/watch?v=2h-xb5bUhTE" duration="185" emb
ed="true">
<title>Back To An Empty Room by Lee Caron</title>
<description>Please leave comment.</description>
</video>
</videos>
</master>

并非完全如此:例如,如果我们进一步查看文档,我们会发现一个artists元素有时包含多个artist元素。此外,并非每个master元素都包含一个videos元素。artist然而,只要我们意识到可以有连续的元素,我们就会发现到目前为止我们对 XML 文档的了解就足够了。 

我们将专注于我们的master,main_release和artists元素。我们的目标只不过是一个简单的文本表格。每一行都将包含master元素的id属性,该main_release元素的内容,以及各自的内容id和name元素是的子artist元素。 

只要我们的.xdef文件可以识别我们感兴趣的元素或属性,这不是一个挑战。其中第一个是我们之前指定的xd:def元素root属性的元素: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
</masters>
</xd:def>

我们以这样一种方式将其他元素合并masters到我们的.xdef文件中,就像在我们的 XML 文档中一样。但是,我们输入的内容并不像看起来那么简单,相当于: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters xd:script="required">
</masters>
</xd:def>

我们将在xd:script其他地方使用辅助属性,但在不使用它或不包含我们很快会发现的是量词的地方(或者,如上所述,相当于量词的关键字,在本例中为occurs 1), X 定义将默认为required,这意味着“仅发生一次”。 

因为该master元素不仅包含id我们将需要的属性 ( ),而且还包含我们将需要的相应内容的其他元素,所以它比其他元素更需要我们的关注。对于我们的.xdef文件来说,独一无二的是,它将有一个与 XML 文档中的属性相对应的属性,以及一个辅助xd:script属性: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='' xd:script=''>
</master>
</masters>
</xd:def>

这些值将用单引号引起来以容纳双引号,并且将由 X-definition 的开发人员称为 X-script 的内容组成。 

开始考虑的两个属性中的第一个是xd:script。请记住:一旦两者都填写完毕,我们真正需要在.xdef文件相关的地方做的大部分工作都将完成。

我之前注意到该master元素包含我们需要的其他元素 ( main_release, artists)。然而,并非所有都需要,如果我们options在xd:script属性值中包含一个部分,我们可以告诉 X-definition 只考虑我们在.xdef文件中提到的那些: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='' xd:script='options ignoreOther;'>
</master>
</masters>
</xd:def>

每当我们所在的元素包含我们希望忽略的子元素时,我们都需要这样做。否则,只要.xdef在 XML 文档中检测到我们在文件中忽略的元素,X-definition 就会生成错误输出(在我们的例子中是不可见的,因为我们将在 Java 代码中调用 API 时确定)。选项部分在手册的 域名 节中讨论。 

接下来我们需要考虑的是master元素可能出现的次数。在masters担心的地方,我们拒绝这样做,因为 X-definition 的默认值(理解为required关键字,相当于occurs 1)已经可以接受了。如果我们在这里做同样的事情,只会处理一个master元素(并且将为其余每个元素生成一条错误消息)。我们可以指出master出现零次或多次: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='' xd:script='options ignoreOther;occurs *;'>
</master>
</masters>
</xd:def>

occurs *每当我们知道一个元素(例如,artist)将出现不止一次时,就需要像上面这样的量词()。手册的 域名 节讨论了量词。 

我们需要考虑(目前)关于master元素xd:script属性的最后一件事是处理非常多的master元素——超过 150 万——可供我们使用。为了在masterX 定义准备好移动到以下master元素时从内存中删除每个元素,forget需要将关键字部署在xd:script属性值的最尾部: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='' xd:script='options ignoreOther;occurs *;forget;'>
</master>
</masters>
</xd:def>

如果我们的 XML 文档在树中具有与 相同级别的任何其他元素master,并且它们在我们的.xdef文件中也被引用,那么forget关键字将需要在它们的上下文以及master. 否则,JVM 可用的堆内存最终可能会耗尽。的forget关键字是该手册的部分4.1.9所讨论的。 

到目前为止,我们的 X 脚本已通过辅助属性 ( xd:script)分配给文档的一部分(元素)。我们可以通过简单地在我们的.xdef文件的相应位置输入 X 脚本,对已知存在于我们的 XML 文档中的属性或文本节点执行类似的操作:对于我们master元素的id属性,该位置将在单引号内。id在我们的 XML 文档中找到的属性值是我们文本表每一行的初始单元格的来源: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;forget;'>
</master>
</masters>
</xd:def>

该onTrue关键字是指一个事件。当 X 定义出现在一个master元素上时,它的id属性变为可用。在当前情况下,onTrue如果该id属性带有任意长度的字符串作为其值,则该事件发生。如果它在 X 脚本的其他地方指出 的值id只需要是一个整数,并且 X 定义无法解释该值,那么onFalse事件就会发生。X-script 中的事件有相应的动作。此处,X 脚本将操作定义为将id属性值(后跟制表符)写入标准输出。事件在手册的 域名 和 4.1.9 节中讨论;的getText()和out()功能在部分域名。7.1节(6.1版本中 在 GitHub 上)讨论了 X-definition 处理 XML 文档部分的顺序。 

在离开master元素之前,X-definition 将从其子元素中提取表格行的其余数据。我们可以通过重新访问我们的辅助属性并向其添加代码来终止我们的表格行,一旦它完成了main_release和artists元素——也就是说,一旦finally事件发生在master元素的上下文中xd:script: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;finally outln();forget;'>
</master>
</masters>
</xd:def>

回车将终止每一行。 

部分原因是我们的文本表按照它在 XML 文档中出现的顺序来呈现它的数据——因此我们不需要定义用于存储任何变量的变量——需要编写的代码非常少。我们不需要再引用 XML 文档中的任何属性,因此将我们剩余的元素添加到我们目前所拥有的元素中真的很容易: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;finally outln();forget;'>
<main_release>
</main_release>
<artists>
<artist>
<id>
</id>
<name>
</name>
</artist>
</artists>
</master>
</masters>
</xd:def>

的文本内容main_release,id以及name元素将提供数据的其余部分为我们的表行。 

我们将添加到的 X 脚本main_release与之前用于master元素id属性的 X 脚本相同,除了制表符(此时将证明是多余的): 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;finally outln();forget;'>
<main_release>
onTrue out(getText());
</main_release>
<artists>
<artist>
<id>
</id>
<name>
</name>
</artist>
</artists>
</master>
</masters>
</xd:def>

我们在master元素的id属性值出现在 XML 文档中的位置之前添加它的地方,我们将它添加到main_release元素文本内容所在位置的上方。该getText()函数将获取main_release元素的文本内容,并将其out()发送到标准输出。 

我们处理artist标签的方式与我们之前在master元素xd:script标签中所做的类似: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;finally outln();forget;'>
<main_release>
onTrue out(getText());
</main_release>
<artists>
<artist xd:script="options ignoreOther;occurs *;">
<id>
</id>
<name>
</name>
</artist>
</artists>
</master>
</masters>
</xd:def>

和以前一样,省略该ignoreOther选项将确保大量的错误输出,因为我们不打算考虑artist元素的每个子元素的最后一个,并且省略量词 ( occurs *) 将导致更多的错误输出,并且只会导致第一个artist正在访问的元素。 

完成就像将 X 脚本添加到name和id标签一样简单,文本内容将出现在原始 XML 文档中: 

<?xml version="1.0" encoding="UTF-8"?>
<xd:def xmlns:xd="http://域名/xdef/4.1" root="masters">
<masters>
<master id='onTrue out(getText() + "\t");' xd:script='options ignoreOther;occurs *;finally outln();forget;'>
<main_release>
onTrue out(getText());
</main_release>
<artists>
<artist xd:script="options ignoreOther;occurs *;">
<id>
onTrue out("\t" + getText());
</id>
<name>
onTrue out("\t" + getText());
</name>
</artist>
</artists>
</master>
</masters>
</xd:def>

当 X-definition 检测到任一元素中的文本时,该元素的内容(以制表符开头)将写入标准输出。如前所述,元素标签中的finally子句将最终终止表格行。 masterxd:script

随着我们的.xdef文件完成(正如我们在本文开头预览的那样),我们准备好运行 X-definition! 

运行X定义 

调用 X 定义 API 的说明包含在手册的第 9 和 9.1 节(GitHub 上的版本中为 8.1)。将进入我们的 Java 源文件的大部分工作与导入我们的代码所需的少量接口和类有关: 

import 域名.NullReportWriter;
import 域名cument;
import 域名ctory;
import 域名ol;

该main方法实际上只需要实例化我们的驱动程序类,我将其命名为Xdefinition: 

import 域名.NullReportWriter;
import 域名cument;
import 域名ctory;
import 域名ol;
public class Xdefinition {
    public static void main(String[] argv) throws Exception
        {
            Xdefinition xdefinition = new Xdefinition();
        }

正如开头所预知的,初始化程序块应包含所有三行代码。 

我们的第一行需要将我们.xdef命名为“ 域名”的文件编译成一个XDPool: 

XDPool xpool = 域名ileXD(null,"域名");

XDPool在 API 中定义为接口:在这里,您通过将.xdef文件名传递给XDFactory类的compileXD()方法来获取实例。如果第一个参数compileXD()not null,它将是属性文件的名称(详细信息在 API 文档中域名ctory)。 

初始化块的下一行很简单: 

XDDocument xdoc = 域名teXDDocument();

我们只是调用我们XDPool实例的createXDDocument()方法来获取一个XDDocument实例。XDDocument在 API 中定义为接口。API JavaDoc 暗示其实例对我们的 XML 文档进行实际处理。 

实际上,最后一行是触发处理的内容: 

域名se("域名",new NullReportWriter(false));

域名已假定XML 文档 ( ) 与.xdef之前传递给的文件位于同一目录中compileXD()。我们XDDocument实例的xparse()方法有效地启动了 X 定义。X-definition 或多或少要求任何错误输出到某个地方:相应地,API 中定义的专用类的实例NullReportWriter被作为xparse()的第二个参数传递,位于源 XML 的位置之后。的 API 文档域名.NullReportWriter建议了在创建新实例时传递true而不是false作为参数的潜在后果。 

我们将编译的代码,包括初始化程序块,如下所示: 

import 域名.NullReportWriter;
import 域名cument;
import 域名ctory;
import 域名ol;
public class Xdefinition {
        
    public static void main(String[] argv) throws Exception
        {
            Xdefinition xdefinition = new Xdefinition();
        }
        {
            XDPool xpool = 域名ileXD(null,"域名");
            XDDocument xdoc = 域名teXDDocument();
            域名se("域名",new NullReportWriter(false));
        }}

为了通过javac命令编译它,我们需要xdef-41.域名 在我们的类路径上有这个文件。我们将.xdef文件的位置和大型 XML 文档连接到我们的代码中,因此为了最终运行 X-definition,我们只需要一个类似以下的命令: 

java -cp .:xdef-41.域名 Xdefinition

当 X-definition 运行时,除非您的命令的输出被重定向,否则文本表将溢出到终端上。在任何情况下您都不会看到任何错误消息,因为NullReportWriter该类具有使错误输出消失的区别。你甚至不知道是否有。 

将NullReportWriter实例传递给的替代方法xparse()是传递 的实例域名.FileReportWriter。如果我们选择这样做,我们的代码可能如下所示: 

import 域名.FileReportWriter; import 域名cument; import 域名ctory; import 域名ol; public class Xdefinition {              public static void main(String[] argv) throws Exception         {             Xdefinition xdefinition = new Xdefinition();         }         {             XDPool xpool = 域名ileXD(null,"域名");             XDDocument xdoc = 域名teXDDocument();             域名se("域名",new FileReportWriter("/dev/stderr"));         } }

如果上面的代码被编译并运行,结果将是相同的,因为我之前确保我们的.xdef文件不会导致 X-definition 检测到任何错误。如果您使用FileReportWriter该类并且确实有错误,您可能会得到一个终端(或文件:上面,我使用系统标准错误流的位置作为FileReportWriter构造函数的参数,而不是将潜在的错误消息存储到磁盘)很好大而详细的输出;尽管如此,您的代码仍会运行,并且您可能不必考虑错误,前提是您找到的输出完全符合您的预期。像我在这里所做的那样严格避免错误,恐怕与其说是方便问题,不如说是使用与其整体设计一致的 X 定义。 

您会看到,X-definition 主要用于检查给定的 XML 文档在您处理它时是否有效。我们没有在此有意尝试,但如果您愿意,您可以设置标准,就像您使用可识别模式的 XSLT 处理器一样。就像 iText 的开发人员建立在他对 PDF 规范的了解之上一样,X-definition 的开发人员熟悉 XSD 并设计了实现其可能性的替代方法。 

湘ICP备14001474号-3  投诉建议:234161800@qq.com   部分内容来源于网络,如有侵权,请联系删除。