I need to convert a some basic XHTML output from an WYSIWYG editor to a proprietairy typesetting XML format using XSLT.
The typesetting format may contain multiple txt elements, each which can have bold, italic and underline boolean attributes.
Now I want to be able to convert nested combinations of <I>, <B> and <U> tags and convert them to this XML format. For example:
<block><b><i><u>BoldItalicUnderlined</u>BoldItalic</i>Bold</b></block>
To:
<txt bold="true" italic="true" underlined="true">BoldItalicUnderlined</txt>
<txt bold="true" italic="true" underlined="false">BoldItalicUnderlined</txt>
<txt bold="true" italic="false" underlined="false">Bold</txt>
Can this be done with XSLT?
So far I have:
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xls="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|#*" mode="mPass2">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="mPass2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="block">
<xsl:variable name="vrtfPass1Result">
<xsl:apply-templates/>
</xsl:variable>
<xsl:apply-templates mode="mPass2" select="ext:node-set($vrtfPass1Result)/*"/>
</xsl:template>
<xsl:template match="text()">
<txt underline="false" italic="false" bold="false" the-text="true">
<xsl:value-of select="."/>
</txt>
</xsl:template>
<xsl:template match="em|i">
<txt underline="false" italic="true" bold="false" the-italic="true">
<xsl:apply-templates/>
</txt>
</xsl:template>
<xsl:template match="strong|b">
<txt underline="false" italic="false" bold="true" the-bold="true">
<xsl:apply-templates/>
</txt>
</xsl:template>
<xsl:template match="u">
<txt underline="true" italic="false" bold="false" the-underline="true">
<xsl:apply-templates/>
</txt>
</xsl:template>
<xsl:template match="txt[txt]" mode="mPass2" name="txt">
<xsl:variable name="bold">
<xsl:value-of select="#bold"/>
</xsl:variable>
<xsl:for-each select="txt">
<xsl:apply-templates select="." mode="mPass2"/>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Which produces:
<txt underline="false" italic="false" bold="false">BoldItalicUnderlined</txt>
<txt underline="false" italic="false" bold="false">BoldItalic</txt>
<txt underline="false" italic="false" bold="false">Bold</txt>
Try this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates select="//text()"/>
</xsl:template>
<xsl:template match="text()[normalize-space()='']"/>
<xsl:template match="text()">
<text bold="{boolean(ancestor::b)}" italic="{boolean(ancestor::i)}" underlined="{boolean(ancestor::u)}">
<xsl:value-of select="."/>
</text>
</xsl:template>
</xsl:stylesheet>
Related
Hi I have been trying to replace values in a certain tag with sequential numbers, I used position function, but it did not work.
Input xml:
<?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<Header>
<User id="swarnai" />
<Request-Id id="592149819" />
<Type name="Request" />
<Application-Source version="8.1.1.10" name="Siebel" />
<Application-Destination version="1.2.2" name="EVA" />
<Outgo-Timestamp time="11:40:59" date="2015-08-04" />
<DealerCode>82536</DealerCode>
<Market>00000</Market>
</Header>
<Content>
<ClaimContext>
<ClaimControl>
<ClaimEntryFlag>3</ClaimEntryFlag>
<ClaimSaveFlag>1</ClaimSaveFlag>
</ClaimControl>
<Claim>
<DealerClaimNumber>1091871</DealerClaimNumber>
<WHC>WDD</WHC>
<FIN>2120026L020301</FIN>
<RegistrationNumber>JH07E2786</RegistrationNumber>
<FirstRegistrationDate>2012-11-29</FirstRegistrationDate>
<Mileage>14317</Mileage>
<MileageIndicator>0</MileageIndicator>
<RepairDate>2013-12-03</RepairDate>
<RegularlyMaintained>true</RegularlyMaintained>
<NoFirstRegDateInd>false</NoFirstRegDateInd>
<ClaimCurrencyId>EUR</ClaimCurrencyId>
<Taxi>false</Taxi>
<DamagePosition>
<DamageSeqNumber>1</DamageSeqNumber>
<WarrantyType>0</WarrantyType>
<DamageCode>0121504</DamageCode>
<OperationPosition>
<SeqNumber>1</SeqNumber>
<Opcode>02770501</Opcode>
<WorkingUnits>18</WorkingUnits>
<MainOperationCode>true</MainOperationCode>
<OperationText>OPERATIONS: FITTING FOR COOLANT CONNECTION TO CYLINDER</OperationText>
<PriceGroup>01</PriceGroup>
</OperationPosition>
</DamagePosition>
<DamagePosition>
<DamageSeqNumber>1</DamageSeqNumber>
<WarrantyType>0</WarrantyType>
<DamageCode>0121504</DamageCode>
<PartPosition>
<SeqNumber>1</SeqNumber>
<Quantity>10</Quantity>
<PartNumber>A6512001056</PartNumber>
<DamageCausingPart>true</DamageCausingPart>
<RetailPriceAmount>1499</RetailPriceAmount>
<Express>false</Express>
</PartPosition>
</DamagePosition>
<DamagePosition>
<DamageSeqNumber>1</DamageSeqNumber>
<WarrantyType>0</WarrantyType>
<DamageCode>0121504</DamageCode>
<PartPosition>
<SeqNumber>1</SeqNumber>
<Quantity>20</Quantity>
<PartNumber>A0009890825 10</PartNumber>
<DamageCausingPart>false</DamageCausingPart>
<RetailPriceAmount>1319</RetailPriceAmount>
<Express>false</Express>
</PartPosition>
</DamagePosition>
</Claim>
</ClaimContext>
</Content>
</Envelope>
In this xml within PartPosition/SeqNumber tag I want to replace or generate sequence of numbers in SeqNumber tag.
I tried below xslt:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="utf-8" indent="no"/>
<xsl:key name="Damage_Group" match="/Envelope/Content/ClaimContext/Claim/DamagePosition" use="DamageCode" />
<xsl:template match="Claim">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
<xsl:for-each select="DamagePosition[count(. | key('Damage_Group', DamageCode)[1]) = 1]">
<xsl:sort select="DamageCode" />
<DamagePosition>
<DamageSeqNumber>
<xsl:value-of select="position()" />
</DamageSeqNumber>
<DamageCode>
<xsl:value-of select="DamageCode" />
</DamageCode>
<WarrantyType><xsl:value-of select="WarrantyType" /></WarrantyType>
<xsl:for-each select="key('Damage_Group', DamageCode)">
<xsl:copy-of select="current()/PartPosition"/>
<xsl:copy-of select="current()/OperationPosition"/>
<xsl:copy-of select="current()/SubletPosition"/>
</xsl:for-each>
</DamagePosition>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="DamagePosition">
</xsl:template>
</xsl:stylesheet>
but it is not giving me the desired output as below.
<?xml version="1.0" encoding="UTF-8"?>
<Envelope>
<Header>
<User id="swarnai" />
<Request-Id id="592149819" />
<Type name="Request" />
<Application-Source version="8.1.1.10" name="Siebel" />
<Application-Destination version="1.2.2" name="EVA" />
<Outgo-Timestamp time="12:15:47" date="2015-08-04" />
<DealerCode>82536</DealerCode>
<Market>00000</Market>
</Header>
<Content>
<ClaimContext>
<ClaimControl>
<ClaimEntryFlag>3</ClaimEntryFlag>
<ClaimSaveFlag>1</ClaimSaveFlag>
</ClaimControl>
<Claim>
<DealerClaimNumber>1091871</DealerClaimNumber>
<WHC>WDD</WHC>
<FIN>2120026L020301</FIN>
<RegistrationNumber>JH07E2786</RegistrationNumber>
<FirstRegistrationDate>2012-11-29</FirstRegistrationDate>
<Mileage>14317</Mileage>
<MileageIndicator>0</MileageIndicator>
<RepairDate>2013-12-03</RepairDate>
<RegularlyMaintained>true</RegularlyMaintained>
<NoFirstRegDateInd>false</NoFirstRegDateInd>
<ClaimCurrencyId>EUR</ClaimCurrencyId>
<Taxi>false</Taxi>
<DamagePosition>
<DamageSeqNumber>1</DamageSeqNumber>
<DamageCode>0121504</DamageCode>
<WarrantyType>0</WarrantyType>
<OperationPosition>
<SeqNumber>1</SeqNumber>
<Opcode>02770501</Opcode>
<WorkingUnits>18</WorkingUnits>
<MainOperationCode>true</MainOperationCode>
<OperationText>OPERATIONS: FITTING FOR COOLANT CONNECTION TO CYLINDER</OperationText>
<PriceGroup>01</PriceGroup>
</OperationPosition>
<PartPosition>
<SeqNumber>1</SeqNumber>
<Quantity>10</Quantity>
<PartNumber>A6512001056</PartNumber>
<DamageCausingPart>true</DamageCausingPart>
<RetailPriceAmount>1499</RetailPriceAmount>
<Express>false</Express>
</PartPosition>
<PartPosition>
<SeqNumber>2</SeqNumber>
<Quantity>20</Quantity>
<PartNumber>A0009890825 10</PartNumber>
<DamageCausingPart>false</DamageCausingPart>
<RetailPriceAmount>1319</RetailPriceAmount>
<Express>false</Express>
</PartPosition>
</DamagePosition>
</Claim>
</ClaimContext>
</Content>
</Envelope>
Any help is much appreciated.
Thanks
Firstly, instead of doing this, which copies the elements without further changes to it (or its descendants)
<xsl:copy-of select="current()/PartPosition"/>
<xsl:copy-of select="current()/OperationPosition"/>
<xsl:copy-of select="current()/SubletPosition"/>
You should you template matching instead, to allow you to write a template later on to change the sequence number
<xsl:apply-templates select="PartPosition"/>
<xsl:apply-templates select="OperationPosition"/>
<xsl:apply-templates select="SubletPosition"/>
Now, what you could do, is write a template that matches the various Sequence numbers and counts the preceding siblings for the same DamageCode. Something like this
<xsl:template match="PartPosition/SeqNumber">
<SeqNumber>
<xsl:variable name="DamageCode" select="../../DamageCode" />
<xsl:value-of select="count(../../preceding-sibling::*[PartPosition][DamageCode = $DamageCode]) + 1" />
</SeqNumber>
</xsl:template>
You could repeat the template for OperationPosition and SubletPosition too, although this would be quite repetitive. Alternatively, have a single generic template to match all three:
<xsl:template match="SeqNumber">
<SeqNumber>
<xsl:variable name="DamageCode" select="../../DamageCode" />
<xsl:variable name="Parent" select="name(..)" />
<xsl:value-of select="count(../../preceding-sibling::*[*[name() = $Parent]][DamageCode = $DamageCode]) + 1" />
</SeqNumber>
</xsl:template>
However, this is not necessarily that efficient, as it is not utilizing the key and so would have to check back through all preceding siblings to find a match.
Another solution would be to pass the position of the current DamagePosition in a parameter, and then use that in checking the key.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="xml" encoding="utf-8" indent="yes"/>
<xsl:key name="Damage_Group"
match="/Envelope/Content/ClaimContext/Claim/DamagePosition"
use="DamageCode"/>
<xsl:template match="Claim">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
<xsl:for-each select="DamagePosition[count(. | key('Damage_Group', DamageCode)[1]) = 1]">
<xsl:sort select="DamageCode"/>
<DamagePosition>
<DamageSeqNumber>
<xsl:value-of select="position()"/>
</DamageSeqNumber>
<DamageCode>
<xsl:value-of select="DamageCode"/>
</DamageCode>
<WarrantyType>
<xsl:value-of select="WarrantyType"/>
</WarrantyType>
<xsl:for-each select="key('Damage_Group', DamageCode)">
<xsl:apply-templates select="PartPosition">
<xsl:with-param name="Position" select="position()" />
</xsl:apply-templates>
<xsl:apply-templates select="OperationPosition">
<xsl:with-param name="Position" select="position()" />
</xsl:apply-templates>
<xsl:apply-templates select="SubletPosition">
<xsl:with-param name="Position" select="position()" />
</xsl:apply-templates>
</xsl:for-each>
</DamagePosition>
</xsl:for-each>
</xsl:copy>
</xsl:template>
<xsl:template match="DamagePosition/*">
<xsl:param name="Position" />
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="Position" select="$Position" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="SeqNumber">
<xsl:param name="Position" />
<SeqNumber>
<xsl:value-of select="count(key('Damage_Group', ../../DamageCode)[position() <= $Position][*[name() = name(current()/..)]])" />
</SeqNumber>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="DamagePosition" />
</xsl:stylesheet>
I am getting stuck at a point where I need to remove an element from the input XML:
<message
xmlns="http://www.origoservices.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<m_control>
<control_timestamp>2013-06-06T14:55:37</control_timestamp>
<initiator_id>ASL</initiator_id>
</m_control>
<m_content>
<b_control>
<quote_type>Single Company</quote_type>
<quote_or_print>Quote And Print</quote_or_print>
<generic_quote_ind>Yes</generic_quote_ind>
<tpsdata>
<tps_quote_type>Comparison</tps_quote_type>
</tpsdata>
</b_control>
<application>
<product>
<tpsdata>
<service_type>QuickQuote</service_type>
<quote_type>Standard</quote_type>
</tpsdata>
</product>
</application>
</m_content>
</message>
if <tps_quote_type> is 'Comparison' then change the value of <quote_type> to 'Comparison' and the <tpsdata> field should be removed. The output should look like below.
<message
xmlns="http://www.origoservices.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
>
<m_control>
<control_timestamp>2013-06-06T14:55:37</control_timestamp>
<initiator_id>ASL</initiator_id>
</m_control>
<m_content>
<b_control>
<quote_type>Comparison</quote_type>
<quote_or_print>Quote And Print</quote_or_print>
<generic_quote_ind>Yes</generic_quote_ind>
</b_control>
<application>
<product>
<tpsdata>
<service_type>QuickQuote</service_type>
<quote_type>Standard</quote_type>
</tpsdata>
</product>
</application>
</m_content>
</message>
So far I have tried this XSLT, but I don't know how to remove <tpsdata> field from the output. Could anyone help me in this?
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="dp"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="*">
<!-- identity with closing tags -->
<xsl:element name="{name()}">
<xsl:apply-templates select="#*|node()"/>
</xsl:element>
</xsl:template>
<xsl:variable name="quoteType">
<xsl:value-of select="/*[namespace-uri()='http://www.origoservices.com' and local- name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name() ='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='quote_type']"/>
</xsl:variable>
<xsl:variable name="tpsQuoteType">
<xsl:value-of select="/*[namespace-uri()='http://www.origoservices.com' and local-name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name()='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='tpsdata']/*[namespace-uri()='http://www.origoservices.com' and local-name()='tps_quote_type']"/>
</xsl:variable>
<xsl:template match="/*[namespace-uri()='http://www.origoservices.com' and local-name()='message']/*[namespace-uri()='http://www.origoservices.com' and local-name()='m_content']/*[namespace-uri()='http://www.origoservices.com' and local-name()='b_control']/*[namespace-uri()='http://www.origoservices.com' and local-name()='quote_type']">
<xsl:choose>
<xsl:when test="$tpsQuoteType = 'Comparison' ">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:text>Comparison</xsl:text>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*|comment()|processing-instruction()">
<xsl:copy>
<xsl:copy-of select="#*|namespace::*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Maybe you noticed that your handling of those elements with a namespace is a little painful. Just add the http://www.origoservices.com namespace to your XSLT and the pain goes away.
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:o="http://www.origoservices.com"
xmlns:dp="http://www.datapower.com/extensions"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="dp"
exclude-result-prefixes="fn date"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="o:b_control/o:quote_type[../o:tpsdata/o:tps_quote_type = 'Comparison']">
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:text>Comparison</xsl:text>
</xsl:copy>
</xsl:template>
<xsl:template match="o:tpsdata[o:tps_quote_type = 'Comparison']" />
</xsl:stylesheet>
Notes
Most of your "plumbing" is not necessary.
Template match expressions don't need to be a full path.
Use match expressions rather than <xsl:choose> to pinpoint elements you want to change.
Start with a basic identity template, overriding it as needed with more specific templates. This makes your live much easier than starting with a modified identity template.
Use empty templates to remove specific elements.
<xsl:stylesheet version="1.0" extension-element-prefixes="dp" exclude-result-prefixes="dp regexp fn dpconfig" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dp="http://www.datapower.com/extensions" xmlns:dpconfig="http://www.datapower.com/param/config" xmlns:dpfunc="http://www.datapower.com/extensions/functions" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:regexp="http://exslt.org/regular-expressions" >
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[local-name()='tpsdata']/*[local-name()='quote_type']">
<xsl:message dp:priority="debug"> Found quote_type </xsl:message>
<xsl:variable name = "First">
<xsl:value-of select="/*[local-name()='message']/*[local-name()='m_content']/*[local-name()='b_control']/*[local-name()='tpsdata']/*[local-name()='tps_quote_type']/text()"/>
</xsl:variable>
<xsl:variable name = "Second">
<xsl:value-of select = "."/>
</xsl:variable>
<xsl:message dp:priority="debug"> Second:<xsl:value-of select = "$Second"/></xsl:message>
<xsl:message dp:priority="debug"> First: <xsl:value-of select = "$First"/> </xsl:message>
<xsl:choose>
<xsl:when test="$Second = $First">
<xsl:message dp:priority="debug"> Stand and Comp are same </xsl:message>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:when>
<xsl:otherwise>
<xsl:message dp:priority="debug"> Stand and Comp are different </xsl:message>
<xsl:copy>
<xsl:value-of select="regexp:replace(*[local-name()='quote_type'],'','',$First)"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="*[local-name()='b_control']/*[local-name()='tpsdata']"/>
</xsl:stylesheet>
For each child node, I want to duplicate my parent node so that the resulting xml, contains only one child for the parent node with the other nodes being the same.
Here is a sample input
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
What I want to do is - repeat a3 for as many times as the node a311 comes. Rest all nodes are retained
Output
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a3>
<a31>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
More semantic with "tunnel param" pattern, this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()" name="identity">
<xsl:param name="pCurrent"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()">
<xsl:with-param name="pCurrent" select="$pCurrent"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="a3">
<xsl:variable name="vCurrent" select="."/>
<xsl:variable name="vDescendants" select=".//a311"/>
<xsl:for-each select="$vDescendants|$vCurrent[not($vDescendants)]">
<xsl:variable name="vDescendant" select="."/>
<xsl:for-each select="$vCurrent">
<xsl:call-template name="identity">
<xsl:with-param name="pCurrent" select="$vDescendant"/>
</xsl:call-template>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template match="a311">
<xsl:param name="pCurrent"/>
<xsl:if test="generate-id()=generate-id($pCurrent)">
<xsl:call-template name="identity"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output:
<a>
<a1>header1</a1>
<a2>header2</a2>
<a3>
<a31>
<a311>line_1</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a3>
<a31>
<a311>line_2</a311>
</a31>
<a32>5o$</a32>
<a33>Add</a33>
</a3>
<a4>account_holder</a4>
</a>
EDIT: Handling no descendants case.
The following (XSLT 1.0) stylesheet produces the desired result:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="a3">
<xsl:apply-templates select="a31/a311" />
</xsl:template>
<xsl:template match="a311">
<a3>
<a31>
<a311>
<xsl:value-of select="." />
</a311>
</a31>
<xsl:apply-templates select="../../*[not(self::a31)]" />
</a3>
</xsl:template>
</xsl:stylesheet>
You didn't specify the XSLT version. Here's an XSLT2 stylesheet that will accomplish what you want:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a3" mode="#default">
<xsl:apply-templates select="a31/a311"/>
</xsl:template>
<xsl:template match="a311">
<xsl:apply-templates select="../.." mode="x">
<xsl:with-param name="label" select="text()"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="a3" mode="x">
<xsl:param name="label"/>
<xsl:copy>
<xsl:apply-templates select="#*"/>
<a31>
<a311><xsl:value-of select="$label"/></a311>
</a31>
<xsl:apply-templates select="* except a31"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
It uses an identity transform to pass through the "uninteresting" elements while trapping the <a3> node and applying special handling. If you need an XSLT1 solution, merely
Replace select="* except a31" with select="*[name() != 'a31']"
Remove the mode="#default" in the first "a3" template
How can i replace an attribute in xml using xsl transformation, depending on its value.
For example, if there is such xml
<Text Style='style1'>
...
</Text>
transform it to
<Text Font='Arial' Bold='true' Color='Red'>
...
</Text>
For Style='style2' set another attributes and values, for example Font='Sans' Italic='true'.
One posible way: ussing attribute sets. This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:attribute-set name="style1">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="style2">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Italic">true</xsl:attribute>
</xsl:attribute-set>
<xsl:template match="Text[#Style='style1']">
<xsl:copy use-attribute-sets="style1">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="Text[#Style='style2']">
<xsl:copy use-attribute-sets="style2">
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With this input:
<root>
<Text Style='style1'></Text>
<Text Style='style2'></Text>
</root>
Output:
<Text Font="Arial" Bold="true" Color="Red"></Text>
<Text Font="Sans" Italic="true"></Text>
Other way: inline "attribute sets". This stylesheet:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my">
<xsl:output indent="yes"/>
<my:style1 Font="Arial" Bold="true" Color="Red"/>
<my:style2 Font="Sans" Italic="true"/>
<xsl:template match="Text[#Style]">
<xsl:copy>
<xsl:copy-of select="document('')/*/my:*
[local-name()=current()/#Style]/#*"/>
<xsl:copy-of select="#*[name()!='Style']"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
You will need to have some kind of rule that you use to convert the style from one to the other. Making the assumption you are inputting html you will need something like.
<xsl:template match="#* | node()">
<xsl:choose>
<xsl:when test="local-name() = 'Style'">
<xsl:apply-templates select="." mode="Style" />
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#Style" mode="Style">
<xsl:choose>
<xsl:when test="node() = 'style1'">
<xsl:attribute name="Font">Arial</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
<xsl:attribute name="Color">Red</xsl:attribute>
</xsl:when>
<xsl:when test="node() = 'style2'">
<xsl:attribute name="Font">Sans</xsl:attribute>
<xsl:attribute name="Bold">true</xsl:attribute>
</xsl:when>
</xsl:choose>
</xsl:template>
Now I have an XML file as below:
<DM Name="A DM">
<DV id="SQL:Select something from db" Name="DV 1">
<Sample aid="SQL:Select something from db" />
</DV>
<DV id="SQL:Select something from db" Name="DV 2">
<Sample aid="SQL:Select something from db" name ="DC">
good
</Sample>
</DV>
</DM>
I want to use an XSLT to transform it, there is a parameter in this tamplet to determine which DV should be transformed: if the parameter($dvIndex = 0), then just keep all the elements and attributes, just transform the attriform the attritributes with the value started with "SQL:", if ($dvindext > 0), just transform the specific DV,(remove other DV). Now I write the XSLT as below, but it miss the DM's attributes, I don't know how to copy DM's attributes. I donot know if there is better solution. XML File:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts"
>
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="C#" implements-prefix="user">
<![CDATA[
public string UpperCase(string value){
return value.ToUpper();
}
]]>
</msxsl:script>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:choose>
<xsl:when test="$dvIndex > 0">
<xsl:apply-templates select="DV[$dvIndex]"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]-->
<xsl:template match="#*[user:UpperCase(substring(.,1,4))='SQL:']">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
this question is also related to my question 2# (How to only convert an XML file's attribute using XSLT, and leave the other content?)
Thanks very much!
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*|DV[$dvIndex]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
Result:
<DM Name="A DM">
<DV id="parsedSQL" Name="DV 2">
<Sample aid="parsedSQL" name="DC">
good
</Sample>
</DV>
</DM>
With param dvIndex in 0:
<DM Name="A DM"></DM>
Note: Avoid scripting: it's not standar, it'll force to load script engine every time is used.
EDIT: If you want to process every DV when $dvIndex is 0, then this stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:param name="dvIndex" select="2" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*|DV[$dvIndex]|DV[not($dvIndex)]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#*[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
With $dvIndex is 0, output:
<DM Name="A DM">
<DV id="parsedSQL" Name="DV 1">
<Sample aid="parsedSQL"></Sample>
</DV>
<DV id="parsedSQL" Name="DV 2">
<Sample aid="parsedSQL" name="DC">
good
</Sample>
</DV>
</DM>
The following probably does what you need. Note the additional <xsl:apply-templates select="#*" /> to copy the attributes.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
xmlns:user="urn:my-scripts">
<xsl:output method="xml" indent="yes"/>
<msxsl:script language="C#" implements-prefix="user">
<![CDATA[
public string UpperCase(string value){
return value.ToUpper();
} ]]>
</msxsl:script>
<xsl:param name="dvIndex" select="0" />
<xsl:template match="DM" >
<xsl:copy>
<xsl:apply-templates select="#*" />
<xsl:choose>
<xsl:when test="$dvIndex > 0">
<xsl:apply-templates select="DV[$dvIndex]"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="DV"/>
</xsl:otherwise>
</xsl:choose>
</xsl:copy>
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!--[starts-with(translate(substring(.,1,4),'SQL:','sql:'),'sql:')]-->
<xsl:template match="#*[user:UpperCase(substring(.,1,4))='SQL:']">
<xsl:attribute name="{name()}">
<xsl:value-of select="'parsedSQL'"/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>