xsl优先配置文件

xsl prioritized configuration files

最终,我需要创建一种方便的方法来读取多个配置文件,以控制复杂 xsl 转换的处理(目前为 2.0)。每个配置文件可能有也可能没有特定节点。配置文件之间存在相对优先级,任何特定值的最终值应该来自该值所在的最高优先级配置文件。

下面给出了一个简单的配置文件(so.xml)和一个变量:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="urn:config.template.config" >
    <primary>Yes, this is primary</primary>
</config>

旧方法: 我通过在节点 "primary" 的单个配置文件中将参数设置为值 "primary" 来读取一个文件:

<xsl:param name="primary" select="$primaryConfig/myConfig:config/myConfig:primary/text()"/>

现在: 我可能有最多四个配置文件,可能 有 "primary" 作为值。为此,我选择编写两个模板。 pickConfigNode是在模板文件中搜索(使用select进行读取的优先级排序)以查看请求的节点是否具有'level1'中包含的值。

<xsl:template name="pickConfigNode">
    <xsl:param name="level1"/>

    <xsl:choose>
        <xsl:when test="$primaryConfig/myConfig:config/*[local-name() = $level1]">
            <xsl:value-of select="$primaryConfig/myConfig:config/*[local-name() = $level1]"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$secondaryConfig/myConfig:config/*[local-name() = $level1]"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

这对我来说已经足够好了只要该值存在于某些配置文件中(在搜索主要和次要时显示)。但是,该值可能未在任何地方定义。我想我希望返回一个空序列,如果节点不存在 old 方法,就会发生这种情况。但是,当没有找到任何内容时,我可能误解了“*”是如何工作的。

pickConfigNode returns 部分文档。因此,这会导致 pickConfigText:

出现问题
<xsl:template name="pickConfigText" as="xs:string">
    <xsl:param name="level1"/>

    <xsl:variable name="chosenNode">
        <xsl:call-template name="pickConfigNode">
            <xsl:with-param name="level1" select="$level1"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:value-of select="$chosenNode/text()"/>
</xsl:template>

这是辅助文件:

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="urn:config.template.config" >
    <onlySecondary>from secondary</onlySecondary>
    <primary>No, this is secondary</primary>
</config>

这是使用两个配置文件的完整测试用例:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:myConfig="urn:config.template.config"
                xpath-default-namespace="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="myConfig xs"
>

    <xsl:output method="xml" omit-xml-declaration="no" indent="yes" encoding="us-ascii" cdata-section-elements="p i b u li"/>

    <xsl:variable name="configFile" select="'so.xml'"/>
    <xsl:variable name="primaryConfig" select="document($configFile)"/>
    <xsl:variable name="secondaryConfig" select="document('second.xml')"/>

    <xsl:template name="pickConfigNode">
        <xsl:param name="level1"/>

        <xsl:choose>
            <xsl:when test="$primaryConfig/myConfig:config/*[local-name() = $level1]">
                <xsl:value-of select="$primaryConfig/myConfig:config/*[local-name() = $level1]"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$secondaryConfig/myConfig:config/*[local-name() = $level1]"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

    <xsl:template name="pickConfigText" as="xs:string">
        <xsl:param name="level1"/>

        <xsl:variable name="chosenNode">
            <xsl:call-template name="pickConfigNode">
                <xsl:with-param name="level1" select="$level1"/>
            </xsl:call-template>
        </xsl:variable>
        <xsl:value-of select="$chosenNode/text()"/>
    </xsl:template>

    <xsl:param name="primary">
        <xsl:call-template name="pickConfigText">
            <xsl:with-param name="level1" select="'primary'"/>
        </xsl:call-template>
    </xsl:param>

    <xsl:param name="onlySecondary">
        <xsl:call-template name="pickConfigText">
            <xsl:with-param name="level1" select="'onlySecondary'"/>
        </xsl:call-template>
    </xsl:param>

    <xsl:param name="neither">
        <xsl:call-template name="pickConfigText">
            <xsl:with-param name="level1" select="'neither'"/>
        </xsl:call-template>
    </xsl:param>

    <xsl:template match="/">
        <PRIMARY>
            <xsl:choose>
                <xsl:when test="$primary"><SUCCESS><xsl:value-of select="$primary"/></SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'No value for primary'"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </PRIMARY>
        <SECONDARY>
            <xsl:choose>
                <xsl:when test="$onlySecondary"><SUCCESS><xsl:value-of select="$onlySecondary"/></SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'No value for onlySecondary'"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </SECONDARY>
        <NEITHER>
            <xsl:choose>
                <xsl:when test="not($neither)"><SUCCESS>NOT in either file</SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'Got value of for neither='"/>
                        <xsl:value-of select="$neither"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </NEITHER>       
    </xsl:template>

</xsl:stylesheet>

输出为:

<?xml version="1.0" encoding="us-ascii"?>
<PRIMARY>
   <SUCCESS>Yes, this is primary</SUCCESS>
</PRIMARY>
<SECONDARY>
   <SUCCESS>from secondary</SUCCESS>
</SECONDARY>
<NEITHER>
   <FAILURE>Got value of for neither=</FAILURE>
</NEITHER>

所以,我不希望结果是 "SUCCESS"。

感谢您帮助我理解我对 xslt 处理的误解。此外,如果您有其他方法来处理优先配置文件,我也很想听听。

我认为,您宁愿使用 xsl:sequence 而不是 xsl:value-of 例如

<xsl:sequence select="$primaryConfig/myConfig:config/*[local-name() = $level1]"/>

如果您随后在命名函数而不是命名模板中执行此操作,您也可以坚持 <xsl:param name="foo" select="my:pickConfigText('arg')"/> 并且您的代码肯定只是 select 现有节点或空序列。

变量包含文档片段的整个问题是由于您将 xsl:param/xsl:variable 与嵌套 xsl:call-template 一起使用,而没有在 [=14] 上使用 as 属性=] 正在创建片段。

Ultimately, I need to create a convenient way to read multiple configuration files for controlling the processing of a complex xsl transformation (2.0, currently). Each configuration file may or may not have particular nodes. A relative priority exists between the configuration files, and the ultimate value for any particular value should come from the highest priority configuration file in which the value exists.

这是根据预定义的优先级从多个文件中提取值的通用技术:

让我们将这四个配置文件放在 C:\temp\DeleteMe 目录中 -- 那里没有其他 .xml 文件:

Config1.xml

<config xmlns="urn:config.template.config" >
    <exists>Yes</exists>
</config>

Config2.xml

<config xmlns="urn:config.template.config" >
    <SomethingElse>Yes</SomethingElse>
</config>

Config3.xml

<config xmlns="urn:config.template.config" >
    <exists>YesConfig3</exists>
</config>

Config4.xml

<config xmlns="urn:config.template.config" >
    <SomethingElseEvenMore>Yes</SomethingElseEvenMore>
</config>

请注意只有 Config1.xml 和 Config3.xml 有 <exists> 元素。

这个变换:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my"
 xmlns:x="urn:config.template.config" >
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:param name="pDirectory" as="xs:string" select="'file:///c:/temp/DeleteMe'"/>
 <xsl:variable name="vConfigs" select="collection(concat($pDirectory, '?select=*.xml'))"/>

<xsl:template match="/">
    <xsl:value-of select="my:GetConfigValue('exists', $vConfigs)"/>
  </xsl:template>

  <xsl:function name="my:GetConfigValue">
    <xsl:param name="pConfigName" as="xs:string"/>
    <xsl:param name="pConfigs" as="document-node()*"/>

    <xsl:variable name="vConfigsMatching" as="document-node()*">
       <xsl:perform-sort select="$pConfigs[*/*[name() eq $pConfigName and text()]]">
         <xsl:sort 
           select="number(substring-before(substring-after(base-uri(.), 'Config'), '.xml'))" 
           order="descending"/>
       </xsl:perform-sort>
    </xsl:variable>

    <xsl:sequence select="$vConfigsMatching[1]/*/*[name() eq $pConfigName]/text()"/>
  </xsl:function>
