2010-03-26 14 views
30

Co to jest XPath (w C# API do XDocument.XPathSelectElements (xpath, nsman), jeśli to ma znaczenie) do wysyłania zapytań do wszystkich MyNodes z tego dokumentu?Jak używać XPath z domyślną przestrzenią nazw bez prefiksu?

<?xml version="1.0" encoding="utf-8"?> 
<configuration> 
    <MyNode xmlns="lcmp" attr="true"> 
    <subnode /> 
    </MyNode> 
</configuration> 
  • Próbowałem /configuration/MyNode co jest źle, ponieważ ignoruje nazw.
  • Próbowałem /configuration/lcmp:MyNode co jest złe, ponieważ lcmp jest identyfikatorem URI, a nie prefiksem.
  • Próbowałem /configuration/{lcmp}MyNode którym powiodło się, ponieważ Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: Nie mogę używać mgr.AddNamespace("df", "lcmp"); jak niektóre Główni odpowiadający sugerowali. Wymaga to, aby program parsujący XML znał wszystkie przestrzenie nazw, które planuję użyć z wyprzedzeniem. Ponieważ ma to być stosowane do dowolnego pliku źródłowego, nie wiem, które przestrzenie nazw, aby ręcznie dodawać prefiksy. Wygląda na to, że {my uri} jest składnią XPath, ale Microsoft nie zawracał sobie głowy implementacją tego ... prawda?

+0

Nie jest jasne, co dokładnie chcesz osiągnąć. Jakie są kryteria, które określają, których węzłów szukasz? Szukasz elementów opartych na ich przestrzeni nazw? W takim przypadku twój kod znałby przestrzeń nazw. Co do {my uri} jest "Składnia XPath", gdzie według specyfikacji XPath 1.0, czy uważasz, że ta składnia została zdefiniowana? Niezależnie od tego, czy wstawisz identyfikator URI przestrzeni nazw w nawiasach klamrowych, czy przekażesz identyfikator URI przestrzeni nazw do metody AddNamespace, nie powinno to mieć znaczenia dla kodu C#, w obu przypadkach identyfikator URI przestrzeni nazw musi być dostępny jako ciąg znaków. –

+0

@Martin: Chciałbym określić przestrzeń nazw w XPath, ale mam tylko identyfikator URI przestrzeni nazw i bez przedrostka przestrzeni nazw. Przyjrzałem się bliżej, skąd "wymyśliłem" {} i prawdopodobnie przeszedłem niepoprawnie ... Mam to z tego odniesienia: http://www.jclark.com/xml/xmlns.htm. Dziękuję za wskazanie tego. Oczywiście, nawet jeśli nie jest to poprawne, wydaje się, że użyteczną rzeczą jest to, aby móc zrobić to łatwo ..;) –

+0

Scott, musisz wybrać dowolny dozwolony prefiks, który ci się podoba, powiązać go z identyfikatorem URI przestrzeni nazw, który używasz AddNamespace (prefiks, namespaceURI) i użyj wybranego prefiksu w wyrażeniu XPath. Tak działa XPath, co najmniej XPath 1.0. Prefiks nie musi w ogóle istnieć w wejściowym kodzie XML lub może być inny niż w wejściowym kodzie XML, wybór elementu nastąpi na podstawie dopasowania przestrzeni nazw, a nie przedrostka. –

Odpowiedz

34

Element configuration znajduje się w bezimiennej przestrzeni nazw, a węzeł MyNode jest powiązany z obszarem nazw lcmp bez prefiksu przestrzeni nazw.

Ten XPATH zestawienie pozwoli Ci zająć element MyNode bez ogłoszony nazw lcmp lub użyć przedrostka przestrzeni nazw w XPATH:

/configuration/*[namespace-uri()='lcmp' and local-name()='MyNode'] 

pasuje dowolny element, który jest dzieckiem configuration i następnie korzysta z filtru predykatów z funkcjami namespace-uri() i local-name(), aby ograniczyć je do elementu MyNode.

Jeśli nie wiesz, który przestrzeni nazw URI zostaną wykorzystane dla elementów, można sprawić, że XPATH bardziej ogólny i po prostu pasuje na local-name():

/configuration/*[local-name()='MyNode'] 

Jednak uruchomieniu ryzyko dopasowania różnych elementów do różnych słowników (związanych z różnymi nazwami-uri), które używają tej samej nazwy.

+0

@Mads: Ah, ciekawe, nie wiedziałem o składni "[namespace-uri() = 'lcmp'" ... powinno działać, a jeśli tak (spróbuję w poniedziałek) zaznaczę to jako odpowiedź. Czy wiesz, czy "/ configuration/{lcmp} MyNode" jest rzeczywiście poprawne i po prostu nie jest obsługiwane przez C#? –

+0

@Scott Nie, składnia, której próbujesz użyć, nie jest poprawną instrukcją XPATH i nie jest obsługiwana w żadnej implementacji, o której mi wiadomo. Mimo że może rozwinąć się do tej nazwy QName, nie możesz jej zaadresować w ten sposób w swoim oświadczeniu XPATH. –

+0

Pracowałem jak urok, wielkie dzięki. –

12

Musisz użyć XmlNamespaceManager następująco:

XDocument doc = XDocument.Load(@"..\..\XMLFile1.xml"); 
    XmlNamespaceManager mgr = new XmlNamespaceManager(new NameTable()); 
    mgr.AddNamespace("df", "lcmp"); 
    foreach (XElement myNode in doc.XPathSelectElements("configuration/df:MyNode", mgr)) 
    { 
     Console.WriteLine(myNode.Attribute("attr").Value); 
    } 
+2

Tak, myślę, że to zadziała, ale nie mogę tego zrobić. Ponieważ kod parsujący XML jest agnostyczny względem rzeczywistego pliku XML i wszystkich używanych przez niego przestrzeni nazw, mgr.AddNamespace ("df", "lcmp"); jest niemożliwą do napisania linią ... –

+2

Ale parsujący kod nie może być agnostyczny dla nazw elementów, prawda?Przestrzeń nazw jest uważana za część nazwy, więc ignorowanie jej jest złym projektem, ale jeśli na pewno nie będzie konfliktów nazw, możesz zrobić coś takiego jak "configuration/* [local-name() = 'MyNode']" –

+0

Scott, proszę wyjaśnić, w jaki sposób twój kod ma identyfikować element, jeśli identyfikator URI przestrzeni nazw nie jest znany? Jaki jest dokładnie twój kod, elementy o lokalnej nazwie "MyNode" w dowolnej przestrzeni nazw? Następnie użyj sugestii Olega. W przeciwnym razie wyjaśnij bardziej szczegółowo, jakich elementów dokładnie szukasz. –

4

Oto przykład jak zrobić nazw dostępny do wyrażenia XPath w XPathSelectElements metodę rozszerzenia:

using System; 
using System.Xml.Linq; 
using System.Xml.XPath; 
using System.Xml; 
namespace XPathExpt 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
    XElement cfg = XElement.Parse(
     @"<configuration> 
      <MyNode xmlns=""lcmp"" attr=""true""> 
      <subnode /> 
      </MyNode> 
     </configuration>"); 
    XmlNameTable nameTable = new NameTable(); 
    var nsMgr = new XmlNamespaceManager(nameTable); 
    // Tell the namespace manager about the namespace 
    // of interest (lcmp), and give it a prefix (pfx) that we'll 
    // use to refer to it in XPath expressions. 
    // Note that the prefix choice is pretty arbitrary at 
    // this point. 
    nsMgr.AddNamespace("pfx", "lcmp"); 
    foreach (var el in cfg.XPathSelectElements("//pfx:MyNode", nsMgr)) 
    { 
     Console.WriteLine("Found element named {0}", el.Name); 
    } 
    } 
} 
} 
+0

@Dan: Tak, myślę, że działa, ale wymaga hardcoding wszelkich używanych przestrzeni nazw .. podczas gdy ja mogę kontrolować XPath - zobacz mój komentarz pod odpowiedzią @Martin Honnen. –

5

XPath (celowo) nie jest przeznaczony dla przypadku, w którym chcesz użyć tego samego wyrażenia XPath dla niektórych nieznanych obszarów nazw, które występują tylko w dokumencie XML. Oczekuje się, że znasz przestrzeń nazw z wyprzedzeniem, zadeklaruj przestrzeń nazw procesorowi XPath i użyj nazwy w swoim wyrażeniu. Odpowiedzi od Martina i Dana pokazują, jak to zrobić w języku C#.

Przyczyną tych trudności jest najlepiej wyrażona w XML namespaces specyfikacji:

Przedstawiamy aplikacje Extensible Markup Language (XML), gdzie pojedynczy dokument XML może zawierać elementy i atrybuty (tutaj określane jako " Słownik znaczników "), które są zdefiniowane i używane przez wiele modułów oprogramowania. Jedną z motywacji jest modułowość: jeśli istnieje taki słownik znaczników, który jest dobrze zrozumiały i dla którego dostępne jest użyteczne oprogramowanie, lepiej jest ponownie użyć tego znacznika niż go ponownie wymyślić.

Takie dokumenty, zawierające wiele słowników, stanowią problem rozpoznawania i kolizji. Moduły oprogramowania muszą być w stanie rozpoznać elementy i atrybuty, które są zaprojektowane do przetwarzania, nawet w obliczu "kolizji" występujących, gdy znaczniki przeznaczone dla innego pakietu oprogramowania używają tej samej nazwy elementu lub nazwy atrybutu.

Te rozważania wymagają, aby konstrukcje dokumentów miały nazwy skonstruowane w taki sposób, aby uniknąć konfliktów między nazwami z różnych słowników znaczników. Ta specyfikacja opisuje mechanizm, przestrzenie nazw XML, które to umożliwiają, przypisując rozszerzone nazwy elementom i atrybutom.

Oznacza to, że przestrzenie nazw mają być stosowane, aby upewnić się, co dokument mówi o: czy to <head> elementem rozmowy o preambule dokumentu XHTML lub somebodies uderzeniem w dokumencie AnatomyML?Nigdy nie "przypuszczasz", że jesteś agnostykiem w przestrzeni nazw i jest to prawie pierwsza rzecz, którą powinieneś zdefiniować w dowolnym słowniku XML.

Powinno być możliwe robienie tego, co chcesz, ale myślę, że nie można tego zrobić w jednym wyrażeniu XPath. Przede wszystkim musisz pogrzebać w dokumencie i wyodrębnić wszystkie obszary nazwURIS, a następnie dodać je do menedżera przestrzeni nazw, a następnie uruchomić rzeczywiste wyrażenie XPath, które chcesz (i musisz wiedzieć coś o dystrybucji przestrzeni nazw w dokumencie w tym miejscu punkt, lub masz wiele wyrażeń do uruchomienia). Myślę, że prawdopodobnie najlepiej jest użyć czegoś innego niż XPath (np. DOM lub podobny do SAX API), aby znaleźć przestrzeń nazwURIS, ale można również odkryć oś przestrzeni nazw XPath (w XPath 1.0), użyć funkcji (w XPath 2.0) lub użyj wyrażeń, takich jak Oleg's "configuration/*[local-name() = 'MyNode']". W każdym razie myślę, że najlepiej jest spróbować uniknąć pisania agnostycznego XPath! Dlaczego nie znasz swojego obszaru nazw przed czasem? Jak zamierzasz unikać dopasowywania rzeczy, których nie chcesz dopasować?

Edytuj - znasz obszar nazwURI?

Okazuje się, że twoje pytanie myliło nas wszystkich. Najwyraźniej znasz identyfikator URI przestrzeni nazw, ale nie znasz prefiksu przestrzeni nazw, który jest używany w dokumencie XML. Rzeczywiście, w tym przypadku nie jest używany prefiks przestrzeni nazw, a URI staje się domyślnym obszarem nazw, w którym jest zdefiniowany. Kluczową rzeczą jest to, że wybrany prefiks (lub brak przedrostka) nie ma znaczenia dla twojego wyrażenia XPath (i ogólnie parsowania XML). Atrybut prefix/xmlns to tylko jeden sposób skojarzenia węzła z identyfikatorem URI przestrzeni nazw, gdy dokument jest wyrażony jako tekst. Możesz rzucić okiem na this answer, gdzie próbuję i wyjaśnić przedrostki przestrzeni nazw.

Powinieneś spróbować myśleć o dokumencie XML w ten sam sposób, w jaki parser myśli o nim - każdy węzeł ma identyfikator URI przestrzeni nazw i lokalną nazwę. Prefiks/reguły dziedziczenia przestrzeni nazw tylko zapisują wielokrotne wpisywanie URI. Jednym ze sposobów na zapisanie tego jest notacja Clark: oznacza to, że piszesz {http://www.example.com/namespace/example} LocalNodeName, ale ta notacja jest zwykle używana tylko do dokumentacji - XPath nic nie wie o tej notacji.

Zamiast tego, XPath używa własnych przedrostków przestrzeni nazw. Coś jak /ns1:root/ns2:node. Są one jednak całkowicie oddzielne i nie mają nic wspólnego z żadnym prefiksem, który może być użyty w oryginalnym dokumencie XML. Każda implementacja XPath będzie miała możliwość odwzorowania własnych prefiksów na identyfikatory URI przestrzeni nazw. Do implementacji C# używasz XmlNamespaceManager, w Perlu podajesz hasz, xmllint pobiera argumenty wiersza poleceń ... Wszystko, co musisz zrobić, to utworzyć dowolny prefiks dla znanego identyfikatora URI przestrzeni nazw i użyć tego przedrostka w wyrażeniu XPath . Nie ma znaczenia, jakiego prefiksu używasz, w XML'ie zależy ci tylko na połączeniu URI i localName.

Inną rzeczą do zapamiętania (często jest to niespodzianka) jest to, że XPath nie dziedziczy dziedzin. Musisz dodać prefiks dla każdego, który ma przestrzeń nazw, niezależnie od tego, czy przestrzeń nazw pochodzi z dziedziczenia, z atrybutu xmlns, czy z prefiksu przestrzeni nazw. Ponadto, chociaż powinieneś zawsze myśleć w kategoriach URI i localNames, istnieją również sposoby uzyskania dostępu do przedrostka z dokumentu XML. Rzadko kiedy trzeba z nich korzystać.

+0

@Andrew: Znam przestrzeń nazw z wyprzedzeniem i mogę ją umieścić w XPath. To, czego nie wiem, to prefiks przestrzeni nazw, który jest używany, gdy mówisz coś w stylu "/ configuration/lcmp: MyNode". "/ configuration/{lcmp} MyNode" wydaje się być poprawną składnią do używania identyfikatora URI przestrzeni nazw zamiast prefiksu, ale C# nie obsługuje notacji {}. I nie mam prefiksu. –

+0

Ah, rozumiem. Napiszę nową odpowiedź - po prostu musisz wiedzieć, że prefiks przestrzeni nazw w twoim dokumencie XML nie ma nic wspólnego z prefiksem przestrzeni nazw w wyrażeniu XPath, innym niż oba muszą mapować do tego samego nsURI. –

+0

Bardzo pouczające i obszerne redagowanie-zapisywanie, ale nie sądzę, że to faktycznie odpowiada na moje pytanie, które jest: czym XPath znajduje ten węzeł? Ponadto, czy mówisz, że jeśli XML DID określa prefiks (który nie jest), to zapytanie XPath, aby znaleźć, że nie może z niego korzystać? –

0

lubię @ Mads-Hansen, jego odpowiedź, więc dobrze, że Napisałem tych ogólnych użytkowników klasy użytkowej:

/// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <returns></returns> 
    public static string GetLocalNameXPathQuery(string childElementName) 
    { 
     return GetLocalNameXPathQuery(namespacePrefixOrUri: null, childElementName: childElementName, childAttributeName: null); 
    } 

    /// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <returns></returns> 
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName) 
    { 
     return GetLocalNameXPathQuery(namespacePrefixOrUri, childElementName, childAttributeName: null); 
    } 

    /// <summary> 
    /// Gets the <see cref="XNode" /> into a <c>local-name()</c>, XPath-predicate query. 
    /// </summary> 
    /// <param name="namespacePrefixOrUri">The namespace prefix or URI.</param> 
    /// <param name="childElementName">Name of the child element.</param> 
    /// <param name="childAttributeName">Name of the child attribute.</param> 
    /// <returns></returns> 
    /// <remarks> 
    /// This routine is useful when namespace-resolving is not desirable or available. 
    /// </remarks> 
    public static string GetLocalNameXPathQuery(string namespacePrefixOrUri, string childElementName, string childAttributeName) 
    { 
     if (string.IsNullOrEmpty(childElementName)) return null; 

     if (string.IsNullOrEmpty(childAttributeName)) 
     { 
      return string.IsNullOrEmpty(namespacePrefixOrUri) ? 
       string.Format("./*[local-name()='{0}']", childElementName) 
       : 
       string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']", namespacePrefixOrUri, childElementName); 
     } 
     else 
     { 
      return string.IsNullOrEmpty(namespacePrefixOrUri) ? 
       string.Format("./*[local-name()='{0}']/@{1}", childElementName, childAttributeName) 
       : 
       string.Format("./*[namespace-uri()='{0}' and local-name()='{1}']/@{2}", namespacePrefixOrUri, childElementName, childAttributeName); 
     } 
    } 
Powiązane problemy