XML XSLT使用SAXON EE10.6流式处理大型XML文件



我必须将大型xml文件(>5Gb)导入SOLR。我想先用SAXON EE10.6和streaming xsl转换一个xml文件。我已经读过它应该可以与SAXON EE10.6,但我得到以下错误:

mytest.xsl第20行第34列出现错误:XTSE3430模板规则不可流化

  • 有多个消耗操作数:{<字段{(attr{name=…},…)}/>}在线21和第27行的{xsl:apply-templates}
  • 模板规则的结果可以包含流式节点模板规则不可流化
  • 有多个使用操作数:{<字段{(attr{name=…},…)}/>}第21行,第27行的{xsl:apply-templates}
  • 模板规则的结果可以包含流式节点

我不熟悉流媒体xslt和Saxon。如何使xslt适合流式传输,以输出所需的Solr添加文档xml。

我在这里摆弄了我的xml和我使用的xslt的简化版本:https://xsltfiddle.liberty-development.net/asoTKU

它适用于较小的xml文件(<1Gb)

XSLT3.0流的规则非常复杂,教程介绍很少也于事无补。一个非常有用的资源是Abel Braaksma在XML Prague 2014上的演讲:这里有一份文字记录和YouTube录音的链接https://www.xfront.com/Transcript-of-Abel-Braaksma-talk-on-XSLT-Streaming-at-XML-Prague-2014.pdf

需要记住的最重要的规则是:模板规则只能进行一次向下选择(它只有一次扫描后代树的机会)。这是你在写时打破的规则

<xsl:template match="node()">
<xsl:element name="field">
<xsl:attribute name="name">
<xsl:value-of select="local-name()"/>
</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
<xsl:apply-templates select="*"/>
</xsl:template>

实际上,该代码可以简化为

<xsl:template match="node()">
<field name="{local-name()}">{.}</field>
<xsl:apply-templates select="*"/>
</xsl:template>

但这不会影响流的能力:您要处理匹配节点的子节点两次,一次是获取字符串值(.),另一次是将模板应用于子节点。

现在,在我看来,这个模板规则似乎只是用于处理";叶片元件";,即具有文本节点子元素但没有子元素的元素。如果是这种情况,那么<xsl:apply-templates select="*"/>从不选择任何内容:它是多余的,并且可以删除,这使得规则可以进行流化。

您收到的另一条错误消息是,模板规则可以返回流式节点。不允许返回流式节点的原因有点微妙;它基本上使得处理器不可能进行数据流分析来证明流传输是否可行。但问题的原因还是<xsl:apply-templates select="*"/>,去掉它可以解决问题。

您的下一个问题是Property元素的模板规则。你已经把它写成

<xsl:template match="Property">
<xsl:element name="field">
<xsl:attribute name="name">
<xsl:value-of select="key"/>_s</xsl:attribute>
<xsl:value-of select="value"/>
</xsl:element>
<xsl:apply-templates select="Property"/>
</xsl:template>

它简化为:

<xsl:template match="Property">
<field name="{key}_s">{value}</field>
<xsl:apply-templates select="Property"/>
</xsl:template>

这将进行三个向下选择:child::keychild::valuechild::Property。在您的数据示例中,没有一个Property元素有一个名为Property的子元素,因此<xsl:apply-templates/>可能又是多余的。对于keyvalue,一个有用的技巧是将它们读取到地图中:

<xsl:template match="Property">
<xsl:variable name="pair" as="map(*)">
<xsl:map>
<xsl:map-entry key="'key'" select="string(key)"/>
<xsl:map-entry key="'value'" select="string(value)"/>
</xsl:map>
</xsl:variable>
<field name="{$pair?key}_s">{$pair?value}</field>
</xsl:template>

