将XML转换为格式化文本样式瓶XSLT

时间:2014-03-05 22:05:07

标签: c# xslt text

我对游戏完全不熟悉。我正在进行的项目需要能够交替渲染为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”值(或坐标)从Text元素构建字符串的方法,这就是我需要对文本输出做的事情,这样才能写入上面的示例xml中的文本文件是:

First Line Continued on Same Line Still Going
    Slightly Indented New Line

所以,如果我在代码中做相同的操作,它可能看起来像:

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属性的“Text”元素中的下一个最高x属性是“6”。因此,它的值将附加到现有字符串。 y值的逻辑相同。

如果不够清楚,请告诉我,我会高兴地解释更多,或者不同。

1 个答案:

答案 0 :(得分:0)

这比看起来要复杂一点。

  • 您必须按<Text>@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>

提供您的输入

First Line Continued on Same Line Still Going
     Slightly Indented New Line

这里使用的值得注意的技术称为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|First Line          Continued on Same Lin Still Going
2|
3|                              30 spaces before this
4|
5|   Slightly Indented New Line

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

另请注意,您必须决定是使用基于1的(如使用@y)还是基于0(使用@x)协同系统。目前,您的样本输入不一致。