Nice programing

C #에서 기본 네임 스페이스와 함께 Xpath 사용

nicepro 2020. 12. 2. 21:57
반응형

C #에서 기본 네임 스페이스와 함께 Xpath 사용


기본 네임 스페이스가있는 XML 문서가 있습니다. 다음과 같이 Xpath를 사용하여 노드 집합을 선택하기 위해 XPathNavigator를 사용하고 있습니다.

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

결과가 다시 나오지 않습니다. 네임 스페이스를 지정하지 않았기 때문이라고 가정합니다. 내 선택에 네임 스페이스를 포함하려면 어떻게해야합니까?


첫째-네비게이터가 필요하지 않습니다. SelectNodes / SelectSingleNode이면 충분합니다.

그러나 네임 스페이스 관리자가 필요할 수 있습니다. 예를 들면 다음과 같습니다.

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);

XPath Visualizer 도구를 사용하여 도움을받을 수 있습니다.

XPathVisualizer 는 무료이며 사용하기 쉽습니다.

대체 텍스트

중요 : Windows 7/8을 사용 중이고 파일, 편집 및 도움말 메뉴 항목이 보이지 않으면 Alt 키를 누르십시오.


빠른 해킹 솔루션을 찾고있는 사람, 특히 XML 알고 있고 네임 스페이스 등에 대해 걱정할 필요가없는 경우에는 파일을 문자열로 읽어서이 성가신 작은 "기능"을 해결할 수 있습니다. 공격 속성 대체 :

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");

단일 파일을 처리 할 때 기본 네임 스페이스에 대한 접두사가 필요한 다른 모든 말도 안되는 것보다이 작업이 더 쉽습니다. 도움이 되었기를 바랍니다.


네임 스페이스가있는 XML에서 .NET에서 XPath (네비게이터 또는 SelectNodes / SelectSingleNode를 통해)를 사용하는 경우 다음을 수행해야합니다.

  • 자신의 XmlNamespaceManager 제공

  • 명시 적으로 네임 스페이스에있는 XPath 식의 모든 요소를 접두사.

후자는 (아래 링크 된 MS 소스에서 패러 프레이징 됨) : XPath 1.0은 기본 네임 스페이스 사양 (xmlns = "some_namespace")을 무시하기 때문입니다. 따라서 접두사없이 요소 이름을 사용하면 null 네임 스페이스로 간주됩니다.

이것이 XPath의 .NET 구현이 XmlNamespaceManager에서 접두사 String.Empty가있는 네임 스페이스를 무시하고 항상 null 네임 스페이스를 사용하는 이유입니다.

자세한 내용은 XmlNamespaceManager 및 UndefinedXsltContext가 기본 네임 스페이스처리하지 않음 을 참조하세요.

기본 네임 스페이스 선언을 추가하는 것만으로는 오래된 XPath 네임 스페이스를 인식 할 수 없기 때문에이 "기능"이 매우 불편하다고 생각합니다.하지만 이것이 작동하는 방식입니다.


다음과 같이 XmlNamespaceManager를 사용하지 않고 XPath 문을 사용할 수 있습니다.

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

이는 기본 네임 스페이스가 정의 된 XML 내에서 요소를 선택하는 간단한 방법입니다.

요점은 다음을 사용하는 것입니다.

namespace-uri() = ''

접두사를 사용하지 않고 기본 네임 스페이스가있는 요소를 찾습니다.


내 대답은 Brandon의 이전 대답을 확장합니다. 그의 예제를 사용하여 다음과 같이 확장 메서드를 만들었습니다.

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;
    }
}

그런 다음 XML 구문 분석 코드에서 한 줄만 추가합니다.

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

이 방법은 소스 XML 파일에서 네임 스페이스를로드하는 측면에서 완전히 동적이고 XML 네임 스페이스의 개념을 완전히 무시하지 않으므로 충돌 해제를 위해 여러 네임 스페이스가 필요한 XML과 함께 사용할 수 있기 때문에이 방법을 정말 좋아합니다.


외부 요소와 내부 요소의 네임 스페이스가 다른 경우

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);

빈 기본 네임 스페이스에서 비슷한 문제가 발생했습니다. 이 XML 예제에서는 네임 스페이스 접두사가있는 요소와 다음이없는 단일 요소 (DataBlock)가 혼합되어 있습니다.

<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>

XPath Visualizer에서 작동하는 XPath를 사용하려고했지만 내 코드에서는 작동하지 않았습니다.

  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;
  }

XPath의 "DataBlock"요소로 범위를 좁혔지만 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;
  }

많은 헤드 스크래칭과 인터넷 검색을 거친 후 XmlNamespaceManager 로더에서 기본 네임 스페이스를 다음과 같이 변경하여 직접 처리하기로 결정했습니다.

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

이제 "default"와 ""는 동일한 네임 스페이스를 가리 킵니다. 이렇게하면 XPath "/ src : SRCExample / default : DataBlock / a : DocID / a : IdID"가 원하는대로 결과를 반환했습니다. 바라건대 이것은 다른 사람들에게 문제를 명확히하는 데 도움이되기를 바랍니다.


제 경우에는 접두사를 추가하는 것이 실용적이지 않았습니다. 런타임에 너무 많은 xml 또는 xpath가 결정되었습니다. 결국 나는 XmlNode에서 메소드를 확장했습니다. 이것은 성능에 최적화되지 않았으며 아마도 모든 경우를 처리하지는 않지만 지금까지는 저에게 효과적입니다.

    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;
    }
}

그런 다음 코드에서 다음과 같이 사용하십시오.

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

도움이 되었기를 바랍니다


위의 SpikeDog에서 설명한 해키하지만 유용한 접근 방식을 사용했습니다. 파이프를 사용하여 여러 경로를 결합하는 xpath 표현식을 던지기 전까지는 매우 잘 작동했습니다.

그래서 정규 표현식을 사용하여 다시 작성했으며 다음과 같이 공유 할 것이라고 생각했습니다.

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);
                });
}

또는 나와 같은 사람이 XPathDocument를 사용해야하는 경우 :

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);

이 경우 문제의 원인은 네임 스페이스 확인 일 수 있지만 XPath 표현식 자체가 올바르지 않을 수도 있습니다. 먼저 평가할 수 있습니다.

다음은 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);

1] 네임 스페이스에 접두사가없는 XML 파일이있는 경우 :

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

이 해결 방법이 있습니다.

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] 네임 스페이스에 접두사가있는 XML 파일이있는 경우 :

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

이것을 사용하십시오 :

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

물론 필요한 경우 네임 스페이스 관리를 사용할 수 있습니다.

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);

대부분의 경우 코드를 작동시키는 가장 쉬운 방법이라고 생각합니다.

이 Microsoft 문제를 해결하는 데 도움이 되었기를 바랍니다.

참고 URL : https://stackoverflow.com/questions/585812/using-xpath-with-default-namespace-in-c-sharp

반응형