سؤال

لقد حصلت على xElement في أعماق وثيقة. بالنظر إلى xelement (و xDocument؟) ، هل هناك طريقة تمديد للحصول على ممتلئة (على سبيل المثال ، على سبيل المثال /root/item/element/child) xpath؟

مثل myxelement.getxpath ()؟

تحرير: حسنًا ، يبدو أنني أغفلت شيئًا مهمًا جدًا. يصيح! يجب أخذ فهرس العنصر في الاعتبار. انظر إجابتي الأخيرة للحل الذي تم تصحيحه المقترح.

هل كانت مفيدة؟

المحلول

طرق الامتدادات:

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement
    /// (e.g. "/people/person[6]/name[1]/last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();
            string name = e.Name.LocalName;

            // If the element is the root, no index is required

            return (index == -1) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name, 
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) + 
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

والاختبار:

class Program
{
    static void Main(string[] args)
    {
        Program.Process(XDocument.Load(@"C:\test.xml").Root);
        Console.Read();
    }

    static void Process(XElement element)
    {
        if (!element.HasElements)
        {
            Console.WriteLine(element.GetAbsoluteXPath());
        }
        else
        {
            foreach (XElement child in element.Elements())
            {
                Process(child);
            }
        }
    }
}

وإخراج العينة:

/tests/test[1]/date[1]
/tests/test[1]/time[1]/start[1]
/tests/test[1]/time[1]/end[1]
/tests/test[1]/facility[1]/name[1]
/tests/test[1]/facility[1]/website[1]
/tests/test[1]/facility[1]/street[1]
/tests/test[1]/facility[1]/state[1]
/tests/test[1]/facility[1]/city[1]
/tests/test[1]/facility[1]/zip[1]
/tests/test[1]/facility[1]/phone[1]
/tests/test[1]/info[1]
/tests/test[2]/date[1]
/tests/test[2]/time[1]/start[1]
/tests/test[2]/time[1]/end[1]
/tests/test[2]/facility[1]/name[1]
/tests/test[2]/facility[1]/website[1]
/tests/test[2]/facility[1]/street[1]
/tests/test[2]/facility[1]/state[1]
/tests/test[2]/facility[1]/city[1]
/tests/test[2]/facility[1]/zip[1]
/tests/test[2]/facility[1]/phone[1]
/tests/test[2]/info[1]

يجب أن تسوية هذا. رقم؟

نصائح أخرى

قمت بتحديث الرمز بواسطة كريس لأخذ بادئات مساحة الاسم في الاعتبار. يتم تعديل طريقة getabsolutexpath فقط.

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement, including the namespace.
    /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;
            if (currentNamespace == null)
            {
                name = e.Name.LocalName;
            }
            else
            {
                string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                name = namespacePrefix + ":" + e.Name.LocalName;
            }

            // If the element is the root, no index is required
            return (index == -1) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name,
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) +
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            return -1;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

اسمحوا لي أن أشارك أحدث تعديلات في هذا الفصل. Basicaly يستبعد الفهرس إذا كان العنصر لا يحتوي على شقيق ويتضمن مساحات أسماء مع مشغل محلي الاسم () هل كنت أواجه مشكلات مع بادئة مساحة الاسم.

public static class XExtensions
{
    /// <summary>
    /// Get the absolute XPath to a given XElement, including the namespace.
    /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]").
    /// </summary>
    public static string GetAbsoluteXPath(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }


        Func<XElement, string> relativeXPath = e =>
        {
            int index = e.IndexPosition();

            var currentNamespace = e.Name.Namespace;

            string name;
            if (String.IsNullOrEmpty(currentNamespace.ToString()))
            {
                name = e.Name.LocalName;
            }
            else
            {
                name = "*[local-name()='" + e.Name.LocalName + "']";
                //string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace);
                //name = namespacePrefix + ":" + e.Name.LocalName;
            }

            // If the element is the root or has no sibling elements, no index is required
            return ((index == -1) || (index == -2)) ? "/" + name : string.Format
            (
                "/{0}[{1}]",
                name,
                index.ToString()
            );
        };

        var ancestors = from e in element.Ancestors()
                        select relativeXPath(e);

        return string.Concat(ancestors.Reverse().ToArray()) +
               relativeXPath(element);
    }

    /// <summary>
    /// Get the index of the given XElement relative to its
    /// siblings with identical names. If the given element is
    /// the root, -1 is returned or -2 if element has no sibling elements.
    /// </summary>
    /// <param name="element">
    /// The element to get the index of.
    /// </param>
    public static int IndexPosition(this XElement element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }

        if (element.Parent == null)
        {
            // Element is root
            return -1;
        }

        if (element.Parent.Elements(element.Name).Count() == 1)
        {
            // Element has no sibling elements
            return -2;
        }

        int i = 1; // Indexes for nodes start at 1, not 0

        foreach (var sibling in element.Parent.Elements(element.Name))
        {
            if (sibling == element)
            {
                return i;
            }

            i++;
        }

        throw new InvalidOperationException
            ("element has been removed from its parent.");
    }
}

