файлы конфигурации с приоритетом xsl - PullRequest
1 голос
/ 28 октября 2019

В конечном итоге мне нужно создать удобный способ чтения нескольких файлов конфигурации для управления обработкой сложного преобразования 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()"/>

Теперь: У меня может быть до четырех файлов конфигурации, которые может иметь«первичный» как значение. Для этого я решил написать два шаблона. pickConfigNode - для поиска в файлах шаблонов (используя выбор для определения приоритетов чтения), чтобы узнать, запрошен ли узел со значением, содержащимся в '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>

Это работает достаточно хорошо для меня , пока значение существует в некотором файле конфигурации (показывается при поиске основного и дополнительного). Однако возможно, что значение нигде не определено. Я думаю, что я хотел бы, чтобы возвращалась пустая последовательность, что происходит, если узел не существует с старым методом . Однако я, возможно, неправильно понимаю что-то о том, как работает *, когда ничего не найдено.

pickConfigNode возвращает частичный документ. Таким образом, это приводит к проблемам с 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>

Итак, я хочу, чтобы результат НИКОГДА не был "УСПЕХ".

Благодарим Вас за помощь в понимании моего неправильного представления об обработке xslt. Кроме того, если у вас есть альтернативный подход к обработке приоритетных файлов конфигурации, я бы тоже хотел это услышать.

Ответы [ 3 ]

2 голосов
/ 29 октября 2019

В конечном итоге мне нужно создать удобный способ чтения нескольких файлов конфигурации для управления обработкой сложного преобразования xsl (2.0, в настоящее время). Каждый файл конфигурации может иметь или не иметь конкретные узлы. Относительный приоритет существует между файлами конфигурации, и конечное значение для любого конкретного значения должно исходить из файла конфигурации с наивысшим приоритетом, в котором оно существует.

Вот общий метод извлечениязначения из нескольких файлов в соответствии с их предопределенным приоритетом :

Давайте получим эти четыре файла конфигурации в каталоге 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

Преобразованиеправильно вывел значение элемента конфигурации "exists" из файла congfig с наивысшим приоритетом (Config3.xml):

YesConfig3

Если мы запрашиваем значение несуществующегоelement :

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

Функция корректно возвращает пустую последовательность - count() из приведенного выше равно 0.

Если мы удалим дочерний элемент text-node<exists> в Config3.xml , тогда корректно функция возвращает строковое значение <exists> в Config1.xml "

Yes

Если мытакже удалите дочерний элемент текстового узла <exists> в Config1.xml , затем корректно функция не вернет текстовый узел - пустую последовательность - что подтверждается счетчиком результирующей последовательности, возвращаемой как ноль.

1 голос
/ 29 октября 2019

В конечном итоге я получил структуру, которая ближе к тому, что требует моя более сложная проблема. У меня есть много файлов конфигурации в каталоге, и мне нужно выбрать соответствующие имена и предоставить им порядок. Я мог бы создать последовательность, основанную на этих именах, использовать этот порядок и более точно следовать решению Димитра.

Я также был немного обеспокоен производительностью. Мои файлы конфигурации могут быть довольно большими (и иметь несколько уровней конфигурации). Я не хотел искать несколько структур, если это возможно;следовательно, я предпочитаю свою структуру выбора. Я не доволен наличием '*' для моих селекторов узлов, но я не вижу другого способа найти общее решение.

Я также добавил функцию (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>
1 голос
/ 28 октября 2019

Я думаю, вы скорее хотите использовать xsl:sequence вместо xsl:value-of например

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

Если вы затем сделаете это в именованной функции вместо именованного шаблона, вы также можете придерживаться <xsl:param name="foo" select="my:pickConfigText('arg')"/>и ваш код, безусловно, просто выберет существующий узел или пустую последовательность.

Вся проблема наличия переменной, содержащей фрагмент документа, связана с использованием xsl:param/xsl:variable с вложенным xsl:call-template без использованияas атрибут xsl:param/xsl:variable, который создает фрагменты.

...