Reputation: 10091
Here comes a tricky question.
I got one file, MainFile.XML that looks like this:
<xml>
<header>
<some></some>
<addThis></addThis>
</header>
<footer></footer>
<this>
<is>
<deep>
<like></like>
</deep>
</is>
</this>
<test></test>
<page></page>
<addThis></addThis>
And my other file, LangFile.XML looks like this.
<xml>
<header>
<some>English file</some>
</header>
<footer>Footer</footer>
<this>
<is>
<deep>
<like>Hey</like>
</deep>
</is>
</this>
<test>Something</test>
</xml>
I want to update my LangFile.XML so that it matches my MainFile.XML but I need to keep all Text values in the LangFile.
I want the LangFile to look like this after an update: EXPECTED OUTPUT
<xml>
<header>
<some>English file</some>
<addThis></addThis>
</header>
<footer>Footer</footer>
<this>
<is>
<deep>
<like>Hey</like>
</deep>
</is>
</this>
<test>Something</test>
<page></page>
<addThis></addThis>
</xml>
I've looked at this answer but I need to update the file and keep the values... Compare two text files line by line
The tricky part is the nesting, it can be anything between 1 level up to X levels deep...
My problem is that I dont know how to compare the rows row by row when going deeper in the tree, I've tried something like this but im stuck... I dont know how to add the specific descendant to the new list.
String directory = @"C:\Utv\XmlTest";
var mainFile = XDocument.Load(Path.Combine(directory, "MainFile.XML"));
var langFile = XDocument.Load(Path.Combine(directory, "LangFile.XML"));
//Get all descendant nodes
var mainFileDesc = mainFile.Root.Descendants().ToList();
var langFileDesc = langFile.Root.Descendants().ToList();
//Loop through the mainfile
for(var i = 0; i < mainFileDesc.Count(); i++)
{
var mainRow = mainFileDesc[i];
var langRow = langFileDesc[i];
//Compare the rows descendants, if not the same, add the mainRow to the langRow
if(mainRow.Descendants().Count() != langRow.Descendants().Count())
{
//Here I want to check if the mainRow != the langRow
//if not, add the mainRow to the langFile list
if(mainRow != langRow)
{
langFileDesc.Insert(i, mainRow);
}
}
}
Im getting the following error now:
var langRow = langFileDesc[i];
Message Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index
That's because the lists doesn't have the same length, that's why I need to add it to the list...
Upvotes: 4
Views: 2159
Reputation: 11955
You would probably want to do this recursively. I'm presuming a simple file copy is not what you want.
Guess while writing this, you checked the other as an answer. I hope it works for you, but here is another approach. My approach looks way more complicated, so if Mariano's works for you great.
/// <summary>
/// Copy A to B where B doesn't have A nodes.
/// </summary>
public static void EvenUp(XElement A, XElement B)
{
XNode lastB = null, nodeA = null, nodeB = null;
Action Copy_A_To_B = () =>
{
if (null == lastB)
B.AddFirst(nodeA);
else
lastB.AddAfterSelf(nodeA);
};
var listA = A.Nodes().ToList();
var listB = B.Nodes().ToList();
int a, b;
for (a = 0, b = 0; a < listA.Count && b < listB.Count; a++, b++)
{
nodeA = listA[a];
nodeB = listB[b];
XElement xA = nodeA as XElement,
xB = nodeB as XElement;
XText tA = nodeA as XText,
tB = nodeB as XText;
if (null != xA && null != xB)
{
if (xA.Name.LocalName == xB.Name.LocalName)
EvenUp(xA, xB);
else
{
Copy_A_To_B();
EvenUp(A, B); // Restart this iteration for various reasons such as
// the next nodeA might be the same as current nodeB
return;
}
}
else if (null != xA)
Copy_A_To_B();
else if (null != tA && null != tB)
{
if (tA.Value != tB.Value)
tB.Value = tA.Value;
}
else if (null != tA)
Copy_A_To_B();
lastB = nodeB;
}
for (; a < listA.Count; a++)
{
nodeA = listA[a];
Copy_A_To_B();
if (null == lastB)
lastB = B.FirstNode;
else
lastB = lastB.NextNode;
}
}
You can test it with this:
XElement mainFile = XElement.Load("xmlfile1.xml");
XElement langFile = XElement.Load("xmlfile2.xml");
EvenUp(mainFile, langFile);
Console.WriteLine(langFile.ToString());
Console.ReadLine();
If you then want to copy the other direction, simply call it again but with the arguments switched: EvenUp(langFile, mainFile)
Then both files should be duplicates.
Upvotes: 2
Reputation: 36
As I understand it, what you want to do is update an xml file with another xml file, considering that the two files have a similar structure. If so, you can try to do this with xpath , this would be something like:
private static void Main(string[] args)
{
try
{
XmlDocument xml1 = new XmlDocument();
xml1.Load(@"C:\testxml\MainFile.xml");
XPathNavigator nav = xml1.CreateNavigator();
//Create the langFile Navigator
XPathDocument xml2 = new XPathDocument(@"C:\testxml\LangFile.xml");
XPathNavigator nav2 = xml2.CreateNavigator();
//Select all text nodes.
var nodes = nav2.SelectDescendants(XPathNodeType.Text, true);
while (nodes.MoveNext())
{
//Update the MainFile with the values in the LangFile
var c = nav.SelectSingleNode(GetPath(nodes.Clone().Current));//(1*)
if(c != null)
{
c.MoveToFirstChild();
c.SetValue(nodes.Current.Value);
}
}
Console.WriteLine(xml1.InnerXml);
Console.ReadKey();
}
catch
{
}
}
private static string GetPath(XPathNavigator navigator)
{
string aux =string.Empty;
while (navigator.MoveToParent())
{
aux = navigator.Name + "/"+aux;
}
return "/" + (aux.EndsWith("/")?aux.Remove(aux.LastIndexOf('/')):aux);
}
With this you don't need to know how much nested have your files, but if the xml have more than one node with the same name in the same level, in the line with (*1) comment, you should manage it. I hope this helps.
Upvotes: 2