0

I would like to transform Processing instructions with open/close tags like so:

    <para><?Pub _font Weight="bold"?>Date Re-inspected<?Pub /_font?></para>

to

 <div class="x-para-9-5"><span style="font-weight: bold">Date Re-inspected</span></div>

I tried to implement Processing instructions transform but the second copy of the first PI's immediate-sibling text node is not being deleted (and as a novice, I don't understand why this code would delete it):

My undesired result:

<div class="x-para-9-5"><span style="font-weight:bold;">Date Re-inspected</span>Date Re-inspected</div>

This is my code, slightly modified from the other question referenced above:

<xsl:template match="processing-instruction('Pub')">
<xsl:choose>
    <xsl:when test="starts-with(., '_font')">
      <xsl:choose>
          <xsl:when test="contains(.,'bold')">
              <span style="font-weight:bold;">
              <xsl:apply-templates select="following-sibling::node()[1][self::text()]"/>
              </span>
         </xsl:when>
    </xsl:choose>
   </xsl:when>
   <xsl:when test="starts-with(., '/_font')
      | text()[preceding-sibling::node()[1][self::processing-instruction('_font')]]">
   </xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>

Any advice appreciated, this is my first week with XSL.

Community
  • 1
  • 1
Caroline
  • 167
  • 1
  • 11

2 Answers2

0

This is not something you should be undertaking in your first week with XSLT. In a nutshell, the main problem here is that the two processing instructions are individual nodes - and so is the text between them.

To achieve your expected result, you must:
(1) turn the "opening" PI into a span;
(2) place the "contained" text node inside the span element;
(3) suppress the same text node from being copied by the default template; and
(4) suppress the "closing" PI.

Note that the terms "opening PI", "contained text" and "closing PI" are a fiction; XSLT sees them as three sibling nodes.

Try the following stylesheet:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>

<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

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

<xsl:template match="processing-instruction('Pub')[starts-with(., '_font Weight')]">
    <span style="font-weight: {substring-before(substring-after(., '&quot;'), '&quot;')}">
        <xsl:copy-of select="following-sibling::text()[1]"/>
    </span>
</xsl:template>

<!-- suppress text between PIs -->
<xsl:template match="text()[count(preceding-sibling::processing-instruction()) = count(following-sibling::processing-instruction())]"/>

<!-- suppress "closing" PIs -->
<xsl:template match="processing-instruction('Pub')[starts-with(., '/')]"/>

</xsl:stylesheet>

When the above is applied to the following test input:

<para>Opening plain text <?Pub _font Weight="bold"?>bold part<?Pub /_font?> closing plain text.</para>

the result will be:

<?xml version="1.0" encoding="UTF-8"?>
<div>Opening plain text <span style="font-weight: bold">bold part</span> closing plain text.</div>
michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thank you for your time in answering my question. I could not get it to work correctly because there are conflicting templates already defined in the style sheet. I will continue to work on it. Does `"@*|node()"` mean match attribute or node? – Caroline Jul 11 '14 at 17:33
  • "*Does "@*|node()" mean match attribute or node?*" Yes. -- "*there are conflicting templates already defined in the style sheet.*" Use either priorities or modes (or both) to fine tune your matches. – michael.hor257k Jul 11 '14 at 18:37
0

The question is a bit unclear. If the PIs always only mark elements (meaning you do not have mixed content and they only surround all the text in the element), then you could do the following:

Not sure where you are getting the "div" from and perhaps you should show more of the XML. However, an easier answer would be to only trigger output on the PI "start" and throw an attribute using xsl:attribute.

Given this input:

 <para><?Pub _font Weight="bold"?>Date Re-inspected<?Pub /_font?></para>

And this XSL:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        version="1.0">
    <xsl:template match="para">
        <span>
            <xsl:apply-templates/>
        </span>
    </xsl:template>
    <xsl:template match="processing-instruction('Pub')">
        <xsl:choose>
        <xsl:when test="starts-with(., '_font')">
            <xsl:choose>
                <xsl:when test="contains(.,'bold')">
                    <xsl:attribute name="style">
                        <xsl:text>font-weight:bold;</xsl:text>
                    </xsl:attribute>
                </xsl:when>
            </xsl:choose>
        </xsl:when>
        </xsl:choose>
    </xsl:template>
    </xsl:stylesheet>

You get this:

 <span style="font-weight:bold;">Date Re-inspected</span>

And you could extend this to handle multiple PIs in the element as well as making decisions on the content of those (like "bold" or "italic").

The other answer given is a better solution for handling inline PIs that are not an immediate child of the parent element (i.e. they can be anywhere inline). Which is why you should show more of the input XML required.

Kevin Brown
  • 8,805
  • 2
  • 20
  • 38
  • Thank you for your time in answering my question. I was able to get the bolding applied, but to the parent div, so this was helpful. There were already conflicting para templates defined in the stylesheet so I could not implement your code exactly. I will try testing para templates with different priorities, not sure if that is good form or not. – Caroline Jul 11 '14 at 17:37