summary c#




كيفية الحصول على زباث من مثيل زملنود (9)

هل يمكن لشخص توفير بعض التعليمات البرمجية التي من شأنها الحصول على زباث مثيل System.Xml.XmlNode؟

شكر!


جون الصحيح أن هناك أي عدد من تعبيرات زباث التي سوف تسفر عن نفس العقدة في مستند مثيل. أبسط طريقة لبناء تعبير الذي ينتج بشكل لا لبس فيه عقدة محددة هي سلسلة من عقدة الاختبارات التي تستخدم موقف العقدة في المسند، على سبيل المثال:

/node()[0]/node()[2]/node()[6]/node()[1]/node()[2]

من الواضح أن هذا التعبير لا يستخدم أسماء العناصر، ولكن بعد ذلك إذا كان كل ما تحاول القيام به هو تحديد عقدة داخل مستند، لا تحتاج إلى اسمها. كما أنه لا يمكن استخدامها للعثور على سمات (لأن السمات ليست العقد وليس لها موقف؛ يمكنك العثور عليها فقط بالاسم)، ولكنها سوف تجد جميع أنواع العقدة الأخرى.

لبناء هذا التعبير، تحتاج إلى كتابة أسلوب إرجاع موضع العقدة في العقد التابعة الأصل، لأن XmlNode لا يعرض ذلك كخاصية:

static int GetNodePosition(XmlNode child)
{
   for (int i=0; i<child.ParentNode.ChildNodes.Count; i++)
   {
       if (child.ParentNode.ChildNodes[i] == child)
       {
          // tricksy XPath, not starting its positions at 0 like a normal language
          return i + 1;
       }
   }
   throw new InvalidOperationException("Child node somehow not found in its parent's ChildNodes property.");
}

(ربما يكون هناك طريقة أكثر أناقة للقيام بذلك باستخدام لينق، منذ XmlNodeList تنفذ IEnumerable ، ولكن أنا ذاهب مع ما أعرف هنا.)

ثم يمكنك كتابة طريقة عودية مثل هذا:

static string GetXPathToNode(XmlNode node)
{
    if (node.NodeType == XmlNodeType.Attribute)
    {
        // attributes have an OwnerElement, not a ParentNode; also they have
        // to be matched by name, not found by position
        return String.Format(
            "{0}/@{1}",
            GetXPathToNode(((XmlAttribute)node).OwnerElement),
            node.Name
            );            
    }
    if (node.ParentNode == null)
    {
        // the only node with no parent is the root node, which has no path
        return "";
    }
    // the path to a node is the path to its parent, plus "/node()[n]", where 
    // n is its position among its siblings.
    return String.Format(
        "{0}/node()[{1}]",
        GetXPathToNode(node.ParentNode),
        GetNodePosition(node)
        );
}

كما ترون، أنا اختراق في وسيلة للعثور على سمات كذلك.

وانزلق جون في إصداره بينما كنت أكتب الألغام. هناك شيء حول التعليمات البرمجية التي من شأنها أن تجعل لي رانت قليلا الآن، وأنا أعتذر مقدما إذا كان يبدو وكأنه أنا خرقة على جون. (أنا لست متأكدا من أن قائمة الأشياء التي يجب أن يتعلمها جون مني قصيرة جدا). لكنني أعتقد أن النقطة التي سأقوم بها هي مهمة مهمة لكل من يعمل مع شمل إلى فكر.

أظن أن حل جون نشأ من شيء أرى الكثير من المطورين القيام به: التفكير في وثائق شمل كأشجار العناصر والصفات. وأعتقد أن هذا يأتي إلى حد كبير من المطورين الذين يستخدمون الأساسي من شمل هو شكل تسلسل، لأن كل شمل انهم تستخدم لاستخدامها منظم بهذه الطريقة. يمكنك اكتشاف هؤلاء المطورين لأنهم يستخدمون العبارة "العقدة" و "العنصر" بالتبادل. وهذا يؤدي بهم إلى التوصل إلى حلول تعالج جميع أنواع العقدة الأخرى كحالات خاصة. (كنت واحدا من هؤلاء الرجال نفسي لفترة طويلة جدا.)

هذا يبدو وكأنه افتراض مبسط بينما كنت جعله. لكنها ليست كذلك. فإنه يجعل المشاكل أكثر صعوبة ورمز أكثر تعقيدا. فإنه يقودك لتجاوز أجزاء من تكنولوجيا شمل (مثل node() وظيفة في زباث) التي تم تصميمها خصيصا لعلاج جميع أنواع العقدة بشكل عام.

