Создать XML-узлы на основе XPath? - PullRequest
46 голосов
/ 03 февраля 2009

Кто-нибудь знает о существующих средствах создания программной иерархии XML из выражения XPath?

Например, если у меня есть фрагмент XML, такой как:

<feed>
    <entry>
        <data></data>
        <content></content>
    </entry>
</feed>

Учитывая выражение XPath / feed / entry / content / @ source, у меня будет:

<feed>
    <entry>
        <data></data>
        <content @source=""></content>
    </entry>
</feed>

Я понимаю, что это возможно при использовании XSLT, но из-за динамического характера того, что я пытаюсь выполнить, фиксированное преобразование не будет работать.

Я работаю в C #, но если у кого-то есть решение, использующее какой-либо другой язык, пожалуйста, присоединяйтесь.

Спасибо за помощь!

Ответы [ 12 ]

42 голосов
/ 04 февраля 2009

В приведенном вами примере единственное, что создается, - это атрибут ...

XmlElement element = (XmlElement)doc.SelectSingleNode("/feed/entry/content");
if (element != null)
    element.SetAttribute("source", "");

Если вы действительно хотите создать иерархию там, где ее нет, то вы можете создать свой собственный простой парсер xpath. Я не знаю, как сохранить атрибут в xpath. Я бы предпочел привести узел в качестве элемента и использовать атрибут .SetAttribute, как я сделал здесь:


static private XmlNode makeXPath(XmlDocument doc, string xpath)
{
    return makeXPath(doc, doc as XmlNode, xpath);
}

static private XmlNode makeXPath(XmlDocument doc, XmlNode parent, string xpath)
{
    // grab the next node name in the xpath; or return parent if empty
    string[] partsOfXPath = xpath.Trim('/').Split('/');
    string nextNodeInXPath = partsOfXPath.First();
    if (string.IsNullOrEmpty(nextNodeInXPath))
        return parent;

    // get or create the node from the name
    XmlNode node = parent.SelectSingleNode(nextNodeInXPath);
    if (node == null)
        node = parent.AppendChild(doc.CreateElement(nextNodeInXPath));

    // rejoin the remainder of the array as an xpath expression and recurse
    string rest = String.Join("/", partsOfXPath.Skip(1).ToArray());
    return makeXPath(doc, node, rest);
}

static void Main(string[] args)
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml("<feed />");

    makeXPath(doc, "/feed/entry/data");
    XmlElement contentElement = (XmlElement)makeXPath(doc, "/feed/entry/content");
    contentElement.SetAttribute("source", "");

    Console.WriteLine(doc.OuterXml);
}
14 голосов
/ 12 августа 2010

Вот мой быстрый взлом, который также может создавать атрибуты, если вы используете такой формат, как /configuration/appSettings/add[@key='name']/@value.

static XmlNode createXPath(XmlDocument doc, string xpath)
{
  XmlNode node=doc;
  foreach (string part in xpath.Substring(1).Split('/'))
  {
    XmlNodeList nodes=node.SelectNodes(part);
    if (nodes.Count>1) throw new ComponentException("Xpath '"+xpath+"' was not found multiple times!");
    else if (nodes.Count==1) { node=nodes[0]; continue; }

    if (part.StartsWith("@"))
    {
      var anode=doc.CreateAttribute(part.Substring(1));
      node.Attributes.Append(anode);
      node=anode;
    }
    else
    {
      string elName, attrib=null;
      if (part.Contains("["))
      {
        part.SplitOnce("[", out elName, out attrib);
        if (!attrib.EndsWith("]")) throw new ComponentException("Unsupported XPath (missing ]): "+part);
        attrib=attrib.Substring(0, attrib.Length-1);
      }
      else elName=part;

      XmlNode next=doc.CreateElement(elName);
      node.AppendChild(next);
      node=next;

      if (attrib!=null)
      {
        if (!attrib.StartsWith("@")) throw new ComponentException("Unsupported XPath attrib (missing @): "+part);
        string name, value;
        attrib.Substring(1).SplitOnce("='", out name, out value);
        if (string.IsNullOrEmpty(value) || !value.EndsWith("'")) throw new ComponentException("Unsupported XPath attrib: "+part);
        value=value.Substring(0, value.Length-1);
        var anode=doc.CreateAttribute(name);
        anode.Value=value;
        node.Attributes.Append(anode);
      }
    }
  }
  return node;
}

