Crear Nodos XML basados ​​en XPath?

¿Alguien sabe de un medio existente para crear una jerarquía XML programáticamente a partir de una expresión XPath?

Por ejemplo, si tengo un fragmento de XML como, por ejemplo:

      

Dada la expresión XPath / feed / entry / content / @ source tendría:

       

Me doy cuenta de que esto es posible usando XSLT, pero debido a la naturaleza dinámica de lo que bash lograr, una transformación fija no funcionará.

Estoy trabajando en C #, pero si alguien tiene una solución usando otro idioma, por favor, toque.

¡Gracias por la ayuda!

En el ejemplo que presenta, lo único que se crea es el atributo …

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

Si lo que realmente quieres es poder crear la jerarquía donde no existe, entonces podrías hacer tu propio analizador simple de xpath. Sin embargo, no sé cómo mantener el atributo en xpath. Prefiero lanzar el nodo como elemento y fijarlo en .SetAttribute como lo he hecho aquí:

 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(""); makeXPath(doc, "/feed/entry/data"); XmlElement contentElement = (XmlElement)makeXPath(doc, "/feed/entry/content"); contentElement.SetAttribute("source", ""); Console.WriteLine(doc.OuterXml); } 

Este es mi truco rápido que también puede crear atributos siempre que use un formato como /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 es un método de extensión:

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

Muestra:

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

p.ej

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

Un problema con esta idea es que xpath “destruye” la información.

Hay un número infinito de árboles xml que pueden coincidir con muchos xpaths. Ahora, en algunos casos, al igual que en el ejemplo que das, existe un árbol xml mínimo obvio que coincide con tu xpath, donde tienes un predicado que usa “=”.

Pero, por ejemplo, si el predicado no utiliza igual o cualquier otro operador aritmético que no sea igual, existe un número infinito de posibilidades. Podría tratar de elegir un árbol xml “canónico” que requiera, por ejemplo, la menor cantidad de bits para representar.

Supongamos, por ejemplo, que tiene xpath /feed/entry/content[@source > 0] . Ahora cualquier árbol xml de la estructura apropiada en la que el contenido del nodo tuviera un origen de atributo cuyo valor fuera> 0 coincidiría, pero hay un número infinito de números mayor que cero. Al elegir el valor “mínimo”, presumiblemente 1, podría intentar canonicalizar su xml.

Los predicados de Xpath pueden contener expresiones aritméticas bastante arbitrarias, por lo que la solución general a esto es bastante difícil, si no imposible. Se podría imaginar una ecuación enorme allí, y tendría que resolverse a la inversa para encontrar valores que coincidieran con la ecuación; pero dado que puede haber un número infinito de valores coincidentes (siempre que en realidad sea una desigualdad, no una ecuación), sería necesario encontrar una solución canónica.

Muchas expresiones de otras formas también destruyen la información. Por ejemplo, un operador como “o” siempre destruye la información. Si sabes que (X or Y) == 1 , no sabes si X es 1, Y es 1, o ambos son 1; ¡todo lo que sabes con certeza es que uno de ellos es 1! Por lo tanto, si tiene una expresión que usa O, no puede decir cuál de los nodos o valores que son entradas para el OR debe ser 1 (puede hacer una elección arbitraria y establecer ambos 1, ya que eso satisfará la expresión, como lo hará las dos opciones en las que solo una de ellas es 1).

Ahora supongamos que hay varias expresiones en xpath que se refieren al mismo conjunto de valores. Luego terminas con un sistema de ecuaciones o desigualdades simultáneas que pueden ser virtualmente imposibles de resolver. Nuevamente, si restringe el xpath permitido a un pequeño subconjunto de su potencia máxima, puede resolver este problema. Sospecho que el caso completamente general es similar al problema de detención de Turing, sin embargo; en este caso, dado un progtwig arbitrario (el xpath), descubra un conjunto de datos consistentes que coincida con el progtwig, y ​​en algún sentido es mínimo.

Aquí está mi versión. Espero que esto también ayude a alguien.

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

La versión C # de Mark Miller

  ///  /// Makes the X path. Use a format like //configuration/appSettings/add[@key='name']/@value ///  /// The doc. /// The xpath. ///  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; } 

Si la cadena XPath se procesa de atrás hacia adelante, es más fácil procesar XPaths no rooteados, por ej. // a / b / c … También debería soportar la syntax de XPath de Gordon aunque no lo he intentado …

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

Aquí hay un RegEx mejorado basado en 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) 

Esta es una versión mejorada de la solución Christian Peeters que admite espacios de nombres en la expresión 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; } 

Necesitaba un XNode en lugar de una implementación de XmlNode, y el RegEx no me funcionaba (porque los nombres de los elementos con o – no funcionan)

Entonces, esto es lo que funcionó para mí:

 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; } 
  • Para XDocument
  • Admite la creación de atributos

Utilizar

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

definir

 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().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() .Select(it => { var groups = it.Groups.Cast().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; } 

Sé que este es un hilo muy viejo … pero he estado intentando lo mismo y se me ocurrió la siguiente expresión regular, que no es perfecta, pero encuentro que es más genérica.

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

El valor string / configuration / appSettings / add [@ key = ‘name’] / @

debe ser analizado para

Encontrado 14 partido (s):

start = 0, end = 14 Group (0) = / configuration Group (1) = configuración 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 Grupo (2) = [@ clave = ‘nombre’] Grupo (3) = clave Grupo (4 ) = nombre Grupo (5) = nulo

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


Lo que significa que tenemos

Grupo (0) = Grupo ignorado (1) = El nombre del elemento Grupo (2) = Grupo ignorado (3) = Nombre del atributo del filtro Grupo (4) = Valor del atributo del filtro

Aquí hay un método de Java que puede usar el patrón

 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; 

}