Wrap some fields in CDATA but not others OF THE SAME NAME - xslt

Forking from a previous question I asked, how do I ensure that example/one/field and example/three/field are enclosed in CDATA whilst example/two/field is not?
Input:
<?xml version="1.0"?>
<example>
<one>
<field>CDATA required here</field>
</one>
<two>
<field>No CDATA thanks</field>
</two>
<three>
<field>More CDATA please</field>
</three>
</example>
Required output:
<?xml version="1.0"?>
<example>
<one>
<field><![CDATA[CDATA required here]]></field>
</one>
<two>
<field>No CDATA thanks</field>
</two>
<three>
<field><![CDATA[More CDATA please]]></field>
</three>
</example>
I could specify <xsl:output cdata-section-elements="field"/> but this will affect example/two/field as well. I have tried putting in a path like <xsl:output cdata-section-elements="example/one/field example/three/field"/> but this produces an error (Error XTSE0280: Invalid element name. Invalid QName {example/one/field}). Where am I going wrong?

With your current markup I don't think there is a clean way with XSLT. You would need to use different element names or different namespaces at least to allow you and the XSLT processor's serializer to distinguish which elements to output as CDATA sections and which not.
Or you would need to consider to use disable-output-escaping e.g.
<xsl:template match="one/field | three/field">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
[edit]
Here is a complete sample stylesheet:
<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="one/field | three/field">
<xsl:copy>
<xsl:text disable-output-escaping="yes"><![CDATA[<![CDATA[]]></xsl:text>
<xsl:value-of select="."/>
<xsl:text disable-output-escaping="yes">]]></xsl:text>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Note however that disable-output-escaping is an optional serialization feature that is not supported by all XSLT processors.

Related

XSLT 2.0 3.0 for-each context error when tokenizing attributes

Given this XML
<dmodule>
<content>
<warningsAndCautionsRef>
<warningRef id="w001" warningIdentNumber="warning-001">
</warningRef>
<warningRef id="w002" warningIdentNumber="warning-002">
</warningRef>
<cautionRef id="c001" cautionIdentNumber="caution-001">
</cautionRef>
<cautionRef id="c002" cautionIdentNumber="caution-002">
</cautionRef>
</warningsAndCautionsRef>
<faultReporting>
<preliminaryRqmts>
<reqSafety>
<safetyRqmts cautionRefs="c001 c002" warningRefs="w001 w002"/>
</reqSafety>
</preliminaryRqmts>
</faultReporting>
</content>
</dmodule>
I would like to tokenize the attributes #cautionRefs (and #warningRefs) and then find the cautionRef element that matches its #id to the tokenized value:
<xsl:template match="#cautionRefs">
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="//*[#id=.]"/>
</xsl:for-each>
</xsl:template>
but the apply-templates fails: Fatal error during transformation Leading '/' selects nothing: the context item is not a node. It works if I don't tokenize and use string functions instead but that is not desirable.
Desired result:
Tokenize #cautionRefs="c001 c002" (which has multiple parent elements)
So each value is passed to the <cautionRef>template that will retrieve the caution and warning statements, to be displayed in a PDF:
<xsl:apply-templates select="//*[#id='c001']"/>
<xsl:apply-templates select="//*[#id='c002']"/>
I tried using <xsl:key name="id" match="*" use="#id"/> with
<xsl:for-each select="key('id',tokenize(.,'\s'))">
but the for-each is blank.
The above apply-templates will match with this <cautionRef> template, which retrieves the caution and warning statements correctly. I just need help with the context of the #cautionRefs template:
<xsl:template match="cautionRef">
<xsl:variable name="IdentNumber" select="#cautionIdentNumber"/>
<xsl:apply-templates select="//cautionSpec[cautionIdent/#cautionIdentNumber=$IdentNumber]"/>
</xsl:template>
You could use a variable and use that for context:
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:variable name="ctx" select="/"/>
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="$ctx//*[#id=.]"/>
</xsl:for-each>
</xsl:template>
but I would use a key like you hinted at (updated to include context based on comments)...
<xsl:key name="by_id" match="*[#id]" use="#id"/>
<xsl:variable name="root" select="/"/>
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:for-each select="tokenize(.,'\s')">
<xsl:apply-templates select="key('by_id',.,$root)"/>
</xsl:for-each>
</xsl:template>
Here's a full working example. NB it's best to have this level of detail in the actual question; i.e. a sample input file, the XSLT, and output, along with an example of what you want the output to look like.
Input:
<test>
<safetyRqmts cautionRefs="c001 c002" warningRefs="w001"/>
<cautionRef id="c001" cautionIdentNumber="caution-001"/>
<cautionRef id="c002" cautionIdentNumber="caution-001"/>
</test>
Stylesheet:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:key name="by_id" match="*[#id]" use="#id"/>
<xsl:template match="#cautionRefs|#warningRefs">
<xsl:apply-templates select="key('by_id', tokenize(.))"/>
</xsl:template>
</xsl:stylesheet>
Output:
<test>
<safetyRqmts><cautionRef id="c001" cautionIdentNumber="caution-001"/><cautionRef id="c002" cautionIdentNumber="caution-001"/></safetyRqmts>
<cautionRef id="c001" cautionIdentNumber="caution-001"/>
<cautionRef id="c002" cautionIdentNumber="caution-001"/>
</test>