هناك علم أحمر في رمز جون من شأنه أن يجعلني الاستعلام عنه في مراجعة التعليمات البرمجية حتى لو لم أكن أعرف ما هي المتطلبات، وهذا هو GetElementsByTagName . كلما رأيت هذه الطريقة قيد الاستخدام، فإن السؤال الذي يقفز إلى الذهن هو دائما "لماذا يجب أن يكون عنصرا؟" والجواب هو في كثير من الأحيان "أوه، هل يحتاج هذا الرمز للتعامل مع العقد النصية أيضا؟"


ليس هناك شيء مثل "و" زباث عقدة. لأي عقدة معينة قد يكون هناك العديد من تعبيرات زباث التي سوف تتطابق مع ذلك.

يمكنك ربما العمل حتى شجرة لبناء تعبير الذي سوف يطابق ذلك، مع الأخذ بعين الاعتبار مؤشر عناصر معينة وما إلى ذلك، لكنه لن يكون رمز لطيف بشكل رهيب.

لماذا تحتاج هذه؟ قد يكون هناك حل أفضل.


بلدي 10p قيمتها هي مزيج من روبرت و كوري إجابات. لا أستطيع سوى المطالبة الائتمان للكتابة الفعلية من خطوط إضافية من التعليمات البرمجية.

    private static string GetXPathToNode(XmlNode node)
    {
        if (node.NodeType == XmlNodeType.Attribute)
        {
            // attributes have an OwnerElement, not a ParentNode; also they have
            // to be matched by name, not found by position
            return String.Format(
                "{0}/@{1}",
                GetXPathToNode(((XmlAttribute)node).OwnerElement),
                node.Name
                );
        }
        if (node.ParentNode == null)
        {
            // the only node with no parent is the root node, which has no path
            return "";
        }
        //get the index
        int iIndex = 1;
        XmlNode xnIndex = node;
        while (xnIndex.PreviousSibling != null) { iIndex++; xnIndex = xnIndex.PreviousSibling; }
        // the path to a node is the path to its parent, plus "/node()[n]", where 
        // n is its position among its siblings.
        return String.Format(
            "{0}/node()[{1}]",
            GetXPathToNode(node.ParentNode),
            iIndex
            );
    }

حسنا، لم أتمكن من مقاومة وجود الذهاب في ذلك. انها سوف تعمل فقط للسمات والعناصر، ولكن مهلا ... ماذا يمكن أن تتوقع في 15 دقيقة :) وبالمثل قد يكون هناك جيدا جدا أن يكون وسيلة أكثر نظافة للقيام بذلك.

لا لزوم لإدراج المؤشر على كل عنصر (وخاصة الجذر واحد!) ولكن الأمر أسهل من محاولة معرفة ما إذا كان هناك أي غموض خلاف ذلك.

using System;
using System.Text;
using System.Xml;

class Test
{
    static void Main()
    {
        string xml = @"
<root>
  <foo />
  <foo>
     <bar attr='value'/>
     <bar other='va' />
  </foo>
  <foo><bar /></foo>
</root>";
        XmlDocument doc = new XmlDocument();
        doc.LoadXml(xml);
        XmlNode node = doc.SelectSingleNode("//@attr");
        Console.WriteLine(FindXPath(node));
        Console.WriteLine(doc.SelectSingleNode(FindXPath(node)) == node);
    }

    static string FindXPath(XmlNode node)
    {
        StringBuilder builder = new StringBuilder();
        while (node != null)
        {
            switch (node.NodeType)
            {
                case XmlNodeType.Attribute:
                    builder.Insert(0, "/@" + node.Name);
                    node = ((XmlAttribute) node).OwnerElement;
                    break;
                case XmlNodeType.Element:
                    int index = FindElementIndex((XmlElement) node);
                    builder.Insert(0, "/" + node.Name + "[" + index + "]");
                    node = node.ParentNode;
                    break;
                case XmlNodeType.Document:
                    return builder.ToString();
                default:
                    throw new ArgumentException("Only elements and attributes are supported");
            }
        }
        throw new ArgumentException("Node was not in a document");
    }

    static int FindElementIndex(XmlElement element)
    {
        XmlNode parentNode = element.ParentNode;
        if (parentNode is XmlDocument)
        {
            return 1;
        }
        XmlElement parent = (XmlElement) parentNode;
        int index = 1;
        foreach (XmlNode candidate in parent.ChildNodes)
        {
            if (candidate is XmlElement && candidate.Name == element.Name)
            {
                if (candidate == element)
                {
                    return index;
                }
                index++;
            }
        }
        throw new ArgumentException("Couldn't find element within parent");
    }
}

