tags:

views:

751

answers:

4

I would like to take the contents of an XML tag and have it displayed flipped both horizontally (mirror image) and vertically (as a column) when viewed through a stylesheet. Is this possible without using random third party libraries?

<mytag>Random Data</mytag>
+3  A: 

XSLT is for transformation. To change presentation you should use either CSS or XSL-FO.

In XSL-FO you can set writing-mode to tb-lr

vartec
+2  A: 

Vartec is right. I ended up accomplishing it with the following CSS:

.verticaltext {
  writing-mode: tb-rl;
  filter: flipv;
}

Thanks!

cakeforcerberus
IIRC, that one works only in IE.
vartec
Thankfully, I only have to support IE in this instance. ;)
cakeforcerberus
+2  A: 

As such, XSLT is ill-suited for string processing. With XSLT 2.0, things get better since more string functions are available, and sequence-based operations are possible.

In XSLT 1.0 (which is still the most portable version to write code for), character-by-character string processing can only be achieved through recursion. For the fun of it, this:

<xsl:output method="text" />

<xsl:variable name="CRLF" select="'&#13;&#10;'" />

<xsl:template match="/mytag">
  <!-- flip string -->
  <xsl:call-template name="reverse-string">
    <xsl:with-param name="s" select="string(.)" />
  </xsl:call-template>
  <xsl:value-of select="$CRLF" />

  <!-- vertical string -->
  <xsl:call-template name="vertical-string">
    <xsl:with-param name="s" select="string(.)" />
  </xsl:call-template>
</xsl:template>

<xsl:template name="reverse-string">
  <xsl:param name="s" select="''" />

  <xsl:variable name="l" select="string-length($s)" />

  <xsl:value-of select="substring($s, $l, 1)" />

  <xsl:if test="$l &gt; 0">
    <xsl:call-template name="reverse-string">
      <xsl:with-param name="s" select="substring($s, 1, $l - 1)" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

<xsl:template name="vertical-string">
  <xsl:param name="s" select="''" />

  <xsl:variable name="l" select="string-length($s)" />

  <xsl:value-of select="concat(substring($s, 1, 1), $CRLF)" />

  <xsl:if test="$l &gt; 0">
    <xsl:call-template name="vertical-string">
      <xsl:with-param name="s" select="substring($s, 2, $l)" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

Produces:

ataD modnaR
R
a
n
d
o
m

D
a
t
a

EDIT: To be clear: I do not endorse actual use of the above code sample in any way. Presentational issues should by all means be solved in the presentation layer. The above will work, but char-by-char recursion is among the most inefficient ways to do string processing, and unless you have no other choice, avoid string processing in XSLT.

Tomalak
+1 for hacking ;-)
vartec
*lol* Thanks! :-) The time investment/reputation gain is not very good with XSLT questions though. Too bad. ;-)
Tomalak
Interesting. Thanks Tomalak!
cakeforcerberus
+1 from me. And see my answer to yout comment.
Dimitre Novatchev
+1  A: 

This is very easy to do with XPath 2.0 / XSLT 2.0 (in XSLT 1.0 one can use the functions/templates of FXSL 1.x):

Both tasks can be produced as the result of XPath 2.0 expressions:

1. Reverse a string:

  codepoints-to-string(
           reverse(string-to-codepoints($vText))
                       )

2. Verticalize a string:

  replace($vText, '(.)', '$1&#xA;')

where the string we want to operate upon is contained in the variable $vText.

To see this in action, we just put the above XPath expressions into an XSLT 2.0 stylesheet:

<xsl:stylesheet version="2.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
  <xsl:output method="text"/>

  <xsl:variable name="vText" as="xs:string"
       select="'Random Data'"/>

  <xsl:variable name="vReversed" as="xs:string"
       select="codepoints-to-string(
                       reverse(string-to-codepoints($vText))
                                    )
               "
  />

  <xsl:variable name="vVertical" as="xs:string*"
       select="replace($vText, '(.)', '$1&#xA;')"/>

  <xsl:template match="/">
    <xsl:sequence select="$vReversed"/>
===================================
<xsl:text/>
    <xsl:sequence select="$vVertical"/>
  </xsl:template>
</xsl:stylesheet>

When this transformation is performed (it doesn't matter on what (if any) XML document), the wanted result is produced: :)

ataD modnaR
===================================
R
a
n
d
o
m

D
a
t
a

Do note the use of the following standard XPath 2.0 functions:

Dimitre Novatchev
Seems you are always taking the easy route with your new-fashioned XPath 2.0 stuff. :-D +1
Tomalak
@Tomalak You didn't leave anything else for me to do... :) Thanks, and just +1 from mee for your XSLT 1.0 solution. Although I have done a lot of string processing with XSLT 1.0 and do not rule it out.
Dimitre Novatchev
I meant to emphasize "avoid char-by-char recursion". If it is "The Right Thing" (or simply necessary) to do it in XSLT 1.0, then there is nothing wrong with it. But here we have a case of "probably The Wrong Thing/unnecessary". I just wanted to make clear what the pitfalls are with my solution.
Tomalak
@Tomalak There is nothing wrong with recursion per-se. What do you have against recursion?
Dimitre Novatchev
@Tomalak See for example how to perform text justification (line-breaking) -- something done 8 years ago:http://sources.redhat.com/ml/xsl-list/2001-12/msg00651.html
Dimitre Novatchev
No, there is nothing wrong with recursion per-se. Recursion is perfectly fine with me. But when I have the choice to reverse a string through recursion, or through a more suited reverse() function, I would take the latter any time, for performance and memory reasons.
Tomalak
@Tomalak You are right, but it is good to know that stack overflow (not SO) and the related to it very slow performance can be effectively avoided using several techniques. Ask this as a question and I'll be glad to provide the details.
Dimitre Novatchev