我想尝试使用MSXML和XPath解析Excel XML Spreadsheet文件。
它有一个根元素<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灵活性:直接获取那些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;
当我使用dumbed down '/Workbook/Worksheet'
查询时,MSXML正确返回节点。但是只要我将该属性添加到查询中 - MSXML就会返回空集。
其他XPath实现(如XMLPad Pro或http://www.freeformatter.com/xpath-tester.html)正确返回ss:Name
属性节点列表。但MSXML没有。
什么是XPath查询文本以帮助MSXML返回具有给定名称的属性节点?
UPD。 @koblik建议链接到MS.Net选择器(不是MSXML一个),那里有两个例子 https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx
book[@style]
- 所有元素,具有当前上下文的样式属性。book/@style
- 当前上下文的所有元素的样式属性。这就是我在上面的“注意”中说的差异:我不需要那些book
,我需要style
s。我需要属性节点,而不是元素节点!
而示例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是使用表达式上下文中的名称空间声明扩展为扩展名。这与开始和结束标记中的元素类型名称的扩展相同,除了不使用xmlns声明的默认名称空间:如果QName不有一个前缀,然后名称空间URI为空“。所以在XPath 1.0中,像“/ Workbook / Worksheet”这样的路径在没有名称空间的情况下选择该名称的元素。
UPD.4:因此选择适用于'/ss:Workbook/ss:Worksheet/@ss:Name'
XPath查询,返回“ss:Name”属性节点。在源XML文档中,默认(无前缀)和“ss:”命名空间都绑定到同一URI。此URI由XPath引擎确认。但不是默认命名空间,不能在MSXML XPath引擎中重新定义(实现1.0规范)。因此,要使其工作,应通过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解释那些微不足道的事后但并非明显的先验关系。
答案 0 :(得分:3)
问题是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
设置的自己的前缀。