Может ли MSXML XPath выбирать атрибуты? (UPD: реальная проблема была с пространством имен без префикса по умолчанию)

Я хочу попробовать разобрать файл электронной таблицы Excel XML с помощью MSXML и XPath.

Он имеет корневой элемент <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;

Когда я использую упрощенный '/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] - Все элементы с атрибутами стиля текущего контекста.
  • Пример 2: book/@style - Атрибут стиля для всех элементов текущего контекста.

Вот в чем разница, которую я сказал в «ПРИМЕЧАНИЕ» выше: мне не нужны эти book, мне нужны style. Мне нужны узлы-атрибуты, а не узлы-элементы! И этот синтаксис примера 2 - это то, в чем MSXML, похоже, терпит неудачу.

UPD.2: один тестер показывает интересное сообщение об ошибке: URI пространства имен по умолчанию (без префикса) для запросов XPath всегда '', и его нельзя переопределить на 'urn: schemas-microsoft- com: office: spreadsheet ' Интересно, действительно ли это утверждение об отсутствии пространств имен по умолчанию в XPath является частью стандартного ограничения реализации или просто ограничения реализации MSXML. http://i.imgbox.com/gw9v28ax.png

Затем, если удалить NS по умолчанию, результаты будут такими, какими они должны быть: Вариант 1: Вариант 2:

Интересно, действительно ли это утверждение об отсутствии пространств имен по умолчанию в XPath является частью стандартного ограничения реализации MSXML или просто ограничения его реализации.

UPD.3: Мартин Хоннен в комментариях объясняет эту строку: См. W3.org/TR/xpath/#node-tests для XPath 1.0 (как поддерживается Microsoft MSXML), в нем четко указано: «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' );

Спасибо Асбьёрну и Мартину Хоннену за объяснение этих тривиальных постфактум, но не очевидных априорных соотношений.


person Arioch 'The    schedule 26.09.2016    source источник
comment
Что-то вроде *[@Name] или *[@ss:Name]. см. msdn.microsoft.com/en-us /library/ms256086(v=vs.110).aspx   -  person kobik    schedule 26.09.2016
comment
[@ss:Name] у меня отлично работает, как упоминает @kobik.   -  person Ken White    schedule 26.09.2016
comment
Что именно он возвращает? Список Worksheet узлов (неверно!) Или список ss:Name узлов (правильно)? Вот в чем разница. Я сделал обновление и повторил сказанное выше.   -  person Arioch 'The    schedule 26.09.2016
comment
Возможно, если бы вы предоставили более полезный образец XML и показали результаты, которые вы пытаетесь получить из этого образца, это упростило бы понимание того, что вам нужно. Я получаю список узлов с атрибутом @ss:Name.   -  person Ken White    schedule 26.09.2016
comment
Получаю тот же результат, что и @kobik и KenW. Мне, должно быть, не хватает вашей точки или списка узлов ss: Name (правильно) Что может содержать список узлов ss: Name, кроме повторений буквального текста ss: Name?   -  person MartynA    schedule 26.09.2016
comment
Учитывая установленный вами SelectionNamespaces и SelectionLanguage как XPath, вам понадобится путь ns := sel.selectNodes('/ss:Workbook/ss:Worksheet/@ss:Name');.   -  person Martin Honnen    schedule 26.09.2016
comment
@MartynA значения этих атрибутов, конечно.   -  person Arioch 'The    schedule 26.09.2016
comment
@KenWhite вы получаете список элементов (тегов). Я могу получить список тегов, но хочу пропустить дополнительный этап обхода этих элементов, чтобы получить их внутренние узлы атрибутов. Я хочу получить список самих атрибутов, а не элементов, содержащих эти атрибуты. PS. Элементы действительно являются узлами. Но атрибуты истинные узлы. Так что список узлов здесь немного двусмысленный. Мне тоже нужен список узлов, но мне нужны узлы, которые являются атрибутами, а не тегами.   -  person Arioch 'The    schedule 26.09.2016
comment
См. w3.org/TR/xpath/#node-tests для XPath 1.0 (как поддерживается Microsoft MSXML), в нем четко указано, что QName в тесте узла расширяется до расширенного имени с использованием объявлений пространства имен из контекста выражения. Таким же образом выполняется расширение для имен типов элементов в начальных и конечных тегах, за исключением того, что пространство имен по умолчанию, объявленное с помощью xmlns, не используется: если QName не имеет префикса, то URI пространства имен имеет значение null. Таким образом, в XPath 1.0 такой путь, как / Workbook / Worksheet, выбирает элементы с этим именем без пространства имен.   -  person Martin Honnen    schedule 26.09.2016
comment
Я задал аналогичный вопрос, но с помощью TXMLDocument (который, если я помню, внутренне использует MSXML) ... stackoverflow.com/questions/30687619/   -  person Jerry Dodge    schedule 27.09.2016
comment
@JerryDodge Здесь я тоже использую TXMLDocument, как вы можете видеть в тегах. Я видел ваш вопрос, и он кажется ощутимо похожим, но это не тот же вопрос. Мой вопрос касается получения узлов-атрибутов, а не узлов-тегов, а не самих пространств имен.   -  person Arioch 'The    schedule 27.09.2016


