【问题标题】:Converting XML to formatted text vial XSLT将 XML 转换为格式化文本小瓶 XSLT
【发布时间】:2014-03-07 15:24:38
【问题描述】:

我对游戏完全陌生。我正在处理的项目需要能够交替呈现为 html 或文本。我决定尝试 XSLT,这样我就可以在不真正改变底层代码结构的情况下修改输出。 HTML 输出很好。在尝试将转换写入文本时有点迷失。原因如下:

我正在转换的 xml 在这种类型的结构中:

<Data>
  <Text x="0" y="1">First Line</Text>
  <Text x="12" y="1">Continued on Same Line</Text>
  <Text x="36" y="1">Still Going</Text>
  <Text x="5" y="2">Slightly Indented New Line</Text>
</Data>

我用于 html 的基本模板运行良好。我现在正在尝试为文本输出创建一个模板,即

<xsl:output method="text"/>

但我目前还无法设计一种方法来基于“x”和“y”值(或坐标)从文本元素构建字符串,这是我需要为文本输出做的事情,以便写入上面示例 xml 中的文本文件是:

第一行继续同一行仍在继续 略微缩进的新行

所以,如果我要在代码中做同样的事情,它可能看起来像:

private string SomeMethod(XPathNavigator TestNav)
{
int iRow = 0;
int iColumn = 0;
XPathNodeIterator lineIterator = TestNav.SelectChildren("Data", "");
StringBuilder text = new StringBuilder();
while (lineIterator.MoveNext())
{

 XPathNavigator curNav= lineIterator.Current;
 XPathExpression Exp = curNav.Compile("*[@x]|*/*[@x]");
 Exp.AddSort("@y", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
 Exp.AddSort("@x", XmlSortOrder.Ascending, XmlCaseOrder.None, "", XmlDataType.Number);
 XPathNodeIterator positionIterator = curNav.Select(Exp);
 while (positionIterator.MoveNext())
     {                        
         String elValue = positionIterator.Current.InnerXml;
         int xTxt = int.Parse(positionIterator.Current.GetAttribute("x", ""));
         int yTxt = int.Parse(positionIterator.Current.GetAttribute("y", ""));

             if (iRow < yTxt)
             {
                string newLines = new string('\n', yTxt - iRow);
                text = text.Append(newLines);
                iColumn = 0;
             }

             if (iColumn < xTxt)
             {
                 string newLines = new string(' ', xTxt - iColumn);
                 text = text.Append(newLines);
             }

             text = text.Append(elValue);

             iRow = yTxt;
             iColumn = xTxt + elValue.Length;
      }

      if (lineIterator.Count != 0)
      {
         text = text.Append("\n\f\n");
         iRow = 0;
         iColumn = 0;
      }
}            
        return text.ToString();
}

鉴于上述 xml 的结构,任何想法我可以如何在 XSLT 中做同样的事情,再次将输出方法设置为文本,以便它持久保存到文件中,以便从 x 和构建完整的行单个单词的 y 坐标。因此,如果对于两个单词,“Hello World”,它们表示为

<Text x="0" y="1">Hello</Text>
<Text x="6" y="1">World</Text>

然后 "Hello" 实例化一个字符串,并消耗 5 个字符的空间,0-4。 “世界”从 6 开始,因此字符索引“5”被空白填充(并将继续这样做,直到达到下一个最高的 x 属性)。具有相同 y 属性的“文本”元素中的下一个最高 x 属性是“6”。所以它的值然后被附加到现有的字符串中。 y 值的逻辑相同。

如果这还不够清楚,请告诉我,我很乐意解释更多或不同。

【问题讨论】:

  • 你能从文本输出中阐明你想要什么吗?听起来您需要不同的 HTML 和文本模式,因为它们有不同的要求。
  • 是的,完全正确。我将修改帖子以反映澄清。
  • 所以你真的想在文本模式下对所有单个位进行“坐标”定位?
  • 嗯,这至少是梦想。不过,我对 xsl 很陌生,只是不确定进一步追求这一点所需的复杂性。每个坐标永远不会代表少于一个单词,但我认为这不会真正产生影响,因为输出中需要空格,但除非出现在单词之间,否则不会在 xml 中给出。
  • XSLT 不是执行此操作的最佳工具。可以构建一个样式表来执行所有必要的计算并输出格式正确的纯文本,但结果会非常复杂。考虑直接在 C# 中执行此操作 - 有一个字符串/缓冲区列表很容易,允许您在通过 XML 时在定义的位置随机写入。 XSLT 并不是为这种工作而设计的。

标签: c# xslt text


【解决方案1】:

这比看起来要复杂一些。

  • 您必须将&lt;Text&gt; 项目按@y 分组。所有具有相同@y 值的都在同一行。
  • 您必须决定是否要表示空行/缺失行(即不连续的@y 值)。不代表他们更简单。
  • 您必须决定是只想连接共享同一行的所有内容,还是按各自的@x 值单独排列。后者要复杂得多。
  • 您至少可以在每行的第一项前面加上定义的 @x 数量。

这个 XSLT 转换

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

  <xsl:output method="text" />

  <xsl:key name="kLine" match="Text" use="@y" />
  <xsl:variable name="padding" select="'                                     '"/>

  <xsl:template match="Data">
    <xsl:for-each select="Text[generate-id() = generate-id(key('kLine', @y))]">
      <xsl:apply-templates select="key('kLine', @y)">
        <xsl:sort select="@x" data-type="number" />
      </xsl:apply-templates>
      <xsl:text>&#xA;</xsl:text>
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="Text">
    <xsl:if test="generate-id() = generate-id(key('kLine', @y))">
      <xsl:value-of select="substring($padding, 1, @x)" />
    </xsl:if>
    <xsl:value-of select="." />
    <xsl:if test="position() &lt; last()">
      <xsl:text> </xsl:text>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

提供您的意见

第一行继续同一行仍在继续 略微缩进的新行

这里使用的值得注意的技术称为 Muenchian 分组。


FWIW,这是一个满足您要求的解决方案。自己决定是否值得麻烦。

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

  <xsl:output method="text" />

  <xsl:key name="kLine" match="Text" use="@y" />
  <xsl:variable name="padX" select="'                                     '"/>
  <xsl:variable name="padY" select="'&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;'"/>

  <xsl:template match="Data">
    <xsl:for-each select="Text[generate-id() = generate-id(key('kLine', @y))]">
      <xsl:sort select="@y" data-type="number" />

      <xsl:apply-templates select="key('kLine', @y)">
        <xsl:sort select="@x" data-type="number" />
      </xsl:apply-templates>

      <!-- find the vertical position of the logically following text -->
      <xsl:variable name="nextY">
        <xsl:for-each select="../Text[@y &gt; current()/@y]">
          <xsl:sort select="@y" data-type="number" />
          <xsl:if test="position() = 1">
            <xsl:value-of select="@y" />
          </xsl:if>
        </xsl:for-each>
      </xsl:variable>
      <xsl:value-of select="substring($padY, 1, $nextY - @y)" />
    </xsl:for-each>
  </xsl:template>

  <xsl:template match="Text">
    <!-- indent first item on line -->
    <xsl:if test="generate-id() = generate-id(key('kLine', @y))">
      <xsl:value-of select="substring($padX, 1, @x)" />
    </xsl:if>

    <xsl:variable name="text" select="normalize-space()" />

    <!-- calculate the available text block width -->
    <xsl:variable name="width">
      <!-- find the horizontal position of the logically following text -->
      <xsl:variable name="nextX">
        <xsl:for-each select="key('kLine', @y)[@x &gt; current()/@x]">
          <xsl:sort select="@x" data-type="number" />
          <xsl:if test="position() = 1">
            <xsl:value-of select="@x" />
          </xsl:if>
        </xsl:for-each>
      </xsl:variable>
      <xsl:choose>
        <xsl:when test="$nextX &gt; 0">
          <xsl:value-of select="$nextX - @x - 1" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="string-length($text)" />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <!-- current text + necessary right padding for next item -->
    <xsl:value-of select="substring(concat($text, $padX), 1, $width)" />

    <xsl:if test="position() &lt; last()">
      <xsl:text> </xsl:text>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

用于此:

<Data>
  <Text x="0" y="1">First Line</Text>
  <Text x="20" y="1">Continued on Same Line</Text>
  <Text x="42" y="1">Still Going</Text>
  <Text x="4" y="5">Slightly Indented New Line</Text>
  <Text x="30" y="3">30 spaces before this</Text>
</Data>

给出(为方便起见,添加网格数字):

| 1 2 3 4 5 | 5 0 5 0 5 0 5 0 5 0 5 -+------------------------------------------------ -------- 1|第一行继续同林 还在继续 2| 3|在此之前 30 个空格 4| 5|略微缩进的新行

注意它是如何删除不适合的文本的。此外,这没有经过彻底测试,它只是一个概念验证。它可能会在您的输入中表现出各种有趣的行为。

还请注意,您必须决定是要使用基于 1(如 @y)还是基于 0(如 @x)的坐标系统。目前在您的示例输入中并不一致。

【讨论】:

    猜你喜欢
    • 2012-02-06
    • 1970-01-01
    • 1970-01-01
    • 2022-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-02
    • 1970-01-01
    相关资源
    最近更新 更多