App using saxonHE (9.2.1.1) api to process XSLT (v2.0) against multiple XML files
Asked Answered
R

2

6

I have an application that uses the SAXONHE 9.2.1.1 api files to transform XML data to plain text. My form has textboxes for

  1. XMLInput_FilePath
  2. XSLT_FilePath
  3. TextOutput_FilePath

On the okButton_Click() event of my form, I have the following:

private void okButton_Click(object sender, EventArgs e) {
    FileStream xsltTransform_FileStream = File.Open(xsltTransform_FilePath.Text, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
    FileStream xmlInput_FileStream = File.Open(xmlInput_FilePath.Text, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

    XmlTextReader modelFileXML = new XmlTextReader(xmlInput_FileStream);
    modelFileXML.XmlResolver = null;

    Processor processor = new Processor();

    XdmNode input = processor.NewDocumentBuilder().Build(modelFileXML);

    XsltTransformer xsltTransformer = processor.NewXsltCompiler().Compile(xsltTransform_FileStream).Load();
    xsltTransformer.InputXmlResolver = null;
    xsltTransformer.InitialContextNode = input;

    Serializer serializer = new Serializer();
    serializer.SetOutputFile(writeFile);

    xsltTransformer.Run(serializer);

    xsltTransform_FileStream.Close();
    modelFileStream.Close();
}

Within the context of my XMLInput file there is a reference to data in another XML file - see below:

XML:

<XMLInput_File
  Name="XMLInput_File">
  <Subsystem Name="Subsystem">
    <Requirements Name="Requirement_1">
      <Rows>
        <Path Text="XMLInput2_File:/XMLInput2_File/Subsystem_1/Field_1" />
      </Rows>
      <Rows>
        <Path Text="XMLInput2_File:/XMLInput2_File/Subsystem_1/Field_2" />
      </Rows>
    </Requirements>
    <Requirements Name="Requirement_2">
      <Rows>
        <Path Text="XMLInput2_File:/XMLInput2_File/Subsystem_1/Field_3" />
      </Rows>
      <Rows>
        <Path Text="XMLInput2_File:/XMLInput2_File/Subsystem_2/Field_1" />
      </Rows>
    </Requirements>
  </Subsystem>
</XMLInput_File>

The Text attribute is where the external XML File path is stored, in the above example, the XML filename would be "XMLInput2_File.xml".

XML2:

<XMLInput2_File Name="XMLInput2_File">
  <Subsystem Name="Subsystem_1">
    <Fields Name="Field_1">
      S1_Field_One
    </Fields>
    <Fields Name="Field_2">
      S1_Field_Two
    </Fields>
     <Fields Name="Field_3">
      S1_Field_Three
    </Fields>
 </Subsystem>
  <Subsystem Name="Subsystem_2">
    <Fields Name="Field_1">
      S2_Field_One
    </Fields>
    <Fields Name="Field_2">
      S2_Field_Two
    </Fields>
     <Fields Name="Field_3">
      S2_Field_Three
    </Fields>
 </Subsystem>
</XMLInput2_File>

XSLT:

  <xsl:template match="/">
    <xsl:for-each select ="//Rows/Path">
      <xsl:variable name ="interfaceData" select ="@Text"/>
      <xsl:variable name ="_intfModelName" select ="substring-before(@Text,':/')"/>
      <xsl:variable name ="_intfFileName" select ="concat('../../OtherXMLFiles/',$_intfModelName,'.xml')"/>
      <xsl:apply-templates select ="document($_intfFileName)/*[@Name=$_intfModelName]/*">
      </xsl:apply-templates>
     </xsl:for-each>
  </xsl:template>

I'm using Microsoft Visual Studion 2008 Professional Edition to test my transform and the above scenario works exactly as it should - the document() referencing the external file more specifically. However, when I use my C# Winform app and the saxon api calls, my output file contains blank data (blank lines).

After a few tests and Internet searches, I came to the conclusion that the relative path [in my XSLT] is not being applied as it should. It seems that the saxon api calls process the document() function from the location of the transform.exe file and not the input file (which is what I'd prefer).

I've been trying to do more Internet searches on this issue and I'm confused of whether the issue is in my XSLT file or in the saxon api calls within the okButton_Click() event. Furthermore, I've been to saxon's website and documentation for help, but to no avail.

Recognition answered 12/4, 2012 at 15:0 Comment(0)
V
1

In your XSLT, use

  <xsl:apply-templates select ="document($_intfFileName, /)/*[@Name=$_intfModelName]/*">

if you want the relative URL to be resolved relative to the input document (and not the stylesheet).

Volteface answered 12/4, 2012 at 16:32 Comment(6)
I updated the XSLT according to your reply and I'm still getting the same result - blank data.Recognition
I see some other issues with your posted samples, you have posted a Rows element with a Path child element with a Text attribute yet your code does a for-each select="//Rows" and then accesses the @Text of those Rows elements, not of the Path child. So based on the posted samples you might want for-each select="//Rows/Path".Volteface
If you still have problems then I would start debugging the code by doing <xsl:copy-of select="document($_intfFileName, /)"/> instead of the apply-templates. Or if you want us to help we need to see the XML input so that we can check whether *[@Name=$_intfModelName]/* is going to select something.Volteface
Okay. I will try the <xsl:copy-of ... > test. In the meantime, I've updated my original post with more info on XML: and included data for XML2: (it's not very indepth but gives the major node structure).Recognition
I have tried the <xsl-copy-of .. > and again there is no data being output. Again, when I use Microsoft Visual Studio to test this, it works fine; once I use the saxon api calls, the results are nothing.Recognition
Did you also post on the Saxon help mailing list by now? I think my suggestion with doing document($var, /) if you want the file name to be resolved relative to the input document's base URL is correct, with the Saxon API I am sure you will find better support on that mailing list.Volteface
R
0

After posting my inquiry on the saxon help-mailing-list, Michael Kay of Saxonica helped me with this issue. The error was in my saxon api calls (C# applicaiton). Below is the updated code:

private void okButton_Click(object sender, EventArgs e) {
    Processor processor = new Processor();
    XdmNode input = processor.NewDocumentBuilder().Build(new Uri(inputFile));
    XsltCompiler compiler = processor.NewXsltCompiler();
    compiler.BaseUri = new Uri(inputFile);
    XsltTransformer xsltTransformer = compiler.Compile(new Uri(transformFileTextBox.Text)).Load();
    xsltTransformer.InitialContextNode = input;
    Serializer serializer = new Serializer();
    serializer.SetOutputFile(writeFile);
    xsltTransformer.Run(serializer);
    xsltTransform_FileStream.Close();
    modelFileStream.Close();
} 

"The problem is that you are calling document() to resolve a relative URI whose base URI is unknown. Because you use substring-before to compute the relative URI, the argument is a string, so the base URI it will use is that of the stylesheet. The stylesheet's base URI is unknown because you built the stylesheet from a stream; in this situation Saxon defaults to the current working directory. You can set the base URI using the XsltCompiler.BaseUri property." - Micheal Kay

So, here is what I did

  • removed both FileStream variables
  • created an XsltCompiler object and set BaseUri to the input file
  • replaced the FileStream signature with 'new Uri(filename)' signature
  • removed this line of code 'xsltTransformer.InputXmlResolver = null;'

My output is as it should be. Many thanks to Michael Kay of Saxonica for his help in resolving this. - Lorentz

Recognition answered 13/4, 2012 at 21:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.