delphi - MSXML XPath 可以选择属性吗? (更新: real issue was with default no-prefix namespace )

标签 delphi xpath delphi-xe2 msxml txmldocument

我想尝试使用 MSXML 和 XPath 解析 Excel XML 电子表格文件。

它的根元素为 <Workbook xmlns.... xmlns....>和一堆下一级节点 <Worksheet ss:Name="xxxx"> .

<?xml version="1.0" encoding="UTF-8"?>
<?mso-application progid="Excel.Sheet"?>
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:x="urn:schemas-microsoft-com:office:excel"
 xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
 xmlns:html="http://www.w3.org/TR/REC-html40">

....

 <Worksheet ss:Name="Карточка">

....

 </Worksheet>
 <Worksheet ss:Name="Баланс">
...
...
...
  </Worksheet>
</Workbook>

在某个步骤中,我想使用 XPath 来获取工作表的名称。

注意:我不想间接获取名称,即选择那些 Worksheet首先手动枚举节点,然后读取它们的 ss:Name子属性节点。我能做到,但这不是这里的主题。

我想要的是利用 XPath 的灵 active :直接获取那些 ss:Name没有额外间接层的节点。

procedure DoParseSheets( FileName: string );
var
  rd: IXMLDocument;
  ns: IDOMNodeList;
  n: IDOMNode;
  sel: IDOMNodeSelect;
  ms:  IXMLDOMDocument2;
  ms1: IXMLDOMDocument;
  i: integer;
  s: string;
begin
  rd := TXMLDocument.Create(nil);

  rd.LoadFromFile( FileName );

  if Supports(rd.DocumentElement.DOMNode,
     IDOMNodeSelect, sel) then
  begin
    ms1 := (rd.DOMDocument as TMSDOMDocument).MSDocument;
    if Supports( ms1, IXMLDOMDocument2, ms) then begin
       ms.setProperty('SelectionNamespaces',
            'xmlns="urn:schemas-microsoft-com:office:spreadsheet" '+
            'xmlns:o="urn:schemas-microsoft-com:office:office" '+
            'xmlns:x="urn:schemas-microsoft-com:office:excel" '+
            'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"');
       ms.setProperty('SelectionLanguage', 'XPath');
    end;

//    ns := sel.selectNodes('/Workbook/Worksheet/@ss:Name/text()');
//    ns := sel.selectNodes('/Workbook/Worksheet/@Name/text()');
    ns := sel.selectNodes('/Workbook/Worksheet/@ss:Name');
//    ns := sel.selectNodes('/Workbook/Worksheet/@Name');
//    ns := sel.selectNodes('/Workbook/Worksheet');

    for i := 0 to ns.length - 1 do
    begin
      n := ns.item[i];
      s := n.nodeValue;
      ShowMessage(s);
    end;
  end;
end;

当我使用简化的 '/Workbook/Worksheet' 时查询 MSXML 正确返回节点。但是,一旦我将属性添加到查询中,MSXML 就会返回空集。

其他 XPath 实现,例如 XMLPad Pro 或 http://www.freeformatter.com/xpath-tester.html正确返回 ss:Name 的列表属性节点。但 MSXML 没有。

帮助 MSXML 返回具有给定名称的属性节点的 XPath 查询文本是什么?

UPD。 @koblik 建议链接到 MS.Net 选择器(不是 MSXML),那里有两个例子 https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx

  • 示例 1:book[@style] - 当前上下文中具有样式属性的所有元素
  • 示例 2:book/@style - 当前上下文中所有元素的样式属性

这就是我在上面的“注意”中所说的区别:我不需要那些 book s,我需要style s。我需要属性节点,而不是元素节点! 示例 2 的语法似乎是 MSXML 失败的地方。

UPD.2:一位测试人员展示了一个有趣的错误声明: XPath 查询的默认(无前缀)命名空间 URI始终为 '',并且无法重新定义为 'urn:schemas-microsoft-com:office:spreadsheet' 我想知道关于 XPath 中没有默认 namespace 的声明是否真的是标准的一部分,或者只是 MSXML 实现限制的一部分。 http://i.imgbox.com/gw9v28ax.png