هذا هو في الواقع مكررة من هذه سؤال. في حين أنه لم يتم وضع علامة عليه كإجابة ، فإن الطريقة في إجابتي هذا السؤال هو الطريقة الوحيدة لصياغة XPath بشكل لا لبس فيه إلى عقدة ضمن مستند XML الذي سيعمل دائمًا في جميع الظروف. (يعمل أيضًا لجميع أنواع العقدة ، وليس فقط العناصر.)

كما ترون ، فإن XPath التي تنتجها هي قبيحة وتجريدية. لكنه يعالج المخاوف التي أثارها العديد من المستفيدين هنا. تنتج معظم الاقتراحات المقدمة هنا XPath ، عند استخدامها للبحث في المستند الأصلي ، ستنتج مجموعة من العقد أو أكثر تتضمن العقدة المستهدفة. إنها "أو أكثر" هذه هي المشكلة. على سبيل المثال ، إذا كان لدي تمثيل XML لمجموعة بيانات ، فإن XPath Saft to a DataRow's Adem ، /DataSet1/DataTable1, ، يعيد أيضًا عناصر جميع البيانات الأخرى في DataTable. لا يمكنك إزالة الغموض أنه دون معرفة شيء ما حول كيفية تعامل XML (مثل ، هل هناك عنصر مفتاح أساسي؟).

ولكن /node()[1]/node()[4]/node()[11], ، هناك عقدة واحدة فقط ستعود ، بغض النظر عن ماذا.

كجزء من أ مشروع مختلف لقد طورت طريقة تمديد لإنشاء XPath بسيط لعنصر. يشبه الإجابة المحددة ، ولكنها تدعم Xattribute و Xtext و XCDATA و XCOMMENT بالإضافة إلى XELEMENT. إنه متاح على أنه رمز Nuget, ، صفحة المشروع هنا: xmlspecificationcompare.codeplex.com

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

يمكن أن يكون هناك العديد من XPaths التي تؤدي إلى نفس العنصر ، لذا فإن العثور على أبسط XPath الذي يؤدي إلى العقدة ليس تافدًا.

ومع ذلك ، من السهل جدًا العثور على XPath للعقدة. ما عليك سوى زيادة شجرة العقدة حتى تقرأ عقدة الجذر ودمج أسماء العقدة ولديك XPath صالح.

من خلال "Full Xpath" ، أفترض أنك تعني سلسلة بسيطة من العلامات لأن عدد XPaths الذي يمكن أن يكون قد يكون مطابقة لأي عنصر جداً كبير.

المشكلة هنا هي أنه من الصعب للغاية إن لم يكن من المستحيل على وجه التحديد بناء أي XPath معين والذي سيعود إلى نفس العنصر بشكل عكسي - هل هذا شرط؟

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

قدمت Microsoft طريقة تمديد للقيام بذلك منذ .NET Framework 3.5:

http://msdn.microsoft.com/en-us/library/bb156083(v=vs.100).aspx

فقط أضف استخدامًا إلى System.Xml.XPath واستدعاء الطرق التالية:

  • XPathSelectElement: حدد عنصرًا واحدًا
  • XPathSelectElements: حدد العناصر والعودة كـ IEnumerable<XElement>
  • XPathEvaluate: حدد العقد (ليس فقط العناصر ، ولكن أيضًا نصًا ، تعليقات ، وما إلى ذلك) والعودة كـ IEnumerable<object>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top