it-swarm.dev

Použití Xpath s výchozím jmenným prostorem v C #

Mám XML dokument s výchozím jmenným prostorem. Používám XPathNavigator vybrat sadu uzlů pomocí Xpath takto: 

XmlElement myXML = ...;  
XPathNavigator navigator = myXML.CreateNavigator();
XPathNodeIterator result = navigator.Select("/outerelement/innerelement");

Nejsem dostat žádné výsledky zpět: Předpokládám, že je to proto, že nejsem určování jmenného prostoru. Jak mohu zahrnout jmenný prostor do svého výběru?

58
macleojw

Nejprve - nepotřebujete navigátor; Volby SelectNodes/SelectSingleNode by měly stačit.

Můžete však potřebovat správce jmenného prostoru - například:

XmlElement el = ...; //TODO
XmlNamespaceManager nsmgr = new XmlNamespaceManager(
    el.OwnerDocument.NameTable);
nsmgr.AddNamespace("x", el.OwnerDocument.DocumentElement.NamespaceURI);
var nodes = el.SelectNodes(@"/x:outerelement/x:innerelement", nsmgr);
77
Marc Gravell

Můžete zkusit nástroj XPath Visualizer, který vám pomůže. 

XPathVisualizer je zdarma, snadno ovladatelný. 

 alt text

DŮLEŽITÉ: Pokud používáte Windows 7/8 a nevidíte položky Soubor, Upravit a Nápověda, stiskněte klávesu ALT.

48
Cheeso

Pro každého, kdo hledá rychlé hackové řešení, zejména v těch případech, kdy vy víte XML a nemusíte se obávat jmenných prostorů a všeho, můžete se obejít touto nepříjemnou "funkcí" jednoduše čtením souboru na řetězec a nahrazující urážlivý atribut:

XmlDocument doc = new XmlDocument();
string fileData = File.ReadAllText(fileName);
fileData = fileData.Replace(" xmlns=\"", " whocares=\"");
using (StringReader sr = new StringReader(fileData))
{
   doc.Load(sr);
}

XmlNodeList nodeList = doc.SelectNodes("project/property");

Považuji to za jednodušší než všechny ostatní nesmysly vyžadující předponu pro výchozí obor názvů, když se zabývám jedním souborem. Snad to pomůže.

24
Mitselplik

Pokud používáte XPath v .NET (přes navigátor nebo SelectNodes/SelectSingleNode) na XML s jmennými prostory, musíte:

  • poskytnout vlastní XmlNamespaceManager

  • a explicitně předponují všechny prvky v výrazu XPath, které jsou v oboru názvů.

Ten je (parafrázován ze zdrojového kódu MS níže): protože XPath 1.0 ignoruje standardní specifikace jmenného prostoru (xmlns = "some_namespace"). Když tedy používáte název prvku bez předpony, předpokládá null jmenný prostor.

To je důvod, proč .NET implementace XPath ignoruje jmenný prostor s prefixem String.Empty v XmlNamespaceManager a allways používá null namespace.

Viz XmlNamespaceManager a UndefinedXsltContext nezpracovávají výchozí jmenný prostor pro více informací.

Považuji tuto "vlastnost" za velmi nepohodlnou, protože nemůžete učinit starý XPath jmenný prostor-vědomé pouhým přidáním výchozího jmenného prostoru prohlášení, ale to funguje.

19
Tomek Szpakowicz

Příkaz XPath můžete použít bez použití XmlNamespaceManager takto:

...
navigator.Select("//*[ local-name() = 'innerelement' and namespace-uri() = '' ]")
...

To je jednoduchý způsob výběru elementu v XML s definovaným výchozím jmenným prostorem.

Cílem je použít: 

namespace-uri() = ''

který najde element s výchozím jmenným prostorem bez použití předpon.

6

Moje odpověď rozšiřuje předchozí odpověď Brandona. Použil jsem jeho příklad k vytvoření metody rozšíření následujícím způsobem:

static public class XmlDocumentExt
{
    static public XmlNamespaceManager GetPopulatedNamespaceMgr(this System.Xml.XmlDocument xd)
    {
        XmlNamespaceManager nmsp = new XmlNamespaceManager(xd.NameTable);
        XPathNavigator nav = xd.DocumentElement.CreateNavigator();
        foreach (KeyValuePair<string,string> kvp in nav.GetNamespacesInScope(XmlNamespaceScope.All))
        {
            string sKey = kvp.Key;
            if (sKey == "")
            {
                sKey = "default";
            }
            nmsp.AddNamespace(sKey, kvp.Value);
        }

        return nmsp;
    }
}

Pak v mém kódu analýzy XML přidám pouze jeden řádek:

