web-dev-qa-db-fra.com

Créer des nœuds XML basés sur XPath?

Quelqu'un connaît-il un moyen existant de créer une hiérarchie XML par programme à partir d'une expression XPath?

Par exemple, si j'ai un fragment XML tel que:

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

Étant donné l'expression XPath/flux/entrée/contenu/@ source, j'aurais:

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

Je me rends compte que c'est possible en utilisant XSLT mais en raison de la nature dynamique de ce que j'essaie d'accomplir, une transformation fixe ne fonctionnera pas.

Je travaille en C # mais si quelqu'un a une solution en utilisant un autre langage, veuillez sonner.

Merci pour l'aide!

46
Fred Strauss

Dans l'exemple que vous présentez, la seule chose en cours de création est l'attribut ...

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

Si ce que vous voulez vraiment, c'est de pouvoir créer la hiérarchie là où elle n'existe pas, vous pouvez créer votre propre analyseur xpath simple. Cependant, je ne sais pas comment conserver l'attribut dans le xpath. Je préfère lancer le nœud comme élément et virer de bord sur un .SetAttribute comme je l'ai fait ici:


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

Voici mon hack rapide qui peut également créer des attributs tant que vous utilisez un format comme /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 est une méthode d'extension:

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

Échantillon:

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

par exemple.

Set(doc, "/configuration/appSettings/add[@key='Server']/@value", "foobar");
14
laktak

Un problème avec cette idée est que xpath "détruit" les informations.

Il existe un nombre infini d'arbres xml qui peuvent correspondre à de nombreux chemins x. Maintenant, dans certains cas, comme l'exemple que vous donnez, il existe une arborescence xml minimale évidente qui correspond à votre xpath, où vous avez un prédicat qui utilise "=".

Mais par exemple si le prédicat n'utilise pas égal, ou tout autre opérateur arithmétique autre qu'égal, il existe un nombre infini de possibilités. Vous pouvez essayer de choisir un arbre xml "canonique" qui nécessite, par exemple, le moins de bits à représenter.

Supposons par exemple que vous disposiez de xpath /feed/entry/content[@source > 0]. Désormais, tout arbre xml de la structure appropriée dans laquelle le contenu du nœud avait une source d'attribut dont la valeur était> 0 correspondrait, mais il existe un nombre infini de nombres supérieur à zéro. En choisissant la valeur "minimale", vraisemblablement 1, vous pourriez tenter de canoniser votre xml.

Les prédicats Xpath peuvent contenir des expressions arithmétiques assez arbitraires, donc la solution générale à cela est assez difficile, voire impossible. Vous pourriez imaginer une énorme équation là-dedans, et il faudrait la résoudre à l'envers pour arriver à des valeurs qui correspondraient à l'équation; mais comme il peut y avoir un nombre infini de valeurs correspondantes (tant qu'il s'agit vraiment d'une inégalité et non d'une équation), une solution canonique devrait être trouvée.

De nombreuses expressions d'autres formes détruisent également des informations. Par exemple, un opérateur comme "ou" détruit toujours les informations. Si vous savez que (X or Y) == 1, vous ne savez pas si X est 1, Y est 1, ou les deux sont 1; tout ce que vous savez avec certitude, c'est que l'un d'eux est 1! Par conséquent, si vous avez une expression utilisant OR, vous ne pouvez pas dire lequel des nœuds ou des valeurs qui sont entrées dans le OR doit être 1 (vous pouvez faire un choix arbitraire et définir les deux 1, comme cela satisfera à coup sûr l'expression, de même que les deux choix dans lesquels un seul d'entre eux est 1).

Supposons maintenant qu'il y ait plusieurs expressions dans le xpath qui se réfèrent au même ensemble de valeurs. On se retrouve alors avec un système d'équations ou d'inégalités simultanées qui peuvent être pratiquement impossibles à résoudre. Encore une fois, si vous limitez le xpath autorisé à un petit sous-ensemble de sa pleine puissance, vous pouvez résoudre ce problème. Je soupçonne cependant que le cas tout à fait général est similaire au problème de l'arrêt de Turing; dans ce cas, étant donné un programme arbitraire (le xpath), déterminez un ensemble de données cohérentes qui correspond au programme, et est dans un certain sens minimal.