然后,如果删除默认 NS,结果应该是这样的: 变体 1: 变体 2:

我想知道 XPath 中没有默认 namespace 的说法是否真的是标准的一部分,或者只是 MSXML 实现限制的一部分。

UPD.3:Martin Honnen 在评论中解释了这一行:请参阅 w3.org/TR/xpath/#node-tests for XPath 1.0(由 Microsoft MSXML 支持),它明确指出“节点测试中的 QName 是使用表达式上下文中的命名空间声明扩展为扩展名称。这与开始和结束标记中的元素类型名称的扩展方式相同,只是不使用使用 xmlns 声明的默认命名空间:如果 QName 不使用有前缀,则命名空间 URI 为 null”。因此,在 XPath 1.0 中,像“/Workbook/Worksheet”这样的路径会在没有命名空间的情况下选择该名称的元素。

UPD.4:因此选择适用于 '/ss:Workbook/ss:Worksheet/@ss:Name' XPath查询,直接返回“ss:Name”属性节点。在源 XML 文档中,默认(无前缀)和“ss:”命名空间都绑定(bind)到相同的 URI。该 URI 由 XPath 引擎确认。但不是默认命名空间,它不能在 MSXML XPath 引擎中重新定义(实现 1.0 规范)。因此,为了使其正常工作,默认 namespace 应通过 URI 映射到另一个显式前缀(无论是现有前缀还是新创建的前缀),然后在 XPath 选择字符串中使用该替代前缀。由于命名空间匹配是通过 URI 而不是通过前缀进行的,因此文档和查询中使用的前缀是否匹配并不重要,它们将通过其 URI 进行比较。

ms.setProperty('SelectionLanguage', 'XPath');
ms.setProperty('SelectionNamespaces',
   'xmlns:AnyPrefix="urn:schemas-microsoft-com:office:spreadsheet"');

然后

ns := sel.selectNodes( 
       '/AnyPrefix:Workbook/AnyPrefix:Worksheet/@AnyPrefix:Name' );

感谢 Asbjørn 和 Martin Honnen 解释了这些琐碎的事后但不明显的先验关系。

最佳答案

问题是 MSXML 在使用 XPath 时不支持默认 namespace 。为了克服这个问题,您必须为默认 namespace 指定一个显式前缀,并使用:

ms.setProperty('SelectionNamespaces',
  'xmlns:d="urn:schemas-microsoft-com:office:spreadsheet" '+
  'xmlns:o="urn:schemas-microsoft-com:office:office" '+
  'xmlns:x="urn:schemas-microsoft-com:office:excel" '+
  'xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"');

请注意我如何将 d 前缀添加到默认命名空间。然后你可以像这样进行选择:

ns := sel.selectNodes('/d:Workbook/d:Worksheet/@ss:Name');

这样做的原因是,在解析 XML 数据时,MSXML 将命名空间与每个节点相关联。在此阶段,它确实处理默认命名空间,因此 Workbook 元素与 urn:schemas-microsoft-com:office:spreadsheet 命名空间关联。

但是请注意,它存储命名空间前缀!因此,在设置 SelectionNamespaces 时,您可以使用自己的命名空间前缀。

现在,在进行 XPath 选择时,如果节点具有 namespace ,则必须为 XPath 中的所有元素指定 namespace ,就像上面的示例一样。然后您可以使用通过 SelectionNamespaces 设置的自己的前缀。

关于delphi - MSXML XPath 可以选择属性吗? (更新: real issue was with default no-prefix namespace ),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39707666/

相关文章:

delphi - 如何显示TActionMainMenuBar组件的自定义对话框?

delphi - 在 DevExpress 网格中根据提示显示单元格内容

javascript - Delphi 欢迎页面不会加载菜单

delphi - 在Delphi中调试时如何可视化指针的值?

XPath:如何使用 A B--AB 搜索 AB??AB 或 AB?-AB

java - 直接用xpath检查xml元素业务规则或将其转换为java对象

javascript - 使用 CMD 或 JavaScript 批量查找和删除部分 HTML 文件

multithreading - 德尔福: How to create and use Thread locally?

delphi - Application.terminate会导致内存泄漏吗?

delphi - 如何像那里一样正确发送 POST 请求?