MSXML XPath select 属性可以吗? (更新:真正的问题是默认的 no-prefix 命名空间)
Can MSXML XPath select attributes? ( UPD: real issue was with default no-prefix namespace )
我想尝试使用 MSXML 和 XPath 解析 Excel XML 电子表格文件。
- https://technet.microsoft.com/en-us/magazine/2006.01.blogtales
- https://msdn.microsoft.com/en-us/library/aa140066.aspx
它有一个根元素<Workbook xmlns.... xmlns....>
和一堆next-level个节点<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 来获取工作表的名称。
注意:我不想间接获取名称,即首先 select 那些 Worksheet
节点然后手动枚举它们读取它们的 ss:Name
子属性节点。我能做到,这不是这里的主题。
我想要的是利用 XPath 的灵活性:直接获取那些 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 正确 return 节点。但是一旦我将属性添加到查询中 - MSXML returns empty set.
其他 XPath 实现,如 XMLPad Pro 或 http://www.freeformatter.com/xpath-tester.html 正确 return ss:Name
属性节点列表。但是 MSXML 没有。
帮助 MSXML return 具有给定名称的属性节点的 XPath 查询文本是什么?
更新。 @koblik 建议 link 到 MS.Net select 或(不是 MSXML 一个),那里有两个例子
https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx
- 示例 1:
book[@style]
- 当前上下文中具有样式属性的所有元素。
- 示例 2:
book/@style
- 当前上下文的所有元素的样式属性。
这就是我在上面 "NOTE" 中所说的区别:我不需要那些 book
,我需要 style
。我需要 attribute-nodes,而不是 element-nodes!
示例 2 的语法是 MSXML 似乎失败的原因。
UPD.2:一位测试人员显示了一个有趣的错误声明:
XPath 查询的默认(无前缀)命名空间 URI 始终为 '',并且无法将其重新定义为 'urn:schemas-microsoft-com:office:spreadsheet'
我想知道关于 XPath 中没有默认名称空间的声明是否真的是标准的一部分或只是 MSXML 实现限制。
那么如果删除默认的 NS 结果应该是这样的:
变体 1:
变体 2:
我想知道关于 XPath 中没有默认名称空间的声明是否真的是标准的一部分或只是 MSXML 实现限制。
UPD.3:Martin Honnen 在评论中解释了这一行:请参阅 w3.org/TR/xpath/#node-tests for XPath 1.0(Microsoft MSXML 支持),它清楚州 "A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null"。因此,在 XPath 1.0 中,类似“/Workbook/Worksheet”的路径 selects 没有名称空间中该名称的元素。
UPD.4:因此 selection 使用 '/ss:Workbook/ss:Worksheet/@ss:Name'
XPath 查询,returning "ss:Name" 属性节点目录。在源 XML 文档中,默认 (no-prefix) 和 "ss:" 命名空间都绑定到相同的 URI。此 URI 由 XPath 引擎确认。但不能在 MSXML XPath 引擎(实现 1.0 规范)中重新定义默认命名空间。因此,要使其工作,默认名称空间应通过 URI 映射到另一个显式前缀(已经存在的或新创建的),然后该替代前缀将用于 XPath selection 字符串。由于名称空间匹配是通过 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 解释那些微不足道的 after-the-fact 但不明显的先验关系。
问题是 MSXML 在使用 XPath 时不支持默认名称空间。为了克服这个问题,你必须给默认命名空间一个明确的前缀,并使用这个:
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 选择时,如果节点有名称空间,则必须为 XPath 中的所有元素指定名称空间,如我上面的示例。然后你使用你自己设置的前缀 SelectionNamespaces
.
我想尝试使用 MSXML 和 XPath 解析 Excel XML 电子表格文件。
- https://technet.microsoft.com/en-us/magazine/2006.01.blogtales
- https://msdn.microsoft.com/en-us/library/aa140066.aspx
它有一个根元素<Workbook xmlns.... xmlns....>
和一堆next-level个节点<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 来获取工作表的名称。
注意:我不想间接获取名称,即首先 select 那些 Worksheet
节点然后手动枚举它们读取它们的 ss:Name
子属性节点。我能做到,这不是这里的主题。
我想要的是利用 XPath 的灵活性:直接获取那些 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 正确 return 节点。但是一旦我将属性添加到查询中 - MSXML returns empty set.
其他 XPath 实现,如 XMLPad Pro 或 http://www.freeformatter.com/xpath-tester.html 正确 return ss:Name
属性节点列表。但是 MSXML 没有。
帮助 MSXML return 具有给定名称的属性节点的 XPath 查询文本是什么?
更新。 @koblik 建议 link 到 MS.Net select 或(不是 MSXML 一个),那里有两个例子 https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx
- 示例 1:
book[@style]
- 当前上下文中具有样式属性的所有元素。 - 示例 2:
book/@style
- 当前上下文的所有元素的样式属性。
这就是我在上面 "NOTE" 中所说的区别:我不需要那些 book
,我需要 style
。我需要 attribute-nodes,而不是 element-nodes!
示例 2 的语法是 MSXML 似乎失败的原因。
UPD.2:一位测试人员显示了一个有趣的错误声明:
XPath 查询的默认(无前缀)命名空间 URI 始终为 '',并且无法将其重新定义为 'urn:schemas-microsoft-com:office:spreadsheet'
我想知道关于 XPath 中没有默认名称空间的声明是否真的是标准的一部分或只是 MSXML 实现限制。
那么如果删除默认的 NS 结果应该是这样的:
变体 1:
我想知道关于 XPath 中没有默认名称空间的声明是否真的是标准的一部分或只是 MSXML 实现限制。
UPD.3:Martin Honnen 在评论中解释了这一行:请参阅 w3.org/TR/xpath/#node-tests for XPath 1.0(Microsoft MSXML 支持),它清楚州 "A QName in the node test is expanded into an expanded-name using the namespace declarations from the expression context. This is the same way expansion is done for element type names in start and end-tags except that the default namespace declared with xmlns is not used: if the QName does not have a prefix, then the namespace URI is null"。因此,在 XPath 1.0 中,类似“/Workbook/Worksheet”的路径 selects 没有名称空间中该名称的元素。
UPD.4:因此 selection 使用 '/ss:Workbook/ss:Worksheet/@ss:Name'
XPath 查询,returning "ss:Name" 属性节点目录。在源 XML 文档中,默认 (no-prefix) 和 "ss:" 命名空间都绑定到相同的 URI。此 URI 由 XPath 引擎确认。但不能在 MSXML XPath 引擎(实现 1.0 规范)中重新定义默认命名空间。因此,要使其工作,默认名称空间应通过 URI 映射到另一个显式前缀(已经存在的或新创建的),然后该替代前缀将用于 XPath selection 字符串。由于名称空间匹配是通过 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 解释那些微不足道的 after-the-fact 但不明显的先验关系。
问题是 MSXML 在使用 XPath 时不支持默认名称空间。为了克服这个问题,你必须给默认命名空间一个明确的前缀,并使用这个:
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 选择时,如果节点有名称空间,则必须为 XPath 中的所有元素指定名称空间,如我上面的示例。然后你使用你自己设置的前缀 SelectionNamespaces
.