9
Roderick Llewellyn

Voici ma version. J'espère que cela aiderait aussi quelqu'un.

    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;
    }
5
Eranga Dissanayaka

La version C # de Mark Miller's Java solution

    /// <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;
    }
3
Ben

Il s'agit d'une version améliorée de la solution de Christian Peeters qui prend en charge les espaces de noms dans l'expression 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;
}
2
Jan Lauridsen
  • Pour XDocument
  • Prend en charge la création d'attributs

Utilisez

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

définir

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
tchelidze

J'avais besoin d'un XNode au lieu d'une implémentation XmlNode, et le RegEx ne fonctionnait pas pour moi (car les noms d'élément avec. Ou - ne fonctionnent pas)

Voilà donc ce qui a fonctionné pour moi:

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

Si la chaîne XPath est traitée de l'arrière vers l'avant, il est plus facile de traiter les XPath non enracinés, par exemple. // a/b/c ... Il devrait également prendre en charge la syntaxe XPath de Gordon même si je n'ai pas essayé ...

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

Voici un RegEx amélioré basé sur code de Mark Miller

/([\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
user1984091

Je sais que c'est un fil vraiment ancien ... mais je viens d'essayer la même chose et j'ai trouvé l'expression régulière suivante qui n'est pas parfaite mais je trouve plus générique

/+([\w]+)(\[@([\w]+)='([^']*)'\])?|/@([\w]+)

La chaîne/configuration/appSettings/add [@ key = 'name']/@ value

devrait être analysé

14 correspondance (s) trouvée (s):

start = 0, end = 14 Group (0) =/configuration Group (1) = configuration Group (2) = null Group (3) = null Group (4) = null Group (5) = null

start = 14, end = 26 Group (0) =/appSettings Group (1) = appSettings Group (2) = null Group (3) = null Group (4) = null Group (5) = null

start = 26, end = 43 Group (0) =/add [@ key = 'name'] Group (1) = add Group (2) = [@ key = 'name'] Group (3) = key Group (4 ) = nom Groupe (5) = null

start = 43, end = 50 Group (0) =/@ value Group (1) = null Group (2) = null Group (3) = null Group (4) = null Group (5) = value


Ce qui signifie que nous avons

Groupe (0) = ignoré Groupe (1) = le nom de l'élément Groupe (2) = ignoré Groupe (3) = nom de l'attribut de filtre Groupe (4) = valeur de l'attribut de filtre

Voici une méthode Java qui peut utiliser le modèle

public static Node createNodeFromXPath(Document doc, String expression) throws XPathExpressionException {
StringBuilder currentPath = new StringBuilder();
Matcher matcher = xpathParserPattern.matcher(expression);

Node currentNode = doc.getFirstChild();

while (matcher.find()) {
    String currentXPath = matcher.group(0);
    String elementName = matcher.group(1);
    String filterName = matcher.group(3);
    String filterValue = matcher.group(4);
    String attributeName = matcher.group(5);

    StringBuilder builder = currentPath.append(currentXPath);
    String relativePath = builder.toString();
    Node newNode = selectSingleNode(doc, relativePath);

    if (newNode == null) {
        if (attributeName != null) {
            ((Element) currentNode).setAttribute(attributeName, "");
            newNode = selectSingleNode(doc, relativePath);

        } else if (elementName != null) {
            Element element = doc.createElement(elementName);
            if (filterName != null) {
                element.setAttribute(filterName, filterValue);
            }
            currentNode.appendChild(element);
            newNode = element;

        } else {
            throw new UnsupportedOperationException("The given xPath is not supported " + relativePath);
        }
    }

    currentNode = newNode;
}

if (selectSingleNode(doc, expression) == null) {
    throw new IllegalArgumentException("The given xPath cannot be created " + expression);
}

return currentNode;

}

0
Mark Miller