Is it possible to match attributes that do not belong to a subset of attributes? For example, I would like to match everything but #attr1 and #attr2. Is there a way to write a template match statement similar to the following, or am I going about this the wrong way?
<xsl:template match="NOT(#attr1) and NOT(#attr2)">
Thanks
The easiest way would be to use two templates:
<xsl:template match="#attr1|#attr2"/>
<xsl:template match="#*">
....
</xsl:template>
The first template will catch the references to those you want to ignore, and simply eat them. The second will match the remaining attributes.
The original inquiry is possible. Use the following:
<xsl:template match="#*[local-name()!='attr1' and local-name()!='attr2']">
....
</xsl:template>
This is especially useful if you want to change an attribute or add it if missing withing a single copy operation. The other answer does not work in such situation. e.g.
...
<xsl:copy>
<xsl:attribute name="attr1">
<xsl:value-of select="'foo'"/>
</xsl:attribute>
<xsl:apply-templates select="#*[local-name()!='attr1']|node()"/>
</xsl:copy>
...
Related
I have looked but haven't been able to find an answer to this specific problem, but if I'm wrong I apologize.
Basically I have things like this all over my XML:
<some-text>
Here is something interesting<em>/</em>cool to look at
</some-text>
I want to get the text but remove the "em" tags without replacing them with a space so that I end up with:
Here is something interesting/cool to look at
and not:
Here is something interesting / cool to look at
Note: I am using xslt 2.0 and this isn't just in one tag, it's document wide, so I need a solution to remove them from all over.
Thanks
I tested the following template with Saxon:
<xsl:template match="em">
<xsl:value-of select="string(.)"/>
</xsl:template>
The result is without (unwanted) spaces.
This works in combination with the identity template
<!-- Identity template -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
I have a piece of an XSLT stylesheet that works as expected using xsltproc but produces a different output in my actual application, where the transform is applied via org.jdom.transform.XSLTransformer (jdom 1.0), I believe using Xalan.
Stylesheet snippet (this is part of a larger template that starts like this: <xsl:template match="/dspace:dim[#dspaceType='ITEM']">):
<xsl:if test="//dspace:field[#mdschema='dc' and #element='rights']">
<rightsList>
<xsl:if test="//dspace:field[#mdschema='dc' and #element='rights' and not(#qualifier) and #language='*']">
<rights>
<xsl:if test="//dspace:field[#mdschema='dc' and #element='rights' and #qualifier='uri' and #language='*']">
<xsl:attribute name="rightsUri">
<xsl:value-of select="//dspace:field[#mdschema='dc' and #element='rights' and #qualifier='uri' and #language='*']"/>
</xsl:attribute>
</xsl:if>
<xsl:value-of select="//dspace:field[#mdschema='dc' and #element='rights' and not(#qualifier) and #language='*']" />
</rights>
</xsl:if>
<xsl:apply-templates select="//dspace:field[#mdschema='dc' and #element='rights' and not(#language='*')]" />
</rightsList>
</xsl:if>
and
<xsl:template match="//dspace:field[#mdschema='dc' and #element='rights' and not(#language='*')]">
<rights><xsl:value-of select="." /></rights>
</xsl:template>
XML snippet:
<dim:dim dspaceType="ITEM" xmlns:dim="http://www.dspace.org/xmlns/dspace/dim">
<dim:field element="rights" language="en_NZ" mdschema="dc">Actual text redacted</dim:field>
<dim:field element="rights" language="*" mdschema="dc">Attribution 3.0 New Zealand</dim:field>
<dim:field element="rights" qualifier="uri" language="*" mdschema="dc">http://creativecommons.org/licenses/by/3.0/nz/</dim:field>
</dim:dim>
With xsltproc, this produces
<rightsList>
<rights rightsUri="http://creativecommons.org/licenses/by/3.0/nz/">Attribution 3.0 New Zealand</rights>
<rights>Actual text redacted</rights>
</rightsList>
In my application, this produces
<rightsList>
<rights>Actual text redacted</rights>
<rights>Attribution 3.0 New Zealand</rights>
<rights>http://creativecommons.org/licenses/by/3.0/nz/</rights>
</rightsList>
So to me it looks like the not(#qualifier) bit doesn't work using jdom.
I'd appreciate any insight into what's going on here and how I might change the stylesheet to get the same result in my application that I currently get via xsltproc.
Edited to add: just in case it makes any difference, the stylesheet starts out as
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dspace="http://www.dspace.org/xmlns/dspace/dim"
xmlns:exslt="http://exslt.org/common"
xmlns="http://datacite.org/schema/kernel-3"
extension-element-prefixes="exslt"
exclude-result-prefixes="exslt"
version="1.0">
and also includes this template:
<!-- Don't copy everything by default! -->
<xsl:template match="#* | text()" />
See my answer below the XML structure is actually different from what I thought it was, so the problem wasn't in the XSL after all.
Apart from solving your original problem, let's have a quick look at how to reorganize your code.
You use a lot of //foo expressions. Starting an expression with //foo means "search the whole document, at any level, for the element with the name foo". Apart from this being a potentially expensive operation, this often has unwanted side effects and makes your code hard to read, because it requires you to specify each element uniquely, leading to a lot of duplicated code.
You also use a lot of xsl:if, but in XSLT, it is hardly ever necessary to use if-statements (an exception in XSLT 1.0 and 2.0 being when you deal with something other than nodes). In almost all cases, you can replace an xsl:if with a simple xsl:apply-templates.
That said, let's have a look how we can rewrite your code to get the same effect and have less chance for error:
<xsl:if test="//dspace:field[#mdschema='dc' and #element='rights']">
<rightsList>
.....
Is similar to having a matching template as follows (assuming you have a throw-away template for uninteresting nodes):
<xsl:template match="dspace:dim[dspace:field[#mdschema='dc' and #element='rights']]">
<rightsList>
This says: if you encounter a dim element with any field element that has those properties set, then output <rightsList>.
Then you have:
<xsl:if test="//dspace:field[#mdschema='dc' and #element='rights' and not(#qualifier) and #language='*']">
<rights>
Which is precisely equivalent to the following apply-template expression (assuming a matching template with it):
<xsl:apply-templates select="dspace:field[#mdschema='dc' and #element='rights' and not(#qualifier) and #language='*']" />
Here we find that a little bit below that, we have an almost equivalent expression, this time with not(#language='*'). So let's see if we can get rid of those duplicate expressions altogether.
First, let's go back a bit and have a look at what you were doing:
If anywhere any "dc" and "rights", then create a <rightsList>
If anywhere any of these have do not have a qualifier but have a language "*", create <rights>
Inside this, create an attribute rightsUri if anywhere any qualifier has value "uri" and language "*", set its value to the first such you find
After this <rights> element (there can be at most one of them in your current structure), create a list of <rights> for each field element with language "*"
If this is correct, then this can be rewritten as follows:
<xsl:template match="dspace:dim[dspace:field[#mdschema='dc' and #element='rights']]">
<xsl:variable name="adjusted">
<xsl:copy-of select="dspace:field[#mdschema='dc' and #element='rights']"/>
</xsl:variable>
<rightsList>
<xsl:apply-templates select="exsl:node-set($adjusted)/*[not(#qualifier) and #language='*'][1]" mode="noquali"/>
<xsl:apply-templates select="exsl:node-set($adjusted)/*[not(#language='*')]" />
</rightsList>
</xsl:template>
<xsl:template match="dspace:field" mode="noquali">
<rights>
<xsl:apply-templates select="/dspace:field[#qualifier='uri' and #language='*'][1]" mode="uri"/>
<xsl:value-of select="."/>
</rights>
</xsl:template>
<xsl:template match="dspace:field" mode="uri">
<xsl:attribute name="rightsUri" select="." />
</xsl:template>
<!-- matching anything else -->
<xsl:template match="dspace:field">
<rights><xsl:value-of select="." /></rights>
</xsl:template>
The exsl:node-set function is supported by just about every XSLT 1.0 processor, just add the namespace xmlns:exsl="http://exslt.org/common" to your xsl:stylesheet declaration.
Note that I added a few times [1] to the select-expressions. While you don't do that in your code, your current code has the same effect, but if you use apply-templates, if you encounter multiple matches, you have to specify that you are only interested in the first match.
I think your code can be further simplified, but I wanted to make sure that the logic remains exactly the same. As you can see, the end result is without any //. However, you do see one /, which is now pointing to the root of the node-set, which conveniently only has the nodes you are interested in: the ones with schema "dc" and "rights" element attributes, so we do not have to repeat that expression over and over again.
You may try this rewrite and see if it helps with your current bug, otherwise I'll gladly to help you further.
Edit
After your edit, your original context item will have been dspace:dim already. If you don't mind always outputting <rightsList> (even if it ends up empty), you can simply replace my first template match pattern above with your existing dspace:dim pattern.
Duh. Forest/trees indeed. Even though the language attribute is called "language" pretty much everywhere else in the application (see also, the XML snippet I gave), it is actually called "lang" in the XML that my stylesheet operates on - I finally gave in and used this answer to be sure what the XML structure is. Surprise!
Anyway, I followed the advice Abel gave in his answer in part and simplified the templates for this particular case quite a bit. I now just have
<xsl:if test="dspace:field[#mdschema='dc' and #element='rights']">
<rightsList>
<xsl:apply-templates select="dspace:field[#mdschema='dc' and #element='rights']"/>
</rightsList>
</xsl:if>
in the big template, plus a couple of custom ones:
<xsl:template match="dspace:field[#mdschema='dc' and #element='rights']">
<xsl:choose>
<xsl:when test="#qualifier='uri'"/>
<xsl:otherwise>
<rights>
<xsl:if test="#lang='*'">
<xsl:apply-templates select="//dspace:field[#mdschema='dc' and #element='rights' and #qualifier='uri' and #lang='*'][1]" mode="rightsURI"/>
</xsl:if>
<xsl:value-of select="."/>
</rights>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="dspace:field[#mdschema='dc' and #element='rights' and #qualifier='uri' and #lang='*']" mode="rightsURI">
<xsl:attribute name="rightsURI"><xsl:value-of select="."/></xsl:attribute>
</xsl:template>
I have the below template that is xsl:apply template
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'PO')]" />
as shown above it is working fine for 'PO' now i want to make it for CPTY too so I have developed it as shown..
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'CPTY')]" />
but the problem is that there can't be two seprate templates with the same name payerPartyReference can you please advise what is the best approach to deal with this ..
what approach i am thinking is ..
<xsl:if test="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'PO')]">
</xsl:if>
<xsl:if test="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'CPTY')]">
</xsl:if>
You're right that you can't have two templates with exactly the same matching pattern, but you can have
<xsl:template match="fpml:payerPartyReference[starts-with(#href, 'PO')]">
<!-- ... -->
</xsl:template>
<xsl:template match="fpml:payerPartyReference[starts-with(#href, 'CPTY')]">
<!-- ... -->
</xsl:template>
With these separate templates in place you may find you don't need to split out the apply-templates. Depending on the precise details of your problem you might find that you can just do one
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference" />
and let the template matcher handle the conditional behaviour by picking the appropriate matching template for each target node.
I have an input xml document that I am transforming which has this content in an node:
<misc-item>22 mm<br></br><fraction>7/8</fraction> in.</misc-item>
When I create a variable by selecting 'misc-item', the br and fraction tags disappear. However, if I create a variable using 'misc-item/br' and test to see if it is finding the br, the test seems to work.
What I want to do is make the
'<br></br>'
into a space or semicolon or something, but I have had no luck. I tried getting the siblings of 'misc-item/br', but it has none. I checked the child count of 'misc-item' and it is one.
Any help greatly appreciated.
I looked at the post identified as possible dupe. I tried this to no avail:
<xsl:template match="#*|node()" mode='PageOutput'>
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="PageOutput" />
</xsl:copy>
</xsl:template>
<xsl:template match="br" mode='PageOutput'>
<xsl:value-of select="' '" />
</xsl:template>
Since I am not ignoring an element as in the suggested dupe, but rather substituting, this doesn't seem to be quite right.
When I create a variable by selecting 'misc-item', the br and fraction tags disappear. However, if I create a variable using 'misc-item/br' and test to see if it is finding the br, the test seems to work.
When you create a variable you're storing a reference to the misc-item node in the variable. If you ask for the value-of that node you'll get just the text, with elements stripped out, but the variable still holds the node itself.
This is probably something you need to tackle using apply-templates instead of value-of. A common theme is to have an "identity template" which essentially copies everything as-is but can be overridden with different behaviour for certain nodes by providing more specific templates.
<xsl:template match="#*|node()">
<xsl:copy><xsl:apply-templates select="#*|node()" /></xsl:copy>
</xsl:template>
<!-- replace any br element with a semicolon -->
<xsl:template match="br">;</xsl:template>
You can use a mode to restrict these templates for use in specific situations only
<xsl:template match="#*|node()" mode="strip-br">
<xsl:copy><xsl:apply-templates select="#*|node()" mode="strip-br" /></xsl:copy>
</xsl:template>
<!-- replace any br element with a semicolon -->
<xsl:template match="br" mode="strip-br">;</xsl:template>
and now you can use e.g.
<xsl:apply-templates select="$miscitem/node()" mode="strip-br" />
instead of <xsl:value-of select="$miscitem"/> to get the result you're after.
want to make a comma-delimited string from a list of 3 possible attributes of an element.
I have found a thread here:
XSLT concat string, remove last comma
that describes how to build a comma-delimited string from elements. I want to do the same thing with a list of attributes.
From the following element:
<myElement attr1="Don't report this one" attr2="value1" attr3="value2" attr4="value3" />
I would like to produce a string that reads: "value1,value2,value3"
One other caveat: attr2 thru attr4 may or may not have values but, if they do have values, they will go in order. So, attr4 will not have a value if attr3 does not. attr3 will not have a value if attr2 does not. So, for an attribute to have a value, the one before it in the attribute list must have a value.
How can I modify the code in the solution to the thread linked to above so that it is attribute-centric instead of element-centric?
Thanks in advance for whatever help you can provide.
This is easy in principe, but only if it is really clear which attribute you want to exclude. Since attributes are not by definition ordered in XML (in contrast the elements), you need to say how the attribute(s) to skip can be identified.
Edit: Regarding the attribute order, XML section 3.1 says:
Note that the order of attribute specifications in a start-tag or empty-element tag is not significant.
That said, something like this should do the trick (adjust the [] condition as you see fit):
<xsl:template match="myElement">
<xsl:for-each select="#*[position()!=1]">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
In XSLT 2.0 it is as easy as
<xsl:template match="myElement">
<xsl:value-of select="#* except #att1" separator=","/>
</xsl:template>
I would appreciate Lucero's answer .. He definitely has nailed it ..
Well, Here is one more code which truncates attribute attr1, which appears at any positions other than 1 in attribute list.
scenario like this::
<myElement attr2="value1" attr3="value2" attr4="value3" attr1="Don't report this one" />
Here is the XSLT code .. ::
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The output will be:
value1,value2,value3
If you wish to omit more than one attribute say .. attr1 and attr2 ..
<xsl:template match="myElement">
<xsl:for-each select="#*[name()!='attr1' and name()!='attr2']">
<xsl:value-of select="." />
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:for-each>
</xsl:template>
The corresponding output will be:
value2,value3