</xsl:stylesheet>

当应用于任何 xml 文件(未使用)时,使用配置文件名称中的数字作为优先级 -- 因此从最高到最低的优先级是:

  • Config4.xml
  • Config3.xml
  • Config2.xml
  • Config1.xml

转换正确地从最高优先级的配置文件 (Config3.xml):

中生成了配置项 "exists" 的值
YesConfig3

如果我们要求一个不存在的元素的值:

<xsl:value-of select="my:GetConfigValue('not-found', $vConfigs)"/>

函数正确returns空序列--上面的count()为0.

如果我们在 Config3.xml 中删除 <exists> 的文本节点子节点,那么函数 returns 的字符串值是正确的<exists> 在 Config1.xml"

Yes

如果我们也删除 Config1.xml<exists> 的文本节点子节点,那么函数 returns 没有文本节点 - - 空序列——由返回为零的结果序列的计数确认。

最终,我得到了一个更接近我的更复杂问题所需的结构。我在一个目录中有很多配置文件,需要按名称选择适当的文件并为它们提供命令。我本可以根据这些名称创建一个序列并使用该顺序并更严格地遵循 Dimitre 的解决方案。

我也有点担心性能。我的配置文件可能相当大(并且配置有多个级别)。如果可能的话,我不想搜索多个结构;因此,我更喜欢我选择的结构。我不满意我的节点选择器有一个“*”,但我看不出有其他方法可以得到通用的解决方案。

