1. Summary
  2. Files
  3. Support
  4. Report Spam
  5. Create account
  6. Log in

CallingDotNet

From saxon

Jump to: navigation, search

Contents

Calling Extension Functions in .NET

Dynamic loading in .NET can be a bit of a headache. The general technique for defining extension functions (or "external functions" in XQuery) is documented at http://www.saxonica.com/documentation/extensibility/dotnetextensions.html

This page supplements the documentation with some notes based on actual experience.

From a C# or other .NET application

If your transformation or query is driven from an application written in C# or another .NET language, the simplest technique is to define a static dependency (reference) from your controlling application to the assembly containing the extension functions. In your XSLT or XQuery code you can then invoke the function as, for example

<e att="{my:ext()}" xmlns:my="clitype:Namespace.ClassName?asm=AssemblyName"/>

and it should load without trouble.

This should also work if you place the assembly containing the extension functions in the same directory as the controlling application, even without declaring a reference.

Added 13 December 2008 - by ACM

Note: Saxon requires that the external function being called, be static. In VB.NET there is no "static" modifier. VB.net uses the "Shared" modifier. For some reason this will not work if you try to create your method/function in vb.net. If you need to use vb.net, your best bet is to create the function/method in c# and then reference that in your vb.net project.

Here is C# working Example:
- You will need to create a class that will check if a file exist.
- Create a C# project (Class Library) called FileExists. Make sure your method/Function is static

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace FileExists{
 public class FileExists {
  static Boolean exist = false;
  public static Boolean getFile(String myFile) {
   exist = File.Exists(myFile);
   return exist;
  }
 }
}

- Compile the project. Your new .dll file is located: \FileExists\FileExists\bin\Release folder
- Next, start a new project (Windows Forms Application) called SaxonExtensionCS
- Add a button on the form
- Add your reference to FileExist
- Add your references to the saxon API

saxon9
saxon9api
IKVM.GNU.Classpath
IKVM.Runtime

- Add your imports (using) statements:

using System.IO;
using System.Collections;
using System.Xml;
using System.Net;
using Saxon.Api;
using FileExists;

- Add a textbox to your form called textbox
- Add the following to your button click:

private void button1_Click(object sender, EventArgs e){

                XmlDocument doc = new XmlDocument();
                Processor processor = new Processor();
                XmlTextReader xslt = new XmlTextReader(Application.StartupPath + "\\test.xsl");

                XmlTextReader xmlTextReader = new XmlTextReader(Application.StartupPath + "\\data.xml");
                Serializer serializer = new Serializer();
                XsltCompiler compiler = processor.NewXsltCompiler();
                FileStream filestrm = new FileStream(Application.StartupPath + "\\ExampleSimple2.xml", FileMode.Create, FileAccess.Write);

            try{
                compiler.ErrorList = new ArrayList();
                XsltTransformer transformer = compiler.Compile(xslt).Load();
                doc.Load(xmlTextReader);
                XdmNode input = processor.NewDocumentBuilder().Wrap(doc);
                transformer.InitialContextNode = input;
                //processor.SetProperty("http://saxon.sf.net/feature/trace-external-functions", true);
                 // Call your new method/function
                FileExists.FileExists fs = new FileExists.FileExists();
                // Wrap your obj
                transformer.SetParameter(new QName("", "fileUtil"), XdmAtomicValue.wrapExternalObject(fs));
                serializer.SetOutputProperty(Serializer.INDENT, "yes");
                serializer.SetOutputStream(filestrm);
                transformer.Run(serializer);
            }catch (Exception ex){
                MessageBox.Show(ex.Message, "Error");
                foreach (StaticError error in compiler.ErrorList)                {
                    textBox1.Text = textBox1.Text + "At line " + error.LineNumber + ": " + error.Message;
                }

            }finally{

                serializer.Close();
                xslt.Close();
                xmlTextReader.Close();
                filestrm.Close();
            }

         }

- Everything in your project should look like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Collections;
using System.Xml;
using System.Net;
using Saxon.Api;
using FileExists;

namespace SaxonExtensionCS{
    public partial class Form1 : Form{
        public Form1(){
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e){

                XmlDocument doc = new XmlDocument();
                Processor processor = new Processor();
                XmlTextReader xslt = new XmlTextReader(Application.StartupPath + "\\test.xsl");

                XmlTextReader xmlTextReader = new XmlTextReader(Application.StartupPath + "\\data.xml");
                Serializer serializer = new Serializer();
                XsltCompiler compiler = processor.NewXsltCompiler();
                FileStream filestrm = new FileStream(Application.StartupPath + "\\ExampleSimple2.xml", FileMode.Create, FileAccess.Write);

            try{
                compiler.ErrorList = new ArrayList();
                XsltTransformer transformer = compiler.Compile(xslt).Load();
                doc.Load(xmlTextReader);
                XdmNode input = processor.NewDocumentBuilder().Wrap(doc);
                transformer.InitialContextNode = input;
                processor.SetProperty("http://saxon.sf.net/feature/trace-external-functions", true);
                 // Call your new method/function
                FileExists.FileExists fs = new FileExists.FileExists();
                // Wrap your obj
                transformer.SetParameter(new QName("", "fileUtil"), XdmAtomicValue.wrapExternalObject(fs));
                serializer.SetOutputProperty(Serializer.INDENT, "yes");
                serializer.SetOutputStream(filestrm);
                transformer.Run(serializer);
            }catch (Exception ex){
                MessageBox.Show(ex.Message, "Error");
                foreach (StaticError error in compiler.ErrorList)                {
                    textBox1.Text = textBox1.Text + "At line " + error.LineNumber + ": " + error.Message;
                }

            }finally{

                serializer.Close();
                xslt.Close();
                xmlTextReader.Close();
                filestrm.Close();
            }

         }
      }

 }