这起作用的原因是CCD_ 11(类似于CCD_;一个向下选择";规则-映射可以在一次输入中构建。通过调用string(),我们注意不要将任何流式节点放入映射中,因此我们稍后需要的数据已经在映射中捕获,我们永远不需要返回流式输入文档进行第二次读取。

我希望这能给你一种前进的感觉。XSLT中的流式处理不适合胆小的人,但如果您有>5Gb输入文档,那么您就没有太多选项可供选择。

假设您的Properties元素和Category是";"小";足够缓冲我想

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" expand-text="yes">
<xsl:output method="xml" encoding="utf-8" indent="yes" />

<xsl:strip-space elements="*"/>

<xsl:mode streamable="yes" on-no-match="shallow-skip"/>

<xsl:mode name="grounded"/>

<xsl:template match="Properties | Category">
<xsl:apply-templates select="copy-of()" mode="grounded"/>
</xsl:template>

<xsl:template match="Category" mode="grounded">
<field name="Category">{.}</field>
<xsl:apply-templates mode="#current"/>
</xsl:template>

<xsl:template match="Properties" mode="grounded">
<field name="Properties">{.}</field>
<xsl:apply-templates mode="#current"/>
</xsl:template>

<xsl:template match="Category/*" mode="grounded">
<field name="CAT_{local-name()}_s">{.}</field>
</xsl:template>
<xsl:template match="Property" mode="grounded">
<field name="{key}_s">{value}</field>
</xsl:template>
<xsl:template match="Item/*[not(self::Category | self::Properties)]">
<field name="{local-name()}">{.}</field>
</xsl:template>
<xsl:template match='/Items'>
<add>
<xsl:apply-templates select="Item"/>
</add>
</xsl:template>
<xsl:template match="Item">
<xsl:variable name="pos" select="position()"/>
<doc>
<xsl:apply-templates>
<xsl:with-param name="pos"><xsl:value-of select="$pos"/></xsl:with-param>
</xsl:apply-templates>
</doc>
</xsl:template>
</xsl:stylesheet>

但是您的代码(在<xsl:template match="Property">中执行<xsl:apply-templates select="Property"/>)表明,也许Property元素可以递归嵌套,如果代码像上面所做的那样,试图使用copy-of()在内存中缓冲遇到的第一个Property,那么任意嵌套可能会导致内存问题。

但是,您的示例XML没有任何嵌套的Property元素。

我评论的xsl:fork策略的一部分用于

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" expand-text="yes">
<xsl:output method="xml" encoding="utf-8" indent="yes" />

<xsl:strip-space elements="*"/>

<xsl:mode streamable="yes"/>

<xsl:mode name="text" streamable="yes"/>

<xsl:mode name="grounded"/>

<xsl:template match="Category">
<xsl:apply-templates select="copy-of()" mode="grounded"/>
</xsl:template>

<xsl:template match="Properties">
<xsl:fork>
<xsl:sequence>
<field name="Properties">
<xsl:apply-templates mode="text"/>
</field>
</xsl:sequence>
<xsl:sequence>
<xsl:apply-templates/>
</xsl:sequence>
</xsl:fork>
</xsl:template>

<xsl:template match="Category" mode="grounded">
<field name="Category">{.}</field>
<xsl:apply-templates mode="#current"/>
</xsl:template>

<xsl:template match="Category/*" mode="grounded">
<field name="CAT_{local-name()}_s">{.}</field>
</xsl:template>

<xsl:template match="Property">
<xsl:apply-templates select="copy-of()" mode="grounded"/>
</xsl:template>
<xsl:template match="Property" mode="grounded">
<field name="{key}_s">{value}</field>
</xsl:template>
<xsl:template match="Item/*[not(self::Category | self::Properties)]">
<field name="{local-name()}">{.}</field>
</xsl:template>
<xsl:template match='/Items'>
<add>
<xsl:apply-templates select="Item"/>
</add>
</xsl:template>
<xsl:template match="Item">
<xsl:variable name="pos" select="position()"/>
<doc>
<xsl:apply-templates>
<xsl:with-param name="pos"><xsl:value-of select="$pos"/></xsl:with-param>
</xsl:apply-templates>
</doc>
</xsl:template>
</xsl:stylesheet>

这避免了显式地构造";一棵树;对于每个Properties元素,但我不知道Saxon应用了什么策略来确保xsl:fork的两个分支都可以访问子或子内容。

给定的xsl解决方案适用于简化版本。然而,在大>5Gb的完整xml格式我没有让它工作。我已经解决了将xml文件拆分为大约1Gb的文件,然后在没有流的情况下执行xsl的问题。

如果有人想要挑战,请私下联系我;)

我的xml文件在每个项目后面都有一个换行符。因此,我创建了一个简单的控制台应用程序,可以在500000行处拆分文件,删除空字符,并使用xsl:转换结果

cleanxml.exe items.xml temp-items-solr.xml import.xsl

static void Main(string[] args)
{
string line;
XslCompiledTransform xsltTransform = new XslCompiledTransform();
xsltTransform.Load(@args[2]);
string fileToWriteTo = args[1];
StreamWriter writer = new StreamWriter(fileToWriteTo);
StreamReader file = new System.IO.StreamReader(@args[0]);
string fileOriginal = @args[1];
string firstLine = "<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Items>";
int i = 0;
int j = 1;
while ((line = file.ReadLine()) != null)
{
writer.WriteLine(CleanInvalidXmlChars(line)); 
if(i > 500000)
{
writer.WriteLine("</Items>"); 
writer.Flush();
writer.Dispose();
xsltTransform.Transform(fileToWriteTo, fileToWriteTo.Replace("temp-",""));
System.IO.File.Delete(fileToWriteTo);
fileToWriteTo = fileOriginal.Replace(".xml", "-" + j.ToString() + ".xml");
writer = new StreamWriter(fileToWriteTo);
writer.WriteLine(firstLine);
i = 0;
j += 1;
}
i += 1;
}
writer.Flush();
writer.Dispose();
xsltTransform.Transform(fileToWriteTo, fileToWriteTo.Replace("temp-", ""));
System.IO.File.Delete(fileToWriteTo);
file.Close();
}

private static MemoryStream ApplyXSLT(string xmlInput, string xsltFilePath)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlInput);
XslCompiledTransform xsltTransform = new XslCompiledTransform();
xsltTransform.Load(xsltFilePath);
MemoryStream memoreStream = new MemoryStream();
xsltTransform.Transform(xmlDocument, null, memoreStream);
memoreStream.Position = 0;
return memoreStream;
}

public static string CleanInvalidXmlChars(string text)
{
string re = @"[^x09x0Ax0Dx20-xD7FFxE000-xFFFDx10000-x10FFFF]";
return Regex.Replace(text, re, "");
}

最新更新