IT

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

itgroup 2023. 10. 5. 21:31
반응형

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

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

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

결과를 돌려받지 못합니다.네임스페이스를 지정하지 않기 때문인 것 같습니다.선택한 항목에 네임스페이스를 포함하려면 어떻게 해야 합니까?

먼저 탐색기가 필요 없습니다. 노드 선택 / 단일 노드 선택으로 충분합니다.

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

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 도구를 사용해 볼 수 있습니다.

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

alt text

중요: 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");

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

에서 XPath를 사용할 때.XML에서 NET(내비게이터 또는 SelectNode/SelectSingleNode 선택)에 필요한 네임스페이스를 사용하여 다음 작업을 수행합니다.

  • 자신의 Xml Namespace Manager를 제공합니다.

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

XPath 1.0이 기본 네임스페이스 규격(xmlns="some_ namespace")을 무시하기 때문에 후자는 (아래 링크된 MS 소스에서 구문 분석)입니다.따라서 접두사 없이 요소 이름을 사용하면 null 네임스페이스를 가정합니다.

그래서.XPath의 NET 구현은 접두사 String으로 네임스페이스를 무시합니다.XmlNamespaceManager에서 비어 있으며 항상 null 네임스페이스를 사용합니다.

자세한 내용은 XmlNamespaceManager UndefinedXsltContext에서 기본 네임스페이스를 처리하지 않음을 참조하십시오.

기본 네임스페이스 선언을 추가하는 것만으로는 이전 XPath 네임스페이스를 인식할 수 없기 때문에 이 "기능"이 매우 불편합니다. 하지만 그렇게 작동합니다.

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

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

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

입니다.

namespace-uri() = ''

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

제 답변은 브랜든의 이전 답변을 연장합니다.그의 예제를 사용하여 다음과 같은 확장 메서드를 만들었습니다.

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과 함께 사용할 수 있기 때문에 매우 마음에 듭니다.

빈 기본 네임스페이스에서 비슷한 문제가 발생했습니다.이 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:SRC 예제/기본값:데이터 블록/a:DocID/a:IdID"는 제가 원하는 대로 결과를 회신했습니다.이것이 다른 사람들에게 그 문제를 분명히 하는 데 도움이 되기를 바랍니다.

외부요소와 내부요소의 명칭공간이 다른 경우

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

도움이 되길 바랍니다.

위에서 설명한 스파이크독이 설명한 hacky-but-유용한 접근법을 사용했습니다.파이프를 사용하여 여러 경로를 결합하는 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);
                });
}

또는 저처럼 XPath Document를 사용해야 하는 사람이 있다면 다음과 같이 하십시오.

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] 네임스페이스에 접두사가 없는 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 문제를 해결할 수 있기를 바랍니다.

이건 아직도 계속 신경 쓰여요.제가 지금 테스트를 좀 마쳤으니 제가 도와드릴 수 있기를 바랍니다.

이것이 문제의 핵심인 마이크로소프트사의 소스입니다.

중요한 단락은 다음과 같습니다.

XPath는 빈 접두사를 null 네임스페이스로 처리합니다.즉, 네임스페이스에 매핑된 접두사만 XPath 쿼리에서 사용할 수 있습니다.즉, 기본 네임스페이스라도 XML 문서의 네임스페이스에 대해 쿼리하려면 해당 네임스페이스에 대한 접두사를 정의해야 합니다.

기본적으로 XPath 구문 분석기는 접두사가 상호 교환 가능하도록 설계된 네임스페이스 URI를 사용한다는 점을 기억해야 합니다.이는 프로그래밍할 때 URI가 일치하는 한 원하는 접두사를 지정할 수 있기 때문입니다.

예를 명확히 하기 위해:

예제 A:

<data xmlns:nsa="http://example.com/ns"><nsa:a>World</nsa:a></data>

NULL 기본 URI()를 사용합니다.xmlns=정의되지 않음).이것 때문에./data/nsa:a"World"를 반환합니다.

예제 B:

<data xmlns:nsa="http://example.com/ns" xmlns="https://standardns/"><nsa:a>World</nsa:a></data>

이 문서에는 이름이 지정된 기본 접두사가 있습니다.https://standardns/.XPathNavigator.Execute와 함께/data/nsa:a따라서 결과를 반환하지 않습니다.MS는 XML 네임스페이스 uri를 다음에 대해 고려합니다.dataNULL이어야 하며 다음에 대한 네임스페이스 URI.data실제로는 " https://standardns/"입니다.본질적으로 XPath가 찾고 있습니다./NULL:data/nsa:a- NULL URI를 접두어로 "NULL"이라고 할 수는 없기 때문에 이것은 작동하지 않을 것입니다.NULL 접두사는 모든 XPath에서 기본값이므로 문제가 발생합니다.

이걸 어떻게 해결하죠?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");
result.AddNamespace("nsa", "http://example.com/ns");

이런 식으로, 우리는 이제 a를 다음과 같이 언급할 수 있습니다./DEFAULT:data/nsa:a

예제 C:

<data><a xmlns="https://standardns/">World</a></data>

이 예에서는dataNULL 네임스페이스에 있습니다.a기본 네임스페이스 " https://standardns/"에 있습니다./data/a마이크로소프트에 따르면 작동하지 않아야 합니다. 왜냐하면aNS에 있습니다.https://standardns/그리고.data네임스페이스 NULL에 있습니다.<a>따라서 이상한 "이름공간 ignore" 해킹을 수행하는 것을 제외하고는 숨겨진 상태이며, 현재 상태에서는 선택할 수 없습니다.이것이 근본적인 원인입니다. 두 가지 모두 접두사가 없는 "a"와 "data"를 선택할 수 없습니다. 같은 네임스페이스에 있고 그렇지 않다고 가정하는 것입니다.

이걸 어떻게 해결하죠?

XmlNamespaceManager result = new XmlNamespaceManager(xDoc.NameTable);
result.AddNamespace("DEFAULT", "https://standardns/");

이런 식으로, 우리는 이제 a를 다음과 같이 언급할 수 있습니다./data/DEFAULT:a데이터는 NULL 네임스페이스에서 선택되고 a는 새 접두사 "DEFAULT"에서 선택됩니다.이 예제에서 중요한 것은 네임스페이스 접두사를 동일하게 유지할 필요가 없다는 것입니다.처리 중인 문서에 기록된 내용에 대해 코드에 다른 접두사가 있는 URI 네임스페이스를 참조하는 것은 전적으로 허용됩니다.

이것이 몇몇 사람들에게 도움이 되기를 바랍니다!

이 경우 문제의 원인은 네임스페이스 해결일 수 있지만 XPath 표현식 자체가 올바르지 않을 수도 있습니다.당신은 그것을 먼저 평가해 보는 것이 좋을 것 같습니다.

XPath Navigator를 사용하는 코드는 다음과 같습니다.

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

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

반응형