xsltdita

Copy text and replace character in XSL


I'm transforming a DITA document to a simplified, formatting-based XML to be used as an import into Adobe InDesign. My transformation is going really well, except for one element which omits the text in the output. The element is codeblock. When I don't have a template specifying it at all, the element and any child elements are passed through to the new XML document, but none of the text is passed through. This element should be passed through with text and child elements like every other element in my document for which a specific template is not defined. There's nothing anywhere else in the XSL stylesheet that specifies codeblock or any of its attributes. I am completely stumped and cannot figure out what's going on here.

It is also worth noting that a number of inline elements (cmdname, parmname, userinput, etc.) are converted to bold on output. The downstream XML is for formatting and does not need to know semantic context.

This is what I'm trying to pass through:

<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that <parmname>child elements</parmname> are passed through.</codeblock>

With no template defined for codeblock, this is what I get as a result:

<codeblock><bold/></codeblock>

The actual result I want is:

<codeblock>This is the first line of my code block.&#8232;This is my second line to prove that line feeds are preserved.&#8232;This line proves that <bold>child elements</bold> are passed through.</codeblock>

I need the line feeds replaced with character entities because InDesign sees any new line that does not start with an element as a column break. My goal was to simply replace the line feed character with &#8232; with the following template:

<xsl:template match="codeblock//text()">
  <xsl:analyze-string select="." regex="(&#10;)">
    <xsl:matching-substring>
      <xsl:choose>
        <xsl:when test="regex-group(1)">&#8232;</xsl:when>                
      </xsl:choose>
    </xsl:matching-substring>
  </xsl:analyze-string>
</xsl:template>

But what I get is:

<codeblock>&#8232;<bold/>&#8232;</codeblock>

I was finally able to pass the text through using this template:

<xsl:template match="codeblock//text()">
  <xsl:copy/>
</xsl:template>

Success! Incidentally, I have to match at any level under codeblock so it includes the text of the child parmname element too. Since I was able to successfully pass it through with <xsl:copy>, I tried this to pass the text through while replacing the line feed at the same time:

<xsl:template match="codeblock//text()">
  <xsl:copy>
    <xsl:analyze-string select="." regex="(&#10;)">
      <xsl:matching-substring>
        <xsl:choose>
          <xsl:when test="regex-group(1)">&#8232;</xsl:when>                
        </xsl:choose>
      </xsl:matching-substring>
    </xsl:analyze-string>
  </xsl:copy>
</xsl:template>

But now it won't replace the new line feed. Instead, I get this (which is what I would expect to get without any template defined):

<codeblock>This is the first line of my code block.
This is my second line to prove that line feeds are preserved.
This line proves that <bold>child elements</bold> are passed through.</codeblock>

I know this is a long and somewhat convoluted question. I just feel like if I could resolve the issue of why it's not passing the text through in the first place, the rest would be fairly straightforward. And I'm sorry, I can't provide my source XML or XSL as it's under NDA, but if you need more, let me know and I'll try to provide it. (My XSL stylesheets are made up of 12 different files, so there's no way for me to provide all of it, even if genericized.)

Any suggestions for what I might look for in my stylesheet that might explain why the text is coming through or any suggestions for how to force it through as I did with <xsl:copy> while still replacing the line feeds will be greatly appreciated!

Edited to add: It has occurred to me that the reason it's not doing the replacement is that it looks like it's not actually a line feed character. It's more like a new line in the code than a line feed character (or hard return) in the text. I think I might need to normalize the text while inserting the &#8232; character at the end of each line. Still investigating, but suggestions are welcome!

Edited with update: Thanks to the post How to detect line breaks in XSLT, I have gotten closer, but still not quite where I need to be. With this code, I'm able to detect line feeds in the XML and insert the line break character for InDesign:

<xsl:template match="codeblock//text()">
  <xsl:for-each select="tokenize(., '\n?')[.]">
    <xsl:sequence select="."/>
    <xsl:text>&#8232;</xsl:text>
  </xsl:for-each>
</xsl:template>

However, it also inserts the line feed character at the end of the string, even if it's not the end of the line. For instance, I now get:

<codeblock>This is the first line of my code block.&#8232;This is my second line to prove that line feeds are preserved.&#8232;This line proves that &#8232;<bold>child elements&#8232;</bold> are passed through.&#8232;</codeblock>

I don't want the line feed character in front of the 'bold' start and end tags or the codeblock end tag. I just want it to appear where there's an actual new line. I tried replacing \r but that just ignored the new lines and just put it in front of the tags. Does anyone know of another escape character that would work here?


Solution

  • A very long question - yet it's still not clear what exactly you are asking (and no reproducible example, either).

    If - as it seems - you want to replace newline characters with the line separator character in all text nodes under the codeblock element, you should be able to do simply:

    <xsl:template match="codeblock//text()">
        <xsl:value-of select="translate(., '&#10;', '&#8232;')" />
    </xsl:template>
    

    If this doesn't work, then either you have an overriding template or the text does not contain newline characters. You can test for the first case by changing the template to say:

    <xsl:template match="codeblock//text()">BINGO</xsl:template>
    

    and observe the result to see if all targeted text nodes are changed to "BINGO". To test for the second case, you can analyze the text character-by-character using the string-to-codepoints() function.