Пространства имен XML и XPath

#c# #xml #xpath

#c# #xml #xpath

Вопрос:

У меня есть приложение, которое должно загружать XML-документ и выходные узлы в зависимости от XPath.

Предположим, я начну с документа, подобного этому:

 <aaa>
  ...[many nodes here]...
  <bbb>text</bbb>
  ...[many nodes here]...
  <bbb>text</bbb>
  ...[many nodes here]...
</aaa>
  

С помощью XPath //bbb

Пока все хорошо.

И выбор doc.SelectNodes("//bbb"); возвращает список требуемых узлов.

Затем кто-то загружает документ с одним node like <myfancynamespace:foo/> и дополнительным пространством имен в корневом теге, и все ломается.

Почему? //bbb ему наплевать на myfancynamespace , теоретически это даже должно быть хорошо с //myfancynamespace:foo , поскольку нет никакой двусмысленности, но выражение возвращает 0 результатов и все.

Существует ли обходной путь для такого поведения?

У меня действительно есть менеджер пространств имен для документа, и я передаю его в запрос Xpath. Но пространства имен и префиксы мне неизвестны, поэтому я не могу добавить их перед запросом.

Должен ли я предварительно проанализировать документ, чтобы заполнить диспетчер пространств имен, прежде чем делать какие-либо выборки? С какой стати такое поведение, это просто не имеет смысла.

Редактировать:

Я использую: XmlDocument и XmlNamespaceManager

ПРАВКА2:

 XmlDocument doc = new XmlDocument();
doc.XmlResolver = null;
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
//I wish I could:
//nsmgr.AddNamespace("magic", "http://magicnamespaceuri/
//...
doc.LoadXML(usersuppliedxml);
XmlNodeList nodes = doc.SelectNodes(usersuppliedxpath, nsmgr);//usersuppliedxpath -> "//bbb"

//nodes.Count should be > 0, but with namespaced document they are 0
  

ПРАВКА3:
Нашел статью, в которой описывается фактический сценарий проблемы с одним обходным решением, но не очень красивым обходным решением: http://codeclimber.net.nz/archive/2008/01/09/How-to-query-a-XPath-doc-that-has-a-default.aspx

Почти кажется, что удаление xmlns — это правильный путь…

Комментарии:

1. Не могли бы вы добавить соответствующие фрагменты кода? (Создание экземпляра XmlDocument, XPath и т.д.)

2. Хорошо, отредактировал сообщение, см. Edit2.

3. @Coder: Вы говорите, что неожиданный ввод приводит к неожиданному выводу для данного процесса. Это вариант использования для проверки .

4. «и дополнительное пространство имен в корневом теге» — это почти тарабарщина. Я полагаю, вы имеете в виду, что в начальном теге самого внешнего элемента есть дополнительное объявление пространства имен. Является ли это объявлением пространства имен по умолчанию ( xmlns="..." )? или это объявление для myfancynamespace ( xmlns:myfancynamespace="..." )? Только первое повлияет на пространство имен <bbb> . Вы не показали нам, как выглядит входной XML-файл, и не описали его четко, из-за чего трудно догадаться, в чем проблема.

5. Когда я сказал ‘Вы не показали нам, как выглядит входной XML’, я имел в виду тот, который вызвал проблему.

Ответ №1:

Вы упускаете из виду всю суть пространств имен XML.

Но если вам действительно нужно выполнить XPath для документов, которые будут использовать неизвестное пространство имен, и вас это действительно не волнует, вам нужно удалить его и перезагрузить документ. XPath не будет работать независимо от пространства имен, если вы не хотите использовать local-name() функцию в каждой точке ваших селекторов.

 private XmlDocument StripNamespace(XmlDocument doc)
{
    if (doc.DocumentElement.NamespaceURI.Length > 0)
    {
        doc.DocumentElement.SetAttribute("xmlns", "");
        // must serialize and reload for this to take effect
        XmlDocument newDoc = new XmlDocument();
        newDoc.LoadXml(doc.OuterXml);
        return newDoc;
    }
    else
    {
        return doc;
    }
}
  

Комментарии:

1. Это очень полезно. Я внедрил метод StripNamespaces() в некоторые из моих собственных проектов, но это гораздо более элегантно, чем у меня. Я полностью заимствую это. 🙂

2. Удаление пространств имен — самый простой подход, так как в противном случае мне нужно создать пользовательский префикс, а пользователи не могут этого знать. Спасибо вам за совет.

3. Важно отметить, что эта функция не удаляет все пространства имен в документе; по-видимому, она предназначена для удаления любого объявления пространства имен по умолчанию из самого внешнего элемента документа, самый внешний элемент которого находится в любом пространстве имен (по умолчанию или ином). Довольно странная спецификация, но если весь документ находится в пространстве имен по умолчанию и не имеет объявлений пространства имен по умолчанию ниже в документе, он будет делать то, что вы хотите.