SplitOnce - это метод расширения:

public static void SplitOnce(this string value, string separator, out string part1, out string part2)
{
  if (value!=null)
  {
    int idx=value.IndexOf(separator);
    if (idx>=0)
    {
      part1=value.Substring(0, idx);
      part2=value.Substring(idx+separator.Length);
    }
    else
    {
      part1=value;
      part2=null;
    }
  }
  else
  {
    part1="";
    part2=null;
  }
}

Пример:

public static void Set(XmlDocument doc, string xpath, string value)
{
  if (doc==null) throw new ArgumentNullException("doc");
  if (string.IsNullOrEmpty(xpath)) throw new ArgumentNullException("xpath");

  XmlNodeList nodes=doc.SelectNodes(xpath);
  if (nodes.Count>1) throw new ComponentException("Xpath '"+xpath+"' was not found multiple times!");
  else if (nodes.Count==0) createXPath(doc, xpath).InnerText=value;
  else nodes[0].InnerText=value;
}

, например

Set(doc, "/configuration/appSettings/add[@key='Server']/@value", "foobar");
9 голосов
/ 03 декабря 2011

Одной из проблем этой идеи является то, что xpath «уничтожает» информацию.

Существует бесконечное количество деревьев XML, которые могут соответствовать многим путям. Теперь в некоторых случаях, например в приведенном вами примере, существует очевидное минимальное xml-дерево, которое соответствует вашему xpath, где у вас есть предикат, использующий «=».

Но, например, если предикат использует не равно или любой другой арифметический оператор, кроме равного, существует бесконечное число возможностей. Вы можете попытаться выбрать «каноническое» дерево xml, которое, скажем, требует наименьшего количества битов для представления.

Предположим, например, что у вас был xpath /feed/entry/content[@source > 0]. Теперь любое дерево XML соответствующей структуры, в котором содержимое узла имеет источник атрибута, значение которого> 0, будет соответствовать, но существует бесконечное число чисел, больше нуля. Выбрав «минимальное» значение, предположительно 1, вы можете попытаться канонизировать ваш xml.

Предикаты Xpath могут содержать довольно произвольные арифметические выражения, поэтому общее решение этого достаточно сложное, если не невозможное. Вы можете вообразить огромное уравнение там, и оно должно быть решено в обратном порядке, чтобы найти значения, которые будут соответствовать уравнению; но поскольку может быть бесконечное количество совпадающих значений (если это действительно неравенство, а не уравнение), необходимо найти каноническое решение.

Многие выражения других форм также уничтожают информацию. Например, оператор типа «или» всегда уничтожает информацию. Если вы знаете, что (X or Y) == 1, вы не знаете, если X равен 1, Y равен 1, или оба они равны 1; все, что вы точно знаете, это то, что один из них - 1! Поэтому, если у вас есть выражение, использующее OR, вы не можете сказать, какой из узлов или значений, которые являются входными данными для OR, должен быть 1 (вы можете сделать произвольный выбор и установить оба значения 1, так как это наверняка удовлетворит выражение, а также два варианта, в которых только один из них - 1).

Теперь предположим, что в xpath есть несколько выражений, которые ссылаются на один и тот же набор значений. Затем вы получаете систему уравнений или неравенств, которые практически невозможно решить. Опять же, если вы ограничите допустимый xpath небольшим подмножеством его полной мощности, вы можете решить эту проблему. Я подозреваю, что полностью общий случай похож на проблему остановки Тьюринга; в этом случае, учитывая произвольную программу (xpath), определите набор согласованных данных, которые соответствуют программе, и в некотором смысле минимальны.

