基于XSLT移除缺失工厂引用的产品节点
如何用XSLT移除关联已关闭工厂的产品节点
我之前借助Stack Overflow实现过基于明确条件的XSLT过滤、移除与复制功能,但现在碰到了需要全局验证关联关系的场景,下面是抽象后的示例,想请教实现思路:
示例场景
我有数百个工厂、数千种产品,原始XML结构如下:
<data ID="1"> <type>factory.tires</type> <Country>a</Country> </data> <data ID="2"> <type>factory.glass</type> <Country>a</Country> </data> <data ID="3"> <type>factory.steel</type> <Country>b</Country> </data> <data ID="4"> <type>factory.tires</type> <Country>c</Country> </data> <!-- xxxxxxxxxxxxxx --> <data ID="11"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="12"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="13"> <type>product.cups</type> <factory REF="2" /> </data> <data ID="20"> <type>product.wheel</type> <factory REF="1" /> </data> <data ID="21"> <type>product.wheel</type> <factory REF="4" /> </data>
现在工厂1和2已关闭,对应<data>节点已被移除,当前XML如下:
<data ID="3"> <type>factory.steel</type> <Country>b</Country> </data> <data ID="4"> <type>factory.tires</type> <Country>c</Country> </data> <!-- xxxxxxxxxxxxxx --> <data ID="11"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="12"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="13"> <type>product.cups</type> <factory REF="2" /> </data> <data ID="20"> <type>product.wheel</type> <factory REF="1" /> </data> <data ID="21"> <type>product.wheel</type> <factory REF="4" /> </data>
需求
需要移除所有关联已关闭工厂的产品节点,但无法提前知道哪些工厂已关闭。具体逻辑是:
- 找出所有包含
<factory>子节点的<data>节点(即产品节点) - 检查该节点
<factory>的REF属性值,是否存在于当前XML中某个<data>节点的ID属性里 - 如果找不到对应的
ID(说明关联的工厂已被移除),则删除该产品节点
预期结果是移除ID为13和20的节点,最终XML如下:
<data ID="3"> <type>factory.steel</type> <Country>b</Country> </data> <data ID="4"> <type>factory.tires</type> <Country>c</Country> </data> <!-- xxxxxxxxxxxxxx --> <data ID="11"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="12"> <type>product.screws</type> <factory REF="3" /> </data> <data ID="21"> <type>product.wheel</type> <factory REF="4" /> </data>
解决方案
XSLT 1.0 实现
XSLT 1.0可以通过**键(key)**快速缓存存在的工厂ID,避免重复遍历,适合大数据量场景:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 定义键:匹配所有存在的data节点,用ID作为键值 --> <xsl:key name="existing-factories" match="data" use="@ID" /> <!-- 身份模板:默认复制所有节点 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:template> <!-- 匹配包含factory子节点的产品节点 --> <xsl:template match="data[factory]"> <!-- 检查REF对应的ID是否存在,存在则保留节点 --> <xsl:if test="key('existing-factories', factory/@REF)"> <xsl:copy> <xsl:apply-templates select="@*|node()" /> </xsl:copy> </xsl:if> <!-- 不存在则不输出,即移除节点 --> </xsl:template> </xsl:stylesheet>
XSLT 2.0/3.0 实现
XSLT 2.0及以上支持更简洁的语法,利用exists()直接验证关联关系:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 浅复制模式:替代传统身份模板,简化代码 --> <xsl:mode on-no-match="shallow-copy" /> <!-- 匹配并移除关联不存在工厂的产品节点 --> <xsl:template match="data[factory][not(exists(/data[@ID = current()/factory/@REF]))]" /> </xsl:stylesheet>
说明
- 键的作用是缓存所有存在的
<data>节点ID,查询效率更高,适配你提到的数千产品的场景 - XSLT 3.0的
shallow-copy模式可以大幅简化模板代码,减少冗余 - 核心逻辑都是验证
factory/@REF对应的data[@ID]是否存在,不存在则过滤该产品节点
内容的提问来源于stack exchange,提问作者Pierre de la Verre
相关产品推荐
相关产品推荐