Input XML which has EventType != 'HardBounce' should be copied to output

<?xml version="1.0" encoding="UTF-8"?>
<root>
<Interaction>
<RecipientId>666994250005</RecipientId>
<RecipientType>Normal</RecipientType>
<MailingId>18744460</MailingId>
<ReportId>2121196700</ReportId>
<CampaignId/>
<Email>ilovepizza#mntest.net</Email>
<EventType>HardBounce</EventType>
<EventTimestamp>05/11/202113:46:40</EventTimestamp>
<BodyType/>
<ContentId/>
<ClickName/>
<URL/>
<ConversionAction/>
<ConversionDetail/>
<ConversionAmount/>
<SuppressionReason/>
<MailingName>YourNovemberTrend-TESTING_682-BOUNCES</MailingName>
<MailingSubject>YourNovemberTrend-TESTING_682-BOUNCES</MailingSubject>
<CONTACT_MC_ID>42010A0351251EEBA0EF17B38C3EDC78</CONTACT_MC_ID>
<CAMPAIGN_ID>0000000682</CAMPAIGN_ID>
<SOURCE_OBJECT_ID>C01AFE8349D7F713787E25B656A3D2D6BA205205</SOURCE_OBJECT_ID>
<UUID>ca69251e-8b0e-1d90-1700-1c42c1610f6d</UUID>
</Interaction>
<Interaction>
<RecipientId>672386985145</RecipientId>
<RecipientType>Normal</RecipientType>
<MailingId>18848768</MailingId>
<ReportId>2141674081</ReportId>
<CampaignId/>
<Email>cg#gmail.com</Email>
<EventType>Sent</EventType>
<EventTimestamp>06/08/202119:28:06</EventTimestamp>
<BodyType/>
<ContentId/>
<ClickName/>
<URL/>
<ConversionAction/>
<ConversionDetail/>
<ConversionAmount/>
<SuppressionReason/>
<MailingName>TrendEmailTestSend425(18)</MailingName>
<MailingSubject>TESTING:YourNovemberTrend-710Campaign</MailingSubject>
<CONTACT_MC_ID>42010A0351251EDBA6904634DF983CB0</CONTACT_MC_ID>
<CAMPAIGN_ID>0000000710</CAMPAIGN_ID>
<SOURCE_OBJECT_ID>42948F6B87172477E4BE993B3EC48255EF4A27D4</SOURCE_OBJECT_ID>
<UUID>1292721e-8b0e-1d90-1700-1c42c1610f6d</UUID>
</Interaction>
</root>
I am trying below XSLT but it's not working:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.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="/root/Interaction">
<xsl:if test="/root/Interaction/EventType/text() != 'HardBounce'">
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:apply-templates/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Output Expected is:
<root>
<Interaction>
<RecipientId>672386985145</RecipientId>
<RecipientType>Normal</RecipientType>
<MailingId>18848768</MailingId>
<ReportId>2141674081</ReportId>
<CampaignId/>
<Email>cg#gmail.com</Email>
<EventType>Sent</EventType>
<EventTimestamp>06/08/202119:28:06</EventTimestamp>
<BodyType/>
<ContentId/>
<ClickName/>
<URL/>
<ConversionAction/>
<ConversionDetail/>
<ConversionAmount/>
<SuppressionReason/>
<MailingName>TrendEmailTestSend425(18)</MailingName>
<MailingSubject>TESTING:YourNovemberTrend-710Campaign</MailingSubject>
<CONTACT_MC_ID>42010A0351251EDBA6904634DF983CB0</CONTACT_MC_ID>
<CAMPAIGN_ID>0000000710</CAMPAIGN_ID>
<SOURCE_OBJECT_ID>42948F6B87172477E4BE993B3EC48255EF4A27D4</SOURCE_OBJECT_ID>
<UUID>1292721e-8b0e-1d90-1700-1c42c1610f6d</UUID>
</Interaction>
</root>
Just dipping toe in XSLT. Thanks for help.
The node should be copied to target on meeting the condition.
Do not Read below. This is just to get the stack over
StackoverStackoverflow is asking me to put more details to question but i think i have provided enough details to comprehend the problem.
The instruction:
<xsl:template match="/root/Interaction">
puts you in the context of Interaction. From this context, the test of EventType needs to use the relative path to the EventType that is the child of the current Interaction - e.g.
<xsl:if test="EventType != 'HardBounce'">
What you have starts from the root and tests if there is at least one EventType that meets the condition in the entire XML document. This is of course true for all Interaction elements being tested.
Note that you could accomplish the same thing much more simply by:
<xsl:stylesheet version="3.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root">
<xsl:copy>
<xsl:copy-of select="Interaction[EventType != 'HardBounce']"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Block the elements you don't want from being copied with an empty template <xsl:template match="Interaction[EventType = 'HardBounce']"/>. Handle the rest through your first template or even replace it by declaring <xsl:mode on-no-match="shallow-copy"/>, if you are using XSLT 3.

