4

I'm fairly new to XML and XSL Stylesheets, and I've been tasked with creating a stylesheet for one of our clients. I have already created a stylesheet that outputs an XML in the following format:

<Trip TripType="Normal">
    <Plan BeginTime="2011-08-13T10:00:00" UserDefinedTripID="777" UserDefinedRouteID="777">
        <PlanStop ArrivalTime="2011-08-13T15:30:00" ArrivalLock="true" SiteID="1" PassThru1="test1" PassThru2="test2" PassThru3="test3" PassThru4="test4">
            <PlanNote Line1="Freeform Text" Line2="Line2" Line3="Line3" />
            <PlanCargo Duration="60" BillID="" Weight="100" Units="100.0" XUnitTypeID="10" Action="Pick" />
            <PlanNote Line1="Freeform Text" Line2="Line2" Line3="Line3" />
            <PlanCargo Duration="60" BillID="" Weight="100" Units="100.0" XUnitTypeID="12" Action="Pick" />
        </PlanStop>
    </Plan>
</Trip>

I need to take the output and insert the contents into an attribute within the Trip element to look like this:

<Trip TripID="-1" CurrentRevisionNumber="1" IsDispatch="1" IsActive="0" 
IsComplete="0" OrganizationID="4"
TripData="&lt;Trip TripType=&quot;Normal&quot;&gt;
  &lt;Plan BeginTime=&quot;2011-08-13T10:00:00&quot; UserDefinedTripID=&quot;777&quot;
  UserDefinedRouteID=&quot;777&quot;&gt;
    &lt;PlanStop ArrivalTime=&quot;2011-08-13T10:00:00&quot; ArrivalLock=&    quot;true&quot; SiteID=&quot;1&quot; PassThru1=&quot;test1&quot; PassThru2=&    quot;test2&quot; PassThru3=&quot;test3&quot; PassThru4=&quot;test4&quot;&gt;
    &lt;PlanCargo Duration=&quot;45&quot; BillID=&quot;&quot; Weight=&    quot;100&quot; Units=&quot;100.0&quot; XUnitTypeID=&quot;9&quot; Action=&quot;Pick&quot;     /&gt;
    &lt;/PlanStop&gt; />

So in other words, I need to take an existing XML output and put it in an attribute while performing some character transformations.

Yes, it is extremely ugly, but this is how they want it. I was thinking of making another XSL that will copy over the XML output from the original XSL transformation and place it in an attribute while converting <, >, ", etc into < , > , " , etc (not sure what they're called).

I've scoured the internet for solutions, but I can't seem to find any that are quite like this (I'd imagine that's because this is a ridiculous request). I can provide my original XSL if necessary, but I'd rather not change it, if possible.

Thanks in advance!

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 1
    You need to have a serious word with your clients! This is *such* a stupid idea! I would suggest having the trip data XML as a child of the Trip element, possibly using XML namespaces if they are concerned about conflicting element names. Put your foot down! – ColinE Jul 23 '12 at 20:42
  • 3
    If you give me their phone number, I can give them a call for you if you like ;-) – ColinE Jul 23 '12 at 20:43
  • @ColinE A cdata section might be better. – Colin D Jul 23 '12 at 20:43
  • Your language requiremnts arn't quite clear, are you forced to use xslt1.0 or can you use 2.0? If you can use 2, this might be helpful:http://www.ibm.com/developerworks/xml/library/x-tipxsltmp/index.html – Colin D Jul 23 '12 at 20:44
  • 2
    I would be curious to know why the clients want the XML escaped into a string. – LarsH Jul 23 '12 at 20:47
  • @ColinD: are you saying, a CDATA section instead of an attribute? i.e. changing the customer's requirements? – LarsH Jul 23 '12 at 20:51
  • 1
    A CDATA section that includes XML is just as ridiculous! – ColinE Jul 23 '12 at 21:00
  • I'm still curious about the requirement. As counterproductive as it seems to escape the XML content an extra time, thus requiring an extra parsing step later, it's certainly conceivable that there is a scenario where it's useful to delay the parsing of a payload until a later time than the parsing of the envelope. And there is always the possibility that the client is having to satisfy a 4th party's unreasonable specs, over which the client has no control. I'm not saying this is a well-thought-out strategy, but I would like to know more before deciding the client is stupid. – LarsH Jul 24 '12 at 13:13
  • @ColinE .....no kidding. I wrote my xsl to put it in the first form, and then I find out that I have to put it in this crazy form. I'm not too happy And my coworkers and I both came to the conclusion that CDATA section would be useful. I am, however, new to XML as I said and am not sure how to implement this. – user1546753 Jul 24 '12 at 13:27
  • I don't think you can put a CDATA section inside an attribute value. So how would CDATA help? – LarsH Jul 24 '12 at 14:15

