【问题标题】:Can XSLT generate locally invalid HTML?XSLT 可以生成本地无效的 HTML 吗?
【发布时间】:2017-09-13 05:31:10
【问题描述】:

我有一个结构如下的 XML 文档:

<?xml version="1.0" encoding="UTF-8"?>
<text>
  ...
  <cb n="1" />
  ...
  <cb n="2" />
  ...
  <cb n="" />
  ...
</text>

XML 文档中的每个以列为单位的部分都以&lt;cb n="1" /&gt; 标记开头,以&lt;cb n="" /&gt; 标记结尾,中间有一个或多个&lt;cb n="2" /&gt;&lt;cb n="3" /&gt; 等标记。 &lt;cb&gt; 标签都是&lt;text&gt; 的直接子代。我想生成 HTML,其中每个&lt;cb n="1" /&gt;...&lt;cb n="" /&gt; 块转换为&lt;div&gt;...&lt;/div&gt;,每个&lt;cb n="x" /&gt;...&lt;cb n="x+1" /&gt; 块转换为&lt;div class="column"&gt;...&lt;/div&gt;。例如,上述 XML 的输出将是

<html>
  <body>
    ...
    <div>
      <div class="column">
        ...
      </div>
      <div class="column">
        ...
      </div>
    </div>
    ...
  </body>
</html>

我的 XSLT 样式表是:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html"/>
  <xsl:template match="text">
    <html>
      <body>
        <xsl:apply-templates/>
      </body>
    </html>
  </xsl:template>

  <!-- this is the part that fails -->
  <xsl:template match="cb[@n='1']">
    <div>
      <div class="column">
  </xsl:template>
  <xsl:template match="cb[@n='']">
      </div>
    </div>
  </xsl:template>
  <xsl:template match="cb">
    </div>
    <div class="column">
  </xsl:template>
</xsl:stylesheet>

但这不起作用,因为样式表本身不是有效的 XML。 XSLT 1.0 中是否可以进行这种转换?

【问题讨论】:

  • 是的,当然可以,但不是因为您在标签级别处理问题。不要尝试分别控制开始和结束标签;相反,将它们作为元素的一部分一起控制。如果您需要更多详细信息来理解该解释,请发布minimal reproducible example
  • @kjhughes MCVE 需要哪些额外信息?你想让我包含完整的样式表吗?
  • MCVE 意味着一个最小的、完整的输入 XML 文档、实际运行的最小 XSLT 代码、实际的 XML 输出和所需的 XML 输出。
  • @kjhughes 查看我的编辑,但请注意我的问题不是为什么我的解决方案失败了,而是如何实现这种特殊的转换。
  • 请用文字说明所需的逻辑。这个例子是模棱两可的。通过为每个cb[@n&gt;0] 创建一个div 可以非常简单地实现预期结果。

标签: html xml xslt xslt-1.0


【解决方案1】:

首先要了解的是,当生成 HTML 或 XML 输出时,XSL 会生成整个输出元素;隔离的开始或结束标签不能被发送到输出中(部分原因是它们在输入中不被接受)。因此,输出文档中的每个节点都来自输入文档中特定节点的转换,因此作为转换作者的一部分工作就是选择将哪些输入节点转换为所需的输出节点。

特别是,包含输出列组的&lt;div&gt; 的源节点的唯一合适候选者是&lt;text&gt; 元素和&lt;cb&gt; 元素之一。如果你选择后者,那么你需要选择一个具有显着特征的,例如是第一个或最后一个,或者具有特定的属性值。

此外,无论哪个节点的转换提供包含&lt;div&gt;,都必须负责列组内容,因为模板无法将内容添加到由不同的输出节点生成模板,甚至是同一模板的不同实例。如果您让&lt;text&gt; 元素的模板也转换它们,那么您将不得不做额外的工作以避免不必要的输出。

您可以通过以下方式将它们组合在一起:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="html" />

  <!-- identity transform for nodes not otherwise matched with a template -->
  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="node()|@*"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match='/text'>
    <html>
      <body>
        <!-- transform child nodes up to and including the first <cb>, if any -->
        <xsl:apply-templates select="node()[not(preceding::cb)]" />
      </body>
    </html>
  </xsl:template>

  <!-- template for most <cb> elements: -->
  <xsl:template match="cb">
    <xsl:variable name="column" select="preceding-sibling::cb[1]/@n" />
    <div class="column">
      <!-- contents come from transforming nodes between the previous <cb>
           and this one -->
      <xsl:apply-templates
          select="preceding-sibling::node()[preceding-sibling::cb[@n = $column]]" />
    </div>
  </xsl:template>

  <!-- template for <cb> elements that are their parent's first child;
       produces the column-group div, its contents, and the nodes following -->
  <xsl:template match="cb[1]">
    <div>
      <xsl:apply-templates select="following-sibling::cb" />
    </div>
    <xsl:apply-templates
        select="../cb[position() = last()]/following-sibling::node()" />
  </xsl:template>

</xsl:stylesheet>

这不使用(因此不依赖于)输入n 属性的特定值;它只依赖于它们是不同的。此外,因为它通过转换第一个&lt;cb&gt; 创建了包含&lt;div&gt; 的列组,所以它将完全忽略没有任何&lt;cb&gt; 元素。总的来说,请注意使用preceding-siblingfollowing-sibling 轴来选择其他节点之间的节点。

【讨论】:

    【解决方案2】:

    您的问题仍然不完全清楚。如果我猜对了,你想输入如下:

    XML

    <text>
        <cb n="1">a</cb>
        <cb n="2">b</cb>
        <cb n="2">c</cb>
        <cb n=""></cb>
        <cb n="4">d</cb>
        <cb n="5">e</cb>
        <cb n=""></cb>
        <cb n="6">f</cb>
        <cb n="7">g</cb>
        <cb n="8">h</cb>
        <cb n="9">i</cb>
        <cb n="">j</cb>
    </text>
    

    并为每组以&lt;cb n=""/&gt; 结尾的连续cb 元素创建一个div 包装器。这在 XSLT 2.0 中很容易做到,但在 XSLT 1.0 中有点棘手:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html"/>
    
    <xsl:key name="cb-by-end" match="cb[not(@n='')]" use="generate-id(following-sibling::cb[@n=''][1])" />
    
    <xsl:template match="/text">
        <html>
            <body>
                <xsl:apply-templates select="cb[@n='']" mode="group"/>
            </body>
        </html>
    </xsl:template>
    
    <xsl:template match="cb" mode="group">
        <div>
            <xsl:apply-templates select="key('cb-by-end', generate-id())"/>
        </div>
    </xsl:template>
    
    <xsl:template match="cb">
        <div class="column">
            <xsl:apply-templates/>
        </div>
    </xsl:template>
    
    </xsl:stylesheet>
    

    结果

    <html>
        <body>
            <div>
                <div class="column">a</div>
                <div class="column">b</div>
                <div class="column">c</div>
            </div>
            <div>
                <div class="column">d</div>
                <div class="column">e</div>
            </div>
            <div>
                <div class="column">f</div>
                <div class="column">g</div>
                <div class="column">h</div>
                <div class="column">i</div>
            </div>
        </body>
    </html>
    

    【讨论】:

      猜你喜欢
      • 2021-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-26
      • 2012-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多