How do I match the elements generated by xsl-fo?

I have a .xsl file like this:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:exslt="http://exslt.org/common">
<xsl:template match="/>
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:template>
</xsl:stylesheet>
How can I use templates to match and style the generated fo elements? For example, if I want to give my fo:table-cells red backgrounds, I'd like to be able to do
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
I found this and then tried something along the lines of
<xsl:template match="/>
<xsl:variable name="foRoot">
<fo:root>
<fo:block>...</fo:block>
</fo:root>
</xsl:variable>
<xsl:apply-templates select="exslt:node-set($foRoot)" />
</xsl:template>
but this results in a stack overflow due to endless recursion. When I try to avoid this, for example by doing
<xsl:apply-templates select="exslt:node-set($foRoot)/*" />
I get an empty document. When trying to fix that by adding
<xsl:copy-of select="$foRoot" />
right after, I don't get any errors but the table-cells still have a default white background.
If you really use an XSLT 2 processor then first of all you don't need exsl:node-set.
As for your template
<xsl:template match="fo:table-cell">
<xsl:attribute name="background-color">red</xsl:attribute>
</xsl:template>
that would match a FO table-cell but transform it into an attribute. So you rather want
<xsl:template match="fo:table-cell">
<xsl:copy>
<xsl:apply-templates select="#*"/>
<xsl:attribute name="background-color">red</xsl:attribute>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
as that adds the attribute to a shallow copy of the element and then keeps processing alive for child elements with apply-templates.
Of course you will also need to add the identity transformation template
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
to make sure the elements you don't want to change are copied through. It might be necessary to use modes to separate processing steps if the other templates you have interfere with the identity transformation.