Ответы (1)


Проблема в том, что 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.

person Community    schedule 26.09.2016
comment
Зачем использовать два префикса d и ss, привязанные к одному и тому же пространству имен, в одном выражении XPath? Вы можете просто использовать /ss:Workbook/ss:Worksheet/@ss:Name. - person Martin Honnen; 26.09.2016
comment
это означало бы перефразировать весь XML-файл, который может быть большим и многочисленным. В качестве альтернативы, возможно, был бы способ удалить пространство имен по умолчанию ... Онлайн-тесты XPath утверждают следующее: URI пространства имен по умолчанию (без префикса) для запросов XPath всегда '', и его нельзя переопределить на 'urn: schemas-microsoft -com: office: spreadsheet ' - я не знаю, правда ли это с точки зрения спецификаций, но если это так - то это значительный перебор ... - person Arioch 'The; 26.09.2016
comment
Я могу просто удалить пространство имен по умолчанию, но тогда могут быть файлы с явно префиксными тегами ... Я проверю ваше объяснение. И если это правильно, похоже, что MSXML XPath просто не подходит для этого ... - person Arioch 'The; 26.09.2016
comment
@ Arioch'The Вам вообще не нужно менять XML-файл. Вам просто нужно указать MSXML использовать определенный префикс для пространства имен по умолчанию и использовать это пространство имен в своих запросах XPath. Два изменения, которые я представил в своем ответе, - это единственные два изменения, которые я внес в ваш код. На работе мы сделали вспомогательные функции для добавления этих префиксов, что немного облегчило жизнь, я добавлю функцию к своему ответу. - person ; 26.09.2016
comment
@MartinHonnen Я сделал это, чтобы сделать более ясным, что это единственное необходимое изменение. Это также то, как структурированы его файлы, поэтому я думаю, что лучше держать его как можно ближе к файлам. - person ; 26.09.2016
comment
Но как сделать d пространством имен по умолчанию? Я думал, что пространство имен по умолчанию - это то, которое не имеет префикса, не так ли? Таким образом, похоже, что вы не переопределяли значение по умолчанию, а просто ввели новое пространство имен d, отличное от используемого по умолчанию. Почему в этом конкретном вашем примере d будет по умолчанию, а ss - нет? Или какое-либо первое пространство имен обрабатывается по умолчанию MSXML XPath? Это означает, что вытягивание строки xmlns: ss в первой позиции также сделает ее значением по умолчанию? - person Arioch 'The; 26.09.2016
comment
@ Arioch'The Nothing делает его по умолчанию, поэтому вам нужно указывать его везде, где вы иначе не указали его (из-за того, что он используется по умолчанию). - person ; 26.09.2016
comment
Расширенный мой ответ, надеюсь, это проясняет ситуацию. И я не включил наш код, потому что мы выбираем только узлы, поэтому он не обрабатывает ваш атрибут @. - person ; 26.09.2016
comment
Я проверю его. Я могу получить список узлов элементов, посмотрю, позволит ли это получить список узлов атрибутов. Спасибо. - person Arioch 'The; 26.09.2016
comment
@ Arioch'The Ты можешь получить атрибуты. Я использовал ваш код и XML с моими модификациями и получил два имени. Единственные изменения, которые я внес, - это то, что я удалил ... из XML (конечно) и изменения, которые я упомянул в своем сообщении. - person ; 26.09.2016