5 голосов
/ 15 июля 2010

Вот моя версия. Надеюсь, это тоже кому-нибудь поможет.

    public static void Main(string[] args)
    {

        XmlDocument doc = new XmlDocument();
        XmlNode rootNode = GenerateXPathXmlElements(doc, "/RootNode/FirstChild/SecondChild/ThirdChild");

        Console.Write(rootNode.OuterXml);

    }

    private static XmlDocument GenerateXPathXmlElements(XmlDocument xmlDocument, string xpath)
    {
        XmlNode parentNode = xmlDocument;

        if (xmlDocument != null && !string.IsNullOrEmpty(xpath))
        {
            string[] partsOfXPath = xpath.Split('/');


            string xPathSoFar = string.Empty;

            foreach (string xPathElement in partsOfXPath)
            {
                if(string.IsNullOrEmpty(xPathElement))
                    continue;

                xPathSoFar += "/" + xPathElement.Trim();

                XmlNode childNode = xmlDocument.SelectSingleNode(xPathSoFar);
                if(childNode == null)
                {
                    childNode = xmlDocument.CreateElement(xPathElement);
                }

                parentNode.AppendChild(childNode);

                parentNode = childNode;
            }
        }

        return xmlDocument;
    }
3 голосов
/ 10 января 2012

C # версия Решение Java Марка Миллера

    /// <summary>
    /// Makes the X path. Use a format like //configuration/appSettings/add[@key='name']/@value
    /// </summary>
    /// <param name="doc">The doc.</param>
    /// <param name="xpath">The xpath.</param>
    /// <returns></returns>
    public static XmlNode createNodeFromXPath(XmlDocument doc, string xpath)
    {
        // Create a new Regex object
        Regex r = new Regex(@"/+([\w]+)(\[@([\w]+)='([^']*)'\])?|/@([\w]+)");

        // Find matches
        Match m = r.Match(xpath);

        XmlNode currentNode = doc.FirstChild;
        StringBuilder currentPath = new StringBuilder();

        while (m.Success)
        {
            String currentXPath = m.Groups[0].Value;    // "/configuration" or "/appSettings" or "/add"
            String elementName = m.Groups[1].Value;     // "configuration" or "appSettings" or "add"
            String filterName = m.Groups[3].Value;      // "" or "key"
            String filterValue = m.Groups[4].Value;     // "" or "name"
            String attributeName = m.Groups[5].Value;   // "" or "value"

            StringBuilder builder = currentPath.Append(currentXPath);
            String relativePath = builder.ToString();
            XmlNode newNode = doc.SelectSingleNode(relativePath);

            if (newNode == null)
            {
                if (!string.IsNullOrEmpty(attributeName))
                {
                    ((XmlElement)currentNode).SetAttribute(attributeName, "");
                    newNode = doc.SelectSingleNode(relativePath);
                }
                else if (!string.IsNullOrEmpty(elementName))
                {
                    XmlElement element = doc.CreateElement(elementName);
                    if (!string.IsNullOrEmpty(filterName))
                    {
                        element.SetAttribute(filterName, filterValue);
                    }

                    currentNode.AppendChild(element);
                    newNode = element;
                }
                else
                {
                    throw new FormatException("The given xPath is not supported " + relativePath);
                }
            }

            currentNode = newNode;

            m = m.NextMatch();
        }

        // Assure that the node is found or created
        if (doc.SelectSingleNode(xpath) == null)
        {
            throw new FormatException("The given xPath cannot be created " + xpath);
        }

        return currentNode;
    }
2 голосов
/ 07 августа 2015

Это улучшенная версия решения Кристиана Питерса , поддерживающего пространства имен в выражении xpath.