- Now create the xsl:
- Make sure you add your namespace for the extension:

xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="clitype:FileExists.FileExists?asm=FileExists"
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="clitype:FileExists.FileExists?asm=FileExists">
    <xsl:param name="fileUtil" required="yes"/>
    <xsl:template match="/">
        <out>
            <xsl:for-each select="//file">
                <xsl:text>File </xsl:text>
                <xsl:value-of select="."/>
                <xsl:text> found: </xsl:text>
                <xsl:value-of select="ext:getFile(.)"/>
                <xsl:text>  </xsl:text>
            </xsl:for-each>
        </out>
    </xsl:template>
</xsl:stylesheet>

- Now create the xml:

<root>
   <file>C:\temp2\chap1.xml</file>
   <file>C:\temp2\chap2.xml</file>
</root>

- Now compile and run!


From the Command Line

If you are running Query or Transform from the command line, the following techniques are known to work://

* Copy the assembly containing the extension functions into the directory containing the Saxon Query.exe and Transform.exe executables, and call the extension function as described above'
* Use the "from" keyword to specify the location of the assembly, for example xmlns:my="clitype:Namespace.ClassName?from=file:///lib/AssemblyName.dll". Note: at present if you give a relative URI this is interpreted relative to the current directory, but this is likely to change so that it is relative to the base URI of the query or stylesheet.

  • Give the assembly containing the extension functions a strong name and copy it into the Global Assembly Cache. Then load it using its partialName, thus: xmlns:my="clitype:Namespace.ClassName?partialname=AssemblyName"

What doesn't seem to work at present (8.9.0.3) is loading using a strong name, for example xmlns:my="clitype:Namespace.ClassName?asm=AssemblyName;ver=1.2.3.4;sn=a1b2c3d45a6b7c8d". This will be patched in a future maintenance release.

Added 13 December 2008 - by ACM

Example:
- Create a .dll file:

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.IO;
 namespace FileExistCS{
  public class FileExistCS {
   static Boolean exist = false;
   public static Boolean getFile(String myFile){
    exist = File.Exists(myFile);
    return exist;
  }
 }
}

- Compile the file. Copy the file to where saxon is installed
- Your Stylesheet : ext_command_line_example.xsl

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="clitype:FileExistCS.FileExistCS?asm=FileExistCS" extension-element-prefixes="ext">
 <xsl:template match="/">
  <out>
   <xsl:text>test</xsl:text>
   <xsl:for-each select="//file">
    <xsl:text>File </xsl:text>
    <xsl:value-of select="."/>
    <xsl:text> is </xsl:text>
    <xsl:value-of select="ext:getFile(.)"/>
   </xsl:for-each>
  </out>
 </xsl:template>
</xsl:stylesheet>

- data.xml

<root>
 <file>C:\temp2\chap1.xml</file>
 <file>C:\temp2\chap2.xml</file>
</root>

- Copy your xml and xsl file to the same folder saxon transform.exe is in:
- Run: transform -s:data.xml -xsl:ext_command_line_example.xsl -o:output.xml -TJ & pause
- Thats it!

Running saxon:parse-html() - an extension written in Java

In Saxon 9.2 on .NET, the extension saxon:parse-html() does not work "out of the box". The code supporting this is John Cowan's TagSoup parser (written of course in Java). In 9.3, this code will be incorporated into the saxon9pe.dll and saxon9ee.dll assemblies so that it works without the need for any special configuration. Meanwhile, Joe Edwards reports success in getting it to work as follows. The technique is described here because it may be applicable also to user-written Java extension functions.

First, the JAR file containing the extension code (TagSoup) is cross-compiled to a .NET assembly using IKVMC.

Saxon implements saxon:parse-html() as an "integrated extension function" which attempts to load TagSoup dynamically using the ClassLoader registered with the Configuration, as follows:

Object parser = config.getInstance("org.ccil.cowan.tagsoup.Parser", null);

This can be made to work under .NET by registering a customized ClassLoader (written in C#) with the configuration as follows:

Processor.Implementation.getDynamicLoader().setClassLoader( new CustomClassLoader( Assembly.GetEntryAssembly() ) );

// …

private class CustomClassLoader: ClassLoader {

   private readonly AssemblyClassLoader _TagSoupClassLoader;

   public CustomClassLoader( Assembly assembly ): base( new AssemblyClassLoader( assembly ) ) {
      _TagSoupClassLoader = new AssemblyClassLoader( Assembly.GetAssembly( typeof( Parser ) ) );
   }

   public override Class loadClass( string name ) {
       if (name.StartsWith( "org.ccil.cowan" ) ) {
           return _TagSoupClassLoader.loadClass( name );
       } else {
           return base.loadClass( name );
       }
   }
}

The custom classloader links statically to the required class, and when the dynamic load request arrives for this class, it returns the containing assembly directly; other requests are delegated to the superclass.

Personal tools