4. @LarsH, это правильно. Я использовал его специально для документов xhtml, которые действительно соответствовали этому описанию.

5. @DWRoelands, эта процедура любезно предоставлена pluralsight , где вы можете найти дальнейшее обсуждение метода (который, я полностью признаю, является взломом). Следовало бы упомянуть об этом, но я оставил атрибуцию в своей копии 🙂

Ответ №2:

<myfancynamespace:foo/> не обязательно совпадает с <foo/> .

Но я могу понять ваше разочарование , пространства имен действительно имеют значение.поскольку они обычно имеют тенденцию нарушать работу кодов, поскольку различные реализации (C #, Java, …) имеют тенденцию выводить его по-разному.

Я предлагаю вам изменить свой XPath, чтобы разрешить принимать все пространства имен. Например, вместо

 //bbb 
  

Определите это как

 //*[local-name()='bbb']
  

Это должно позаботиться об этом.

Комментарии:

1. Именно пользователь вводит XPath, поэтому я работаю исходя из предположения, что если он ввел «//foo», то он ожидает «foo» из пространства имен по умолчанию, которое не обязательно является «myfancynamespace», но если он вводит «//ns1:foo», то он должен выбрать «foo» из пространства имен «ns1», независимо от того, каков реальный URI этого пространства имен. Кажется, что сценарий прост…

2. Если пользователь вводит XPath, то он должен понимать о пространствах имен и его последствиях. Это наименее понятная функция XML, поэтому я вижу, что у вас, вероятно, есть несколько проблем с пользователями, но сообщите им об этом local-name() , и они смогут быстро их устранить.

3. @Aliostad: 1 «Если пользователь вводит XPath, то он должен понимать, что такое пространства имен» . Я согласен.

4. Да, но учитывая пространство имен xmlns по умолчанию «a: b: c» и тег «bbb», пользователь не может написать запрос «//a: b: c: bbb», потому что это недопустимо. Для этого мне нужно добавить префикс в таблицу имен, скажем «ns1»-> «a: b: c», и только тогда пользователь может запросить «//ns1: bbb».

5. ‘итак, я работаю над предположением, что если он ввел «//foo», то он ожидает «foo» из пространства имен по умолчанию’ — да, вы правы, но это выражение означает foo в пространстве имен по умолчанию среды XPath , тогда как вы, похоже, имеете в виду пространство имен по умолчанию XML-документа (at element foo ). Сбивает с толку то, что они могут быть разными, но если вы подумаете о проверке XML-документов из множества разных источников, вы поймете, почему они должны быть разными.

Ответ №3:

Вам следует немного подробнее описать, что вы хотите сделать. То, как вы задаете свой вопрос, не имеет никакого смысла вообще. Пространство имен — это всего лишь часть имени. Ни больше, ни меньше. Итак, ваш вопрос такой же, как запрос запроса XPath для получения всех тегов, заканчивающихся на «x». Это не идея XML, но если у вас есть странные причины для этого: не стесняйтесь перебирать все узлы и реализовывать это самостоятельно. То же самое относится и к запрашиваемой вами функциональности.

Комментарии:

1. Учитывая случайный XML-документ DOC, выберите все узлы «bbb» в пространстве имен по умолчанию. Или запрос Xpath «//bbb» без учета пространства имен.

2. @Coder: «Пространство имен по умолчанию» не было бы тем, которое следует выбирать <myfancynamespace:foo/>

3. @Coder, в предыдущем комментарии вы представили две разные (несовместимые) спецификации. Последнее имеет смысл, и, похоже, вы знаете, как это сделать. Но ‘Учитывая случайный XML-документ DOC, выберите все узлы «bbb» в пространстве имен по умолчанию’ означает, что поведение вашего приложения изменится, в зависимости не от того, в каких пространствах имен находятся элементы, а в зависимости от того, какое пространство имен объявлено по умолчанию ! Это полностью нарушает семантику пространств имен. Объявления пространств имен и префиксы указаны прозрачными. Только имя и URI пространства имен каждого элемента должны определять его идентичность.

4. @Coder — в моем предыдущем комментарии я предполагал, что под «пространством имен по умолчанию» вы подразумевали пространство имен XML-документа по умолчанию для каждого bbb элемента. Если вы имели в виду пространство имен среды XPath по умолчанию, то проигнорируйте большую часть моего предыдущего комментария.

Ответ №4:

Вы могли бы использовать XML-классы LINQ, такие как XDocument . Они значительно упрощают работу с пространствами имен.