如何使用XSLT 3.0基于预定义优先级列表筛选开源库许可证?
背景
第三方软件库有时会提供一组许可证供开发者选择。部分许可证可通过ORT工具检测并生成SPDX标识符。我们希望替代手动选择特定许可证的流程,改用预定义的优先级列表自动选择。并非所有许可证都在该列表中。
代码定义
本部分说明相关源文件:
priorities.xml
条目顺序决定了可选时应选择的许可证:
<priorities> <license>Apache-2.0</license> <license>MIT</license> <license>BSD-2-Clause</license> <license>BSD-3-Clause</license> <license>CDDL-1.0</license> <license>EPL-2.0</license> <license>MPL-2.0</license> <license>LGPL-3.0</license> </priorities>
该文档通过以下方式加载:
<xsl:variable name="PRIORITY" select="document( resolve-uri( 'priorities.xml', base-uri( / ) ) )" />
libraries.xml
以下简化的许可证条目构成输入文档:
<copyrights> <copyright> <title>Grizzly HTTP framework</title> <licenses> <license>CDDL-1.0</license> <license>GPL-2.0-or-later</license> </licenses> </copyright> <copyright> <title>Java™ JSON Tools Jackson Coreutils</title> <licenses> <license>LGPL-3.0-or-later</license> <license>Apache-2.0</license> </licenses> </copyright> <copyright> <title>Javassist</title> <licenses> <license>LGPL-2.1-only</license> <license>MPL-1.1</license> <license>Apache-2.0</license> </licenses> </copyright> <copyright> <title>Linux Kernel</title> <licenses> <license with="Linux-syscall-note" order="before">GPL-2.0-only</license> </licenses> </copyright> <copyright> <title>Eclipse Temurin™</title> <licenses> <license with="Classpath-exception-2.0">GPL-2.0-only</license> </licenses> </copyright> </copyrights>
期望输出
期望输出将每个库的许可证缩减为单个条目:
<copyrights> <copyright> <title>Grizzly HTTP framework</title> <licenses> <license>CDDL-1.0</license> </licenses> </copyright> <copyright> <title>Java™ JSON Tools Jackson Coreutils</title> <licenses> <license>Apache-2.0</license> </licenses> </copyright> <copyright> <title>Javassist</title> <licenses> <license>Apache-2.0</license> </licenses> </copyright> <copyright> <title>Linux Kernel</title> <licenses> <license with="Linux-syscall-note" order="before">GPL-2.0-only</license> </licenses> </copyright> <copyright> <title>Eclipse Temurin™</title> <licenses> <license with="Classpath-exception-2.0">GPL-2.0-only</license> </licenses> </copyright> </copyrights>
问题思路
从概念上讲,我们希望根据输入许可证在优先级列表中的位置筛选,然后选取首个。相关转换流程如下:
初始输入片段:
<licenses> <license>LGPL-2.1-only</license> <license>MPL-1.1</license> <license>Apache-2.0</license> </licenses>
根据优先级列表位置分配优先级:
<licenses> <license priority="INFINITY">LGPL-2.1-only</license> <license priority="7">MPL-1.1</license> <license priority="1">Apache-2.0</license> </licenses>
按优先级排序:
<licenses> <license priority="1">Apache-2.0</license> <license priority="7">MPL-1.1</license> <license priority="INFINITY">LGPL-2.1-only</license> </licenses>
选取首个子元素:
<licenses> <license priority="1">Apache-2.0</license> </licenses>
该算法可确保无对应优先级的许可证被默认选中。
约束条件
- 若输入文档中存在多个许可证但无匹配优先级列表的条目,属于错误情况,无法合理选择首个许可证。
- 输入文档的每个条目必须包含许可证,可通过Schema验证强制该约束。
XSLT 3.0实现方案
以下是简洁的实现代码,利用XSLT 3.0的函数式特性完成需求:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:mode on-no-match="shallow-copy"/> <!-- 加载优先级列表 --> <xsl:variable name="PRIORITY" select="document(resolve-uri('priorities.xml', base-uri(/)))/priorities/license"/> <!-- 处理每个copyright下的licenses节点 --> <xsl:template match="licenses"> <xsl:copy> <xsl:variable name="current-licenses" select="license"/> <!-- 提取许可证核心名称(去除-or-later/-only后缀) --> <xsl:function name="local:get-core-name" as="xs:string"> <xsl:param name="license" as="element(license)"/> <xsl:sequence select=" replace($license/text(), '(-or-later|-only)$', '') "/> </xsl:function> <!-- 筛选并排序匹配优先级的许可证 --> <xsl:variable name="matching-licenses" select=" $current-licenses[ local:get-core-name(.) = $PRIORITY ] => sort( (), function($l) { index-of($PRIORITY, local:get-core-name($l)) } ) "/> <!-- 执行选择逻辑 --> <xsl:choose> <xsl:when test="$matching-licenses"> <xsl:copy-of select="$matching-licenses[1]"/> </xsl:when> <xsl:when test="count($current-licenses) = 1"> <xsl:copy-of select="$current-licenses[1]"/> </xsl:when> <xsl:otherwise> <xsl:message terminate="yes">错误:多个许可证均不在优先级列表中,无法自动选择。</xsl:message> </xsl:otherwise> </xsl:choose> </xsl:copy> </xsl:template> </xsl:stylesheet>
代码说明
- 浅拷贝模式:
on-no-match="shallow-copy"让未匹配的节点直接复制,减少冗余代码。 - 核心名称提取:自定义函数
local:get-core-name处理带后缀的许可证,确保能匹配优先级列表中的基础许可证。 - 排序逻辑:用
index-of获取许可证在优先级列表中的位置,位置越靠前排序越优先。 - 错误处理:严格遵循约束条件,当多许可证无匹配时终止转换并抛出错误。
内容的提问来源于stack exchange,提问作者Dave Jarvis
相关产品推荐
相关产品推荐

