Изменение формата XML на основе CDATA с использованием шаблона XSL - PullRequest
0 голосов
/ 01 февраля 2020

Я впервые пытался использовать XSL, но из моих исследований это выглядело как лучший метод. У меня есть несколько файлов для конвертации. Я планирую использовать notepad ++ xmltools для преобразования. Если есть другое решение моей проблемы, я открыт для него.

Мне нужно преобразовать этот формат файла XML:

<?xml version="1.0" encoding="UTF-8"?>

<testcases>
<testcase name="Simple">
    <steps><![CDATA[<p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p>]]></steps>
    <expectedresults><![CDATA[<p>1. result</p>
<p>2. more result</p>
<p>3 again</p>]]></expectedresults>
</testcase>
</testcases>

В этот конечный формат:

<?xml version="1.0" encoding="UTF-8"?>
<testcases>
<testcase name="Simple new">
<steps>
 <step>
    <step_number><![CDATA[1]]></step_number>
    <actions><![CDATA[<p>step 1</p>]]></actions>
    <expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
    <execution_type><![CDATA[1]]></execution_type>
 </step>

 <step>
    <step_number><![CDATA[2]]></step_number>
    <actions><![CDATA[<p>step 2</p>]]></actions>
    <expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
    <execution_type><![CDATA[1]]></execution_type>
 </step>
  <step>
    <step_number><![CDATA[3]]></step_number>
    <actions><![CDATA[<p>step 3</p>]]></actions>
    <expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
    <execution_type><![CDATA[1]]></execution_type>
 </step>
</steps>
</testcase>
</testcases>

Не во всех тестовых случаях будет несколько шагов и ожидаемых результатов.

Я нашел это в другой теме: http://xsltfiddle.liberty-development.net/gWmuiHV отличный инструмент для этого процесса.

Мой XSL пока работает не очень хорошо. Я получаю только блок ожидаемых результатов. Это происходит независимо от того, добавляю ли я блок ожидаемых результатов или нет.

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">
<xsl:template match="steps">
   <xsl:for-each select="p">
      <xsl:copy>
        <xsl:apply-templates select="p"/>
      </xsl:copy>
    </xsl:for-each>

   <!-- <xsl:for-each select="expectedresults">
      <xsl:copy>
        <xsl:apply-templates select="p"/>
      </xsl:copy>
    </xsl:for-each>-- I get the same results whether this code is included or not. >

 </xsl:template>
 </xsl:stylesheet>

Но я получаю это только для вывода:

<?xml version="1.0" encoding="utf-16"?>


    &lt;p&gt;1. result&lt;/p&gt;
&lt;p&gt;2. more result&lt;/p&gt;
&lt;p&gt;3 again&lt;/p&gt;

Эти файлы будут импортированы в Testlink, не использованный для html.

Ответы [ 3 ]

1 голос
/ 01 февраля 2020

Я думаю, что в XSLT 3 вы хотите проанализировать содержимое двух элементов, объединить их и затем сериализовать обратно:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    expand-text="yes"
    version="3.0">

  <xsl:output indent="yes" cdata-section-elements="actions expectedresults"/>

  <xsl:mode on-no-match="shallow-copy"/>

  <xsl:accumulator name="step-count" as="xs:integer" initial-value="0">
      <xsl:accumulator-rule match="p" select="$value + 1"/>
  </xsl:accumulator>

  <xsl:template match="testcase">
      <testcase name="{@name} new">
          <steps>
              <xsl:merge>
                  <xsl:merge-source select="parse-xml-fragment(steps)/*">
                      <xsl:merge-key select="accumulator-before('step-count')"/>
                  </xsl:merge-source>
                  <xsl:merge-source select="parse-xml-fragment(expectedresults)/*">
                      <xsl:merge-key select="accumulator-before('step-count')"/>
                  </xsl:merge-source>
                  <xsl:merge-action>
                      <step>
                          <step_number>{position()}</step_number>
                          <actions>{serialize(current-merge-group()[1])}</actions>
                          <expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
                          <execution_type>1</execution_type>
                      </step>
                  </xsl:merge-action>
              </xsl:merge>              
          </steps>
      </testcase>
  </xsl:template>

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/jz1Q1yb