4 Answers4

4

I would save myself time, effort and depression from others' stupidity and will implement this with a tiny transformation that uses a tiny extension function.

Here is example using the .NET XslCompiledTransform XSLT processor:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:msxsl="urn:schemas-microsoft-com:xslt"
 xmlns:my="my:my" exclude-result-prefixes="msxsl my">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>

 <xsl:template match="/*">
  <t stupid="{my:stringize(.)}"/>
 </xsl:template>

 <msxsl:script language="c#" implements-prefix="my">
  public string stringize(XPathNavigator doc)
  {
   return doc.InnerXml;
  }
 </msxsl:script>
</xsl:stylesheet>

When applied on any XML document, it produces the top element with a stupid attribute that contains the escaped representation of the body of the top element.

For example, when the transformation is applied on this XML document:

<t>
  <a>
    <b x="y">
      <!--  Comment here -->
      <?aPI ?>
    </b>
  </a>
</t>

the wanted result is produced:

<t stupid="&#xD;&#xA;  &lt;a&gt;&#xD;&#xA;    &lt;b x=&quot;y&quot;&gt;&#xD;&#xA;      &lt;!--  Comment here --&gt;&#xD;&#xA;      &lt;?aPI?&gt;&#xD;&#xA;    &lt;/b&gt;&#xD;&#xA;  &lt;/a&gt;&#xD;&#xA;" />

A transformation implementing the same idea most probably can be written for almost every XSLT processor with an extension function that itself can be written in variety of programming languages.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
  • that stupid attribute cracked me up...thanks for the quick reply. I'm going to use this code- sans the "stupid" attribute :P – user1546753 Jul 24 '12 at 13:33
3

I have to agree with other commenters, that inserting an XML document as lexical XML within an attribute is completely nutty. The owners of the data will lose so much power and flexibility and gain nothing in return. However, if you really have no choice, then commit suicide (joke). If this option is unpalatable for you, then you could serialise your inner XML with templates like so ...

<xsl:template match="*" mode="serialise">
  <xsl:value-of select="concat('&lt;',local-name(),' ')">
  <xsl:apply-templates select="@*" mode="serialise">
  <xsl:value-of select="' &gt;'">
  <xsl:apply-templates select="*|text()" mode="serialise">
  <xsl:value-of select="concat('&lt;/',local-name(),' &gt;')">
</xsl:template>

<xsl:template match="@*" mode="serialise">
  <xsl:value-of select="concat(local-name(),'=&quot;',.,'&quot; ')">
</xsl:template>

<xsl:template match="text()" mode="serialise">
  <xsl:value-of select=".">
</xsl:template>

Take the root element of the inner structure that you want to encoded as lexical XML and invoke <xsl:apply-templates> with @mode="serialise". Capture the output of this invocation into a string variable. You can then insert the contents of the string variable in what-ever attribute in the result tree you like.

Caveat

These simple templates do not process comments(), processing-instructions() and DocTypes. Also, as per XDM (contrary to DOM), adjacent text type nodes (text, CDATA, character references etc.) will be merged. Also this does not work with namespaces.

Community
  • 1
  • 1
Sean B. Durkin
  • 12,659
  • 1
  • 36
  • 65
  • Suicide may seem like a better option than having to do this nonsense. As far as I know, they need the code in this format to pass it on to a mobile device in truck cabs. I'm not sure if they need any functionality besides this feature, however. – user1546753 Jul 24 '12 at 14:12
2

An XSLT stylesheet for serializing XML as escaped text can be found at http://code.google.com/p/httpfox/source/browse/trunk/chrome/content/XMLPrettyPrint.xsl?r=3 It outputs HTML, but you can easily adapt it to output XML, mostly by removing unneeded stuff, like expanders.

The only tricky part may be getting the XML output from your existing XSL as input to the new templates. If you implement the latter as a separate stylesheet, or if you use XSLT 2.0, this will be no problem. Otherwise, you may need to use the node-set() extension function.

There are several aspects of the above that could be elaborated on. Holler if you get stuck! HTH.

P.S. Actually, since I started this post, @Sean posted his answer, and it's much easier to use than adapting the above stylesheet.

LarsH
  • 27,481
  • 8
  • 94
  • 152
2

Basically, you need to serialize the XML, capture the result of the serialization as a string, and then write an attribute with this string as its value. There's no direct way of doing this in XSLT 1.0 or XSLT 2.0. XSLT 3.0 has a function fn:serialize() designed to meet this requirement, and it's implemented in recent versions of Saxon. (Earlier versions of Saxon introduced the idea with a saxon:serialize() extension function. You may find similar functions in other products, or you may be able to write your own.) Implementing a serializer in XSLT itself is of course possible, as others have suggested; it's just rather tedious.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164