使用XPath查询节点时如何指定名称空间?

时间:2019-02-28 20:12:21

标签: winapi com msxml msxml6

短版

  • 您可以在.NET中执行以下操作:

    XmlNode.SelectNodes(query, selectionNamespaces);
    
  • 您可以使用javascript吗?

  • 可以在msxml中完成吗?

尝试A

IXMLDOMNode.selectNodes(query); //no namespaces option

尝试B

IXMLDOMNode.ownerDocument.setProperty("SelectionNamespaces", selectionNamespaces);
IXMLDOMNode.selectNodes(query); //doesn't work

尝试C

IXMLDOMDocument3 doc;
doc.setProperty("SelectionNamespaces", selectionNamespaces);
IXMLDOMNodeList list = doc.selectNodes(...)[0].selectNodes(query); //doesn't work

长版

给出一个包含XML片段的IXMLDOMNode

<row>
    <cell>a</cell>
    <cell>b</cell>
    <cell>c</cell>
</row>

我们可以使用IXMLDOMNode.selectNodes方法来选择子元素:

IXMLDOMNode row = //...xml above

IXMLDOMNodeList cells = row.selectNodes("/row/cell");

,这将返回 IXMLDOMNodeList

  • <cell>a</cell>
  • <cell>b</cell>
  • <cell>c</cell>

那很好。

但是命名空间打破了它

如果XML片段源自具有名称空间的文档,例如:

<row xmlns:ss="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
    <cell>a</cell>
    <cell>b</cell>
    <cell>c</cell>
</row>

相同的XPath查询不会有任何结果,因为元素rowcell不存在。它们在另一个名称空间中。

使用默认名称空间查询文档

如果您拥有完整的 IXMLDOMDocument ,则可以使用 setProperty 方法来设置选择名称空间

           一种        b        C    

您可以通过为其命名来查询默认名称空间,例如:

  • 之前xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
  • 之后xmlns:peanut="http://schemas.openxmlformats.org/spreadsheetml/2006/main"

然后您可以查询它:

IXMLDOMDocument3 doc = //...document xml above
doc.setProperty("SelectionNamespaces", "xmlns:peanut="http://schemas.openxmlformats.org/spreadsheetml/2006/main");

IXMLDOMNodeList cells = doc.selectNodes("/peanut:row/peanut:cell");

您会得到细胞:

  • <cell>a</cell>
  • <cell>b</cell>
  • <cell>c</cell>

但这不适用于节点

IXMLDOMNode具有一个method to perform XPath queries:

  

selectNodes方法

     

将指定的模式匹配操作应用于此节点的上下文,并将匹配节点的列表作为IXMLDOMNodeList返回。

HRESULT selectNodes(  
      BSTR expression,  
      IXMLDOMNodeList **resultList); 
     

备注

     

有关将selectNodes方法与命名空间一起使用的更多信息,请参见setProperty Method主题。

但是在对DOM节点发出XPath查询时,无法指定选择命名空间。

使用XPath查询节点时如何指定名称空间?

.NET解决方案

.NET的 XmlNode 提供了 SelectNodes 方法,该方法提供接受XmlNamespaceManager参数:

XmlNamespaceManager ns = new XmlNamespaceManager(doc.NameTable);
ns.AddNamespace("peanut", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
cells = row.SelectNodes("/peanut:row/peanut:cell", ns);

但是我不在C#中(我也不在Javascript中)。什么是本地msxml6等效项?

编辑:我不太喜欢Javascript jsFiddle

完成最小示例

program Project3;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils, msxml, ActiveX;

procedure Main;
var
    s: string;
    doc: DOMDocument60;
    rows: IXMLDOMNodeList;
    row: IXMLDOMElement;
    cells: IXMLDOMNodeList;
begin
    s :=
            '<?xml version="1.0" encoding="UTF-16" standalone="yes"?>'+#13#10+
            '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'+#13#10+
            '<row>'+#13#10+
            '    <cell>a</cell>'+#13#10+
            '    <cell>b</cell>'+#13#10+
            '    <cell>c</cell>'+#13#10+
            '</row>'+#13#10+
            '</worksheet>';

    doc := CoDOMDocument60.Create;
    doc.loadXML(s);
    if doc.parseError.errorCode <> 0 then
        raise Exception.CreateFmt('Parse error: %s', [doc.parseError.reason]);

    doc.setProperty('SelectionNamespaces', 'xmlns:ss="http://schemas.openxmlformats.org/spreadsheetml/2006/main"');

    //Query for all the rows
    rows := doc.selectNodes('/ss:worksheet/ss:row');
    if rows.length = 0 then
        raise Exception.Create('Could not find any rows');

    //Do stuff with the first row
    row := rows[0] as IXMLDOMElement;

    //Get the cells in the row
    (row.ownerDocument as IXMLDOMDocument3).setProperty('SelectionNamespaces', 'xmlns:ss="http://schemas.openxmlformats.org/spreadsheetml/2006/main"');
    cells := row.selectNodes('/ss:row/ss:cell');
    if cells.length <> 3 then
        raise Exception.CreateFmt('Did not find 3 cells in the first row (%d)', [cells.length]);
end;

begin
  try
        CoInitialize(nil);
        Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

1 个答案:

答案 0 :(得分:0)

这在MSDN上得到了回答:

How To Specify Namespace when Querying the DOM with XPath

更新

但是,请注意,在第二个示例XML中,将<row>添加到{{1}时,XPath不会查询<cell>xmlns:peanut元素}属性。这就是为什么找不到SelectionNamespaces元素的原因。

要将它们正确放置到名称空间中,您必须:

  • 将名称空间声明更改为使用<cell>而不是xmlns=

    xmlns:ss=
  • 使用<row xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <cell>a</cell> <cell>b</cell> <cell>c</cell> </row> <ss:row>代替<ss:cell><row>

    <cell>

<ss:row xmlns:ss="http://schemas.openxmlformats.org/spreadsheetml/2006/main"> <ss:cell>a</cell> <ss:cell>b</cell> <ss:cell>c</cell> </ss:row> 属性不会为您神奇地将元素放入命名空间,它仅指定XPath查询可以使用哪些命名空间。 XML本身必须根据需要将元素放入适当的名称空间中。

更新

在新示例中,SelectionNamespaces不起作用,因为XPath查询使用的是绝对路径,其中前导cells := row.selectNodes('/ss:row/ss:cell');从文档根目录开始,并且存在XML文档顶部没有/元素,只有<row>元素。这就是<worksheet>起作用的原因。

如果要执行从要查询的节点开始的XPath查询,请不要使用绝对路径,而应使用相对路径:

rows := doc.selectNodes('/ss:worksheet/ss:row');

或者简单地:

cells := row.selectNodes('ss:row/ss:cell');