XmlDocument xdCandidate = new XmlDocument();
xdCandidate.Load(sCandidateFile);
XmlNamespaceManager nmsp = xdCandidate.GetPopulatedNamespaceMgr();  // 1-line addition
XmlElement xeScoreData = (XmlElement)xdCandidate.SelectSingleNode("default:ScoreData", nmsp);

Opravdu se mi líbí tato metoda, protože je naprosto dynamická, pokud jde o načítání jmenných prostorů ze zdrojového XML souboru, a to zcela nebere v úvahu koncept jmenných prostorů XML, takže to může být použito s XML, které vyžaduje více jmenných prostorů pro dekonfliction.

5
Kent

Narazil jsem na podobný problém s prázdným výchozím oborem názvů. V tomto příkladu XML mám směs prvků s předponami jmenného prostoru a jedním prvkem (DataBlock) bez:

<src:SRCExample xmlns="urn:some:stuff:here" xmlns:src="www.test.com/src" xmlns:a="www.test.com/a" xmlns:b="www.test.com/b">
 <DataBlock>
  <a:DocID>
   <a:IdID>7</a:IdID>
  </a:DocID>
  <b:Supplimental>
   <b:Data1>Value</b:Data1>
   <b:Data2/>
   <b:Extra1>
    <b:More1>Value</b:More1>
   </b:Extra1>
  </b:Supplimental>
 </DataBlock>
</src:SRCExample>

Pokusil jsem se použít XPath, který pracoval v XPath Visualizer, ale nefungoval v mém kódu:

  XmlDocument doc = new XmlDocument();
  doc.Load( textBox1.Text );
  XPathNavigator nav = doc.DocumentElement.CreateNavigator();
  XmlNamespaceManager nsman = new XmlNamespaceManager( nav.NameTable );
  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
  }

  XPathNodeIterator nodes;

  XPathExpression failingexpr = XPathExpression.Compile( "/src:SRCExample/DataBlock/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

Zúžil jsem to na prvek "DataBlock" XPathu, ale nemohl jsem to fungovat, pouze tím, že jednoduše odstraním prvek DataBlock:

  XPathExpression workingexpr = XPathExpression.Compile( "/src:SRCExample/*/a:DocID/a:IdID" );
  failingexpr.SetContext( nsman );
  nodes = nav.Select( failingexpr );
  while ( nodes.MoveNext() ) {
    string testvalue = nodes.Current.Value;
  }

Po hodně headcratching a googling (který mě sem přistál) jsem se rozhodl řešit výchozí jmenný prostor přímo v zavaděči XmlNamespaceManager změnou na:

  foreach ( KeyValuePair<string, string> nskvp in nav.GetNamespacesInScope( XmlNamespaceScope.All ) ) {
    nsman.AddNamespace( nskvp.Key, nskvp.Value );
    if ( nskvp.Key == "" ) {
      nsman.AddNamespace( "default", nskvp.Value );
    }
  }

Takže nyní "výchozí" a "" ukazují na stejný obor názvů. Jakmile jsem to udělal, XPath "/ src: SRCExample/default: DataBlock/a: DocID/a: IdID" vrátil mé výsledky stejně jako jsem chtěl. Doufejme, že to pomůže objasnit problém pro ostatní.

5
Brandon

V případě, že se jmenné prostory liší pro vnější a vnitřní strukturu

XmlNamespaceManager manager = new XmlNamespaceManager(myXmlDocument.NameTable);
                            manager.AddNamespace("o", "namespaceforOuterElement");
                            manager.AddNamespace("i", "namespaceforInnerElement");
string xpath = @"/o:outerelement/i:innerelement"
// For single node value selection
XPathExpression xPathExpression = navigator.Compile(xpath );
string reportID = myXmlDocument.SelectSingleNode(xPathExpression.Expression, manager).InnerText;

// For multiple node selection
XmlNodeList myNodeList= myXmlDocument.SelectNodes(xpath, manager);
5
Rashmi Pandit

V mém případě nebylo přidání předpony praktické. Příliš mnoho xml nebo xpath bylo určeno za běhu. Nakonec jsem rozšířil metds na XmlNode. To nebylo optimalizováno pro výkon a pravděpodobně to neřeší každý případ, ale je to pro mě doposud.

    public static class XmlExtenders
{

    public static XmlNode SelectFirstNode(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectSingleNode(prefixedPath, nsmgr);
    }

    public static XmlNodeList SelectAllNodes(this XmlNode node, string xPath)
    {
        const string prefix = "pfx";
        XmlNamespaceManager nsmgr = GetNsmgr(node, prefix);
        string prefixedPath = GetPrefixedPath(xPath, prefix);
        return node.SelectNodes(prefixedPath, nsmgr);
    }

    public static XmlNamespaceManager GetNsmgr(XmlNode node, string prefix)
    {
        string namespaceUri;
        XmlNameTable nameTable;
        if (node is XmlDocument)
        {
            nameTable = ((XmlDocument) node).NameTable;
            namespaceUri = ((XmlDocument) node).DocumentElement.NamespaceURI;
        }
        else
        {
            nameTable = node.OwnerDocument.NameTable;
            namespaceUri = node.NamespaceURI;
        }
        XmlNamespaceManager nsmgr = new XmlNamespaceManager(nameTable);
        nsmgr.AddNamespace(prefix, namespaceUri);
        return nsmgr;
    }

    public static string GetPrefixedPath(string xPath, string prefix)
    {
        char[] validLeadCharacters = "@/".ToCharArray();
        char[] quoteChars = "\'\"".ToCharArray();

        List<string> pathParts = xPath.Split("/".ToCharArray()).ToList();
        string result = string.Join("/",
                                    pathParts.Select(
                                        x =>
                                        (string.IsNullOrEmpty(x) ||
                                         x.IndexOfAny(validLeadCharacters) == 0 ||
                                         (x.IndexOf(':') > 0 &&
                                          (x.IndexOfAny(quoteChars) < 0 || x.IndexOfAny(quoteChars) > x.IndexOf(':'))))
                                            ? x
                                            : prefix + ":" + x).ToArray());
        return result;
    }
}

Pak ve vašem kódu stačí použít něco jako

        XmlDocument document = new XmlDocument();
        document.Load(pathToFile);
        XmlNode node = document.SelectFirstNode("/rootTag/subTag");

Snad to pomůže

3
SpikeDog

Nebo, pokud by někdo měl používat XPathDocument, jako já:

XPathDocument xdoc = new XPathDocument(file);
XPathNavigator nav = xdoc.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("y", "http://schemas.Microsoft.com/developer/msbuild/2003");
XPathNodeIterator nodeIter = nav.Select("//y:PropertyGroup", nsmgr);
1
Zak

Použil jsem hacky-ale-užitečné přístup popsaný SpikeDog výše. Fungovalo to velmi dobře, dokud jsem na něj nevyhodil výraz xpath, který používal trubky, které kombinovaly více cest.

Tak jsem to přepsal pomocí regulárních výrazů a myslel jsem, že bych se o ně podělil:

public string HackXPath(string xpath_, string prefix_)
{
    return System.Text.RegularExpressions.Regex.Replace(xpath_, @"(^(?![A-Za-z0-9\-\.]+::)|[A-Za-z0-9\-\.]+::|[@|/|\[])(?'Expression'[A-Za-z][A-Za-z0-9\-\.]*)", x =>
                {
                    int expressionIndex = x.Groups["Expression"].Index - x.Index;
                    string before = x.Value.Substring(0, expressionIndex);
                    string after = x.Value.Substring(expressionIndex, x.Value.Length - expressionIndex);
                    return String.Format("{0}{1}:{2}", before, prefix_, after);
                });
}
1
Dan

1] Máte-li soubor XML bez předpony v oboru názvů:

