it-swarm.dev

Jak lze použít XPath s výchozím jmenným prostorem bez předpony?

Co je to XPath (v C # API na XDocument.XPathSelectElements (xpath, nsman), pokud je to důležité) k dotazování všech MyNodes z tohoto dokumentu?

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <MyNode xmlns="lcmp" attr="true">
    <subnode />
  </MyNode>
</configuration>
  • Snažil jsem se /configuration/MyNode, což je špatné, protože ignoruje jmenný prostor.
  • Snažil jsem se /configuration/lcmp:MyNode, což je špatné, protože lcmp je URI, nikoli předpona.
  • Zkoušel jsem /configuration/{lcmp}MyNode, který selhal, protože Additional information: '/configuration/{lcmp}MyNode' has an invalid token.

EDIT: Nemohu použít mgr.AddNamespace("df", "lcmp");, jak někteří z respondentů navrhli. To vyžaduje, aby program pro analýzu XML věděl, že všechny jmenné prostory, které mám v plánu použít, bude v budoucnu. Protože to má být použitelné pro všechny zdrojové soubory, nevím, které jmenné prostory ručně přidat předpony. Vypadá to, že {my uri} je syntaxe XPath, ale Microsoft se neobtěžoval implementovat ... true?

34
Scott Stafford

Prvek configuration je v nepojmenovaném oboru názvů a MyNode je vázán na obor názvů lcmp bez předpony oboru názvů.

TentoXPATHpříkaz vám umožní adresovat prvek MyNode bez deklarace jmenného prostoru lcmp nebo použití předpony jmenného prostoru ve vašem XPATH:

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

To odpovídá nějakému elementu, který je dítě configuration a pak používá predikátový filer s namespace-uri() a local-name() funkce omezit to na MyNode element.

Pokud nevíte, který jmenný prostor-uri bude použit pro elementy, pak můžete provéstXPATHobecnější a stačí se shodnout na local-name():

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

Nicméně riskujete, že se budou v různých slovnících (vázaných na různé jmenné prostory - uri) shodovat s různými jmény.

37
Mads Hansen

Musíte použít XmlNamespaceManager následujícím způsobem:

   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);
   }
12
Martin Honnen

XPath není (záměrně) určen pro případ, kdy chcete použít stejný výraz XPath pro některé neznámé jmenné prostory, které žijí pouze v dokumentu XML. Očekává se, že budete znát obor názvů předem, deklarovat jmenný prostor procesoru XPath a použít tento název ve vašem výrazu. Odpovědi Martina a Dana ukazují, jak to udělat v C #. 

Důvod tohoto problému je nejlépe vyjádřen v jmenných prostorech XML spec:

Představujeme aplikace XML (Extensible Markup Language), kde jeden dokument XML může obsahovat prvky a atributy (zde označované jako "slovník slovníků"), které jsou definovány a používány více softwarovými moduly. Jednou z motivací pro to je modularita: existuje-li takový značkovací slovník, který je dobře srozumitelný a pro který je k dispozici užitečný software, je lepší tento značkovač použít znovu, než jej znovu vynalézat.

Tyto dokumenty, obsahující více značkovacích slovníků, představují problémy s rozpoznáním a kolizí. Softwarové moduly musí být schopny rozpoznat prvky a atributy, které jsou určeny ke zpracování, a to i v případě „kolizí“, které nastanou, když označení určené pro jiný softwarový balíček používá stejný název prvku nebo název atributu.

Tyto úvahy vyžadují, aby konstrukty dokumentů měly názvy vytvořené tak, aby se zabránilo střetům mezi názvy z různých značek slovníků. Tato specifikace popisuje mechanismus, jmenné prostory XML, které toto provede přiřazením rozšířených jmen prvkům a atributům. 

To znamená, že jmenné prostory by měly být použity k tomu, abyste se ujistili, o čem váš dokument mluví: je to <head> prvek, který hovoří o preambuli dokumentu XHTML nebo o hlavě v dokumentu AnatomyML? Nikdy si nemyslíte, že je agnostic o jmenném prostoru a je to skoro první věc, kterou byste měli definovat v libovolném slovníku XML. 

