如何通过流处理单个 xml 的两个部分?
How to process two parts of a single xml by streaming?
我有一个非常大的 XML,它有两组不同的数据。我需要流过一个并在另一个上进行查找。我的数据如下所示
<?xml version="1.0" encoding="utf-8"?>
<Root>
<EmployeePersonal>
<Employee>
<ID>21234</ID>
<Name>Jim Carrey</Name>
<Age>43</Age>
<City>Chicago</City>
<State>IL</State>
</Employee>
<Employee>
<ID>41876</ID>
<Name>Edward Norton</Name>
<Age>33</Age>
<City>New York</City>
<State>NY</State>
</Employee>
<Employee>
<ID>51239</ID>
<Name>Eli Roth</Name>
<Age>46</Age>
<City>Los Angeles</City>
<State>CA</State>
</Employee>
</EmployeePersonal>
<EmployeeEmployment>
<Empl>
<Emplid>21234</Emplid>
<Title>HR Partner</Title>
<HireDate>2008-12-29</HireDate>
</Empl>
<Empl>
<Emplid>41876</Emplid>
<Title>Comp Partner</Title>
<HireDate>1999-07-09</HireDate>
</Empl>
<Empl>
<Emplid>51239</Emplid>
<Title>Programmer</Title>
<HireDate>2004-12-06</HireDate>
</Empl>
</EmployeeEmployment>
</Root>
我想遍历 /Root/EmployeePersonal 数据并通过匹配员工 ID 查找 /Root/EmployeeEmployment。
我尝试循环一个,然后加载到地图中,然后循环另一个,但一直出现错误。最后,我尝试将一组加载到一个变量中,然后尝试流式传输另一组,但没有成功。这是我到目前为止尝试过的。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:mode streamable="yes" on-no-match="shallow-skip"/>
<xsl:output indent="no"/>
<xsl:variable name="vEmploymentData" select="Root/copy-of(EmployeeEmployment)"/>
<xsl:template match="/Root/EmployeePersonal">
<AllEmployeeData>
<xsl:for-each select="Employee/copy-of()">
<Employee>
<xsl:copy-of select="./*"/>
<xsl:copy-of select="$vEmploymentData/Empl[Emplid=current()/ID]/*[local-name() ne 'Emplid']"/>
</Employee>
</xsl:for-each>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
我需要将这两组数据合并在一起。由于数据量很大,有没有办法把两组数据都流进去?
谢谢
鉴于您需要同时对不同的数据集进行比较和合并,我不确定是否存在一种简单的单次传递、可流式传输且因此仅转发的方法,因此一些简单的方法可以解决这个问题将同一个文档设置为 xsl:merge-source
两次 xsl:merge
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:param name="input-uri" as="xs:string" select="'input1.xml'"/>
<xsl:output indent="yes"/>
<xsl:template name="xsl:initial-template">
<AllEmployeeData>
<xsl:merge>
<xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
<xsl:merge-key select="ID"/>
</xsl:merge-source>
<xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
<xsl:merge-key select="Emplid"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
由于 xsl:merge
使用快照,因此访问 ID
和 Emplid
子节点不是问题。您也不必采取任何特定操作来缓冲映射或累加器中的数据。主要缺点是 xsl:merge
期望源按合并键排序,您的示例数据似乎就是这种情况,但我不确定您的完整数据是否如此。
你会 运行 使用命令行选项 -it
的 Saxon 9 EE 这样的样式表开始时没有主要输入,而是使用名为 xsl:initial-template
的初始模板。
作为仅读取主输入一次但使用 Saxon 9.9 EE 特定 saxon:capture
属性在累加器中缓冲 EmployeePersonal/Employee
的替代方法,您可以使用:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="#all">
<xsl:output indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="emp-details1"/>
<xsl:accumulator name="emp-details1" streamable="yes" as="element(Employee)*" initial-value="()">
<xsl:accumulator-rule match="EmployeePersonal/Employee" phase="end" saxon:capture="yes" select="$value, ."/>
</xsl:accumulator>
<xsl:template match="/*">
<AllEmployeeData>
<xsl:apply-templates/>
</AllEmployeeData>
</xsl:template>
<xsl:template match="EmployeeEmployment/Empl">
<Employee>
<xsl:variable name="this" select="copy-of()"/>
<xsl:copy-of select="accumulator-before('emp-details1')[ID = $this/Emplid]/*, $this/(* except Emplid)"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
这只读取一次输入,但将所有 EmployeePersonal/Employee
缓冲在内存中,然后在 EmployeeEmployment/Empl
匹配时使用后面的 buffered/accumulated 元素输出必要的相应数据。当然,这也是匹配 EmployeeEmployment/Empl
时创建的输出,需要该数据中存在所有 id
,我不确定是这种情况还是 EmployeePersonal/Employee
是主要输入,你只寻找匹配 EmployeeEmployment/Empl
如果它存在。
最后,您还可以尝试使用映射将 EmployeePersonal/Employee
的详细信息存储在累加器中,然后从 EmployeeEmployment/Empl
:
的模板访问它
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:saxon="http://saxon.sf.net/"
expand-text="yes"
exclude-result-prefixes="#all">
<xsl:output indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="#all"/>
<xsl:accumulator name="current-emp-id" streamable="yes" as="xs:integer?" initial-value="()">
<xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="xs:integer(.)"/>
</xsl:accumulator>
<xsl:accumulator name="emp-details" streamable="yes" as="map(xs:integer, map(xs:string, xs:string))" initial-value="map{}">
<xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="map:put($value, xs:integer(.), map{})"/>
<xsl:accumulator-rule match="EmployeePersonal/Employee/*[not(self::ID)]/text()"
select="map:put($value, accumulator-before('current-emp-id'), map:put(map:get($value, accumulator-before('current-emp-id')), local-name(..), string()))"/>
</xsl:accumulator>
<xsl:template match="/*">
<AllEmployeeData>
<xsl:apply-templates/>
</AllEmployeeData>
</xsl:template>
<xsl:template match="EmployeeEmployment/Empl">
<Employee>
<xsl:variable name="this" select="copy-of()"/>
<ID>{$this/Emplid}</ID>
<xsl:apply-templates select="map:get(accumulator-before('emp-details'), xs:integer($this/Emplid))" mode="entry-to-element"/>
<xsl:copy-of select="$this/(* except Emplid)"/>
</Employee>
</xsl:template>
<xsl:template match=".[. instance of map(*)]" mode="entry-to-element">
<xsl:variable name="map" select="."/>
<xsl:for-each select="map:keys(.)">
<xsl:element name="{.}">{map:get($map, .)}</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
当然它仍然会缓冲所有 EmployeePersonal/Employee
数据,只是这次使用轻量级映射而不是一系列 XML 快照元素。主要缺点是 XSLT 3/XPath 3.1 映射没有顺序,因此除非您稍后以想要的顺序拼出您要查找的每个条目,否则您将以随机顺序获得结果。在上面的示例中,我只是使用 for-each
将映射条目转换回 XML 元素,因此顺序是随机顺序而不是输入 XML 中的一个(xsl:merge
方法或 saxon:capture
方法将保留)。
正如您在关于使用 xsl:merge
但将输入作为主要输入源提供的评论中所问,我能想到的唯一方法是将其用作虚拟可流式输入,然后提供其 document-uri()
用于 xsl:merge
/xsl:merge-source
,即将上面的代码改编为
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:mode streamable="yes"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<AllEmployeeData>
<xsl:variable name="input-uri" as="xs:anyURI" select="document-uri()"/>
<xsl:message select="$input-uri"/>
<xsl:merge>
<xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
<xsl:merge-key select="ID"/>
</xsl:merge-source>
<xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
<xsl:merge-key select="Emplid"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
有趣的是,它适用于 Saxon 9.7 EE(当然,打开相同的输入三次以进行流式传输)但失败并出现错误“XTSE3430:模板规则不可流式传输 * The merge-source/@select 在 Saxon 9.8 和 9.9 EE 中表达式不跨步”。
不确定这是否有帮助,在之前的问题中,您似乎指出您使用的工具嵌入了 Saxon 9.7 EE,因此它可能是一个选项。
我有一个非常大的 XML,它有两组不同的数据。我需要流过一个并在另一个上进行查找。我的数据如下所示
<?xml version="1.0" encoding="utf-8"?>
<Root>
<EmployeePersonal>
<Employee>
<ID>21234</ID>
<Name>Jim Carrey</Name>
<Age>43</Age>
<City>Chicago</City>
<State>IL</State>
</Employee>
<Employee>
<ID>41876</ID>
<Name>Edward Norton</Name>
<Age>33</Age>
<City>New York</City>
<State>NY</State>
</Employee>
<Employee>
<ID>51239</ID>
<Name>Eli Roth</Name>
<Age>46</Age>
<City>Los Angeles</City>
<State>CA</State>
</Employee>
</EmployeePersonal>
<EmployeeEmployment>
<Empl>
<Emplid>21234</Emplid>
<Title>HR Partner</Title>
<HireDate>2008-12-29</HireDate>
</Empl>
<Empl>
<Emplid>41876</Emplid>
<Title>Comp Partner</Title>
<HireDate>1999-07-09</HireDate>
</Empl>
<Empl>
<Emplid>51239</Emplid>
<Title>Programmer</Title>
<HireDate>2004-12-06</HireDate>
</Empl>
</EmployeeEmployment>
</Root>
我想遍历 /Root/EmployeePersonal 数据并通过匹配员工 ID 查找 /Root/EmployeeEmployment。
我尝试循环一个,然后加载到地图中,然后循环另一个,但一直出现错误。最后,我尝试将一组加载到一个变量中,然后尝试流式传输另一组,但没有成功。这是我到目前为止尝试过的。
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
version="3.0">
<xsl:mode streamable="yes" on-no-match="shallow-skip"/>
<xsl:output indent="no"/>
<xsl:variable name="vEmploymentData" select="Root/copy-of(EmployeeEmployment)"/>
<xsl:template match="/Root/EmployeePersonal">
<AllEmployeeData>
<xsl:for-each select="Employee/copy-of()">
<Employee>
<xsl:copy-of select="./*"/>
<xsl:copy-of select="$vEmploymentData/Empl[Emplid=current()/ID]/*[local-name() ne 'Emplid']"/>
</Employee>
</xsl:for-each>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
我需要将这两组数据合并在一起。由于数据量很大,有没有办法把两组数据都流进去?
谢谢
鉴于您需要同时对不同的数据集进行比较和合并,我不确定是否存在一种简单的单次传递、可流式传输且因此仅转发的方法,因此一些简单的方法可以解决这个问题将同一个文档设置为 xsl:merge-source
两次 xsl:merge
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:param name="input-uri" as="xs:string" select="'input1.xml'"/>
<xsl:output indent="yes"/>
<xsl:template name="xsl:initial-template">
<AllEmployeeData>
<xsl:merge>
<xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
<xsl:merge-key select="ID"/>
</xsl:merge-source>
<xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
<xsl:merge-key select="Emplid"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
由于 xsl:merge
使用快照,因此访问 ID
和 Emplid
子节点不是问题。您也不必采取任何特定操作来缓冲映射或累加器中的数据。主要缺点是 xsl:merge
期望源按合并键排序,您的示例数据似乎就是这种情况,但我不确定您的完整数据是否如此。
你会 运行 使用命令行选项 -it
的 Saxon 9 EE 这样的样式表开始时没有主要输入,而是使用名为 xsl:initial-template
的初始模板。
作为仅读取主输入一次但使用 Saxon 9.9 EE 特定 saxon:capture
属性在累加器中缓冲 EmployeePersonal/Employee
的替代方法,您可以使用:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:saxon="http://saxon.sf.net/"
exclude-result-prefixes="#all">
<xsl:output indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="emp-details1"/>
<xsl:accumulator name="emp-details1" streamable="yes" as="element(Employee)*" initial-value="()">
<xsl:accumulator-rule match="EmployeePersonal/Employee" phase="end" saxon:capture="yes" select="$value, ."/>
</xsl:accumulator>
<xsl:template match="/*">
<AllEmployeeData>
<xsl:apply-templates/>
</AllEmployeeData>
</xsl:template>
<xsl:template match="EmployeeEmployment/Empl">
<Employee>
<xsl:variable name="this" select="copy-of()"/>
<xsl:copy-of select="accumulator-before('emp-details1')[ID = $this/Emplid]/*, $this/(* except Emplid)"/>
</Employee>
</xsl:template>
</xsl:stylesheet>
这只读取一次输入,但将所有 EmployeePersonal/Employee
缓冲在内存中,然后在 EmployeeEmployment/Empl
匹配时使用后面的 buffered/accumulated 元素输出必要的相应数据。当然,这也是匹配 EmployeeEmployment/Empl
时创建的输出,需要该数据中存在所有 id
,我不确定是这种情况还是 EmployeePersonal/Employee
是主要输入,你只寻找匹配 EmployeeEmployment/Empl
如果它存在。
最后,您还可以尝试使用映射将 EmployeePersonal/Employee
的详细信息存储在累加器中,然后从 EmployeeEmployment/Empl
:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:saxon="http://saxon.sf.net/"
expand-text="yes"
exclude-result-prefixes="#all">
<xsl:output indent="yes"/>
<xsl:mode streamable="yes" on-no-match="shallow-skip" use-accumulators="#all"/>
<xsl:accumulator name="current-emp-id" streamable="yes" as="xs:integer?" initial-value="()">
<xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="xs:integer(.)"/>
</xsl:accumulator>
<xsl:accumulator name="emp-details" streamable="yes" as="map(xs:integer, map(xs:string, xs:string))" initial-value="map{}">
<xsl:accumulator-rule match="EmployeePersonal/Employee/ID/text()" select="map:put($value, xs:integer(.), map{})"/>
<xsl:accumulator-rule match="EmployeePersonal/Employee/*[not(self::ID)]/text()"
select="map:put($value, accumulator-before('current-emp-id'), map:put(map:get($value, accumulator-before('current-emp-id')), local-name(..), string()))"/>
</xsl:accumulator>
<xsl:template match="/*">
<AllEmployeeData>
<xsl:apply-templates/>
</AllEmployeeData>
</xsl:template>
<xsl:template match="EmployeeEmployment/Empl">
<Employee>
<xsl:variable name="this" select="copy-of()"/>
<ID>{$this/Emplid}</ID>
<xsl:apply-templates select="map:get(accumulator-before('emp-details'), xs:integer($this/Emplid))" mode="entry-to-element"/>
<xsl:copy-of select="$this/(* except Emplid)"/>
</Employee>
</xsl:template>
<xsl:template match=".[. instance of map(*)]" mode="entry-to-element">
<xsl:variable name="map" select="."/>
<xsl:for-each select="map:keys(.)">
<xsl:element name="{.}">{map:get($map, .)}</xsl:element>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
当然它仍然会缓冲所有 EmployeePersonal/Employee
数据,只是这次使用轻量级映射而不是一系列 XML 快照元素。主要缺点是 XSLT 3/XPath 3.1 映射没有顺序,因此除非您稍后以想要的顺序拼出您要查找的每个条目,否则您将以随机顺序获得结果。在上面的示例中,我只是使用 for-each
将映射条目转换回 XML 元素,因此顺序是随机顺序而不是输入 XML 中的一个(xsl:merge
方法或 saxon:capture
方法将保留)。
正如您在关于使用 xsl:merge
但将输入作为主要输入源提供的评论中所问,我能想到的唯一方法是将其用作虚拟可流式输入,然后提供其 document-uri()
用于 xsl:merge
/xsl:merge-source
,即将上面的代码改编为
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all">
<xsl:mode streamable="yes"/>
<xsl:output indent="yes"/>
<xsl:template match="/">
<AllEmployeeData>
<xsl:variable name="input-uri" as="xs:anyURI" select="document-uri()"/>
<xsl:message select="$input-uri"/>
<xsl:merge>
<xsl:merge-source name="emp-details1" for-each-source="$input-uri" streamable="yes" select="Root/EmployeePersonal/Employee">
<xsl:merge-key select="ID"/>
</xsl:merge-source>
<xsl:merge-source name="emp-details2" for-each-source="$input-uri" streamable="yes" select="Root/EmployeeEmployment/Empl">
<xsl:merge-key select="Emplid"/>
</xsl:merge-source>
<xsl:merge-action>
<xsl:copy>
<xsl:copy-of select="current-merge-group('emp-details1')/*, current-merge-group('emp-details2')/(* except Emplid)"/>
</xsl:copy>
</xsl:merge-action>
</xsl:merge>
</AllEmployeeData>
</xsl:template>
</xsl:stylesheet>
有趣的是,它适用于 Saxon 9.7 EE(当然,打开相同的输入三次以进行流式传输)但失败并出现错误“XTSE3430:模板规则不可流式传输 * The merge-source/@select 在 Saxon 9.8 和 9.9 EE 中表达式不跨步”。
不确定这是否有帮助,在之前的问题中,您似乎指出您使用的工具嵌入了 Saxon 9.7 EE,因此它可能是一个选项。