如何使用 XSLT 1.0 提取 EDI 204 中 LAD10 的不重复值?
结论
可以使用 XSLT 1.0 的 Muenchian 分组:通过 xsl:key 按 LAD10 的值建立索引,再从每组的第一个 LAD 节点输出 LAD10。
前提是 EDI 数据已经转换成 XML。题目提供的是以 * 分隔的原始 EDI 文本,标准 XSLT 1.0 不能直接将它作为 XML 源文档处理。由于尚未提供实际 XML,下面的元素名称和路径均为示例,需要根据转换后的结构调整。
先确认 LAD10 的位置和 XML 结构
以题目中的第一条记录为例:
LAD*PCS*0*K*1.308***PQ*000000000000177808*ON*200002415***APPLETON
ESTATE V/X SIGNATURE BLEND
以 * 为分隔符,LAD 是段标识,其后的元素从 LAD01 开始编号。连续分隔符之间的空元素也要计入位置,因此这里的 LAD09 是 ON,LAD10 是 200002415。
后续多条记录的 LAD10 也是 200002415,该值只需输出一次。商品名称和其他字段是否相同,不影响这次去重。
假设 EDI 解析后,每个 LAD 段对应一个 LAD 元素,第十个字段对应其子元素 LAD10。下面从原始数据中取几个值构造简化示例,只保留去重需要的字段:
<EDI204>
<LAD><LAD10>200002415</LAD10></LAD>
<LAD><LAD10>200002415</LAD10></LAD>
<LAD><LAD10>200002398</LAD10></LAD>
<LAD><LAD10>200002398</LAD10></LAD>
<LAD><LAD10>200002357</LAD10></LAD>
</EDI204>
使用 XSLT 1.0 输出不重复值
下面的完整样式表适用于上述无命名空间的 XML 结构。它在整个源文档中去重,跳过缺失或仅含空白的 LAD10,每行输出一个值:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="UTF-8"/>
<xsl:key
name="lad-by-lad10"
match="LAD[normalize-space(LAD10) != '']"
use="normalize-space(LAD10)"/>
<xsl:template match="/">
<xsl:for-each select="//LAD[
normalize-space(LAD10) != ''
and generate-id() = generate-id(
key('lad-by-lad10', normalize-space(LAD10))[1]
)
]">
<xsl:value-of select="normalize-space(LAD10)"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
对于上面的简化 XML,输出为:
200002415
200002398
200002357
这是简化示例的输出,不代表题目中全部 LAD 记录的完整去重结果。
为什么每个值只会输出一次
xsl:key 根据 LAD10 的值索引 LAD 节点,把值相同的节点归为一组。例如:
key('lad-by-lad10', '200002415')
这个表达式会返回当前源文档中按该键匹配到的所有 LAD10 为 200002415 的 LAD 节点。
再用 [1] 选出该组在文档顺序中的第一个节点,并比较节点身份:
generate-id() = generate-id(
key('lad-by-lad10', normalize-space(LAD10))[1]
)
只有组内第一个节点满足条件,所以同一个值只输出一次。未添加 xsl:sort 时,结果按各值在源文档中首次出现的顺序排列。
XSLT 1.0 使用的 XPath 1.0 没有标准的 distinct-values() 函数,因此这里采用键索引分组。
如何适配实际文件
XML 元素名称和路径必须对应
EDI 解析器不一定生成 <LAD><LAD10>...</LAD10></LAD> 这种结构。实际字段可能使用其他名称,也可能通过属性保存字段编号。需要同时调整:
xsl:key的match:匹配实际的 LAD 段节点。xsl:key的use:读取实际的第十个字段。xsl:for-each的选择路径、过滤条件以及输出表达式。
仅修改其中一处,可能导致索引和查询使用不同的节点或值。
有命名空间时使用前缀
如果源 XML 使用默认命名空间,在 XPath 1.0 中直接写 LAD 和 LAD10 无法匹配这些元素。需要在样式表中声明一个前缀,并将它绑定到源 XML 的实际命名空间 URI。
然后将相关名称统一改为带前缀的形式,例如:
<xsl:key
name="lad-by-lad10"
match="e:LAD[normalize-space(e:LAD10) != '']"
use="normalize-space(e:LAD10)"/>
这里的 e 必须先在样式表上声明,选择和输出表达式中的 LAD、LAD10 也要同步改为 e:LAD、e:LAD10。
明确空白处理和去重范围
示例使用 normalize-space(),会去掉首尾空白,并将内部连续的 XML 空白字符折叠成一个空格。如果需要逐字符区分值,应统一修改键值、查询值和输出值,避免部分值经过规范化,部分保留原样。
LAD10 应按字符串处理。将它转成数字再比较,可能丢失前导零。
该样式表按整个 XML 源文档去重。如果一个源文档包含多个 EDI 204 交易,且要求每个交易分别去重,需要将交易范围加入分组键并限制选择范围。仅缩小 for-each 的路径,不能改变键的文档级作用范围。
如果目前只有原始 EDI 文件,应先按文件声明的分隔符解析成 XML。题目示例中的商品名称存在换行,不能仅凭展示出来的换行判断实际段边界。
备注:内容仅供参考。