لقد وجدت أن أيا من ما سبق عملت مع XDocument ، لذلك كتبت بلدي التعليمات البرمجية لدعم XDocument واستخدام التكرار. أعتقد أن هذا الرمز يعالج عقد متطابقة متعددة أفضل من بعض التعليمات البرمجية الأخرى هنا لأنه يحاول أولا أن يذهب عميقا في مسار شمل كما يمكن ومن ثم النسخ الاحتياطي لبناء فقط ما هو مطلوب. حتى إذا كان لديك /home/white/bob و /home/white/mike وترغب في إنشاء /home/white/bob/garage التعليمات البرمجية سوف نعرف كيفية إنشاء ذلك. ومع ذلك، لم أكن أريد أن فوضى مع المسند أو أحرف البدل، لذلك أنا رفضت صراحة تلك؛ ولكن سيكون من السهل إضافة دعم لهم.

Private Sub NodeItterate(XDoc As XElement, XPath As String)
    'get the deepest path
    Dim nodes As IEnumerable(Of XElement)

    nodes = XDoc.XPathSelectElements(XPath)

    'if it doesn't exist, try the next shallow path
    If nodes.Count = 0 Then
        NodeItterate(XDoc, XPath.Substring(0, XPath.LastIndexOf("/")))
        'by this time all the required parent elements will have been constructed
        Dim ParentPath As String = XPath.Substring(0, XPath.LastIndexOf("/"))
        Dim ParentNode As XElement = XDoc.XPathSelectElement(ParentPath)
        Dim NewElementName As String = XPath.Substring(XPath.LastIndexOf("/") + 1, XPath.Length - XPath.LastIndexOf("/") - 1)
        ParentNode.Add(New XElement(NewElementName))
    End If

    'if we find there are more than 1 elements at the deepest path we have access to, we can't proceed
    If nodes.Count > 1 Then
        Throw New ArgumentOutOfRangeException("There are too many paths that match your expression.")
    End If

    'if there is just one element, we can proceed
    If nodes.Count = 1 Then
        'just proceed
    End If

End Sub

Public Sub CreateXPath(ByVal XDoc As XElement, ByVal XPath As String)

    If XPath.Contains("//") Or XPath.Contains("*") Or XPath.Contains(".") Then
        Throw New ArgumentException("Can't create a path based on searches, wildcards, or relative paths.")
    End If

    If Regex.IsMatch(XPath, "\[\]()@='<>\|") Then
        Throw New ArgumentException("Can't create a path based on predicates.")
    End If

    'we will process this recursively.
    NodeItterate(XDoc, XPath)

End Sub

وهنا طريقة بسيطة لقد استخدمت، عملت بالنسبة لي.

    static string GetXpath(XmlNode node)
    {
        if (node.Name == "#document")
            return String.Empty;
        return GetXpath(node.SelectSingleNode("..")) + "/" +  (node.NodeType == XmlNodeType.Attribute ? "@":String.Empty) + node.Name;
    }

ماذا عن استخدام تمديد الطبقة؟ ؛) نسختي (بناء على عمل الآخرين) يستخدم اسم سينتاكس [الفهرس] ... مع مؤشر حذف هو عنصر ليس لديه "الإخوة". الحلقة للحصول على مؤشر العنصر هو خارج في روتين مستقل (أيضا امتداد الطبقة).

فقط الماضي ما يلي في أي فئة المرافق (أو في فئة البرنامج الرئيسي)

static public int GetRank( this XmlNode node )
{
    // return 0 if unique, else return position 1...n in siblings with same name
    try
    {
        if( node is XmlElement ) 
        {
            int rank = 1;
            bool alone = true, found = false;

            foreach( XmlNode n in node.ParentNode.ChildNodes )
                if( n.Name == node.Name ) // sibling with same name
                {
                    if( n.Equals(node) )
                    {
                        if( ! alone ) return rank; // no need to continue
                        found = true;
                    }
                    else
                    {
                        if( found ) return rank; // no need to continue
                        alone = false;
                        rank++;
                    }
                }

        }
    }
    catch{}
    return 0;
}

static public string GetXPath( this XmlNode node )
{
    try
    {
        if( node is XmlAttribute )
            return String.Format( "{0}/@{1}", (node as XmlAttribute).OwnerElement.GetXPath(), node.Name );

        if( node is XmlText || node is XmlCDataSection )
            return node.ParentNode.GetXPath();

        if( node.ParentNode == null )   // the only node with no parent is the root node, which has no path
            return "";

        int rank = node.GetRank();
        if( rank == 0 ) return String.Format( "{0}/{1}",        node.ParentNode.GetXPath(), node.Name );
        else            return String.Format( "{0}/{1}[{2}]",   node.ParentNode.GetXPath(), node.Name, rank );
    }
    catch{}
    return "";
}   