public static XNode CreateNodeFromXPath(XElement elem, string xpath)
{
    // Create a new Regex object
    Regex r = new Regex(@"/*([a-zA-Z0-9_\.\-\:]+)(\[@([a-zA-Z0-9_\.\-]+)='([^']*)'\])?|/@([a-zA-Z0-9_\.\-]+)");

    xpath = xpath.Replace("\"", "'");
    // Find matches
    Match m = r.Match(xpath);

    XNode currentNode = elem;
    StringBuilder currentPath = new StringBuilder();
    XPathNavigator XNav = elem.CreateNavigator();

    while (m.Success)
    {
        String currentXPath = m.Groups[0].Value;    // "/ns:configuration" or "/appSettings" or "/add"
        String NamespaceAndElementName = m.Groups[1].Value;     // "ns:configuration" or "appSettings" or "add"
        String filterName = m.Groups[3].Value;      // "" or "key"
        String filterValue = m.Groups[4].Value;     // "" or "name"
        String attributeName = m.Groups[5].Value;   // "" or "value"

        XNamespace nspace = "";
        string elementName;
        int p = NamespaceAndElementName.IndexOf(':');
        if (p >= 0)
        {
            string ns = NamespaceAndElementName.Substring(0, p);
            elementName = NamespaceAndElementName.Substring(p + 1);
            nspace = XNav.GetNamespace(ns);
        }
        else
            elementName = NamespaceAndElementName;


        StringBuilder builder = currentPath.Append(currentXPath);
        String relativePath = builder.ToString();
        XNode newNode = (XNode)elem.XPathSelectElement(relativePath, XNav);

        if (newNode == null)
        {
            if (!string.IsNullOrEmpty(attributeName))
            {
                ((XElement)currentNode).Attribute(attributeName).Value = "";
                newNode = (XNode)elem.XPathEvaluate(relativePath, XNav);
            }
            else if (!string.IsNullOrEmpty(elementName))
            {
                XElement newElem = new XElement(nspace + elementName);
                if (!string.IsNullOrEmpty(filterName))
                {
                    newElem.Add(new XAttribute(filterName, filterValue));
                }

                ((XElement)currentNode).Add(newElem);
                newNode = newElem;
            }
            else
            {
                throw new FormatException("The given xPath is not supported " + relativePath);
            }
        }

        currentNode = newNode;
        m = m.NextMatch();
    }

    // Assure that the node is found or created
    if (elem.XPathEvaluate(xpath, XNav) == null)
    {
        throw new FormatException("The given xPath cannot be created " + xpath);
    }

    return currentNode;
}
1 голос
/ 30 июля 2017
  • Для XDocument
  • Поддерживает создание атрибутов

Использование

var xDoc = new XDocument(new XElement("root",
                        new XElement("child1"),
                        new XElement("child2")));

CreateElement(xDoc, "/root/child3");
CreateElement(xDoc, "/root/child4[@year=32][@month=44]");
CreateElement(xDoc, "/root/child4[@year=32][@month=44]/subchild1");
CreateElement(xDoc, "/root/child4[@year=32][@month=44]/subchild1/subchild[@name='jon']");
CreateElement(xDoc, "/root/child1");

определить

public static XDocument CreateElement(XDocument document, string xpath)
{
    if (string.IsNullOrEmpty(xpath))
        throw new InvalidOperationException("Xpath must not be empty");

    var xNodes = Regex.Matches(xpath, @"\/[^\/]+").Cast<Match>().Select(it => it.Value).ToList();
    if (!xNodes.Any())
        throw new InvalidOperationException("Invalid xPath");

    var parent = document.Root;
    var currentNodeXPath = "";
    foreach (var xNode in xNodes)
    {
        currentNodeXPath += xNode;
        var nodeName = Regex.Match(xNode, @"(?<=\/)[^\[]+").Value;
        var existingNode = parent.XPathSelectElement(currentNodeXPath);
        if (existingNode != null)
        {
            parent = existingNode;
            continue;
        }

        var attributeNames =
          Regex.Matches(xNode, @"(?<=@)([^=]+)\=([^]]+)")
                .Cast<Match>()
                .Select(it =>
                {
                    var groups = it.Groups.Cast<Group>().ToList();
                    return new { AttributeName = groups[1].Value, AttributeValue = groups[2].Value };
                });

        parent.Add(new XElement(nodeName, attributeNames.Select(it => new XAttribute(it.AttributeName, it.AttributeValue)).ToArray()));
        parent = parent.Descendants().Last();
    }
    return document;
}
1 голос
/ 26 сентября 2013

