代码之家  ›  专栏  ›  技术社区  ›  David

开放XML到干净HTML列表的XSLT转换

  •  2
  • David  · 技术社区  · 7 年前

    我创建了一个XSLT文件,可以将Word XML中的所有内容转换为干净的HTML,但我无法正确转换嵌套列表。

    我保存了一个单词v16。12文件转换为XML。Word文件包含两个列表

    下面是导出的开放XML(仅与项目符号相关)。

    <w:body>
    <w:p w:rsidR="00875AF6" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="0"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 1 level 1</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="0"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 2 level 1</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 3 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="2"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 4 level 3</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="2"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 5 level 3</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 6 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="2"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 7 level 3</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="007A38EC">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="0"/>
                <w:numId w:val="1"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 1 Bullet 8 level 1</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241"/>
    <w:p w:rsidR="00575241" w:rsidRDefault="00575241" w:rsidP="00575241">
        <w:r>
            <w:t>This is a break</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="00575241" w:rsidRDefault="00575241" w:rsidP="00575241"/>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="0"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 1 level 1</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 2 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="2"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 3 level 3</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="0"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 4 level 1</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 5 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 6 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="2"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 7 level 3</w:t>
        </w:r>
        <w:bookmarkStart w:id="0" w:name="_GoBack"/>
        <w:bookmarkEnd w:id="0"/>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241">
        <w:pPr>
            <w:pStyle w:val="ListParagraph"/>
            <w:numPr>
                <w:ilvl w:val="1"/>
                <w:numId w:val="2"/>
            </w:numPr>
        </w:pPr>
        <w:r>
            <w:t>List 2 Bullet 8 level 2</w:t>
        </w:r>
    </w:p>
    <w:p w:rsidR="007A38EC" w:rsidRDefault="007A38EC" w:rsidP="00575241"/>
    <w:sectPr w:rsidR="007A38EC" w:rsidSect="00D678D3">
        <w:pgSz w:w="11900" w:h="16840"/>
        <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
            w:header="708" w:footer="708" w:gutter="0"/>
            <w:cols w:space="708"/>
            <w:docGrid w:linePitch="360"/>
        </w:sectPr>
    </w:body>
    

    使用XSLT,我需要将XML转换为HTML

    <ul>
      <li>List 1 Bullet 1 level 1</li>
      <li>List 1 Bullet 2 level 1
        <ul>
          <li>List 1 Bullet 3 level 2
            <ul>
              <li>List 1 Bullet 4 level 3</li>
              <li>List 1 Bullet 5 level 3</li>
            </ul>
          </li>
          <li>List 1 Bullet 6 level 2
            <ul>
              <li>List 1 Bullet 7 level 3</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>List 1 Bullet 8 level 1</li>
    </ul>
    <p>This is a gap</p>
    <ul>
      <li>List 2 Bullet 1 level 1
        <ul>
          <li>List 2 Bullet 2 level 2
            <ul>
              <li>List 2 Bullet 3 level 3</li>
            </ul>
          </li>
        </ul>
      </li>
      <li>List 2 Bullet 4 level 1
        <ul>
          <li>List 2 Bullet 5 level 2</li>
          <li>List 2 Bullet 6 level 2
            <ul>
              <li>List 2 Bullet 7 level 3</li>
            </ul>
          </li>
          <li>List 2 Bullet 8 level 2</li>
        </ul>
      </li>
    </ul>
    

    我已经进行了研究,发现最接近的方法是使用函数,对于每个组,如下所示。

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:mf="http://example.com/mf" version="2.0"
        exclude-result-prefixes="xs mf">
    
        <xsl:strip-space elements="*"/>
        <xsl:output indent="yes"/>
    
        <xsl:function name="mf:group" as="node()*">
            <xsl:param name="nodes" as="node()*"/>
            <xsl:param name="level" as="xs:integer"/>
            <xsl:if test="$nodes">
                <list type="ul">
                    <xsl:for-each-group select="$nodes"
                        group-adjacent="boolean(self::*[@level = $level])">
                        <xsl:choose>
                            <xsl:when test="current-grouping-key()">
                                <xsl:apply-templates select="current-group()"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </list>
            </xsl:if>
        </xsl:function>
    
        <xsl:template match="@* | node()">
            <xsl:copy>
                <xsl:apply-templates select="@*, node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="item[@level]">
            <item>
                <xsl:apply-templates/>
            </item>
        </xsl:template>
    
        <xsl:template match="test">
            <xsl:copy>
                <xsl:for-each-group select="*" group-adjacent="boolean(self::item)">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()">
                            <xsl:sequence select="mf:group(current-group(), 0)"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:apply-templates select="current-group()"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>
            </xsl:copy>
        </xsl:template>
    
    </xsl:stylesheet>
    

    不幸的是,使用函数和for each group超出了我的能力。我的问题是,我将如何修改上述XSLT以使用我从Word获得的XML?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Joel M. Lamsen    7 年前

    首先,我们将从一个标识模板开始:

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

    其次,我们必须匹配根节点 w:body 并使用 xsl:for-each-group 。之后,我们将节点存储在变量(firstpass)中,以便稍后进一步操作节点,例如:

    <!-- If you want to specify the target node (1 in 22 as you say),
         you can adjust the xpath below to match your target node.
    -->
    <xsl:template match="w:body">
        <xsl:variable name="firstPass">
            <xsl:for-each-group select="*" group-adjacent="boolean(self::w:p[descendant::w:ilvl])">
                <xsl:choose>
                    <xsl:when test="current-grouping-key()">
                        <!-- the zero (0) was obtained from the value of
                             w:val attribute of w:ilvl node -->
                        <xsl:sequence select="mf:group(current-group(), 0)"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </xsl:variable>
        <xsl:apply-templates select="$firstPass/node()"/>
    </xsl:template>
    

    我们可以调整你提到的功能。我们可以将组相邻的目标节点修改为

    <xsl:function name="mf:group" as="node()*">
        <xsl:param name="nodes" as="node()*"/>
        <xsl:param name="level" as="xs:integer"/>
        <xsl:if test="$nodes">
            <ul>
                <xsl:for-each-group select="$nodes"
                    group-adjacent="boolean(self::*[descendant::w:ilvl/@w:val = $level])">
                    <xsl:choose>
                        <xsl:when test="current-grouping-key()">
                            <xsl:apply-templates select="current-group()"/>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:for-each-group>
            </ul>
        </xsl:if>
    </xsl:function>
    

    以下是清理所需的模板

    <xsl:template match="w:p">
        <xsl:apply-templates select="descendant::w:t"/>
    </xsl:template>
    
    <xsl:template match="w:p[.='']|w:sectPr"/>
    
    <xsl:template match="w:t">
        <xsl:choose>
            <xsl:when test="ancestor::w:p[descendant::w:pStyle[@w:val='ListParagraph']]">
                <li>
                    <xsl:apply-templates/>
                </li>
            </xsl:when>
            <xsl:otherwise>
                <p>
                    <xsl:apply-templates/>
                </p>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    

    之后,我们仍然需要插入 <ul> 子级到父级 <li> .要做到这一点,我们必须进行第二次转变。

    现在,我们将匹配firstpass变量中存在的节点

    <xsl:template match="li[following-sibling::*[1][name()='ul']]">
        <xsl:copy>
            <xsl:apply-templates/>
            <!-- this will copy the target ul nodes, albeit in a different mode -->
            <xsl:apply-templates select="following-sibling::*[1][name()='ul']" mode="transfer"/>
        </xsl:copy>
    </xsl:template>
    
    <!-- this will delete the target node -->
    <xsl:template match="ul[preceding-sibling::*[1][name()='li']]"/>
    

    以及其他模式的标识模板

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

    整个样式表如下所示:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        xmlns:w="www.wnamespace.com"
        version="2.0"
        exclude-result-prefixes="xs mf w">
    
        <xsl:strip-space elements="*"/>
        <xsl:output indent="yes" omit-xml-declaration="yes"/>
    
        <xsl:function name="mf:group" as="node()*">
            <xsl:param name="nodes" as="node()*"/>
            <xsl:param name="level" as="xs:integer"/>
            <xsl:if test="$nodes">
                <ul>
                    <xsl:for-each-group select="$nodes"
                        group-adjacent="boolean(self::*[descendant::w:ilvl/@w:val = $level])">
                        <xsl:choose>
                            <xsl:when test="current-grouping-key()">
                                <xsl:apply-templates select="current-group()"/>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:sequence select="mf:group(current-group(), $level + 1)"/>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:for-each-group>
                </ul>
            </xsl:if>
        </xsl:function>
    
        <xsl:template match="@* | node()">
            <xsl:copy>
                <xsl:apply-templates select="@*, node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="@* | node()" mode="transfer">
            <xsl:copy>
                <xsl:apply-templates select="@*, node()"/>
            </xsl:copy>
        </xsl:template>
    
        <xsl:template match="w:p">
            <xsl:apply-templates select="descendant::w:t"/>
        </xsl:template>
    
        <xsl:template match="w:p[.='']|w:sectPr"/>
    
        <xsl:template match="w:t">
            <xsl:choose>
                <xsl:when test="ancestor::w:p[descendant::w:pStyle[@w:val='ListParagraph']]">
                    <li>
                        <xsl:apply-templates/>
                    </li>
                </xsl:when>
                <xsl:otherwise>
                    <p>
                        <xsl:apply-templates/>
                    </p>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:template>
    
    <xsl:template match="w:body">
        <xsl:variable name="firstPass">
            <xsl:for-each-group select="*" group-adjacent="boolean(self::w:p[descendant::w:ilvl])">
                <xsl:choose>
                    <xsl:when test="current-grouping-key()">
                        <xsl:sequence select="mf:group(current-group(), 0)"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:apply-templates select="current-group()"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each-group>
        </xsl:variable>
        <xsl:apply-templates select="$firstPass/node()"/>
    </xsl:template>
    
        <xsl:template match="ul[preceding-sibling::*[1][name()='li']]"/>
    
    </xsl:stylesheet>
    

    在行动中看到它 here