Looking for a way to merge to XML files where the modified attributes in the second file should override the values of the objects in the first file. Seems like this should be doable with linq to xml but having some trouble figuring out how to do it.
For example take the following two XML files:
File 1:
<root>
<foo name="1">
<val1>hello</val1>
<val2>world</val2>
</foo>
<foo name="2">
<val1>bye</val1>
</foo>
</root>
File 2:
<root>
<foo name="1">
<val2>friend</val2>
</foo>
</root>
The desired end result would be to merge File 2 in to File 1 and end up with
<root>
<foo name="1">
<val1>hello</val1>
<val2>friend</val2>
</foo>
<foo name="2">
<val1>bye</val1>
</foo>
</root>
Sub 'foo' elements should be uniquely identified by their 'name' value with any set values in File 2 overriding the values in File 1.
Any pointers in the right direction would be much appreciated, thanks!
You can just iterate and update values - don't know how generic you want this to be though...
class Program
{
const string file1 = #"<root><foo name=""1""><val1>hello</val1><val2>world</val2></foo><foo name=""2""><val1>bye</val1></foo></root>";
const string file2 = #"<root><foo name=""1""><val2>friend</val2></foo></root>";
static void Main(string[] args)
{
XDocument document1 = XDocument.Parse(file1);
XDocument document2 = XDocument.Parse(file2);
foreach (XElement foo in document2.Descendants("foo"))
{
foreach (XElement val in foo.Elements())
{
XElement elementToUpdate = (from fooElement in document1.Descendants("foo")
from valElement in fooElement.Elements()
where fooElement.Attribute("name").Value == foo.Attribute("name").Value &&
valElement.Name == val.Name
select valElement).FirstOrDefault();
if (elementToUpdate != null)
elementToUpdate.Value = val.Value;
}
}
Console.WriteLine(document1.ToString());
Console.ReadLine();
}
}
You can build new xml from these two:
XDocument xdoc1 = XDocument.Load("file1.xml");
XDocument xdoc2 = XDocument.Load("file2.xml");
XElement root =
new XElement("root",
from f in xdoc2.Descendants("foo").Concat(xdoc1.Descendants("foo"))
group f by (int)f.Attribute("name") into foos
select new XElement("foo",
new XAttribute("name", foos.Key),
foos.Elements().GroupBy(v => v.Name.LocalName)
.OrderBy(g => g.Key)
.Select(g => g.First())));
root.Save("file1.xml");
Thus foo elements from second file selected first, they will have priority over foo elements from first file (when we do grouping).
Related
Let's say I have an xml string:
<?xml version="1.0" encoding="UTF-8"?>
<Return version="1.0">
<File>1</File>
<URL>2</URL>
<SourceUUID>1191CF90-5A32-4D29-9F90-24B2EXXXXXX0</SourceUUID>
</Return>
and I want to extract the value of SourceUUID, how?
I tried:
XDocument doc = XDocument.Parse(xmlString);
foreach (XElement element in doc.Descendants("SourceUUID"))
{
Console.WriteLine(element);
}
If all you want is the content of the SourceUUID element, and there's only going to be 1 in the XML, you can do this:
XDocument doc = XDocument.Parse(xmlString);
var value = doc.Descendants("SourceUUID").SingleOrDefault()?.Value;
If there are going to be more than one, you can do this:
var values = doc.Descendants("SourceUUID").Select(x => x.Value);
This gives you an enumerable of strings that are the text values of the elements.
I'm fairly new to XML and whilst trying to load a series of XML files in through SSIS I've come up against the 'null' value issue when writing the data to a SQL table.
The XML is being provided by a 3rd party supplier and it can't be changed at their end - it is what it is and most of it works fine - it is only one bit of the document that returns this 'null' value problem.
I've read a few articles and found this is down to the XML needing to be wrapped in another root node Link
So my question is how can I add tags around some current tags? (Shoot me down for the awful wording).
The XML looks something like this:
<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<Child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
<Summary>
<text>1234</text>
</Summary>
</Parent>
And what I need to do is dynamically add tags around the child tags above to get something like this:
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<info_2>
<Child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
</info_2>
<Summary>
<text>1234</text>
</Summary>
</Parent>
The SSIS uses a foreach to loop through all of the XML files. My theory at this stage is to add a script task, take the current file variable and use it to load and edit the XML and then save it back before the data flow task picks it up and extracts the data.
I've manually added some tags to one file and it did eliminate the null value problem - so I'm confident it will work.
The current code in my script task is:
public void Main()
{
//Get the variable from the foreach which contains the current file name.
string filename = Convert.ToString(Dts.Variables["User::File_Name"]);
//Create a new XML document object and then load the current file into it.
XmlDocument doc = new XmlDocument();
doc.Load(filename);
}
But then I have no idea how to add those tags!
Find elements which name starts with 'child'
Create new parent element info_2 populated with copy of the found elements as its content
Remove original elements found in step 1 from the tree
Here is an example :
var raw = #"<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
<Summary>
<text>1234</text>
</Summary>
</Parent>";
var doc = XDocument.Parse(raw);
//step 1
var elements = doc.Root.Elements().Where(o => o.Name.LocalName.StartsWith("child"));
//step 2
var newElement = new XElement("info_2", elements);
doc.Root.Element("info").AddAfterSelf(newElement);
//step 3:
elements.Remove();
//print result
Console.WriteLine(doc.ToString());
Output :
<Parent>
<info>
<id>1234</id>
<secondary_id>ABC-1234</secondary_id>
</info>
<info_2>
<child>Something</child>
<child2>Something else</child2>
<child3>Something else here</child3>
</info_2>
<Summary>
<text>1234</text>
</Summary>
</Parent>
Great solution from har07.
I've edited the names, but here is the code which solved my problem:
public void Main()
{
string filename = Convert.ToString(Dts.Variables["User::File_Name"].Value);
XDocument xml = new XDocument();
var doc = XDocument.Load(filename);
var element1 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("bob"));
var element2 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("mice"));
var element3 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("car"));
var element4 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("dog"));
var element5 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("cat"));
var element6 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("number"));
var element7 = doc.Root.Elements().Where(o => o.Name.LocalName.Equals("thing"));
var newElement = new XElement("New_Tag", element1, element2, element3, element4, element5, element6, element7);
doc.Root.Element("Some_Tag").AddBeforeSelf(newElement);
element1.Remove();
element2.Remove();
element3.Remove();
element4.Remove();
element5.Remove();
element6.Remove();
element7.Remove();
doc.Save(filename);
Dts.TaskResult = (int)ScriptResults.Success;
}
I have below a xml file with the below format:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Countries>
<country>India</country>
<country>USA</country>
<country>UK</country>
</Countries>
</Root>
string newCountry="UAE"
I want to insert this "UAE" country to the above xml file, before that I want to check whether "UAE" is already exists in the xml. If not exists then only want to insert otherwise no operation. How can I do this?
Like this:
XDocument xml = XDocument.Load("path_to_file");
string newCountry = "UAE";
XElement countries = xml.Descendants("Countries").First();
XElement el = countries.Elements().FirstOrDefault(x => x.Value == newCountry);
if (el == null)
{
el = new XElement("country");
el.Value = newCountry;
countries.Add(el);
}
//Console.WriteLine(countries.ToString());
The easiest way would probably be to read the xml into C# objects, check for the existance of UAE, potentially add it, and write the objects back to XML.
I've been coding a program that stores employee data using XDocument:
<!-- School Employee Data -->
<SchoolData storeName="mikveIsrael" location="mikve">
<employee id="1">
<personalInfo>
<name>Ilan Berlinbluv</name>
<zip>58505</zip>
</personalInfo>
<employeeInfo>
<salary>5000</salary>
<id>1</id>
</employeeInfo>
</employee>
<employee id="2">...</employee>
</SchoolData>
I want my program to read every employee id attrib, but I don't know how to do so. Instead, I tried doing this:
var ids = from idz in doc.Descendants("SchoolData")
select new
{
id1 = idz.Element("employee").Attribute("id").Value
};
where doc is the XDocument var. It returns just the first one, but I want it to return an array or List<string>, I just don't know how to iterate through all the same-named employee elements.
XDocument doc = XDocument.Parse(xml);
List<string> ids = doc.Descendants("employee")
.Select(e => e.Attribute("id").Value)
.ToList();
This may helps:
var xDoc = XDocument.Load(path);
var result = xDoc.Descendants("employee")
.SelectMany(i => i.Attribute("id").Value)
.ToList();
I have an xml doc similar to this:
<Root>
<MainItem ID="1">
<SubItem></SubItem>
<SubItem></SubItem>
<SubItem></SubItem>
</MainItem>
<MainItem ID="2">
<SubItem></SubItem>
<SubItem></SubItem>
<SubItem></SubItem>
</MainItem>
...
</Root>
I want to return the whole of the MainItem element based on the value of attribute ID.
So effectively if Attribute ID is equal to 2, then give me that MainItem element back.
I can't work out how to do this with LINQ.
There seems to be a load of information on google, but I just can't quite seem to find what I'm looking for.
Little help ?
TIA
:-)
It could be something like this:
XDocument doc = XDocument.Load("myxmlfile.xml");
XElement mainElement = doc.Element("Root")
.Elements("MainItem")
.First(e => (int)e.Attribute("ID") == 2);
// additional work
How about this:
// load your XML
XDocument doc = XDocument.Load(#"D:\linq.xml");
// find element which has a ID=2 value
XElement mainItem = doc.Descendants("MainItem")
.Where(mi => mi.Attribute("ID").Value == "2")
.FirstOrDefault();
if(mainItem != null)
{
// do whatever you need to do
}
Marc
I changed your XML slightly to have values:
<?xml version="1.0"?>
<Root>
<MainItem ID="1">
<SubItem>value 1</SubItem>
<SubItem>val 2</SubItem>
<SubItem></SubItem>
</MainItem>
<MainItem ID="2">
<SubItem></SubItem>
<SubItem></SubItem>
<SubItem></SubItem>
</MainItem>
</Root>
And with this LINQ:
XDocument xmlDoc = XDocument.Load(#"C:\test.xml");
var result = from mainitem in xmlDoc.Descendants("MainItem")
where mainitem.Attribute("ID").Value == "1"
select mainitem;
foreach (var subitem in result.First().Descendants())
{
Console.WriteLine(subitem.Value);
}
Console.Read();
From here: How to: Filter on an Attribute (XPath-LINQ to XML)
// LINQ to XML query
IEnumerable<XElement> list1 =
from el in items.Descendants("MainItem")
where (string)el.Attribute("ID") == "2"
select el;
// XPath expression
IEnumerable<XElement> list2 = items.XPathSelectElements(".//MainItem[#ID='2']");