<bookstore xmlns="http://www.contoso.com/books">
…
</bookstore>

máte toto řešení:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
// ignore the namespace as there is a single default namespace:
reader.Namespaces = false;
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

2] Pokud máte soubor XML s předponou v oboru názvů:

<bookstore xmlns:ns="http://www.contoso.com/books">
…
</bookstore>

Použij toto:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XPathNodeIterator nodes = navigator.Select("//book");

V případě potřeby můžete samozřejmě použít obor názvů:

XmlTextReader reader = new XmlTextReader(@"C:\Temp\books.xml");
XPathDocument document = new XPathDocument(reader);
XPathNavigator navigator = document.CreateNavigator();
XmlNamespaceManager nsmgr = new XmlNamespaceManager(reader.NameTable);
nsmgr.AddNamespace("ns", "http://www.contoso.com/book");
XPathNodeIterator nodes = navigator.Select("//book", nsmgr);

Myslím si, že je to nejjednodušší způsob, jak kód pracovat ve většině případů.

Doufám, že tento problém vyřeší tento problém společnosti Microsoft…

0
Corto

V tomto případě je to pravděpodobně řešení jmenného prostoru, které je příčinou problému, ale je také možné, že váš výraz XPath není sám o sobě správný. Můžete ho nejprve vyhodnotit.

Zde je kód pomocí XPathNavigator. 

//xNav is the created XPathNavigator.
XmlNamespaceManager mgr = New XmlNamespaceManager(xNav.NameTable);
mgr.AddNamespace("prefix", "http://tempuri.org/");

XPathNodeIterator result = xNav.Select("/prefix:outerelement/prefix:innerelement", mgr);
0
Cerebrus