Mělo by být možné dělat to, co chcete, ale nemyslím si, že to lze provést v jediném výrazu XPath. Nejdříve musíte v dokumentu přehrabovat a extrahovat všechny namespaceURI, pak je přidat do Správce jmenného prostoru a pak spustit skutečný výraz XPath, který chcete (a potřebujete vědět něco o distribuci jmenných prostorů v dokumentu na tomto místě). nebo máte spoustu výrazů ke spuštění). Myslím, že pravděpodobně používáte něco jiného než XPath (např. API pro DOM nebo SAX) k nalezení namespaceURI, ale můžete také prozkoumat osu názvů XPath (v XPath 1.0), použít namespace-uri-from-QName function (v XPath 2.0) nebo použít výrazy jako Oleg's "configuration/*[local-name() = 'MyNode']". Každopádně si myslím, že nejlepší je pokusit se vyhnout psaní jmenného prostoru agnostic XPath! Proč ještě neznáte svůj jmenný prostor? Jak se vyhnete tomu, aby se vám nepodařilo vyhovět?

Edit - znáte namespaceURI?

Ukazuje se tedy, že vaše otázka nás všechny zmatila. Zřejmě znáte URI oboru názvů, ale neznáte předponu oboru názvů, která se používá v dokumentu XML. V tomto případě se v žádném případě nepoužívá předpona jmenného prostoru a URI se stává výchozím namspace, kde je definován. Klíčovou věcí je vědět, že vybraná předpona (nebo nedostatek předpony) je irelevantní pro váš výraz XPath (a obecně pro analýzu XML). Atribut prefix/xmlns je jen jedním ze způsobů, jak spojit uzel s URI jmenného prostoru, když je dokument vyjádřen jako text. Možná se budete chtít podívat na tuto odpověď , kde se snažím objasnit předpony jmenného prostoru. 

Měli byste se pokusit myslet na dokument XML stejným způsobem, jakým si ho analyzátor myslí - každý uzel má URI jmenného prostoru a místní název. Pravidla prefixu jmenného prostoru/dědičnosti jednoduše ukládají typování URI mnohokrát. Jedním ze způsobů, jak to napsat, je zápis v Clarku: to znamená, že píšete { http://www.example.com/namespace/example } LocalNodeName, ale tento zápis se obvykle používá pouze pro dokumentaci - XPath neví nic o tomto zápisu.

Místo toho XPath používá své předpony jmenného prostoru. Něco jako /ns1:root/ns2:node. Ty jsou však zcela oddělené od předpon, které mohou být použity v původním dokumentu XML, a nemají s nimi nic společného. Jakákoliv implementace XPath bude mít způsob mapování vlastních předpon s URI jmenného prostoru. Pro implementaci C # používáte XmlNamespaceManager, v Perlu poskytujete hash, xmllint bere argumenty příkazového řádku ... Takže stačí vytvořit libovolnou předponu pro URI jmenného prostoru, kterou znáte, a použít tuto předponu v výrazu XPath . Nezáleží na tom, jakou předponu používáte, v XML se jen staráte o kombinaci URI a localName. 

Další věc, kterou si musíte zapamatovat (je to často překvapení) je to, že XPath nedělá dědičnost jmenného prostoru. Musíte přidat předponu pro každý, který má jmenný prostor, bez ohledu na to, zda obor názvů pochází z dědičnosti, atributu xmlns nebo předpony oboru názvů. Ačkoliv byste měli vždy myslet v termínech URI a localNames, existují také způsoby přístupu k předponě z dokumentu XML. Je vzácné, že je musíte použít. 

6
Andrew Walker

Zde je příklad, jak zpřístupnit jmenný prostor výrazu XPath v metodě rozšíření XPathSelectElements:

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);
     }
   }
 }
}
4
Dan Blanchard

Příklad s Xpath 2.0 + knihovna:

using Wmhelp.XPath2;

doc.XPath2SelectElements("/*:configuration/*:MyNode");

Viz:

XPath a XSLT 2.0 pro .NET?

1
Akli

Líbí se mi @ mads-hansen, jeho odpověď, tak dobře, že jsem napsal tyto členy univerzální utility třídy:

    /// <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);
        }
    }
0
rasx