Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using ms: xpath functions inside XPathExpression

I am trying to use Microsoft XPath Extension Functions (such as ms:string-compare http://msdn.microsoft.com/en-us/library/ms256114.aspx) inside an XPathExpression object.

These functions are extensions inside the MSXML library, and if I use them in an XslCompiledTransform (simply adding the "ms" namespace) they work like a charm:

var xsl =
    @"
<?xml version=""1.0"" encoding=""UTF-8""?>
<xsl:stylesheet version=""2.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform"" 
        xmlns:xs=""http://www.w3.org/2001/XMLSchema"" 
        xmlns:fn=""http://www.w3.org/2005/xpath-functions"" 
        xmlns:ms=""urn:schemas-microsoft-com:xslt"">
 <xsl:output method=""xml"" version=""1.0"" encoding=""UTF-8"" indent=""yes""/>
 <xsl:template match=""/Data"">
  <xsl:element name=""Result"">
   <xsl:value-of select=""ms:string-compare(@timeout1, @timeout2)""/>
  </xsl:element>
 </xsl:template>
</xsl:stylesheet>";

var xslDocument = new XmlDocument();
xslDocument.LoadXml(xsl);

var transform = new XslCompiledTransform();
transform.Load(xslDocument);

Then I tried using them in an XPathExpression:

XPathNavigator nav = document.DocumentElement.CreateNavigator();
XPathExpression expr = nav.Compile("ms:string-compare(/Data/@timeout1, /Data/@timeout2)");

XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable);
manager.AddNamespace("ms", "urn:schemas-microsoft-com:xslt");
expr.SetContext(manager);

nav.Evaluate(expr);

But I get an exception "XsltContext is needed for this query because of an unknown function".

