Domanda

The following XSLT fragment is used to wrap/tag the first and last text nodes with a given @audience attribute for highlighting.

<xsl:template match="text()">
    <xsl:if test=". = ((((ancestor::*[contains(@audience,'FLAG_')])[last()])/descendant::text())[1])">                  
        <xsl:call-template name="flagText"/>            
    </xsl:if>
    <xsl-value-of select="."/>
    <xsl:if test=". = ((((ancestor::*[contains(@audience,'FLAG_')])[last()])/descendant::text())[last()])">                 
        <xsl:call-template name="flagText"/>            
    </xsl:if>
</xsl:template>

Pseudo-code:
Find the last (nearest) ancestor element that matches the flag criteria and then find the first and last text nodes that are descendants of that element and flag them.

The logic is correct but the implementation is wrong. This does indeed find the first and last text nodes but it is matching the values rather than the node. This is flagging any text node that has the same value as the first or last nodes.

Example:
The quick brown fox jumped over the lazy dog.

Current output:
[FLAG]The quick brown fox jumped over [FLAG]the lazy dog[FLAG].

The [1] and dog [last()] are properly flagged but it's also catching the word 'the' in the middle due to the string matching or being equal to the first.

Edit:
Expected (desired) output:
[FLAG]The quick brown fox jumped over the lazy dog.[FLAG]

How can I reorganize my statement to only match the first and last nodes? I don't want to compare the strings I just want to select the first and last.

Edit:

Example Source XML

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<concept audience="Users" id="concept_lsy_5vg_kl"><title>Product ABC</title><conbody><p>This is a blurb about <ph>Product ABC</ph>. Since the text in the phrase (ph) matches the text node in the title (first text node) it will be flagged. I only want the first and last nodes flagged. Basically, I don't want to compare the contents of the nodes. <ph audience="Users">I also need to support inline cases such as this one. </ph>I just want the flags before and after the first and last text nodes for each audience match.</p></conbody></concept>

Example XSLT

<?xml version='1.0' encoding='UTF-8'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" >

    <xsl:output method="text" omit-xml-declaration="yes" indent="yes"/>

    <xsl:template match="text()">
        <xsl:if test=". = ((((ancestor::*[contains(@audience,'Users')])[last()])/descendant::text())[1])">          
            <xsl:text>[USERS]</xsl:text>            
        </xsl:if>
        <xsl:value-of select="."/>
        <xsl:if test=". = ((((ancestor::*[contains(@audience,'Users')])[last()])/descendant::text())[last()])">         
            <xsl:text>[/USERS]</xsl:text>           
        </xsl:if>
    </xsl:template> 



</xsl:stylesheet>

Current Output

[USERS]Product ABCThis is a blurb about [USERS]Product ABC. Since the text in the phrase (ph) matches the text node in the title (first text node) it will be flagged. I only want the first and last nodes flagged. Basically, I don't want to compare the contents of the nodes. [USERS]I also need to support inline cases such as this one. [/USERS]I just want the flags before and after the first and last text nodes for each audience match.[/USERS]

Desired Output [USERS]Product ABCThis is a blurb about Product ABC. Since the text in the phrase (ph) matches the text node in the title (first text node) it will be flagged. I only want the first and last nodes flagged. Basically, I don't want to compare the contents of the nodes. [USERS]I also need to support inline cases such as this one. [/USERS]I just want the flags before and after the first and last text nodes for each audience match.[/USERS]

Thanks.

È stato utile?

Soluzione

In your expression, you are getting the 'last' ancestor with the class name of 'Users' for the text node you are on

...(ancestor::*[contains(@audience,'FLAG_')])[last()])...

But you really want to get the first one here. That is to say, the most immediate ancestor. The list of ancestors will start with the parent node, then the grand-parent, etc...

...(ancestor::*[contains(@audience,'FLAG_')])[1])...

Additionally, you say you don't want to compare the strings, just find out if the text node is first or last, but your current comparison is checking strings. So, if you have two text nodes with the same text (one of which is the first), then you will be get a rogue '[USER]' tag. To test if two nodes are the same node (as opposed to just having the same content), you can use the generate-id function.

generate-id() = generate-id(((ancestor::*[contains(@audience,'Users')][1])/descendant::text())[1])

Try this XSLT

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" >
  <xsl:output method="text" omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="text()">
    <xsl:if test="generate-id() = generate-id(((ancestor::*[contains(@audience,'Users')][1])/descendant::text())[1])">
      <xsl:text>[USERS]</xsl:text>
    </xsl:if>
    <xsl:value-of select="."/>
    <xsl:if test="generate-id() = generate-id(((ancestor::*[contains(@audience,'Users')][1])/descendant::text())[last()])">
      <xsl:text>[/USERS]</xsl:text>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

BUT.... Maybe you can take another approach. Looking purely at your XML and expected output, another approach that could be take is to have a template that matched any element with a 'users' class, and simply write out the start and end [USER] tag in that, with an xsl:apply-templates in between to select all text.

Try this XSLT as an alternative:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0" >
  <xsl:output method="text" omit-xml-declaration="yes" indent="yes"/>

  <xsl:template match="*[contains(@audience,'Users')]">
    <xsl:text>[USERS]</xsl:text>
      <xsl:apply-templates />
    <xsl:text>[/USERS]</xsl:text>
  </xsl:template>
</xsl:stylesheet>

Altri suggerimenti

Excuse the basic question but I don't understand why you just don't use a ditaval file to highlight the text when @audience is set to users? You can just basically color the text so that your reviewers can locate the text and verify. After the review, you can just shut off the highlighting.

Maybe I'm misunderstanding your intent.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top