C# linq get all items in descendant and split the value - c#

Some time ago i asked the question how to read from a xml document and use the value (see C# xdocument read from element and put the value into string)
Now i have the following issue, the solution given in the last thread works, but only if i do this:
<root>
<test>
<Copy01>#SRCDIR#\test1 ,#INSTDIR#\test2</Copy01>
</test>
</root>
but i want something like this:
<root>
<test>
<Copy01>#SRCDIR#\test1 ,#INSTDIR#\test2</Copy01>
<Copy02>#SRCDIR#\test3 ,#INSTDIR#\test4</Copy02>
</test>
</root
but with the following code in C#:
var copyitems = doc.Descendants(param[1])
.Select(s =>
{
var splits = s.Value.Split(new string[] { "#SRCDIR#", "#INSTDIR#" }, StringSplitOptions.RemoveEmptyEntries); // split the string to separate source and destination.
return new { Source = splits[0].Replace(",", ""), Destination = splits[1].Replace(",", "") };
})
.ToList();
Value of param[1] is "test" in this case
it only picks the first copy (copy01) and not the second one.
Any idea how to fix this?
Nick

You seem to want to select the child elements of the test elements. You can use SelectMany and the Elements methods to do it like this:
var copyitems =
doc.Descendants("test") //Select all "test" elements
.SelectMany(x => x.Elements()) //Select all children of all "test" elements
.Select(s =>
{
//...
})
.ToList();

Related

Sort date by value In Xml c#

I'm trying to sort the date-tags in my XML by value:
var noteElements = xDoc.Root.Descendants("Note").OrderBy(o => (DateTime)o.Element("date")).ToList();
foreach (XElement noteEl in noteElements)
{
string noteDateValue = noteEl.Element("date").Value;
noteEl.ReplaceWith(new XElement("notedate", noteEl, new XAttribute("date", noteDateValue)));
}
That doesn't work. The dates are not sorted as expected.
XML:
<Root>
<Notes>
<notedate date="date here"><Note>
<date>1997-07-04T00:00:00</date>
</Note></notedate>
<notedate date="date here"><Note>
<date>1997-06-04T00:00:00</date>
</Note></notedate>
</Notes>
</Root>
Anyone who can explain what I'm doing wrong?
You're replacing each Note element with a notedate element. The order in which you perform that replacement is irrelevant.
It sounds like you actually want something like:
var notes = doc.Root.Element("Notes");
notes.ReplaceNodes(notes.Elements()
.OrderBy(x => (DateTime) x.Element("date"))
.Select(x => new XElement("notedate",
new XAttribute("date", "date here"),
x));

Count ChildElements of the same name, inside an XML Element, with XDocument

I have an XML file that looks like this -
<SST_SignageCompConfig>
<Items>
<Item>
<Index>0</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Branding-Colours-for-business.jpg</Name>
</Item>
<Item>
<Index>1</Index>
<Type>1</Type>
<Duration>7</Duration>
<Name>Flower of Life Meditation - Copy.png</Name>
</Item>
</Items>
</SST_SignageCompConfig>
I need to count how many Item Elements there are within the Items Element.
ie how many images there are.
I'm using XDocument, so my XML file is loaded like this -
string configurationPath = System.IO.Path.Combine("C:\\SST Software\\DSS\\Compilations\\" + compName + #"\\Comp.cfg");
XDocument filedoc = XDocument.Load(configurationPath);
I've tried numerous variations of the following, with all returning a null object reference exception
foreach (var item in filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Nodes())
{
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Attribute("Name").ToString();
files.Append(name + "|");
}
I've found countless examples of how to count how many different child elements are within an element, but I need to know how many instances of the same element exist.
Can anyone point me in the right direction?
You can select all names like so:
var names = from item in filedoc.Descendants("Item")
select (string)item.Element("Name");
Or without the query syntax:
var names = filedoc.Descendants("Item").Elements("Name").Select(e => e.Value);
You can get only unique names by:
var uniqueNames = names.Distinct();
You're on the right track. Try finding out exactly which invocation is giving you the NullReferenceException. My guess is that it's the attempt to find:
.Element("SST_SignageCompConfig")
Which is your root. Try the following instead:
// note the difference between .Element and .Elements
var count = filedoc.Root.Element("Items").Elements("Item").Count();
You could also use XPath to help you nail down the navigation within your XDocument:
// returns the current top level element
var element = filedoc.Root.XPathSelectElement(".");
// If the returned element is "SST_SignageCompConfig", then:
var nextElement = filedoc.Root.XPathSelectElement("./Items")
// If the "." element is *not* "SST_SignageCompConfig", then try and locate where in your XML document that node is.
// You can navigate up with .Parent and down with .Element(s)
And so on.
How about:
var nav = fileDoc.CreateNavigator();
XPathNodeIterator navShape = nav.Select("/SST_SignageCompConfig/Items");
navShape.MoveNext()
var count = navShape.Count;
If your xml has only one Items element, this should do the trick:
filedoc.Descendants("Item")
.GroupBy(e => e.Element("Name")!=null? e.Element("Name").Value:String.Empty)
.Select(g => new
{
Name = g.Key,
Count = g.Count()
});
Because "Name" is an element and not an attribute of your xml structure.
can you try replacing this?
string name = filedoc.Element("SST_SignageCompConfig").Element("Items").Element("Item").Element("Name").ToString();

Make C# function stored in XML

I have a specific function that I want to run, and it is located inside an XML File:
Console.WriteLine("Text for test, {0}, {1}", testWord, testWord2);
The text is stored in an XML file:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<world>
<region name="TestRegion">
<area name="TestArea">
<building name="Outside">
<room name="TutorialRoom">
<textToDisplay>"Text for test, {0},{1}"</textToDisplay>
<extraString>testWord,tesWord2</extraString>
</room>
</building>
</area>
</region>
</world>
</root>
I can easily get the string data using LINQ
XElement xelement = XElement.Load("..\\..\\LocationDatabase.xml");
var textToDisplay= xelement.Elements("world")
.Elements("region").Where(region => (string)region.Attribute("name") == "TestRegion")
.Elements("area").Where(area => (string)area.Attribute("name") == "TestArea")
.Elements("building").Where(building => (string)building.Attribute("name") == "Outside")
.Elements("room").Where(room => (string)room.Attribute("name") == "TutorialRoom")
.Elements("textToDisplay");
var extraString= xelement.Elements("world")
.Elements("region").Where(region => (string)region.Attribute("name") == "TestRegion")
.Elements("area").Where(area => (string)area.Attribute("name") == "TestArea")
.Elements("building").Where(building => (string)building.Attribute("name") == "Outside")
.Elements("room").Where(room => (string)room.Attribute("name") == "TutorialRoom")
.Elements("extraString");
My issue here is how I make the command `Console.WriteLine("Something {0}", extra); to accept both LINQ statements. Anybody have an idea?
You can simply just pass in the strings as they are, however you will probably need to split the second one at the comma.
string one = "format me {0}{1}";
string two = "here, and here";
Console.WriteLine(one, two.Split(','));
IDEOne Example
The call to Console.WriteLine() is nothing special -- it just expects a string and an object[]. Your data is there, but buried in XElement instances. So
Console.WriteLine(
(string) textToDisplay.First(),
((string) extraString.First()).Split(',')
);
does what you want.
Note that this only works when "extraString" is something simple like a comma-separated list. You can't put arbitrary expressions like "2 + 2" in there and expect that to work -- for that we'd need to invoke the compiler and actually produce code, which is much more complicated.
how about this:
First split the extra string into a String[] by using Split(',') and then use a String.Format() to display the textToDisplay with the 2 strings in your string array.
var values = extraString.FirstOrDefault().Value.Split(',');
Console.WriteLine(String.Format(textToDisplay.First().Value, values[0], values[1]));

C# - Linq to XML - Exclude elements from query

I have this XML file:
<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
<MyXml>
All 3 elements that are called 'MandatoryElementX' will always appear in the file. The elements called 'CustomElementX' are unknown. These can be added or removed freely by a user and have any name.
What I need is to fetch all the elements that are not MandatoryElements. So for the file above I would want this result:
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
I don't know what the names of the custom elements may be, only the names of the 3 MandatoryElements, so the query needs to somehow exclude these 3.
Edit:
Even though this was answered, I want to clarify the question. Here is an actual file:
<Partner>
<!--Mandatory elements-->
<Name>ALU FAT</Name>
<InterfaceName>Account Lookup</InterfaceName>
<RequestFolder>C:\Documents and Settings\user1\Desktop\Requests\ALURequests</RequestFolder>
<ResponseFolder>C:\Documents and Settings\user1\Desktop\Responses</ResponseFolder>
<ArchiveMessages>Yes</ArchiveMessages>
<ArchiveFolder>C:\Documents and Settings\user1\Desktop\Archive</ArchiveFolder>
<Priority>1</Priority>
<!--Custom elements - these can be anything-->
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
</Partner>
The result here would be:
<Currency>EUR</Currency>
<AccountingSystem>HHGKOL</AccountingSystem>
You can define a list of mandatory names and use LINQ to XML to filter:
var mandatoryElements = new List<string>() {
"MandatoryElement1",
"MandatoryElement2",
"MandatoryElement3"
};
var result = xDoc.Root.Descendants()
.Where(x => !mandatoryElements.Contains(x.Name.LocalName));
Do you have created this xml or do you get it by another person/application?
If it's yours I would advise you not to number it. You can do something like
<MyXml>
<MandatoryElement id="1">value<\MandatoryElement>
<MandatoryElement id="2">value<\MandatoryElement>
<MandatoryElement id="3">value<\MandatoryElement>
<CustomElement id="1">value<\CustomElement>
<CustomElement id="2">value<\CustomElement>
<MyXml>
In the LINQ-Statement you don't need the List then.
Your question shows improperly formatted XML but I am assuming that is a typo and the real Xml can be loaded into the XDocument class.
Try this...
string xml = #"<MyXml>
<MandatoryElement1>value</MandatoryElement1>
<MandatoryElement2>value</MandatoryElement2>
<MandatoryElement3>value</MandatoryElement3>
<CustomElement1>value</CustomElement1>
<CustomElement2>value</CustomElement2>
</MyXml> ";
System.Xml.Linq.XDocument xDoc = XDocument.Parse(xml);
var result = xDoc.Root.Descendants()
.Where(x => !x.Name.LocalName.StartsWith("MandatoryElement"));
lets say TestXMLFile.xml will contain your xml,
XElement doc2 = XElement.Load(Server.MapPath("TestXMLFile.xml"));
List<XElement> _list = doc2.Elements().ToList();
List<XElement> _list2 = new List<XElement>();
foreach (XElement x in _list)
{
if (!x.Name.LocalName.StartsWith("Mandatory"))
{
_list2.Add(x);
}
}
foreach (XElement y in _list2)
{
_list.Remove(y);
}

LINQ Statement WHERE Question

I am returning a list. This is contains the names of xml nodes that cannot be blank in my XML file.
List<Setting> settingList = SettingsGateway.GetBySettingTypeList("VerifyField");
I have a LINQ Statement. I am trying to return all transactions that have empty nodes. The list here is returning the nodes that CANNOT be empty. Does anyone know what I am doing wrong here?
The following code is supposed to Bind the "transactions" to a DataGrid and display the Txn's that have empty nodes which are required.
var transactionList =
from transactions in root.Elements(XName.Get("Transactions")).Elements().AsEnumerable()
where transactions.Elements().Any
(
el =>
//String.IsNullOrEmpty(el.Value) &&
//elementsThatCannotBeEmpty.Contains(el.Name)
settingList.Any(
name => String.IsNullOrEmpty(el.Element(name.SettingValue).Value)
)
)
select new
{
CustomerName = transactions.Element(XName.Get("CustomerName")).Value,
ConfirmationNumber = transactions.Element(XName.Get("ConfirmationNumber")).Value
};
GridView.DataSource = transactionList;
GridView.DataBind();
XML File Example:
<OnlineBanking>
<Transactions>
<Txn>
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>
Okay, first problem: if the element is missing, you'll get a NullReferenceException.
I'd suggest creating a List<string> of the elements which can't be null, to make the query simple. Then:
var requiredElements = settingList.Select(x => x.SettingValue).ToList();
var transactionList = root
.Elements("Transactions")
.Elements("Txn")
.Where(x => requiredElements
.Any(name => string.IsNullOrEmpty((string) x.Element(name)));
I think that should be okay, and slightly simpler than your original code... but to be honest, your original code looks like it should have worked anyway. What did it actually do? You haven't been very clear about the actual results versus the expected ones...
Something like this:
var transactionList =
root
.Elements(XName.Get("Transactions")) //Get <Transaction> elements
.Elements() //Get <Txn> elements
.Where(txn => txn.Elements().Any(e => e.Value == String.Empty)) //Filter <Txn> Elements if it have any element like this: <CustomerStreet2></CustomerStreet2>
.Select(x => new {
PropertyX = x.Element(XName.Get("UserName")),
PropertyY = x.Element(XName.Get("CustomerStreet")),
...
});
Works with:
<OnlineBanking>
<Transactions>
<Txn> <!-- This one matches! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2></CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
<Txn> <!-- This one doesn't match! -->
<UserName>John Smith</UserName>
<CustomerStreet>123 Main</CustomerStreet>
<CustomerStreet2>ASDASD</CustomerStreet2>
<CustomerCity>New York</CustomerCity>
<CustomerState>NY</CustomerState>
<CustomerZip>12345</CustomerZip>
</Txn>
</Transactions>
</OnlineBanking>

Categories