Как указать пространство имен при запросе узлов с XPath?

Укороченная версия

  • Вы делаете это в.NET с:

    XmlNode.SelectNodes(query, selectionNamespaces);
    
  • Можете ли вы сделать это в JavaScript?

  • Вы можете сделать это в msxml?

Попытка А:

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

Попытка Б:

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

Длинная версия

Дан IXMLDOMNode, содержащий фрагмент xml:

<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 ничего не даст, потому что элементы row а также cell не существует; они находятся в другом пространстве имен.

Запрос документов с пространством имен по умолчанию

Если бы у вас был полный IXMLDOMDocument, вы бы использовали метод setProperty для установки пространства имен выбора:

а б в

Вы бы запросили пространство имен по умолчанию, дав ему имя, например:

  • До: 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 имеет метод для выполнения запросов XPath:

Метод selectNodes

Применяет указанную операцию сопоставления с шаблоном к контексту этого узла и возвращает список совпадающих узлов как IXMLDOMNodeList,

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

замечания

Для получения дополнительной информации об использовании selectNodes метод с пространствами имен, см. тему метода setProperty.

Но невозможно указать пространства имен выбора при отправке запроса XPath к узлу DOM.

Как я могу указать пространство имен при запросе узлов с 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 ответ

Это ответ на MSDN:

Как указать пространство имен при запросе DOM с XPath

Обновить:

Обратите внимание, однако, что во втором примере XML <row> а также <cell> элементы НЕ находятся в пространстве имен, запрашиваемом XPath при добавлении 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>
    

SelectionNamespaces Свойство магически не помещает элементы в пространство имен для вас, оно только указывает, какие пространства имен доступны для использования запроса XPath. Сам XML должен помещать элементы в соответствующие пространства имен по мере необходимости.

Обновить:

В вашем новом примере cells := row.selectNodes('/ss:row/ss:cell'); не работает, потому что запрос XPath использует абсолютный путь, где ведущий / начинается с корня документа, и нет <row> элементы в верхней части документа XML, только <worksheet> элемент. Поэтому rows := doc.selectNodes('/ss:worksheet/ss:row'); работает.

Если вы хотите выполнить запрос XPath, который начинается с запрашиваемого узла, не используйте абсолютный путь, вместо этого используйте относительный путь:

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

Или просто:

cells := row.selectNodes('ss:cell');
Другие вопросы по тегам