tags:

views:

60

answers:

3

For a custom application I need to transform xhtml to (custom) xml. After some experimentation I decided to give php5's XSLT functionality a try, but so far I'm unable to transform nested p tags to their xml equivalent.

Basicly we have code like this:

<p>Some text</p>
<ol>
   <li><p>Some more text</p></li>
   ..
</ol>

This needs to be transformed to:

<par>Some text</par>
<list>
  <li><par>Some more text</par></li>
  ..
</list>

The real problem is: I need to include inline tags, so xsl:value-of is no option and instead I use xsl:copy-of. So far I have templates for ol|ul and p and the result is this:

<par>Some text</par>
<list>
  <li><p>Some more text</p></li>
  ..
</list>

Anybody some tips how to achieve what I really want just by using a more complex xslt?

A: 

You can use nested <xsl:element> tags to output inline tags as you propose.

Something like:

<xsl:element name="li">
  <xsl:element name="p">
   some text
  </xsl:element>
</xsl:element>
Oded
Could you perhaps give an example? Do I need to put this inside of a template and is the result static or dynamic (as in can I 'recycle' arbitrary content and inline tags or does it simply output a fixed set of elements and content)?
Jeroen Beerstra
+1  A: 

Here you go...

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
    <xsl:output method="xml" indent="yes"/>
    <!-- ============================================== -->
    <xsl:template match="/">
        <xsl:apply-templates/>
    </xsl:template>
    <!-- ============================================== -->
    <xsl:template match="p">
        <xsl:element name="par">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <!-- ============================================== -->
    <xsl:template match="ol">
        <xsl:element name="list">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <!-- ============================================== -->
    <xsl:template match="li">
        <xsl:element name="li">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
    <!-- ============================================== -->
</xsl:stylesheet>
dacracot
Thank you very much! This does get me a bit further, however is seems inline elements are lost this way?
Jeroen Beerstra
You can't have it both ways. If you ask for a copy, no further processing occurs. What you should do is create a default template that copies the name of the element, and continues processing, and override that for the elements you're changing.
Kyle Butt
What do you mean by "inline elements"?
dacracot
Do you mean that if the "p" tag is embedded in a "li" tag, it is NOT to be converted to "par"?
dacracot
I mean stuff like this: <p><strong>Bold text</strong> some more text</p>However simply adding more templates for all child and inline elements , seems a good solution :)
Jeroen Beerstra
If you want to preserve those other elements and don't want to create a template for every single combination, then add this identity transform template to the XSLT that @dacracot proposed: ` <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template>`
Mads Hansen
+1  A: 

If you start with the identity transform, the general pattern for transforming elements with one name into elements of another is this:

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

Two things of note:

  1. Generally, unless you know for certain that the element you're transforming has no attributes (or you actively intend to strip off the attributes), you should use the select attribute shown; don't just use <xsl:apply-templates/>. Attributes aren't child nodes, and thus applying templates without a select attribute doesn't apply templates to them.

  2. Unless you really enjoy typing, there is almost never a reason to use <xsl:element>. The exception is when you're generating the output element's name programmatically.

Which, actually, you could do if you wanted to get all fancy-shmancy:

<xsl:template match="*">
   <xsl:variable name="new_name">
      <xsl:when test="name()='p'>par</xsl:when>
      <xsl:when test="name()='ol'>list</xsl:when>
      <xsl:when test="name()='li'>item</xsl:when>
      <xsl:otherwise><xsl:value-of select="name()"/></xsl:otherwise>
   </xsl:variable>
   <xsl:element name="{$new_name}">
      <xsl:apply-templates select="@*|node()"/>
   </xsl:element>
</xsl:template>
Robert Rossney
+1 (and +1 more, if I could) - Note that `test="name()='p'` will not work *unless* the XSL document is in the same default namespace as the XHTML input. One could use `local-name()` instead, but declaring the XHMTL namespace with a prefix and changing to `test="name()='xhtml:p'` is the cleaner solution.
Tomalak