Или, чтобы удалить числа из шагов и действий, вам нужен дополнительный шаг обработки:

  <xsl:mode name="strip-numbers" on-no-match="shallow-copy"/>

  <xsl:function name="mf:strip-numbers" as="node()*">
      <xsl:param name="input" as="node()*"/>
      <xsl:apply-templates select="$input" mode="strip-numbers"/>
  </xsl:function>

  <xsl:template mode="strip-numbers" match="p[matches(., '^\d+\.\s*')]">
      <xsl:copy>{replace(., '^\d+\.\s*', '')}</xsl:copy>
  </xsl:template>

  <xsl:template match="testcase">
      <testcase name="{@name} new">
          <steps>
              <xsl:merge>
                  <xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(steps))/*">
                      <xsl:merge-key select="accumulator-before('step-count')"/>
                  </xsl:merge-source>
                  <xsl:merge-source select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*">
                      <xsl:merge-key select="accumulator-before('step-count')"/>
                  </xsl:merge-source>
                  <xsl:merge-action>
                      <step>
                          <step_number>{position()}</step_number>
                          <actions>{serialize(current-merge-group()[1])}</actions>
                          <expectedresults>{serialize(current-merge-group()[2])}</expectedresults>
                          <execution_type>1</execution_type>
                      </step>
                  </xsl:merge-action>
              </xsl:merge>              
          </steps>
      </testcase>
  </xsl:template>

https://xsltfiddle.liberty-development.net/jz1Q1yb/1

С поддержкой высшего порядка функции (то есть с Saxon PE или EE или Altova XML) также может быть возможно использовать функцию for-each-pair https://www.w3.org/TR/xpath-functions/#func для каждой пары вместо довольно многословного xsl:merge.

Использование аккумулятора также немного утомительно, но необходимо иметь ключ источника слияния на основе позиции, более компактное решение может быть использовано для построения карты положения и элемента на лету. :

  <xsl:template match="testcase">
      <testcase name="{@name} new">
          <steps>
              <xsl:merge>
                  <xsl:merge-source name="step" 
                    select="mf:strip-numbers(parse-xml-fragment(steps))/*!map { 'pos' : position(), 'element' : .}">
                      <xsl:merge-key select="?pos"/>
                  </xsl:merge-source>
                  <xsl:merge-source name="action" 
                    select="mf:strip-numbers(parse-xml-fragment(expectedresults))/*!map { 'pos' : position(), 'element' : .}">
                      <xsl:merge-key select="?pos"/>
                  </xsl:merge-source>
                  <xsl:merge-action>
                      <step>
                          <step_number>{position()}</step_number>
                          <actions>{current-merge-group('step')?element => serialize()}</actions>
                          <expectedresults>{current-merge-group('action')?element => serialize()}</expectedresults>
                          <execution_type>1</execution_type>
                      </step>
                  </xsl:merge-action>
              </xsl:merge>              
          </steps>
      </testcase>
  </xsl:template>

https://xsltfiddle.liberty-development.net/jz1Q1yb/2

1 голос
/ 01 февраля 2020

CDATA не является XML и не может быть обработано напрямую с помощью XSLT. В XSLT 3.0 есть функция parse-xml-fragment, которая может предварительно обрабатывать CDATA или иным образом экранировать XML. Однако вы говорите, что:

Я планирую использовать блокнот ++ xmltools

AFAIK, это ограничит вас XSLT 1.0. В таком случае вам нужно обработать ввод XML дважды .

Сначала примените это преобразование и сохраните результат в файл:

XSLT 1,0 [1]

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="steps | expectedresults">
    <xsl:copy>
        <xsl:value-of select="." disable-output-escaping="yes"/>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

Это должно привести к следующему XML:

XML [2]

<?xml version="1.0" encoding="UTF-8"?>
<testcases>
  <testcase name="Simple">
    <steps><p>1. do something</p>
<p>2. do more</p>
<p>3. even more</p></steps>
    <expectedresults><p>1. result</p>
<p>2. more result</p>
<p>3 again</p></expectedresults>
  </testcase>
</testcases>

Теперь к полученному файлу можно применить следующую таблицу стилей:

XSLT 1.0 [2]]

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" 
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="testcase">
    <xsl:copy>
        <xsl:attribute name="name">
            <xsl:value-of select="@name" />
            <xsl:text> new</xsl:text>
        </xsl:attribute>
        <xsl:for-each select="steps/p">
            <step>
                <xsl:variable name="i" select="position()"/>
                <step_number>
                    <xsl:value-of select="$i"/>
                </step_number>
                <actions>
                    <xsl:text>&lt;p&gt;</xsl:text>
                    <xsl:value-of select="substring-after(., '. ')" />
                    <xsl:text>&lt;/p&gt;</xsl:text>
                </actions>                    
                <expectedresults>
                    <xsl:text>&lt;p&gt;</xsl:text>
                    <xsl:value-of select="substring-after(../following-sibling::expectedresults/p[$i], '. ')"/>
                    <xsl:text>&lt;/p&gt;</xsl:text>
                </expectedresults>
                <execution_type>1</execution_type>
            </step>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>

, чтобы получить:

Окончательный результат

<?xml version="1.0" encoding="UTF-8"?>
<testcases>
  <testcase name="Simple new">
    <step>
      <step_number><![CDATA[1]]></step_number>
      <actions><![CDATA[<p>do something</p>]]></actions>
      <expectedresults><![CDATA[<p>result</p>]]></expectedresults>
      <execution_type><![CDATA[1]]></execution_type>
    </step>
    <step>
      <step_number><![CDATA[2]]></step_number>
      <actions><![CDATA[<p>do more</p>]]></actions>
      <expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
      <execution_type><![CDATA[1]]></execution_type>
    </step>
    <step>
      <step_number><![CDATA[3]]></step_number>
      <actions><![CDATA[<p>even more</p>]]></actions>
      <expectedresults><![CDATA[<p>again</p>]]></expectedresults>
      <execution_type><![CDATA[1]]></execution_type>
    </step>
  </testcase>
</testcases>

Примечания:

  • Мой результат несколько отличается от того, который вы показываете. Тем не менее, я считаю, что это то, что вы хотели;

  • Я изменил ввод, добавив точку после 3 в <p>3 again</p>.



Добавлено:

Если то, что я прочитал, соответствует действительности, и ваш инструмент фактически использует XSLT-процессор libxslt, то вы можете сделать все это за один проход с помощью EXSLT str:split() функция расширения, поддерживаемая libxslt:

XSLT 1.0 + EXSLT

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:str="http://exslt.org/strings"
extension-element-prefixes="str">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" 
cdata-section-elements="step_number actions expectedresults execution_type"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="testcase">
    <xsl:variable name="steps" select="str:split(steps, '&lt;p&gt;')"/>
    <xsl:variable name="expectedresults" select="str:split(expectedresults, '&lt;p&gt;')"/>
    <xsl:copy>
        <xsl:attribute name="name">
            <xsl:value-of select="@name" />
            <xsl:text> new</xsl:text>
        </xsl:attribute>
        <xsl:for-each select="$steps">
            <step>
                <xsl:variable name="i" select="position()"/>
                <step_number>
                    <xsl:value-of select="$i"/>
                </step_number>
                <actions>
                    <xsl:text>&lt;p&gt;</xsl:text>
                    <xsl:value-of select="substring-after(., '. ')" />
                </actions>                    
                <expectedresults>
                    <xsl:text>&lt;p&gt;</xsl:text>
                    <xsl:value-of select="substring-after($expectedresults[$i], '. ')"/>
                </expectedresults>
                <execution_type>1</execution_type>
            </step>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet>
1 голос
/ 01 февраля 2020

Преобразование вашего ввода XML в желаемый вывод XML требует некоторых серьезных искажений:

  1. Декодирование разделов CDATA в xsl:variable с parse-xml-fragment
  2. Получить текущий индекс этих steps|expectedresults элементов с

    count(preceding-sibling::*)+1
    
  3. Перебрать элементы p

  4. Разделите строку на соответствующие части
  5. Выведите элементы с их значениями, заключенными в секции CDATA (здесь элемент <p> должен быть экранирован)

Это дает вам следующий XSLT -3.0 код:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" >
    <xsl:output method="xml" indent="yes" cdata-section-elements="step_number actions expectedresults execution_type" />

    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" />
        </xsl:copy>
    </xsl:template>

    <xsl:template match="steps|expectedresults">
        <xsl:variable name="st"  select="parse-xml-fragment(.)" />
        <xsl:variable name="pos" select="count(preceding-sibling::*)+1" />
        <steps>
            <xsl:for-each select="$st/p">
                <step>
                    <xsl:variable name="cur" select="substring-before(translate(.,'.','  '),' ')" />
                    <step_number>
                        <xsl:value-of select="$cur" />
                    </step_number>
                    <actions><xsl:value-of select="concat('&lt;p&gt;','step ',$cur,'&lt;/p&gt;')" /></actions>                    
                    <expectedresults>
                        <xsl:value-of select="concat('&lt;p&gt;',normalize-space(substring-after(.,' ')),'&lt;/p&gt;')" />
                    </expectedresults>
                    <execution_type>
                        <xsl:value-of select="$pos" />
                    </execution_type>
                </step>
            </xsl:for-each>
        </steps>
    </xsl:template>

</xsl:stylesheet>

Выход:

<?xml version="1.0" encoding="UTF-8"?>
<testcases>
    <testcase name="Simple">
        <steps>
            <step>
                <step_number><![CDATA[1]]></step_number>
                <actions><![CDATA[<p>step 1</p>]]></actions>
                <expectedresults><![CDATA[<p>do something</p>]]></expectedresults>
                <execution_type><![CDATA[1]]></execution_type>
            </step>
            <step>
                <step_number><![CDATA[2]]></step_number>
                <actions><![CDATA[<p>step 2</p>]]></actions>
                <expectedresults><![CDATA[<p>do more</p>]]></expectedresults>
                <execution_type><![CDATA[1]]></execution_type>
            </step>
            <step>
                <step_number><![CDATA[3]]></step_number>
                <actions><![CDATA[<p>step 3</p>]]></actions>
                <expectedresults><![CDATA[<p>even more</p>]]></expectedresults>
                <execution_type><![CDATA[1]]></execution_type>
            </step>
        </steps>
        <steps>
            <step>
                <step_number><![CDATA[1]]></step_number>
                <actions><![CDATA[<p>step 1</p>]]></actions>
                <expectedresults><![CDATA[<p>result</p>]]></expectedresults>
                <execution_type><![CDATA[2]]></execution_type>
            </step>
            <step>
                <step_number><![CDATA[2]]></step_number>
                <actions><![CDATA[<p>step 2</p>]]></actions>
                <expectedresults><![CDATA[<p>more result</p>]]></expectedresults>
                <execution_type><![CDATA[2]]></execution_type>
            </step>
            <step>
                <step_number><![CDATA[3]]></step_number>
                <actions><![CDATA[<p>step 3</p>]]></actions>
                <expectedresults><![CDATA[<p>again</p>]]></expectedresults>
                <execution_type><![CDATA[2]]></execution_type>
            </step>
        </steps>
    </testcase>
</testcases>
...