replace namespace prefix and add attributes using XSLT

suppose I have following XML as source:
<ns0:msg xmlns:ns0="namespace0">
<ns0:hdr a_lot_of_attrs="value">
some nodes...
</ns0:hdr>
<ns0:body>
<ns0:data a_lot_of_attrs="value">
<ns1:purchase_order xmlns:ns1="namespace1">some nodes...</ns1:purchase_order>
</ns0:data>
</ns0:body>
</ns0:msg>
And I need following XML as the result:
<a:msg xmlns:a="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a:hdr a_lot_of_attrs="value">
some nodes...
</a:hdr>
<a:body>
<a:data a_lot_of_attrs="value">
<b:purchase_order
xsi:schemaLocation="filelocation"
xmlns:b="namespace1"
xmlns:c="namespace2">some nodes...</b:purchase_order>
</a:data>
</a:body>
</a:msg>
Basically I need just to replace the namespace prefix ns0 into a and ns1 into b. Further more, the root element <a:msg> as well as the <b:purchase_order> need to be added by some additional attributes.
My attemp is by using following XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ns0="namespace0"
xmlns:ns1="namespace1"
xmlns:a="namespace0"
xmlns:b="namespace1"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="ns0 ns1">
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<a:msg xmlns:msg="namespace1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<xsl:copy>
<xsl:apply-templates select="ns0:msg/*"/>
</xsl:copy>
</a:msg>
</xsl:template>
<xsl:template match="/ns0:msg/ns0:body/ns0:data/ns1:purchase_order">
<b:purchase_order xsi:schemaLocation="filelocation" xmlns:c="namespace2">
<xsl:copy>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</b:purchase_order>
</xsl:template>
<xsl:template match="ns0:*">
<xsl:element name="a:{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="ns1:*">
<xsl:element name="b:{local-name()}">
<xsl:apply-templates select="#* | node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="#*">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
It works fine so far, except the node <purchase_order> has been populated 2 times:
<a:msg xmlns:a="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<a:hdr a_lot_of_attrs="value">
some nodes...
</a:hdr>
<a:body>
<a:data a_lot_of_attrs="value">
<b:purchase_order xmlns:b="namespace1" xmlns:c="namespace2" xsi:schemaLocation="filelocation">
<ns1:purchase_order xmlns:ns0="namespace0" xmlns:ns1="namespace1">some nodes...</ns1:purchase_order>
</b:purchase_order>
</a:data>
</a:body>
</a:msg>
I tried several times by tweaking the second <xsl:template> but could not get it right. Would someone pls advise where I get wrong here and how can I get this done?
Thanks a lot.
You current template that matches ns1:purchase_order contains an xsl:copy as well as the creation of the new b:purchase_order. Therefore you are copying the old node as well as creating a new one.
You can remove the xsl:copy from the template, like so:
<xsl:template match="ns1:purchase_order">
<b:purchase_order xsi:schemaLocation="filelocation" xmlns:c="namespace2">
<xsl:apply-templates select="node()"/>
</b:purchase_order>
</xsl:template>
Note that you don't necessarily have to specify the full path in the template match. You would only really need to do this if you had a second ns1:purchase_order in the XML, at a different location, that you didn't want to match.

Adding new xml element and changing the value of an element within the same parent element