لقد أنتجت فبا ل إكسيل للقيام بذلك لمشروع العمل. فإنه يخرج توابل من زباث والنص المرتبط بها من إحدى الشخصيات أو السمة. والغرض من ذلك هو السماح لمحللي الأعمال بتحديد وتعيين بعض شمل. نقدر أن هذا هو منتدى C #، ولكن يعتقد أن هذا قد يكون من الفائدة.

Sub Parse2(oSh As Long, inode As IXMLDOMNode, Optional iXstring As String = "", Optional indexes)


Dim chnode As IXMLDOMNode
Dim attr As IXMLDOMAttribute
Dim oXString As String
Dim chld As Long
Dim idx As Variant
Dim addindex As Boolean
chld = 0
idx = 0
addindex = False


'determine the node type:
Select Case inode.NodeType

    Case NODE_ELEMENT
        If inode.ParentNode.NodeType = NODE_DOCUMENT Then 'This gets the root node name but ignores all the namespace attributes
            oXString = iXstring & "//" & fp(inode.nodename)
        Else

            'Need to deal with indexing. Where an element has siblings with the same nodeName,it needs to be indexed using [index], e.g swapstreams or schedules

            For Each chnode In inode.ParentNode.ChildNodes
                If chnode.NodeType = NODE_ELEMENT And chnode.nodename = inode.nodename Then chld = chld + 1
            Next chnode

            If chld > 1 Then '//inode has siblings of the same nodeName, so needs to be indexed
                'Lookup the index from the indexes array
                idx = getIndex(inode.nodename, indexes)
                addindex = True
            Else
            End If

            'build the XString
            oXString = iXstring & "/" & fp(inode.nodename)
            If addindex Then oXString = oXString & "[" & idx & "]"

            'If type is element then check for attributes
            For Each attr In inode.Attributes
                'If the element has attributes then extract the data pair XString + Element.Name, @Attribute.Name=Attribute.Value
                Call oSheet(oSh, oXString & "/@" & attr.Name, attr.Value)
            Next attr

        End If

    Case NODE_TEXT
        'build the XString
        oXString = iXstring
        Call oSheet(oSh, oXString, inode.NodeValue)

    Case NODE_ATTRIBUTE
    'Do nothing
    Case NODE_CDATA_SECTION
    'Do nothing
    Case NODE_COMMENT
    'Do nothing
    Case NODE_DOCUMENT
    'Do nothing
    Case NODE_DOCUMENT_FRAGMENT
    'Do nothing
    Case NODE_DOCUMENT_TYPE
    'Do nothing
    Case NODE_ENTITY
    'Do nothing
    Case NODE_ENTITY_REFERENCE
    'Do nothing
    Case NODE_INVALID
    'do nothing
    Case NODE_NOTATION
    'do nothing
    Case NODE_PROCESSING_INSTRUCTION
    'do nothing
End Select

'Now call Parser2 on each of inode's children.
If inode.HasChildNodes Then
    For Each chnode In inode.ChildNodes
        Call Parse2(oSh, chnode, oXString, indexes)
    Next chnode
Set chnode = Nothing
Else
End If

End Sub

يدير عد العناصر باستخدام:

Function getIndex(tag As Variant, indexes) As Variant
'Function to get the latest index for an xml tag from the indexes array
'indexes array is passed from one parser function to the next up and down the tree

Dim i As Integer
Dim n As Integer

If IsArrayEmpty(indexes) Then
    ReDim indexes(1, 0)
    indexes(0, 0) = "Tag"
    indexes(1, 0) = "Index"
Else
End If
For i = 0 To UBound(indexes, 2)
    If indexes(0, i) = tag Then
        'tag found, increment and return the index then exit
        'also destroy all recorded tag names BELOW that level
        indexes(1, i) = indexes(1, i) + 1
        getIndex = indexes(1, i)
        ReDim Preserve indexes(1, i) 'should keep all tags up to i but remove all below it
        Exit Function
    Else
    End If
Next i

'tag not found so add the tag with index 1 at the end of the array
n = UBound(indexes, 2)
ReDim Preserve indexes(1, n + 1)
indexes(0, n + 1) = tag
indexes(1, n + 1) = 1
getIndex = 1

End Function

 public static string GetFullPath(this XmlNode node)
        {
            if (node.ParentNode == null)
            {
                return "";
            }
            else
            {
                return $"{GetFullPath(node.ParentNode)}\\{node.ParentNode.Name}";
            }
        }






.net-2.0