我还添加了一个可以使用默认值的函数 (configTextDefault1)。

我还有很多东西要学。感谢@DimitriNovatchev 和@MartinHonnen。我还需要更完整地理解类型和处理模型。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:myConfig="urn:config.template.config"
                xmlns:myApp="urn:my.app"
                xpath-default-namespace="http://www.w3.org/2001/XMLSchema"
                exclude-result-prefixes="myConfig xs"
>

    <xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>

    <xsl:variable name="primaryConfig" select="document('so.xml')"/>
    <xsl:variable name="secondaryConfig" select="document('second.xml')"/>
    <xsl:variable name="tertiaryConfig" select="document('noExist.xml')"/>  <!-- test what happens if the file does not exist -->

    <xsl:function name="myApp:pickConfigNode1" as="node()*">
        <xsl:param name="level1" as="xs:string"/>

        <xsl:choose>
            <xsl:when test="$primaryConfig/myConfig:config/*[local-name() = $level1]">
                <xsl:sequence select="$primaryConfig/myConfig:config/*[local-name() = $level1]"/>
            </xsl:when>
            <xsl:when test="$secondaryConfig/myConfig:config/*[local-name() = $level1]">
                <xsl:sequence select="$secondaryConfig/myConfig:config/*[local-name() = $level1]"/>
            </xsl:when>
            <xsl:when test="$tertiaryConfig/myConfig:config/*[local-name() = $level1]">
                <xsl:sequence select="$tertiaryConfig/myConfig:config/*[local-name() = $level1]"/>
            </xsl:when>
        </xsl:choose>
    </xsl:function>

    <xsl:function name="myApp:configText1">
        <xsl:param name="level1" as="xs:string"/>

        <xsl:variable name="chosenNode" select="myApp:pickConfigNode1($level1)"/>
        <xsl:sequence select="$chosenNode/text()"/>
    </xsl:function>

    <xsl:function name="myApp:configTextDefault1">
        <xsl:param name="level1" as="xs:string"/>
        <xsl:param name="default" as="xs:string"/>

        <xsl:variable name="chosenText" select="myApp:configText1($level1)"/>
        <xsl:choose>
            <xsl:when test="$chosenText">
                <xsl:sequence select="$chosenText"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:sequence select="($default)"/>
            </xsl:otherwise>
        </xsl:choose>    
    </xsl:function>

    <xsl:template match="/">
        <PRIMARY>
            <xsl:choose>
                <xsl:when test="myApp:configText1('primary')"><SUCCESS><xsl:value-of select="myApp:configText1('primary')"/></SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'No value for primary'"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </PRIMARY>
        <SECONDARY>
            <xsl:choose>
                <xsl:when test="myApp:configText1('onlySecondary')"><SUCCESSS><xsl:value-of select="myApp:configText1('onlySecondary')"/></SUCCESSS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'No value for onlySecondary'"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </SECONDARY>
        <NEITHER>
            <xsl:choose>
                <xsl:when test="not(myApp:configText1('neither'))"><SUCCESS>NOT in any file</SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'Got value of for neither: '"/>
                        <xsl:value-of select="myApp:configText1('neither')"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>
        </NEITHER> 
        <DEFAULT>
            <xsl:choose>
                <xsl:when test="myApp:configTextDefault1('neither','default value') = 'default value'">
                    <SUCCESS><xsl:text>Got 'default value'</xsl:text></SUCCESS></xsl:when>
                <xsl:otherwise>
                    <FAILURE>
                        <xsl:value-of select="'Got wrong value: '"/>
                        <xsl:value-of select="myApp:configTextDefault1('neither','default value')"/>
                    </FAILURE>
                </xsl:otherwise>
            </xsl:choose>        
        </DEFAULT>
    </xsl:template>

</xsl:stylesheet>