Как создать список HTML из плоского XML-файла с использованием XSLT (на основе предыдущего вопроса) - PullRequest
1 голос
/ 26 марта 2019

Мне нужно создать неупорядоченные списки HTML из плоской структуры XML, используя XSLT 1.0.Входной XML состоит из серии узлов, которые должны быть преобразованы в элементы списка.Тем не менее, эта серия может быть прервана не перечисленными узлами разных типов:

<input>
  <paragraph>abc</paragraph>
  <paragraph>def</paragraph>
    <listable>123</listable>
    <listable>456</listable>
  <other-block>
    <other-text>Foo</other-text>
  </other-block>
    <listable>789</listable>
    <listable>012</listable>
</input>

Моя цель:

<div class="output">
  <p>abc</p>
  <p>def</p>
  <ul>
    <li>123</li>
    <li>456</li>
  </ul>
  <div class="my-block">
    <p class="other">Foo</p>
  </div>
  <ul>
    <li>789</li>
    <li>012</li>
  </ul>
</div>

Я нашел старый поток с решением, котороеу меня почти работает (последнее решение на странице, Димитр Новатчев) и адаптировал его.Это минимальная таблица стилей, основанная на этом решении:

<?xml version="1.0"?>

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

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

    <!-- NON-LIST ITEMS: -->
    <xsl:template match="input">
        <div class="output">
            <xsl:apply-templates />
        </div>
    </xsl:template>

    <xsl:template match="paragraph">
        <p>
            <xsl:apply-templates />
        </p>
    </xsl:template>

    <xsl:template match="other-block">
        <div class="my-block">
            <xsl:apply-templates select="descendant::other-text" /> 
        </div>
    </xsl:template> 

    <xsl:template match="other-text">
        <p class="other">
            <xsl:copy-of select="text()" />
        </p>
    </xsl:template>

    <!-- LIST HANDLING: -->
    <xsl:key name="kFollowingUL" match="listable" 
                use="generate-id(preceding-sibling::*[not(self::listable)][1])"/>

    <xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

        <xsl:call-template name="identity" />

        <xsl:variable name="vFolUL"
                select="key('kFollowingUL',generate-id())"/>

        <xsl:if test="$vFolUL">
            <ul>
                <xsl:apply-templates mode="copy"
                        select="key('kFollowingUL',generate-id())" />
            </ul>
        </xsl:if>

    </xsl:template>

    <xsl:template match="listable" mode="copy">
        <li>
            <xsl:value-of select="normalize-space()" />
        </li>
    </xsl:template>   

    <xsl:template match="listable" />

</xsl:stylesheet>

Проблема этого подхода заключается в том, что он не применяет преобразования к последнему неперечисляемому узлу перед каждым списком.Узлы <paragraph> и <other-block> во входе копируются непосредственно в выход, хотя шаблоны применяются к потомкам <other-block>:

<div class="output">
  <p>abc</p>
  <paragraph>def</paragraph>
  <ul>
    <li>123</li>
    <li>456</li>
  </ul>
  <other-block>
    <p class="other">Foo</p>
  </other-block>
  <ul>
    <li>789</li>
    <li>012</li>
  </ul>
</div>

Может кто-нибудь предложить способ изменить более раннюю версию XSLT 1.0решение и добавить преобразование последних узлов перед списочными группами?

Ответы [ 2 ]

1 голос
/ 27 марта 2019

Я бы сделал это так:

XSLT 1.0

<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="*"/>

<xsl:template match="/input">
    <div class="output">
        <xsl:apply-templates/>
    </div>
</xsl:template>

<xsl:template match="paragraph">
    <p>
        <xsl:apply-templates/>
    </p>
</xsl:template>

<xsl:template match="other-block">
    <div class="my-block">
        <xsl:apply-templates/> 
    </div>
</xsl:template> 

<xsl:template match="other-text">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

<xsl:template match="listable">
    <xsl:if test="not(preceding-sibling::*[1][self::listable])">
        <ul>
            <xsl:apply-templates select="." mode="list"/>       
        </ul>
    </xsl:if>
</xsl:template>

<xsl:template match="listable" mode="list">
    <li>
        <xsl:apply-templates/>      
    </li>
    <xsl:apply-templates select="following-sibling::*[1][self::listable]" mode="list"/>         
</xsl:template>

</xsl:stylesheet>
0 голосов
/ 27 марта 2019

Ваша проблема возникла из этого шаблона:

<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

    <xsl:call-template name="identity" />

    <xsl:variable name="vFolUL"
            select="key('kFollowingUL',generate-id())"/>

    <xsl:if test="$vFolUL">
        <ul>
            <xsl:apply-templates mode="copy"
                    select="key('kFollowingUL',generate-id())" />
        </ul>
    </xsl:if>

</xsl:template>

Соответствует любому элементу с элементом listable в качестве первого следующего брата. Затем в шаблоне содержимого он вызывает шаблон с именем identity (в данном случае это правило идентификации). Это имеет приоритет по умолчанию , чем другой шаблон для other-block элементов:

<xsl:template match="other-text">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

Мне нравится решение michael.hor257k, которое я использовал в оригинальном ответе . Другое возможное решение - следовать тому же принципу:

<xsl:template match="*[not(self::listable) and following-sibling::*[1][self::listable]]">

    <xsl:call-template name="separator" />

    <xsl:variable name="vFolUL"
            select="key('kFollowingUL',generate-id())"/>

    <xsl:if test="$vFolUL">
        <ul>
            <xsl:apply-templates mode="copy"
                    select="key('kFollowingUL',generate-id())" />
        </ul>
    </xsl:if>

</xsl:template>

<xsl:template match="other-text" name="separator">
    <p class="other">
        <xsl:apply-templates/>      
    </p>
</xsl:template>

Но учтите, что это плохо масштабируется.

...