Hi,

I've got an XML file that I need to make changes to before creating an output XML file.
I've got an XSLT Transform file which does part of the job fine, but I'm needing to go one step further which is where I'm struggling.

In C# I'm using an XslCompiledTransform object to trigger the transform task and this works fine for the part that works so far, but I'm stuck for where to go from here.

I need to get an attribute value from the XML back into C# which will be put through a separate C++ DLL to get another value which will be inserted into the XML as additional attributes in the same node. It is likely that they'll want to add further values in using the same technique in the future, so it needs to be adaptable.

From what I've read so far, I think I can use the xsl:message element to bounce something back, and I should be able to pick this up in C# as an event fired by the XslCompiledTransform object. But, how do I get the information back from C# into the XslCompiledTransform object in order to add it to the output XML as attributes in the right place.


Some of the files I'll be processing through can be in excess of 20k records, and the XML structure is pretty in-depth. I'm not too bothered about how quick it is as it'll be badged as a data conversion task (following updates to the application that uses the original files). I'm happy to trade off some speed to have code that is easier to maintain in the future.


Example Input XML:

<RootNode>
  <Person ID="1" Name="Bob Smith" Dept="Risks">
    <Address Number="12" PostCode="A12 3BC" Country="UK"/>
  </Person>
</RootNode>

Example Transform XSL:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml"/>
  <xsl:template match="RootNode">
    <RootNode>
      <xsl:foreach select="Person">
        <Person>
          <xsl:copy-of select="@ID"/>
          <xsl:copy-of select="@Name"/>
          <xsl:copy-of select="@Dept"/>
          <xsl:foreach select="Address">
            <xsl:copy-of select="@Number"/>
            <!-- Need to take this PostCode value and pass back to C# -->
            <xsl:copy-of select="@PostCode"/>
            <!-- Based on what C# gets from the DLL, add the following attribute ("StreetName") if it doesn't already exist -->
            <xsl:choose>
              <xsl:when test="StreetName">
                <xsl:copy-of select="@StreetName"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:attribute name="StreetName">
                  <xsl:value-of select="Test Road"/>
                </xsl:attribute>
              </xsl:otherwise>
            </xsl:choose>
            <xsl:copy-of select="@Country"/>
          </xsl:foreach>
        </Person>
      </xsl:foreach>
    </RootNode>
  </xsl:template>
</xsl:stylesheet>

Expected output XML:

<RootNode>
  <Person ID="1" Name="Bob Smith" Dept="Risks">
    <Address Number="12" PostCode="A12 3BC" StreetName="Test Road" Country="UK"/>
  </Person>
</RootNode>

In the above examples, I'm hoping to pass the PostCode back to C#, get the StreetName from a C++ DLL (by looking up the PostCode) and then pass the value back into the XSLT for adding to the XML.

Can anyone help?

Thanks in advance,
Jon

OK, I did a bit more searching after posting the above, and rephrased my google search criteria and found the following URL: http://projects.ischool.washington.edu/tabrooks/545/ContentManagement/PassingParameters.htm

My problem is solved by following the above link. Basically, I was thinking in the wrong direction as in I was thinking C# would do the leg work, but in fact it's the other way around.

I followed the instructions and came up with the following:

In my C# project, I added a new class and added the method "getStreetName" (with the PostCode as a parameter) that calls to the C++ DLL and gets the response which I then use as a return value.

I then passed the object into the XslCompiledTransform object as an argument.

C# code:

using System;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;

private runXMLTrans(string xmlInPath, string xsltPath, string xmlOutPath)
{
  XsltArgumentList xslArg = new XsltArgumentList();
  addressQuery addressQueryObj = new addressQuery();
  xslArg.AddExtensionObject("urn:addressfinder", addressQueryObj);

  XslCompiledTransform xslTrans = new XslCompiledTransform();
  XPathDocument xPathDoc = new XPathDocument(xmlInPath);
  XmlTextWriter writer = new XmlTextWriter(xmlOutPath, null);

  try
  {
    xslTrans.Load(xsltPath);
    xslTrans.Transform(xPathDoc, xslArg, writer);
    return "Success";
  }
  catch (Exception ex)
  {
    return ex.Message.ToString();
  }

  writer.Close();
}

Along with the following XSLT Transform file:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:myObj="urn:addressfinder">
  <xsl:output method="xml"/>
  <xsl:template match="RootNode">
    <RootNode>
      <xsl:foreach select="Person">
        <Person>
          <xsl:copy-of select="@ID"/>
          <xsl:copy-of select="@Name"/>
          <xsl:copy-of select="@Dept"/>
          <xsl:foreach select="Address">
            <xsl:copy-of select="@Number"/>
            <xsl:copy-of select="@PostCode"/>
            <xsl:choose>
              <xsl:when test="StreetName">
                <xsl:copy-of select="@StreetName"/>
              </xsl:when>
              <xsl:otherwise>
                <xsl:attribute name="StreetName">
                  <xsl:value-of select="myObj:getStreetName(PostCode)"/>
                </xsl:attribute>
              </xsl:otherwise>
            </xsl:choose>
            <xsl:copy-of select="@Country"/>
          </xsl:foreach>
        </Person>
      </xsl:foreach>
    </RootNode>
  </xsl:template>
</xsl:stylesheet>

Gives me the results I was after!

Problem solved :)

edit: Just noticed someone had raised this from the grave -.- still below might be useful to some

Forgive me for doing so without request but I thought I'd give you a pointer @jonifen as I used to make this mistake myself too until someone said.

You are using XSLT like a programatical language which it is not.

Therefore we can remove all of the for-each loops in your stylesheet and instead use templates for the specific items we are looping.

The template will match if it exists and do the code specified. If the node occurs more than once, the template will match all occurances individually, depending on how you select them.

See below for a re-written version of what you used, still produces same output (to the extent I could test, had to remove your class calls and used a dud instead).

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:fo="http://www.w3.org/1999/XSL/Format"
                xmlns:myObj="urn:addressfinder">
  <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>

  <xsl:template match="RootNode">
    <RootNode>
      <xsl:apply-templates/>
    </RootNode>
  </xsl:template>

  <xsl:template match="Person">
    <xsl:element name ="Person">
      <xsl:copy-of select="@ID"/>
      <xsl:copy-of select="@Name"/>
      <xsl:copy-of select="@Dept"/>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match ="Address">
    <xsl:element name="Address">
      <xsl:copy-of select="@Number"/>
      <xsl:copy-of select="@PostCode"/>
      <xsl:choose>
        <xsl:when test="StreetName">
          <xsl:copy-of select="@StreetName"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="StreetName">
            <xsl:value-of select="myObj:getStreetName(PostCode)"/>
          </xsl:attribute>
        </xsl:otherwise>
      </xsl:choose>
      <xsl:copy-of select="@Country"/>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

Edited 4 Years Ago by Mike Askew

This question has already been answered. Start a new discussion instead.