首页 文章

MSXML XPath可以选择属性吗? (UPD:真正的问题是默认的无前缀命名空间)

提问于
浏览
1

我想尝试使用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

  • 示例1:具有当前上下文的样式属性的 book[@style] - All elements .

  • 示例2: book/@style - The style attribute for 当前上下文的所有元素 .

这就是我在上面的"NOTE"中所说的差异:我不需要那些 book ,我需要 style . 我需要属性节点,而不是元素节点!并且示例2语法是MSXML似乎失败的原因 .

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

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

变体2:

我想知道在XPath中没有默认名称空间的声明是否真的是标准或MSXML实现限制的一部分 .

UPD.3:Martin Honnen在评论中解释了这一行:请参阅w3.org/TR/xpath/#node-tests for XPath 1.0(由Microsoft MSXML支持),它明确指出“节点测试中的一个QName扩展为一个expand-name使用表达式上下文中的名称空间声明 . 这与开始和结束标记中的元素类型名称的扩展相同,只是不使用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解释那些微不足道的事后但不明显的先验关系 .

1 回答

  • 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 .

相关问题