.NET:有没有办法在XPath 1.0查询中创建默认命名空间?

时间:2010-04-01 16:01:20

标签: .net xml xpath

我正在构建一个在XHTML文档上执行xpath 1.0查询的工具。在查询中使用命名空间前缀的要求正在扼杀我。查询如下所示:

html/body/div[@class='contents']/div[@class='body']/
    div[@class='pgdbbyauthor']/h2[a[@name][starts-with(.,'Quick')]]/
    following-sibling::ul[1]/li/a

(全部在一条线上)

...这已经够糟了,除非因为它是xpath 1.0,我需要在每个QName上使用显式名称空间前缀,所以它看起来像这样:

ns1:html/ns1:body/ns1:div[@class='contents']/ns1:div[@class='body']/
    ns1:div[@class='pgdbbyauthor']/ns1:h2[ns1:a[@name][starts-with(.,'Quick')]]/
    following-sibling::ns1:ul[1]/ns1:li/ns1:a

要设置查询,我会执行以下操作:

var xpathDoc = new XPathDocument(new StringReader(theText));
var nav = xpathDoc.CreateNavigator();
var xmlns = new XmlNamespaceManager(nav.NameTable);
foreach (string prefix in xmlNamespaces.Keys)
    xmlns.AddNamespace(prefix, xmlNamespaces[prefix]);    
XPathNodeIterator selection = nav.Select(xpathExpression, xmlns);

但我想要的是xpathExpression使用隐式默认命名空间。

有没有办法让我在编写之后转换unadorned xpath表达式,为查询中的每个元素名称注入一个名称空间前缀?

我在想,两个斜杠之间的任何东西,我都可以在那里注入一个前缀。当然除了“parent ::”和“preceding-sibling ::”之类的轴名称。和通配符。这就是我所说的“finagle默认命名空间”。

这个黑客会起作用吗?


附录
这就是我的意思。假设我有一个xpath表达式,在将它传递给nav.Select()之前,我将其转换。像这样:

string FixupWithDefaultNamespace(string expr)
{
    string s = expr;
    s = Regex.Replace(s, "^(?!::)([^/:]+)(?=/)", "ns1:$1");                        // beginning
    s = Regex.Replace(s, "/([^/:]+)(?=/)", "/ns1:$1");                             // stanza
    s = Regex.Replace(s, "::([A-Za-z][^/:*]*)(?=/)", "::ns1:$1");                  // axis specifier
    s = Regex.Replace(s, "\\[([A-Za-z][^/:*\\(]*)(?=[\\[\\]])", "[ns1:$1");        // predicate
    s = Regex.Replace(s, "/([A-Za-z][^/:]*)(?!<::)$", "/ns1:$1");                  // end
    s = Regex.Replace(s, "^([A-Za-z][^/:]*)$", "ns1:$1");                          // edge case
    s = Regex.Replace(s, "([-A-Za-z]+)\\(([^/:\\.,\\)]+)(?=[,\\)])", "$1(ns1:$2"); // xpath functions

    return s;
}

这实际上适用于我尝试过的简单案例。要使用上面的示例 - 如果输入是第一个xpath表达式,我得到的输出是第二个,带有所有ns1前缀。真正的问题是,由于xpath表达式变得更复杂,期望这种Regex.Replace方法能够工作是否无望?

4 个答案:

答案 0 :(得分:2)

不,XPath W3C spec is explicit about this

  

“节点测试中的QName已扩展   使用。扩展名称   来自的命名空间声明   表达背景。这是一样的   方式扩展是为元素类型完成的   start和end-tags中的名称除外   声明的默认命名空间   不使用xmlns:如果是QName   没有前缀,那么   namespace URI为null(这是   属性名称相同   扩展)。如果QName是错误的   有一个前缀,没有   名称空间声明   表达语境。“

任何试图动态“按摩”事先知道XPath表达式以使其在这种情况下成功的尝试通常会失败,这意味着一个人应该能够执行完整的解析XPath表达式并隔离所有不是轴,运算符或函数名称的元素名称 - 这不是我要求任何人做的事情。甚至可能非常难以正确找到每个位置步骤的开始("/"运算符),因为字符串“/”可能是文字字符串表达式的一部分。

需要完整解析的(子)表达式的一个示例是:

<强> div div div

如果XPath表达式会对它们施加一些限制,那么这种方法可能会取得一些有限的成功,但我再也不建议去做,因为没有人可以证明 RegExes在所有情况下都能正常工作。

答案 1 :(得分:2)

如果您知道只有一个命名空间(即XHTML命名空间)并将其定义为默认命名空间,那么您可以通过使用不支持命名空间的XmlTextReader处理它来作弊,如下所示:

            XmlTextReader tr = new XmlTextReader(new StringReader(@"<html xmlns=""http://www.w3.org/1999/xhtml"">
  <head>
    <title>Test</title>
  </head>
  <body>
    <h1>Example</h1>
  </body>
</html>"));
            tr.Namespaces = false;
            XPathDocument doc = new XPathDocument(tr);
            tr.Close();
            Console.WriteLine(doc.CreateNavigator().SelectSingleNode("html/body/h1").Value);

这适用于我并输出“示例”,因此路径“html / body / h1”找到“h1”元素。 其他选项是首先通过一些样式表运行输入以去除名称空间,然后使用剥离的名称空间处理转换结果。

当然,您可以考虑不依赖Microsoft XPath 1.0实现,而是转向第三方XPath 2.0或XQuery 1.0实现,如Saxon或类似XQSharp。然后,您可以为XPath或XQuery表达式定义默认元素名称空间,并使用不带前缀的路径来选择XHTML名称空间中的元素。

答案 2 :(得分:1)

这是一个廉价且快速的黑客攻击,完全消除了默认命名空间:更改xmlns属性的名称。

e.g。如果您已经在字符串变量中获得了xml,请在实际创建XPathDocument之前执行此操作:

xml = xml.Replace(" xmlns="," xxxxx=");

(在我的机器上的93kb文件上花了0.00065秒。)

然后,您可以自由使用精彩的无前缀XPath查询。

答案 3 :(得分:0)

我一直在使用默认命名空间regex hack一段时间,它似乎工作正常。我在Stackoverflow上找到了原始版本并添加了一些修改:

    s = Regex.Replace(s, "^(?!(::|([A-Za-z][-A-Za-z]+\\(.+\\))))([^/:]+)(?=/)", prefix + ":$1");                             // beginning
    s = Regex.Replace(s, "/([^\\.^@^/:\\*\\(]+)(?=[/\\[])", "/" + prefix + ":$1"); //segment with fixed attribute
    s = Regex.Replace(s, "(child|descendant|ancestor|ancestor-or-self|descendant-or-self|self|parent|following|following-sibling|preceding|preceding-sibling)::((?!([\\w]*\\(\\)))[A-Za-z][^/:*]*)((?=/)|(?=\\b))", "$1::" + prefix + ":$2");                  // axis specifier
    s = Regex.Replace(s, "\\[([A-Za-z][^/:*\\(]*)(?=[\\[\\]])", "[" + prefix + ":$1");        // within predicate
    s = Regex.Replace(s, "/([A-Za-z][^/:\\*\\(]*)(?!<::)$", "/" + prefix + ":$1");               // end
    s = Regex.Replace(s, "^([A-Za-z][^/:]*)$", prefix + ":$1");                               // edge case
    s = Regex.Replace(s, "([A-Za-z][-A-Za-z]+)\\(([^\\.^@^/:\\.,\\(\\)]+)(?=[,\\)])", "$1(" + prefix + ":$2"); // xpath functions with fixed attributes