XsltContext is a specific XmlNamespaceManager, but I don't know if it's possible to instantiate it without an actual XslCompiledTransform (it's abstract) and use it as my expression context.

Is there any way to do this (or any other way to use ms: extensions inside an XPathExpression)?

like image 532
Filini Avatar asked Feb 23 '10 17:02

Filini


2 Answers

These ms prefixed functions are not included in .net framework dom classes. you need to create your custom functions to do same thing.

you may use sample code below;

string xpath = "my:string-compare('1','1)";

System.Xml.XmlNamespaceManager nsManager = new XsltContext();

nav.Select(xpath, nsManager );

or

XPathExpression compiledXPath = XPathExpression.Compile(xpath);

compiledXPath.SetContext(nsManager);

nav.Evaluate(compiledXPath);

you will need these classes;

public class XsltContext : System.Xml.Xsl.XsltContext
{
    public XsltContext()
    {
        Initialize();
    }

    public XsltContext(System.Xml.NameTable nameTable)
        : base(nameTable)
    {
        Initialize();
    }

    private void Initialize()
    {
        RegisterFunction("my", "string-compare", typeof(StringCompare));
    }

    public override string LookupNamespace(string prefix)
    {
        return base.LookupNamespace(prefix);
    }

    public override int CompareDocument(string baseUri, string nextbaseUri)
    {
        return string.CompareOrdinal(baseUri, nextbaseUri);
    }

    public override bool PreserveWhitespace(System.Xml.XPath.XPathNavigator node)
    {
        return false;
    }

    public void RegisterFunction(string prefix, string name, Type function)
    {
        if (function == null)
            throw new ArgumentNullException("function");

        if (name == null)
            throw new ArgumentNullException("name");

        functions[prefix + ":" + name] = function;
    }

    Dictionary<string, Type> functions = new Dictionary<string, Type>();

    public override System.Xml.Xsl.IXsltContextFunction ResolveFunction(string prefix, string name, System.Xml.XPath.XPathResultType[] argTypes)
    {
        Type functionType = null;

        if (functions.TryGetValue(prefix + ":" + name, out functionType))
        {
            System.Xml.Xsl.IXsltContextFunction function = Activator.CreateInstance(functionType) as System.Xml.Xsl.IXsltContextFunction;

            return function;
        }

        return null;
    }

    public override System.Xml.Xsl.IXsltContextVariable ResolveVariable(string prefix, string name)
    {
        return null;
    }

    public override bool Whitespace
    {
        get
        {
            return false;
        }
    }

    internal static string GetValue(object v)
    {
        if (v == null)
            return null;

        if (v is System.Xml.XPath.XPathNodeIterator)
        {
            foreach (System.Xml.XPath.XPathNavigator n in v as System.Xml.XPath.XPathNodeIterator)
                return n.Value;
        }

        return Convert.ToString(v);
    }

}

class StringCompare : System.Xml.Xsl.IXsltContextFunction
{
    public System.Xml.XPath.XPathResultType[] ArgTypes
    {
        get
        {
            return new System.Xml.XPath.XPathResultType[]
            {
                System.Xml.XPath.XPathResultType.String,
                System.Xml.XPath.XPathResultType.String,
                System.Xml.XPath.XPathResultType.String
            };
        }
    }

    public object Invoke(System.Xml.Xsl.XsltContext xsltContext, object[] args, System.Xml.XPath.XPathNavigator docContext)
    {
        string arg1 = XsltContext.GetValue(args[0]);
        string arg2 = XsltContext.GetValue(args[1]);

        string locale = "en-US";

        if (args.Length > 2)
            locale = XsltContext.GetValue(args[2]);

        System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.GetCultureInfo(locale);

        return string.Compare(arg1, arg2, false, culture);
    }

    public int Maxargs
    {
        get
        {
            return 3;
        }
    }

    public int Minargs
    {
        get 
        {
            return 2;
        }
    }

    public System.Xml.XPath.XPathResultType ReturnType
    {
        get
        {
            return System.Xml.XPath.XPathResultType.Number;
        }
    }
}
like image 83
ertan Avatar answered Oct 21 '22 01:10

ertan


You can use the compiled XPath, or dynamic with Linqtoxml and XElement:

        XPathCustomContext context = new XPathCustomContext(new NameTable());
        context.AddNamespace("windward", XPathCustomContext.Namespace);

        XmlDocument document = new XmlDocument();
        string records = @"
        <records>
            <record id=""m""/>
            <record id=""M""/>
            <record id=""l""/>
        </records>
        ";
        document.LoadXml(records);

        string xpath = @"//record[my:string-compare(@id,""m"")]";

        //solution 1
        XPathExpression compiledXPath = XPathExpression.Compile(xpath, context);
        compiledXPath.SetContext(context);
        XPathNavigator nav = document.CreateNavigator();
        object res = nav.Evaluate(compiledXPath);

        //solution 2
        XElement elm = XElement.Parse(records);
        IEnumerable<XElement> targets = elm.XPathSelectElements(xpath, context);

My compare function:

public class MyStringCompare : IWindwardContextFunction
{
    public System.Xml.XPath.XPathResultType[] ArgTypes
    {
        get
        {
            return new System.Xml.XPath.XPathResultType[]
        {
            System.Xml.XPath.XPathResultType.String,
            System.Xml.XPath.XPathResultType.String,
            System.Xml.XPath.XPathResultType.String
        };
        }
    }
    /// <summary>
    /// The function name.
    /// </summary>
    public string FunctionName
    {
        get { return "string-compare"; }
    }

    public object Invoke(System.Xml.Xsl.XsltContext xsltContext, object[] args, System.Xml.XPath.XPathNavigator docContext)
    {

        string arg1 = "";// Convert.ToString(args[0]);
        object arg1Obj = args[0];
        IEnumerable list = arg1Obj as IEnumerable;
        if (arg1Obj != null)
        {
            IEnumerator listit = list.GetEnumerator();
            listit.MoveNext();

            XPathNavigator nav = (XPathNavigator)listit.Current;
            arg1 = nav.Value;
        }

        string arg2 = Convert.ToString(args[1]);

        string locale = "en-US";

        if (args.Length > 2)
            locale = Convert.ToString(args[2]);

        System.Globalization.CultureInfo culture = CultureInfo.GetCultureInfo(locale);

        return string.Compare(arg1, arg2, true) == 0;
    }

    public int Maxargs
    {
        get
        {
            return 3;
        }
    }

    public int Minargs
    {
        get
        {
            return 2;
        }
    }

    public System.Xml.XPath.XPathResultType ReturnType
    {
        get
        {
            return System.Xml.XPath.XPathResultType.Number;
        }
    }
}
like image 26
roland Avatar answered Oct 21 '22 00:10

roland