LINQ to XML, with conditions and Variable XML Structure with C# - c#

i have an big issue, and just lost like 5 hours on it.
I have to create an XML file, which will be populated by a database reg.
i have variable structure, which in some cases will use specific sub structure.
the logic is simple.
I have to get all agencys,
then in every agency i have to iterate a foreach cycle,
and get all houses that they whant to sell/rent.
But its not only houses, thereĀ“s apartaments, garages, and so on.
Each of this scenes have their own structure with different data.
This is not all, there must have an condition. Only writes xml childs only if they are supposed to.
piece of xml example
<Clients>
<Client>
<aggregator/>
<code/>
<reference/>
<contact>
<secondhandListing>
<property>
<code/>
<reference/>
<scope/>
<address>
<features/>
<operation> // variable structure
1example <price/>
<communityCosts/>
2example <price/>
<communityCosts/>
<depositType/>
</operation>
</property>
</secondhandListing>
Can some one show me an example of how this could be done.
what I archive until now was:
var agenciasConectores = //query of Agencys
foreach (var agenciaConector in agenciasConectores)
{
var imoveisAgencia = // query of homes in each Agency
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XElement("Clients",
from agencia in agenciasConectores
select new XElement("Client", new XAttribute("ID", agencia.AgencyId),
new XElement("aggregator", agencia.ConnectorLicense),
new XElement("code", agencia.Name),
new XElement("Reference", agencia.Phone),
new XElement("contact", agencia.Phone),
new XElement("secondhandListing"),
new XElement("newbuildListing")
)));
foreach (var imovel in imoveisAgencia)
{
if (imoveisAgencia.Count() > 1)
{
doc.Document.Add(new XElement("property",
new XElement("code", "codigo"),
new XElement("reference", "reference"),
new XElement("scope", "scope"),
new XElement("address", "address"),
new XElement("contact", "contact"),
new XElement("features", "features"),
new XElement("operation", "operation"),
new XElement("description", "description")));
}
}
}

