Как указать пространство имен при запросе узлов с помощью 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 < / strong>, содержащий фрагмент 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 для установки пространства имен выбора:

a b c

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

  • Раньше: 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 Method.

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

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

Решение .NET

XmlNode .NET предоставляет метод 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) < / em>

Полный минимальный пример

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.

person Ian Boyd    schedule 28.02.2019    source источник
comment
@ Бен Это была отличная идея. К сожалению, он просто не работает - он просто не возвращает никаких узлов.   -  person Ian Boyd    schedule 28.02.2019
comment
У тебя есть jsfiddle?   -  person Ben    schedule 28.02.2019
comment
@Ben Я не использую javascript (я использую собственный Win32 COM msxml). У вас есть jsFiddle?   -  person Ian Boyd    schedule 28.02.2019
comment
У меня нет, но должна ли быть возможность воспроизвести проблему с хостом сценариев JScript / Windows?   -  person Ben    schedule 01.03.2019


Ответы (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 использует абсолютный путь, где ведущий / начинается в корне документа, а в верхней части XML-документа отсутствуют элементы <row>, только элемент <worksheet>. Вот почему rows := doc.selectNodes('/ss:worksheet/ss:row'); работает.

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

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

Или просто:

cells := row.selectNodes('ss:cell');
person Remy Lebeau    schedule 01.03.2019
comment
Насколько я могу судить, этот код вызывает Document.setProperty. (т.е. он запрашивает документ, а не узел) - person Ian Boyd; 01.03.2019
comment
Да, потому что _1 _ - это свойство документа DOM в целом, а не отдельных узлов. Когда вы запускаете запрос XPath, независимо от того, на каком узле вы запускаете запрос, он будет использовать текущее свойство документа SelectedNamespaces для разрешения пространств имен в запросе. - person Remy Lebeau; 01.03.2019
comment
За исключением установки SelectionNamespaces документа не разрешает пространства имен в запросе; это просто не работает. Бен уже предлагал это выше. - person Ian Boyd; 01.03.2019
comment
@IanBoyd Это действительно работает, я использовал его раньше. Вы обратили внимание на дополнительную информацию, которую я добавил к своему ответу? - person Remy Lebeau; 01.03.2019
comment
Вы говорите, что добавление свойства SelectionNamespaces для меня не волшебным образом помещает элементы в имя. Почему, когда я добавляю SelectionNamespace, он волшебным образом помещает элементы в правильное для меня пространство имен? К вопросу добавлен полный минимальный пример. - person Ian Boyd; 02.03.2019
comment
@IanBoyd Почему, когда я добавляю SelectionNamespace, он волшебным образом помещает элементы в правильное для меня пространство имен? - нет, он только определяет префиксы, которые могут использоваться в запросе. Вы показали совершенно другой пример XML, в котором элементы <row> и <cell> УЖЕ в пространстве имен через объявление xmlns в родительском элементе <worksheet>. Это не то, о чем вы изначально спрашивали. - person Remy Lebeau; 02.03.2019
comment
И если вы посмотрите на промежуточный xml, на нем есть пространство имен - уже - для меня - без каких-либо указаний. (я предполагаю, что вы знаете, что при выборе узлов пространство имен родителя, дочерние элементы автоматически получают то же пространство имен в результирующем фрагменте xml) - person Ian Boyd; 02.03.2019
comment
@IanBoyd в вашем новом примере запрос XPath для row использует абсолютный путь, тогда как вместо этого он должен использовать относительный путь. Вот почему он не дает никаких результатов. Я обновил свой ответ. - person Remy Lebeau; 02.03.2019