Мне нужен XNode вместо реализации XmlNode, и RegEx у меня не работал (потому что имена элементов с. Или - не работают)

Так вот что у меня сработало:

public static XNode createNodeFromXPath(XElement elem, string xpath)
{
    // Create a new Regex object
    Regex r = new Regex(@"/*([a-zA-Z0-9_\.\-]+)(\[@([a-zA-Z0-9_\.\-]+)='([^']*)'\])?|/@([a-zA-Z0-9_\.\-]+)");

    xpath = xpath.Replace("\"", "'");
    // Find matches
    Match m = r.Match(xpath);

    XNode currentNode = elem;
    StringBuilder currentPath = new StringBuilder();

    while (m.Success)
    {
        String currentXPath = m.Groups[0].Value;    // "/configuration" or "/appSettings" or "/add"
        String elementName = m.Groups[1].Value;     // "configuration" or "appSettings" or "add"
        String filterName = m.Groups[3].Value;      // "" or "key"
        String filterValue = m.Groups[4].Value;     // "" or "name"
        String attributeName = m.Groups[5].Value;   // "" or "value"

        StringBuilder builder = currentPath.Append(currentXPath);
        String relativePath = builder.ToString();
        XNode newNode = (XNode)elem.XPathSelectElement(relativePath);

        if (newNode == null)
        {
            if (!string.IsNullOrEmpty(attributeName))
            {
                ((XElement)currentNode).Attribute(attributeName).Value = "";
                newNode = (XNode)elem.XPathEvaluate(relativePath);
            }
            else if (!string.IsNullOrEmpty(elementName))
            {
                XElement newElem = new XElement(elementName);
                if (!string.IsNullOrEmpty(filterName))
                {
                    newElem.Add(new XAttribute(filterName, filterValue));
                }

                ((XElement)currentNode).Add(newElem);
                newNode = newElem;
            }
            else
            {
                throw new FormatException("The given xPath is not supported " + relativePath);
            }
        }

        currentNode = newNode;
        m = m.NextMatch();
    }

    // Assure that the node is found or created
    if (elem.XPathEvaluate(xpath) == null)
    {
        throw new FormatException("The given xPath cannot be created " + xpath);
    }

    return currentNode;
}
1 голос
/ 16 января 2013

Вот расширенный RegEx, основанный на Код Марка Миллера

/([\w]+)(?:(?:[\[])(@|)([\w]+)(?:([!=<>]+)(?:(?:(?:')([^']+)(?:'))|([^']+))|)(?:[]])|)|([.]+))

Group 1: Node name
Group 2: @ (or Empty, for non attributes)
Group 3: Attribute Key
Group 4: Attribute Value (if string)
Group 5: Attribute Value (if number)
Group 6: .. (dots, one or more)
1 голос
/ 23 февраля 2010

Если строка XPath обрабатывается задом наперед, проще обрабатывать нерутированные XPath, например. // a / b / c ... Он также должен поддерживать синтаксис Гордона XPath, хотя я не пробовал ...

static private XmlNode makeXPath(XmlDocument doc, string xpath)
{
    string[] partsOfXPath = xpath.Split('/');
    XmlNode node = null;
    for (int xpathPos = partsOfXPath.Length; xpathPos > 0; xpathPos--)
    {
        string subXpath = string.Join("/", partsOfXPath, 0, xpathPos);
        node = doc.SelectSingleNode(subXpath);
        if (node != null)
        {
            // append new descendants
            for (int newXpathPos = xpathPos; newXpathPos < partsOfXPath.Length; newXpathPos++)
            {
                node = node.AppendChild(doc.CreateElement(partsOfXPath[newXpathPos]));
            }
            break;
        }
    }

    return node;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...