I need to make certain modifications to my XML input, depending on certain conditions. I am using XSLT 1.0.
the value of the message_type element (child element of m_cotrol) should be changed
A new element message_status should be added (as a child of the m_control element).
These changes are reflected in the expected output XML. With my current XSLT code, I am only able to achieve the second requirement.
Input XML:
<?xml version="1.0"?>
<message xmlns="http://www.origoservices.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<m_control>
<control_timestamp>2013-04-12T09:24:38.902</control_timestamp>
<message_id>a50ec030-72ab</message_id>
<retry_number>0</retry_number>
<message_type>Request</message_type>
<message_version>test.XSD</message_version>
<expected_response_type>synchronous</expected_response_type>
<initiator_id>FST</initiator_id>
<initiator_orchestration_id>1637280</initiator_orchestration_id>
<responder_id>mycomp</responder_id>
</m_control>
<m_content>
<b_control>
<service_provider_reference_number>650971</service_provider_reference_number>
<intermediary_case_reference_number>Sample1</intermediary_case_reference_number>
<quote_type>Comparison</quote_type>
<quote_or_print>Print</quote_or_print>
<message_version_number>3.7</message_version_number>
<submission_date>0001-04-12</submission_date>
</b_control>
</m_content>
</message>
Expected Output:
<message xmlns="http://www.origoservices.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<m_control>
<control_timestamp>2013-04-12T09:24:38.902</control_timestamp>
<message_id>a50ec030-72ab</message_id>
<retry_number>0</retry_number>
<message_type>Response</message_type>
<message_version>test.XSD</message_version>
<expected_response_type>synchronous</expected_response_type>
<initiator_id>FST</initiator_id>
<initiator_orchestration_id>1637280</initiator_orchestration_id>
<responder_id>mycomp</responder_id>
<message_status>User not allowed access</message_status>
</m_control>
<m_content>
<b_control>
<service_provider_reference_number>650971</service_provider_reference_number>
<intermediary_case_reference_number>Sample1</intermediary_case_reference_number>
<quote_type>Comparison</quote_type>
<quote_or_print>Print</quote_or_print>
<message_version_number>3.7</message_version_number>
<submission_date>0001-04-12</submission_date>
<quote_response_status>Error</quote_response_status>
<quote_error_note>
<reason>[Error] Check if the User has access to the requested service</reason>
</quote_error_note>
</b_control>
</m_content>
</message>
XSLT code: Based on the value of DataPower variable (var://service/error-message), I need the expected output.
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dp="http://www.datapower.com/extensions" version="1.0" extension-element-prefixes="dp" exclude-result-prefixes="dp">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="//*[contains(name(),'m_control')]">
<xsl:choose>
<xsl:when test="dp:variable('var://service/error-message') = 'not present'">
<m_control xmlns="http://www.origoservices.com">
<xsl:apply-templates select="#* | *"/>
<message_status>User not recognized</message_status>
</m_control>
</xsl:when>
</xsl:choose>
</xsl:template>
<xsl:template match="//*[contains(name(),'b_control')]">
<xsl:choose>
<xsl:when test="dp:variable('var://service/error-subcode')='0x01d30002'">
<b_control xmlns="http://www.origoservices.com">
<xsl:apply-templates select="#* | *"/>
<quote_response_status>Error</quote_response_status>
<quote_error_note>
<reason>[Error] Check if the User has access to the requested service</reason>
</quote_error_note>
</b_control>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The following stylesheet meets both of your requirements. It does a common identity transform (which your XSLT does, too) with exceptions.
Note that I have not taken into consideration any changes that are performed by your stylesheet but not listed as a requirement (i.e. changing quote_error_note and quote_response_status).
This line:
<xsl:template match="text()[parent::ori:message_type]">
meets your first requirement, the one you were unable to code. It matches the text content of message_type and outputs "Response" instead.
But this solution differs from yours in another way: it does not match elements along the lines of:
<xsl:template match="//*[contains(name(),'m_control')]">
Rather, their correct namespace is identified:
<xsl:template match="ori:m_control">
Now, what's the difference? Your way of describing the template match allows elements of any namespace to be matched. This might not be a problem in your case (no conflicting namespaces) but it could be one in general.
Full stylesheet
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ori="http://www.origoservices.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
exclude-result-prefixes="ori xsi">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="ori:m_control">
<xsl:copy>
<xsl:apply-templates/>
<message_status>
<xsl:text>User not allowed access</xsl:text>
</message_status>
</xsl:copy>
</xsl:template>
<xsl:template match="text()[parent::ori:message_type]">
<xsl:text>Response</xsl:text>
</xsl:template>
</xsl:stylesheet>