You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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>

代码说明

  1. 浅拷贝模式:on-no-match="shallow-copy"让未匹配的节点直接复制,减少冗余代码。
  2. 核心名称提取:自定义函数local:get-core-name处理带后缀的许可证,确保能匹配优先级列表中的基础许可证。
  3. 排序逻辑:用index-of获取许可证在优先级列表中的位置,位置越靠前排序越优先。
  4. 错误处理:严格遵循约束条件,当多许可证无匹配时终止转换并抛出错误。

内容的提问来源于stack exchange,提问作者Dave Jarvis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 12:15:43