When I try to run this in Visual Studio, the following line throws an exception
doc.Document.Add(new XElement("property",
...
are you trying to do something more like this?
doc.Root.Add(new XElement("property",
...
or, perhaps, something closer to this
doc.Descendants("Client")
.Single(c => c.code == "something")
.Add(new XElement("property",
...

Already find a solution.
its simple.
XElement root = new XElement("root");
XElement child = new XElement("Child");
XElement child2 = new XElement(Child2);
then I populate the elements.
Child.SetValue(a);
child2.Setvalue(b);
and at final, i just add elements to parent Element.
root.add(Child,Child2);
But like that i also can make some validations(in my case in cycle)
If(a>b)
root.add(child);
else
root.add(child2);
thx for the try. I learn it by the example hehehhe

Related

Using XML to Linq to Iterate an ICollection

I have a collection of objects that I need to iterate over and pull data out to create an XML file. I am trying to use XML Linq to do so, but it appears I am not grasping the concept. Here is my code:
string jSonProducts = File.ReadAllText(settings.productJsonConfig.JSONProductFilePath);
ICollection<ProductSearchModel> prods = null;
prods = JsonConvert.DeserializeObject<ICollection<ProductSearchModel>>(jSonProducts);
foreach (ProductSearchModel prod in prods)
{
var xmlNode =
new XElement("Feed",
new XAttribute("xmlns", settings.bvXMLConfig.xmlns),
new XAttribute("name", settings.bvXMLConfig.xmlName),
new XAttribute("incrmental", settings.bvXMLConfig.xmlIncremental),
new XAttribute("extractDate",DateTime.UtcNow),
new XElement("Products"),
new XElement("Product"),
new XElement("ExternalId", prod.SKU),
new XElement("Name", prod.Description.Name),
new XElement("Description", prod.Description.Description),
new XElement("BrandExternalID",prod.Properties.Brand.FeedName),
new XElement("CategoryExternalId"), //look up the category here by sku
new XElement("ModelNumbers"),
new XElement("ModelNumber",prod.SKU),
new XElement("ManufacturingPartNumbers"),
new XElement("ManufacturingPartNumber",prod.SKU),
new XElement("UPCs"),
new XElement("UPC", prod.UPC),
new XElement("Attributes"),
new XElement("Attribute"),
new XAttribute("id","BV_FE_FAMILY"),
new XElement("Value")
);
}
I am trying to create the following XML:
<?xml version="1.0" encoding="utf-8"?>
<Feed xmlns="http://www.bazaarvoice.com/xs/PRR/ProductFeed/14.7" name="LifetimeProducts" incremental="false" extractDate="2017-10-20T13:21:41">
<Products>
<Product>
<ExternalId>12345</ExternalId>
<Name>Product 1</Name>
<Description>
<![CDATA[Proudct Description]]></Description>
<BrandExternalId>Brandx</BrandExternalId>
<CategoryExternalId>Category</CategoryExternalId>
<ProductPageUrl><![CDATA[http://something.com]]></ProductPageUrl>
<ImageUrl>http://image.com</ImageUrl>
<ModelNumbers>
<ModelNumber>12345</ModelNumber>
</ModelNumbers>
<ManufacturerPartNumbers>
<ManufacturerPartNumber>12345</ManufacturerPartNumber>
</ManufacturerPartNumbers>
<Attributes>
<Attribute id="BV_FE_FAMILY">
<Value>Family</Value>
</Attribute>
<Attribute id="BV_FE_FAMILY">
<Value>Family2</Value>
</Attribute>
<Attribute id="BV_FE_FAMILY">
<Value>Family 3</Value>
</Attribute>
</Attributes>
</Product>
</Products>
I need only one feed node (as the root) and then have the products nodes under this one. This doesn't seem to be working and this is my first time really working with LINQ and XML. The code appears to be pulling the data into the nodes, but with the foreach loop I think I am doing this wrong. What do I need to tweak to get this?
Thanks.
In C#, you pass arguments to methods by putting them inside the parentheses. For example, this won't work:
double sqrt = Math.Sqrt(),
1234;
You do it like this:
double sqrt = Math.Sqrt(1234);
You're also trying to create a new root node all over again for every item in the collection. Instead, you want one root node, which contains multiple children.
So your code should probably look something like this. Bear in mind that I don't have your settings or your ProductSearchModel class, so I haven't tested this. I'm relying on the assumption that your indentation, at least, reflects your intent, and the names of the elements (ModelNumbers/ModelNumber etc.) seem to bear that out.
The guts of the loop look similar to your code at first glance, but pay close attention to the parentheses. They make all the difference in the world.
var xFeed =
new XElement("Feed",
new XAttribute("xmlns", settings.bvXMLConfig.xmlns),
new XAttribute("name", settings.bvXMLConfig.xmlName),
new XAttribute("incrmental", settings.bvXMLConfig.xmlIncremental),
new XAttribute("extractDate", DateTime.UtcNow)
);
var xProducts = new XElement("Products");
xFeed.Add(xProducts);
foreach (ProductSearchModel prod in prods)
{
var xProduct =
new XElement("Product",
new XElement("ExternalId", prod.SKU),
new XElement("Name", prod.Description.Name),
new XElement("Description", prod.Description.Description),
new XElement("BrandExternalID", prod.Properties.Brand.FeedName),
new XElement("CategoryExternalId"), //look up the category here by sku
new XElement("ModelNumbers",
new XElement("ModelNumber", prod.SKU)),
new XElement("ManufacturingPartNumbers",
new XElement("ManufacturingPartNumber", prod.SKU)),
new XElement("UPCs",
new XElement("UPC", prod.UPC)),
new XElement("Attributes"),
new XElement("Attribute",
new XAttribute("id", "BV_FE_FAMILY"),
new XElement("Value"))
);
xProducts.Add(xProduct);
}

Add an existing xml element to a xml document

In this LINQ query I am creating a new XDocument for my own CD with track objects:
var cdXml = new XDocument(new XElement("CD", new XAttribute("Artiest", c.Titel), new XAttribute("Naam", c.Naam),
from t in c.tracks
select new XElement("Tracks",
new XElement("Track"),
new XElement("Artiest", t.Artiest),
new XElement("Titel", t.Titel),
new XElement("Lengte", t.Lengte)
)));
I am using an API from a music website and this is how I created the second XDocument from it:
String xmlString;
using (WebClient wc = new WebClient())
{
xmlString = wc.DownloadString(#"http://link-to-method");
}
XDocument myXMLDoc = XDocument.Parse(xmlString);
After that I want to add the tracks from the myXMLDoc to my own cdXml document, I already added 3 tracks to the cdXml document and I only want to add the tracks from the myXMLDoc who aren't already in my cdXml document.
This is my query but it doesn't work:
var query1 = from track in cdXml.Root.Elements("Tracks")
from track2 in myXMLDoc.Root.Element("album").Element("tracks").Elements("track")
where !track2.Element("name").Value.Contains(track.Element("Titel").Value)
select cdXml.Element("Tracks").Add(new XElement("Artiest", track2.Element("name").Value),
new XElement("Titel", track2.Element("artist").Element("name").Value),
new XElement("Lengte", track2.Element("duration").Value));
How do I add the existing elements from the myXMLDoc to my cdXml?
This is the xml file from the API call from 2 tracks:
<lfm status="ok">
<album>
<name>Awake</name>
<artist>Dream Theater</artist>
<mbid>e5544c68-43e9-4754-9239-b618454557f4</mbid>
<url>https://www.last.fm/music/Dream+Theater/Awake</url>
<image size="small">https://lastfm-img2.akamaized.net/i/u/34s/96e5ac5821bf4a138aec1b8f80f25a6f.png</image>
<listeners>216679</listeners>
<playcount>6046178</playcount>
<tracks>
<track rank="1">
<name>6:00</name>
<url>https://www.last.fm/music/Dream+Theater/_/6:00</url>
<duration>331</duration>
<streamable fulltrack="0">0</streamable>
<artist>
<name>Dream Theater</name>
<mbid>28503ab7-8bf2-4666-a7bd-2644bfc7cb1d</mbid>
<url>https://www.last.fm/music/Dream+Theater</url>
</artist>
</track>
<track rank="2">
<name>Caught in a Web</name>
<url>https://www.last.fm/music/Dream+Theater/_/Caught+in+a+Web</url>
<duration>328</duration>
<streamable fulltrack="0">0</streamable>
<artist>
<name>Dream Theater</name>
<mbid>28503ab7-8bf2-4666-a7bd-2644bfc7cb1d</mbid>
<url>https://www.last.fm/music/Dream+Theater</url>
</artist>
</track>
</album>
</lfm>
I finally found my own solution:
var query = from track in myXMLDoc.Root.Element("album").Element("tracks").Elements("track")
join track2 in cdXml.Root.Elements("Tracks") on track.Element("name").Value equals track2.Element("Titel").Value into joinedT
from track2 in joinedT.DefaultIfEmpty()
where track2 == null
select track;
First, write a query that returns the elements you want to add to the second document. Just that. Write a query that returns those. In the debugger, confirm that you're getting what you want to get.
Then, write a foreach loop which goes over those elements, and adds each one to whatever you want to add them to.
Your attempt at using select to add things is impossible. You can't do that. Linq isn't how you add things to collections. It's just for writing queries.
I think this might be what you were trying to do, I don't understand the track/track2 thing in the first query.
var query1 =
from track in cdXml.Root.Elements("Tracks")
from track2 in myXMLDoc.Root.Element("album").Element("tracks").Elements("track")
where !track2.Element("name").Value.Contains(track.Element("Titel").Value)
select track2;
query1 = query1.ToList();
// Put a breakpoint HERE and examine query1 in the debugger to
// see what you got
;
foreach (var element in query1)
{
cdXml.Element("Tracks").Add(
new XElement("Artiest", element.Element("name").Value),
new XElement("Titel", element.Element("artist").Element("name").Value),
new XElement("Lengte", element.Element("duration").Value));
}
This is my solution :
XElement tracks = cdXml.Root.Element("Tracks");
List<XElement> query1 = myXMLDoc.Root.Element("album").Element("tracks").Elements("track")
.Where(track2 => track2.Element("name").Value.Contains(track.Element("Titel").Value)).ToList();
foreach (XElement q1 in query1)
{
tracks.Add(new XElement("Artiest", q1.Element("name").Value),
new XElement("Titel", q1.Element("artist").Element("name").Value),
new XElement("Lengte", q1.Element("duration").Value));
}

Using LINQ to XML to create a pair of XElement on the same level for each Object in a List of Object

I'm trying to build an XML document that should output, for each object Journey in the list List<Journey> journeys :
<Journeys>
<id>1</id>
<length>28</length>
<id>2</id>
<length>44</length>
etc.
</Journeys>
I've tried to use LINQ to XML with the following code :
var journeyElement = new XElement("Journeys",
journeys.Select(j =>
new XElement("id", j.id),
new XElement("length", j.length));
Which is apparently not the expected syntax.
var journeyElement = new XElement("Journey",
journeys.Select(j => new
{
id = new XElement("id", j.id),
length = new XElement("length", j.length)
}));
Which doesn't produce the expected result.
How would one proceed to create a pair of XElement on the same level from a List using LINQ to XML ?
You can project each Journey into an array of two elements then flatten them into a single list with SelectMany:
var journeyElement = new XElement("Journeys",
journeys.SelectMany(j => new[] { new XElement("id", j.id), new XElement("length", j.length) }));
This produces the desired XML:
<Journeys>
<id>1</id>
<length>28</length>
<id>2</id>
<length>44</length>
</Journeys>
If you can change the XML structure like this,
<Journeys>
<Journey>
<id>1</id>
<length>28</length>
</Journey>
<Journey>
<id>2</id>
<length>44</length>
</Journey>
etc.
</Journeys>
This code will work.
List<Journey> journeys;
var journeyElement = new XElement("Journeys",
journeys.Select(j =>
new XElement("Journey",
new XElement("id", j.id),
new XElement("length", j.length)
)
)
);

Assigning ID to Objects transmitted in XML document

I have an array of objects and I want to pass them into a XML file. The Ojects lack of an attribute ID, in fact the form of the array is:
var people = new[]{
new {Name="James", Age="22", Company="FF"},
new {Name="Susan", Age="31", Company="PK"},
new {Name="Peter", Age="24", Company="TF"},
}
Is there any way to pass them in an xml file, granting to each one of them an ID starting from 1 and increased by 1 for each Object?
The desired form of the xml elemnts should look like:
<People>
<Person ID="1">
<Name>James</Name>
<Age>22</Age>
<Company>FF</Company>
....
</People>
It would be ideal if the solution would constist of one only LINQ query.
var result = new XDocument(
new XElement("People",
people.Select((p, i) =>
new XElement("Person",
new XAttribute("ID", i + 1),
new XElement("Name", p.Name),
new XElement("Age", p.Age),
new XElement("Company", p.Company)
)
)
)
);

Create XElement from List<T> where T.property is null

I am receiving data from a Web Service. The XML coming in is something like:
<data>
<item>
<code>a</code>
<price>2.89</price>
</item>
<item>
<code>a</code>
<price>2.89</price>
<colour>blue</colour>
</item>
</data>
So, we see that one item has an additional property of Colour.
Okay, this gets converted into a List<item> which is the point at which I get hold of it.
I need to convert this list into an XDocument.
Using:
var xml = new XDocument(
new XDeclaration("1.0", "utf-16", "yes"),
new XElement("data",
from i in myList
select new XElement("item",
new XElement("price", i.price),
new XElement("code", i.code),
new XElement("colour", i.colour))));
(I've typed this from memory, so excuse spellings)
Here, it errors because i.colour is null.
How do I cope with this?
Thanks in advance
Griff
You'll want to check whether i.colour is null before trying to access it.
You can do this neatly using the null-coalescing operator like:
new XElement("colour", i.colour ?? ""))));
Assuming that you want an empty string as the value if i.colour is null.
UPDATE
Based on your comment below, if you don't want the element added if i.colour is null then create it independantly of the XDocument instantiation and add it as required.
var xml = new XDocument(...);
if(i.colour != null)
{
xml.Add(new XElement(...));
}
}
To avoid adding a new XElement when colour is null you can use the ternary operator and return null or the new XElement as appropriate:
var xml = new XDocument(
new XDeclaration("1.0", "utf-16", "yes"),
new XElement("data",
from i in myList
select new XElement("item",
new XElement("price", i.price),
new XElement("code", i.code),
i.colour == null ?
null : new XElement("colour", i.colour)
)));

Categories