Example input:
<body>
<ul>
<li>
<p>Some text with <b><i><span style="color:red">formatting</span></i></b></p>
</li>
</ul>
</body>
The 'inline' elements are b, i, or span
Suppose the context node is the nested span element in the example.
How do I get all ancestor inline elements, without the search going past the containing p element?
The expression ancestor-or-self::*[self::b | self::i | self::span] should actually work, as there can't be ancestor inline elements containing 'block' level elements such as p, ul, etc.
But, I wonder if there is some performance cost that could be avoided, as I think the search will continue past the containing p element. Is there a way to avoid that?
Edit
This is what I've come up with so far:
<xsl:function name="my:getInlineSeq" as="element() *">
<xsl:param name="pElem" as="element()" />
<xsl:if test="$pElem[self::b | self::i | self::span]">
<xsl:sequence select="my:getInlineSeq($pElem/parent::*)" />
<xsl:sequence select="$pElem" />
</xsl:if>
</xsl:function>
But, I'm unsure if there is an better or 'standard' way of solving this problem.
Note: the $pElem/parent::* in the code will always return an element as inline items must have a block element container such as p, h1, etc. Also, the assumption is the $pElem parameter provided initially is always an inline element.
Using
<xsl:function name="mf:block" as="element()">
<xsl:param name="inline" as="element()"/>
<xsl:sequence select="$inline/ancestor::*[self::h1 | self::h2 | self::h3 | self::h4 | self::h5 | self::h6 | self::li | self::p][1]"/>
</xsl:function>
and your assumption about block and inline elements you could rewrite ancestor-or-self::*[self::b | self::i | self::span] in the context of match="span | b | i" to
for $block in mf:block(.) return ancestor-or-self::*[. >> $block]
I think (or assuming you use <xsl:variable name="block" select="mf:block(.)"/> to ancestor-or-self::*[. >> $block]. I have no idea whether that performs any better than your attempt.
Tried to test the expression at http://xsltransform.net/3MvmrzK where
<xsl:template match="span | b | i">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="inline-ancestor" select="ancestor-or-self::*[self::b | self::i | self::span]/name()"/>
<xsl:attribute name="test-block" select="for $block in mf:block(.) return ancestor-or-self::*[. >> $block]/name()"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
gives the same result for the inline elements with
<p>Some text with <b inline-ancestor="b" test-block="b"><i inline-ancestor="b i" test-block="b i"><span style="color:red" inline-ancestor="b i span" test-block="b i span">formatting</span></i></b></p>
Related
We need to include parentheses as part of the word they surround while comparing.
For example:
Input A:
Hi (A)
Input B:
Hi (B)
Current Output:
Hi (<Inserted>B<\Inserted> <Deleted>A<\Deleted>)
Expected Output:
Hi <Inserted>(B)<\Inserted> <Deleted>(A)<\Deleted>
Thanks!
In this answer, I'm assuming you're using DeltaXML's DocumentComparator class.
The default behaviour for this class is to allow word-by-word comparison. It does this by splitting text up into space, punctuation or word elements.
So, for example, with Input A you would have:
...
<deltaxml:word>Hi</deltaxml:word>
<deltaxml:space>_</deltaxml:space>
<deltaxml:punctuation>(</deltaxml:punctuation>
<deltaxml:word>A</deltaxml:word>
<deltaxml:punctuation>)</deltaxml:punctuation>
...
As the 'A' input to the comparator.
To prevent text being split in this way you need an XSLT input filter that wraps specific text patterns in a deltaxml:word element with a deltaxml:word-by-word='false' attribute. Here is some sample XSLT that achieves this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:deltaxml="http://www.deltaxml.com/ns/well-formed-delta-v1"
exclude-result-prefixes="#all"
version="2.0">
<xsl:template match="*|comment()|processing-instruction() | #*">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<!--
wrap a specific text pattern in text as a word-element
in this case, any non-whitespace characters surrounded by '(' and ')' chars
-->
<xsl:template match="text()">
<xsl:analyze-string select="." regex="(\([^\s]*?\))">
<xsl:matching-substring>
<deltaxml:word deltaxml:word-by-word="false">
<xsl:value-of select="current()"/>
</deltaxml:word>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="current()"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:template>
</xsl:stylesheet>
If I had a bunch of fields on a screen, say ten for first name and ten for last name, and they're named firstName1, firstName2, etc., and lastName1, lastName2, etc., how would I create a loop that goes through each last name field?
Right now, I have it set up to perform a task ten times, one for each last name field. How can I set up a for-each loop that goes through lastName1, lastName2, lastName3...lastName10 and does a specific task for each of them?
Input XML:
<Arguments>
<EnteredBy>SDSADFSADF</EnteredBy>
<IDNumber>WERWEW</IDNumber>
<Book1>Y</Book1>
<LastName1>ASDFASFASDFASFA</LastName1>
</Arguments>
XSLT:
<xsl:apply-templates select="/myQuery/Arguments/lastName1"/>
and there are lastName2 through lastName10, as well.
I want to loop through each of the ten and truncate the last names to five characters.
The simplest and most idiomatic form of iteration in XSLT is to use xsl:apply-templates on the set of elements you want to handle. If you don't see how to use that idiom to solve your problem, it's worth spending time working to learn it.
[Addendum:]
For example:
<xsl:template match="/myQuery/Arguments">
...
<xsl:element name="Arguments">
<xsl:apply-templates/>
</
...
</
<xsl:template match="lastName1 | lastName2 | lastName3
| ... | lastName9">
<xsl:element name="{name()">
<xsl:value-of select="substring(.,1,5)"/>
</
</
This assumes you have elements names lastName1, lastName2, etc., instead of the simpler idiom where all last names are called (wait for it!) lastName, and that what you want in this particular part of the document is a near-identity transform.
XSLT does also have an xsl:for-each, which can be regarded as syntactic sugar for xsl:apply-templates and is sometimes preferred by programmers with a procedural bent. If you think of it as analogous to a loop in an imperative language, however, your instincts will eventually fail you and you will be surprised because your attempts at variable mutation don't work the way you expected them to.
Systematic work through a good book or tutorial will pay off.
Here's what I got to work:
<xsl:template match="/">
<xsl:element name="Arguments">
<xsl:apply-templates />
</xsl:element>
</xsl:template>
<xsl:template match="Arguments">
<xsl:for-each select="./*[contains(name(), 'LastName')]">
<xsl:call-template name="test">
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="test">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
This way you don't need to make 9 calls, just one per loop.
Or if you want to avoid the for-each, you can do this instead:
<xsl:template match="Arguments/*[contains(name(), 'LastName')]">
<xsl:element name="{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
I have source like
<h1> ... <h1>
(h1 may be h2 and h3 too.)
h1 MAY have an id attribute.
If it does NOT have an "id" attribute, I want to give it one.
How would the template look, to match an h1 WITHOUT and attribute "id", and how would I process the tag then?
Maybe like this?:
<xsl:template match="(h1 | h2 | h3)[not(#id)]" >
<!-- here I have to generate a tag h1, h2 or h3: how? -->
<xsl:attribute name="id">G10.2</xsl:attribute>
</h123>
</xsl:template>
How about:
<xsl:template match="h1[not(#id)] | h2[not(#id)] | h3[not(#id)]" >
<xsl:copy>
<xsl:attribute name="id">G10.2</xsl:attribute>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
I can get an xml with one of three nodes.
<root>
<headerA>
<!-- Content -->
</headerA>
</root>
in place of <headerA> there can be <headerB> or <headerŠ”> node with similar content. In this case I want to have html output as:
<span> Type [X] </span>
Where [X] is A, B or C depending on the tag name of the element.
As Tomalak has already observed, your question is rather vague. It sounds as if you might be asking how to write either a single template for the three kinds of header:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
or else how to write similar but distinct templates for them:
<xsl:template match="headerA">
<span> Type A </span>
</xsl:template>
<xsl:template match="headerB">
<span> Type B </span>
</xsl:template>
<!--* Template for headerC left as an exercise for the reader *-->
or else how to write a single template that does slightly different things based on what it matches:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:choose>
<xsl:when test="self::headerA">
<xsl:text> Type A </xsl:type>
</xsl:when>
<xsl:when test="self::headerB">
<xsl:text> Type B </xsl:type>
</xsl:when>
<!--* etc. *-->
<xsl:otherwise>
<xsl:message terminate="yes"
>I thought this could not happen.</xsl:message>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</span>
</xsl:template>
If you figure out which of these helps you, you will be closer to understanding what question you are trying to ask.
<xsl:template match="*[starts-with(name(), 'header')]">
<xsl:variable name="type" select="substring-after(name(), 'header')" />
<span>
<xsl:value-of select="concat(' Type ', $type, ' ')" />
</span>
</xsl:template>
This would work for any element named headerX, where can be X any string.
In a feedburner RSS I use
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*" />
</xsl:copy>
</xsl:template>
to get all the content of a feed with structure like below :
<feed>
<entry><published></published><title></title><content></content>....</entry>
</feed>
My question is, if it is possible to get only the 10 first <entry></entry> instead of all the 25 ? How to do it ?
Note : The entry tag has this form <entry gd:etag="W/"AkcHRH8yfSp7ImA9WhdUFkg.""> I do not know if this matters
smth like
/feed/entry[position()<10]
I mean, you need add this template:
<xsl:template match="entry[position() > 10]"/>
That drop all the entries after 10's, or something equal. The main suggestion is to take a look at position() function.
Your template was recursive. You then changed the behaviour of the outer template, breaking the recursion.
The simplest way to do what you want would be:
<xsl:template match="/feed/entry[position() < 10]">
<xsl:copy-of select="." />
</xsl:template>