1

If we have this xml file and I want to use the attribute Author as a variable to get the title

<bookstore>
 <book author="Tommy">
   <title>Emma</title>
 </book>
</bookstore>

I know that I must write this

string au = "Tommy";
string query = String.Format("//bookstore/book[@author={0}]/title", au);

If we also have this example and I want to get the title

<bk:bookstore xmlns:bk="http://www.example.com/">
  <book author="Tommy">
    <title>Emma</title>
  </book>
</bk:bookstore>

I know that I must write this

XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("bk", "http://www.test.com/");
XmlNodeList elements0 = xml.SelectNodes("//bk:bookstore/book/title", nsmgr);

But I don't know what to do if I have the second example and I also want to use the attribute Author as a variable. I have tried this

string au = "Tommy";
XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("bk", "http://www.test.com/");
XmlNodeList elements0 = xml.SelectNodes("//bk:bookstore/book[@author={0}]/title", au, nsmgr); 

or this

XmlNodeList elements0 = xml.SelectNodes("//bk:bookstore/book[@author={0}]/title", nsmgr, au); 

or this

XmlNodeList elements0 = xml.SelectNodes("//bk:bookstore/book[@author={1}]/title", nsmgr, au); 

but it doesn't work. Could anybody help me?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Sakis M
  • 13
  • 3
  • 1
    Your XML has the namespace `http://www.example.com/`, but you're using `http://www.test.com/` in your namespace manager. – JLRishe Jan 02 '15 at 17:12

1 Answers1

1

You are mixing things up.

First build a path. Then use it. The namespace manager only matters for the second step.

string au = "Tommy";
string path = String.Format("//bk:bookstore/book[@author = '{0}']/title", au);

XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
nsmgr.AddNamespace("bk", "http://www.test.com/");

XmlNodeList elements0 = xml.SelectNodes(path, nsmgr); 

Note the single quotes in the path.


Also note that this will break when there is a single quote in the input string (au). This can be a source of random run-time errors in the best case and attack vector for XPath injection in the worst case. Which means you must handle that situation.

Your options to work around this issue are:

  1. Explicitly forbid single quotes in the input and additionally use au.Replace("'", "") when building the path. This is probably not suitable for person names.
  2. Build a more complex path that allows single quotes. This isn't trivial since XPath does not have an escaping mechanism for strings.
  3. Use a more advanced way of defining an XPath query.

For option 2, assuming the author "O'Reilly", the path would need to look like this:

//bk:bookstore/book[@author = concat('O', "'", 'Reilly')]/title

because the expression concat('O', "'", 'Reilly') produces the string "O'Reilly" in the XPath engine. Using concat() in this manner is the only way to embed delimiting quotes into XPath strings.

This function produces such an expression:

public static string XPathEscape(string input)
{
    if (String.IsNullOrEmpty(input)) return "''";

    if (input.Contains("'"))
    {
        string[] parts = input.Split("'".ToCharArray());
        return String.Format("concat('{0}')", String.Join(@"', ""'"", '", parts));
    }
    else
    {
        return String.Format("'{0}'", input);
    }
}

use it as follows:

string au = "O'Reilly";
string path = String.Format("//bk:bookstore/book[@author = {0}]/title", XPathEscape(au));

Note that there are no single quotes in the path this time.

Community
  • 1
  • 1
Tomalak
  • 332,285
  • 67
  • 532
  • 628