XPath式からXML階層をプログラムで作成する既存の手段を知っている人はいますか?
たとえば、次のような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#で働いていますが、誰かが他の言語を使用して解決策を持っている場合はチャイムしてください。
助けてくれてありがとう!
この例では、作成されるのは属性のみです...
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);
}
以下に、/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");
このアイデアの1つの問題は、xpathが情報を「破壊」することです。
多くのxpathに一致するXMLツリーの数は無限です。場合によっては、指定した例のように、「=」を使用する述語があるxpathに一致する明らかな最小限のxmlツリーがあります。
しかし、例えば、述部が等しくない、または等しくない他の算術演算子を使用する場合、無限の可能性が存在します。たとえば、表示するビット数が最も少ない「標準的な」xmlツリーを選択してみてください。
たとえば、xpath /feed/entry/content[@source > 0]
があったとします。これで、ノードコンテンツの値が0より大きい属性ソースを持つ適切な構造のxmlツリーは一致しますが、ゼロより大きい数は無限にあります。 「最小」値(おそらく1)を選択することにより、xmlの正規化を試みることができます。
Xpath述語にはかなり任意の算術式を含めることができるため、これに対する一般的な解決策は不可能ではないにしても、非常に困難です。そこに巨大な方程式が想像できますが、方程式と一致する値を見つけるには逆に解く必要があります。しかし、一致する値の数は無限である可能性があるため(実際には方程式ではなく不等式である限り)、標準的な解決策を見つける必要があります。
他の形式の多くの表現も情報を破壊します。たとえば、「または」のような演算子は常に情報を破壊します。 (X or Y) == 1
であることがわかっている場合、Xが1、Yが1、または両方が1であるかどうかはわかりません。確かに知っているのは、そのうちの1つが1であることだけです!したがって、ORを使用する式がある場合、ORへの入力であるノードまたは値のいずれが1であるかを判断できません(任意の選択を行い、両方の1を設定できます)どちらか1つだけである2つの選択肢がそうであるように、確実に式を満たします1)。
ここで、xpathに同じ値のセットを参照するいくつかの式があるとします。その後、解くのが事実上不可能な連立方程式または不等式のシステムになります。繰り返しますが、許容されるxpathをそのフルパワーの小さなサブセットに制限すると、この問題を解決できます。しかし、完全に一般的なケースはチューリング停止問題に似ていると思われます。この場合、任意のプログラム(xpath)が与えられると、そのプログラムに一致する一貫したデータのセットを見つけ出し、ある意味最小です。
これが私のバージョンです。これが誰かを助けることを願っています。
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;
}
Mark Miller's Java solution のC#バージョン
/// <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;
}
これは Christian Peetersのソリューション の改良版で、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;
}
使用
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");
define
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;
}
XmlNode実装の代わりにXNodeが必要でしたが、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;
}
XPath文字列が後ろから前に処理される場合、ルート化されていないXPathを処理する方が簡単です。 // a/b/c ...試していませんが、Gordonの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;
}
Mark Millerのコード に基づく拡張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)
私はこれが本当に古いスレッドであることを知っています...しかし、私はちょうど同じことを試していて、完璧ではない次の正規表現を考え出しましたが、より一般的な
/+([\w]+)(\[@([\w]+)='([^']*)'\])?|/@([\w]+)
文字列/ configuration/appSettings/add [@ key = 'name']/@ value
解析する必要があります
14件見つかりました:
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 )=名前Group(5)= null
start = 43、end = 50 Group(0)=/@ value Group(1)= null Group(2)= null Group(3)= null Group(4)= null Group(5)= value
つまり、私たちは
Group(0)=無視されたGroup(1)=要素名Group(2)=無視されたGroup(3)=フィルタ属性名Group(4)=フィルタ属性値
パターンを使用できるJavaメソッド
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;
}