代码之家  ›  专栏  ›  技术社区  ›  Cheeso

.net:有没有办法在xpath 1.0查询中定义默认命名空间?

  •  3
  • Cheeso  · 技术社区  · 14 年前

    我正在构建一个对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使用隐式默认名称空间。

    在编写完未修饰的xpath表达式之后,是否有方法为查询中的每个元素名插入一个名称空间前缀?

    我在想,两个斜线之间的任何东西,我都可以在那里插入前缀。除了“父项::”和“前辈::”这样的课程轴名称。还有通配符。这就是我的意思 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 回复  |  直到 11 年前
        1
  •  2
  •   Martin Honnen    14 年前

    如果您知道只有一个名称空间(即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);
    

    这对我很有用,输出“example”,所以路径“html/body/h1”会找到“h1”元素。 其他选项是,首先通过一些样式表运行输入以除去名称空间,然后使用除去的名称空间处理转换结果。

    当然,您可以考虑不依赖microsoft xpath 1.0实现,而转向第三方xpath 2.0或xquery1.0实现,如 Saxon 或者像 XQSharp . 然后可以为xpath或xquery表达式定义默认的元素名称空间,并使用不带前缀的路径来选择xhtml名称空间中的元素。

        2
  •  2
  •   Dimitre Novatchev    14 年前

    不, XPath W3C spec is explicit about this :

    “节点测试中的qname被展开 使用 来自的命名空间声明 表达式上下文。这是一样的 元素类型的展开方式 开始和结束标记中的名称,除了 默认命名空间声明 不使用with xmlns:如果qname 没有前缀,则 命名空间uri为空(这是 属性名也是 扩大)。如果qname 有一个前缀,没有 中的命名空间声明 “表达式上下文。”

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

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

    div div div

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

        3
  •  1
  •   agentnega    12 年前

    这里有一个廉价而快速的方法完全消除了默认名称空间:更改xmlns属性的名称。

    例如,如果XML已经包含在字符串变量中,则在实际创建xpathdocument之前执行以下操作:

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

    (在我的计算机上的93KB文件上花费了0.00065秒。)

    然后就可以自由地使用无前缀的xpath查询了。

        4
  •  0
  •   Bobo    11 年前

    我已经使用默认名称空间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