49 votes

Créer des nœuds XML à partir de XPath ?

Quelqu'un connaît-il un moyen existant de créer une hiérarchie XML de manière programmatique à 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 /feed/entry/content/@source, j'aurais :

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

Je me rends compte que cela 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 utilisant un autre langage, merci de m'en faire part.

Merci pour votre aide !

1 votes

Que voulez-vous dire par "une transformation fixe ne fonctionnera pas" ?

0 votes

L'objectif final est de faire correspondre des valeurs d'une base de données à différents emplacements dans un document XML. Dans les cas où la valeur n'existe pas dans la base de données, je ne veux pas créer la hiérarchie correspondante.

48voto

xcud Points 6878

Dans l'exemple que vous présentez, la seule chose qui est créée 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 être capable de créer la hiérarchie là où elle n'existe pas, vous pourriez créer votre propre analyseur xpath simple. Je ne sais pas si vous pouvez conserver l'attribut dans le xpath. Je préférerais que le nœud soit considéré comme un élément et que l'on ajoute 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);
}

0 votes

Merci de répondre. J'espérais ne pas avoir à "créer mon propre analyseur XPath", mais il se peut que nous en arrivions là.

1 votes

Merci, j'en avais besoin et vous venez de m'épargner une heure ou deux d'écriture de ma propre implémentation.

1 votes

@xcud : Même chose que pour Chris ! Merci beaucoup !

15voto

hcris Points 9359

Voici ma petite astuce qui peut également créer des attributs à condition d'utiliser un format tel que /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");

0 votes

Doit permettre d'éviter la concurrence.

9voto

Un problème avec cette idée est que xpath "détruit" l'information.

Il existe un nombre infini d'arbres xml qui peuvent correspondre à de nombreux xpaths. Dans certains cas, comme l'exemple que vous donnez, il existe un arbre xml minimal évident qui correspond à votre xpath, où vous avez un prédicat qui utilise "=".

Mais par exemple, si le prédicat utilise l'opérateur non égal, ou tout autre opérateur arithmétique autre qu'égal, il existe un nombre infini de possibilités. Vous pourriez essayer de choisir un arbre xml "canonique" qui nécessite, disons, le moins de bits possible pour être représenté.

Supposons par exemple que vous ayez xpath /feed/entry/content[@source > 0] . Maintenant, tout arbre xml de la structure appropriée dans lequel le contenu des nœuds a un attribut source dont la valeur est > 0 correspondrait, mais il y a un nombre infini de nombres supérieurs à 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 à ce problème est assez difficile, voire impossible. On pourrait imaginer une énorme équation, et il faudrait la résoudre à l'envers pour trouver des valeurs qui correspondent à 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), il faudrait trouver une solution canonique.

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

Supposons maintenant qu'il y a plusieurs expressions dans le xpath qui font référence au même ensemble de valeurs. Vous vous retrouvez alors avec un système d'équations ou d'inégalités simultanées qui peut être pratiquement impossible à 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 général est similaire au problème de l'arrêt de Turing ; dans ce cas, étant donné un programme arbitraire (le xpath), il faut trouver un ensemble de données cohérentes qui correspondent au programme, et qui sont en quelque sorte minimales.

0 votes

+1 pour un excellent argumentaire expliquant pourquoi une telle fonctionnalité n'est pas déjà courante dans les systèmes XML. Un solveur de contraintes pourrait être utilisé pour aider à résoudre le problème du "système d'équations ou d'inégalités simultanées", mais il s'agit d'une solution assez lourde, qui pourrait nécessiter beaucoup de puissance de traitement (je crois que c'est NP-Complet).

5voto

Voici ma version. J'espère que cela pourra aussi aider 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;
    }

0 votes

Doit permettre d'éviter la concurrence.

1voto

ilen Points 33

Si la chaîne XPath est traitée de l'arrière vers l'avant, il est plus facile de traiter les XPaths sans racine, par exemple //a/b/c.... Il devrait aussi supporter la syntaxe XPath de Gordon, mais 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X