2016-09-26 12 views
1

Я хочу попробовать развернуть файл Excel XML Spreadsheet с помощью MSXML и XPath.Может ли MSXML XPath выбирать атрибуты? (UPD: реальная проблема была с пространством имен без префикса по умолчанию)

Он имеет корневой элемент <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 всегда «» и не может быть переопределен в «урну: схемы-Microsoft-ком: 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 пространства имен имеет значение null ". Таким образом, в XPath 1.0 такой путь, как «/ Workbook/Worksheet», выбирает элементы этого имени без пространства имен.

UPD.4: Таким образом, выбор работает с запросом XPath '/ss:Workbook/ss:Worksheet/@ss:Name', возвращая «ss: Name» атрибуты node directy. В исходном XML-документе оба пространства имен по умолчанию (без префикса) и "ss:" привязаны к одному и тому же URI. Этот URI подтверждается движком XPath. Но не пространство имен по умолчанию, которое не может быть переопределено в MSXML XPath engine (реализация спецификаций 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'); 

Благодаря Asbjorn и Мартин Honnen для объяснения этих тривиальных после-фактум, но не очевидно, априорные отношения.

+0

Что-то вроде '* [@ Name]' или '* [@ ss: Name]'. см. https://msdn.microsoft.com/en-us/library/ms256086(v=vs.110).aspx – kobik

+0

'[@ss: Name]' отлично работает для меня, как упоминает @kobik. –

+0

Что именно возвращается? Список узлов «Worksheet» (неправильно!) или список узлов 'ss: Name' (правильный)? Вот в чем разница. Я сделал обновление и повторил то, что я сказал выше. –

ответ

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.

+0

Зачем использовать два префикса 'd' и' ss', привязанные к одному и тому же пространству имен в одном выражении XPath? Вы можете просто использовать '/ ss: Workbook/ss: Worksheet/@ ss: Name'. –

+0

, что означало бы переименование всего XML-файла, который может быть большим и многочисленным. В качестве альтернативы, возможно, было бы возможным удалить пространство имен по умолчанию ... В онлайн-тестах XPath утверждается следующее: * По умолчанию (без префикса) URI пространства имен для запросов XPath всегда «и он не может быть переопределен в« urn: schemas-microsoft-com » : office: spreadsheet '* - Я не знаю, соответствует ли это истинным спецификациям, но если это так, то это значительный пробел ... –

+0

Я могу просто удалить пространство имен по умолчанию, но тогда могут быть файлы с явно префиксные теги .... Я проверю ваше объяснение. И если это правильно, то MSXML XPath просто не подходит для этого ... –