Hi all I am having an XML data which is formed in StringBuilder as follows
StringBuilder sb = new StringBuilder();
sb.Append("<?xml version=\"1.0\" encoding=\"utf-16\"?>");
sb.Append("<TEST>"
+ "<DEMO><CONTENTINFO name=\"Nani\" receiver=\"Lucky\""
+ "/></DEMO></TEST>");
XmlDocument XMLDocument = new XmlDocument();
XMLDocument.LoadXml(sb.ToString());
XmlNodeList nodeList = XMLDocument.FirstChild.ChildNodes;
foreach (XmlNode node in nodeList)
{
}
I tried using XMLDocument to traverse through child nodes to get the data I need to split the data so that it should give name=Nani and receiver=lucky or store the key and value in a dictionary like dic.Add("name","nani") and dic.Add("receiver","lucky") . So can some one help me how to sort it out
If you prefer having a strong set of class instances being formed then use the XmlSerializer and create classes to represent each of the levels of your XML structure.
[XmlRoot("TEST")]
public class Test
{
[XmlElement(Name = "DEMO")]
public Demo Demo
{
get;
set;
}
}
public class Demo
{
[XmlElement("CONTENTINFO")]
public ContentInfo ContentInfo
{
get;
set;
}
}
public class ContentInfo
{
[XmlAttribute(Name = "name")]
public string Name
{
get;
set;
}
[XmlAttribute(Name = "receiver")]
public string Reciever
{
get;
set;
}
}
XmlSerializer serializer = new XmlSerializer(typeof(Test));
serializer.Serialize(....);
Test testInstance = serializer.Deserialize(....);
... etc.
The above code has not been tested but should give you the gist.
Why are you using a StringBuilder to generate your XML? There are much better ways:
var root = new XDocument();
var test = new XElement("TEST");
var demo = new XElement("DEMO");
var contentInfo = new XElement("CONTENTINFO",
new XAttribute("name", "Nani"),
new XAttribute("receiver", "Lucky"));
demo.Add(contentInfo);
test.Add(demo);
root.Add(test);
To pull out the values you need into a dictionary you can use the following LINQ query:
var foo = root.Descendants("DEMO").Elements("CONTENTINFO")
.SelectMany(x => x.Attributes())
.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());
This will give you a dictionary looking like this:
Key: name =
Value: Nani,
Key: receiver = Value: Lucky
Related
Is there a way of parsing certain data from an XML file, and outputting that information onto an excel(csv) file?
Use this code. You need to convert xmldocument to xdocument. So you can easily capture each element and its data. I used the same file you provided. I also mentioned example of how to read elements in loop and its child.
class Program
{
static void Main(string[] args)
{
Parse();
}
public static void Parse()
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(#"D:\New Text Document.xsd");
var captureElements = new List<CustomElements>();
var xdocument = xmlDoc.ToXDocument();
foreach (var element in xdocument.Elements())
{
foreach (var node in element.Elements()) //childs...
{
if (node.Name.LocalName.Equals("ElementType"))
{
foreach (var scopeNode in node.Elements())
{
if (scopeNode.Name.LocalName.Equals("element"))
{
var xml = XElement.Parse(scopeNode.ToString());
var customElement = new CustomElements();
customElement.Type = xml.Attribute("type")?.Value;
customElement.Label = xml.Attribute("label")?.Value;
customElement.CompTypes = xml.Attribute("CompTypes")?.Value;
customElement.Readonly = xml.Attribute("readonly")?.Value;
customElement.Hidden = xml.Attribute("hidden")?.Value;
customElement.Require = xml.Attribute("require")?.Value;
captureElements.Add(customElement);
}
}
}
}
}
}
}
public static class DocumentExtensions
{
public static XmlDocument ToXmlDocument(this XDocument xDocument)
{
var xmlDocument = new XmlDocument();
using (var xmlReader = xDocument.CreateReader())
{
xmlDocument.Load(xmlReader);
}
return xmlDocument;
}
public static XDocument ToXDocument(this XmlDocument xmlDocument)
{
using (var nodeReader = new XmlNodeReader(xmlDocument))
{
nodeReader.MoveToContent();
return XDocument.Load(nodeReader);
}
}
}
public class CustomElements
{
public string Type { get; set; }
public string Label { get; set; }
public string CompTypes { get; set; }
public string Readonly { get; set; }
public string Hidden { get; set; }
public string Require { get; set; }
}
Very easily done in XSLT. You don't need the schema. Unless there are special characters that need to be escaped, etc, it's as simple as:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0" expand-text="yes">
<xsl:mode on-no-match="shallow-skip"/>
<xsl:output method="text"/>
<xsl:template match="Element"
>{#type},{#label},{#CompTypes},{#readonly},{#hidden},{#required}
</xsl:template>
</xsl:transform>
That's an XSLT 3.0 solution; if you prefer to use the XSLT 1.0 processor that comes bundled with .NET that's a bit more verbose but still quite straightforward. I haven't included a header line but it's a simple matter to add it.
In XSLT 3.0 you can even add the automation to apply this to a whole collection of XML files (in 1.0 you would need to do that in a calling script).
In C#, use System.Xml.XmlDocument, and XPath syntax in SelectNodes
XmlDocument xml = new XmlDocument();
xml.Load( strFile );
foreach (XmlElement ndRow in xml.SelectNodes("//element")) {
string strType = ndRow.GetAttribute("type");
string strLabel = ndRow.GetAttribute("label");
}
I need to serialize an class related to this:
public class Root {
public string[] Elements {get;set;}
}
to an XML like this:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Element_01>1st element</Element_01>
<Element_02>2nd element</Element_02>
<Element_03>3rd element</Element_03>
<Element_04>4th element</Element_04>
</Root>
when the object is instantiated like so:
var root = new Root {
Elements = new[] {
"1st element", "2nd element", "3rd element"
"4th element"
}
};
using the System.Xml.Serialization.XmlSerializer.
I have to do it the other way round, too.
Is there any way to achieve this?
You may want to explore an alternative way using XLinq, for your particular scenario it would be simpler and easier, take a look to how your Root class may be rewritten:
public class Root
{
public string[] Elements { get; set; }
public string GetXmlString()
{
var rootElement = new XElement("Root");
for (var i = 0; i < Elements.Length; i++)
{
var tag = $"Element_{(i + 1).ToString().PadLeft(2, '0')}";
var xElement = new XElement(tag, Elements[i]);
rootElement.Add(xElement);
}
return rootElement.ToString();
}
public static Root DeserializeXmlString(string xmlString)
{
var rootElement = XElement.Parse(xmlString);
var elementsArray = rootElement
.Elements()
.Select(xElement => xElement.Value)
.ToArray();
return new Root {Elements = elementsArray};
}
}
And testing:
private static void Main()
{
var root = new Root
{
Elements = new[]
{
"1st element", "2nd element", "3rd element", "4th element"
}
};
var xmlString = root.GetXmlString();
Console.WriteLine(xmlString);
var deserializedRoot = Root.DeserializeXmlString(xmlString);
foreach (var element in deserializedRoot.Elements)
Console.WriteLine(element);
Console.ReadLine();
}
Result:
You have only to add error validation code and you are pretty much done. For more info on LINQ to XML check this
Each time i get a request from a user, i have to serialize and append it , to an existing xml file like this :
<LogRecords>
<LogRecord>
<Message>Some messagge</Message>
<SendTime>2017-12-13T22:04:40.1109661+01:00</SendTime>
<Sender>Sender</Sender>
<Recipient>Name</Recipient>
</LogRecord>
<LogRecord>
<Message>Some message too</Message>
<SendTime>2017-12-13T22:05:08.5720173+01:00</SendTime>
<Sender>sender</Sender>
<Recipient>name</Recipient>
</LogRecord>
</LogRecords>
Currently Serializing data in this way (which works fine):
var stringwriter = new StringWriter();
var serializer = new XmlSerializer(object.GetType());
serializer.Serialize(stringwriter, object);
var smsxmlStr = stringwriter.ToString();
var smsRecordDoc = new XmlDocument();
smsRecordDoc.LoadXml(smsxmlStr);
var smsElement = smsRecordDoc.DocumentElement;
var smsLogFile = new XmlDocument();
smsLogFile.Load("LogRecords.xml");
var serialize = smsLogFile.CreateElement("LogRecord");
serialize.InnerXml = smsElement.InnerXml;
smsLogFile.DocumentElement.AppendChild(serialize);
smsLogFile.Save("LogRecords.xml");
And the properties class
[XmlRoot("LogRecords")]
public class LogRecord
{
public string Message { get; set; }
public DateTime SendTime { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
}
But what i want to do is to load the file, navigate to the last element/node of it and append a new List<LogRecord> and save, so i can easily deserialize later.
I have tried various ways using XPath Select Methods like SelectSingleNode and SelectNodes but since i am junior with c# i haven't manage to make them work properly. Does anyone have any idea on how to serialize and append properly?
Thank you
Your approach (and most of the answers given to date) rely on having all of the log file in memory in order to append more records to it. As the log file grows, this could cause issues (such as OutOfMemoryException errors) down the road. Your best bet is to use an approach that streams the data from the original file into a new file. While there might be a few bugs in my untested code. The approach would look something like the following:
// What you are serializing
var obj = default(object);
using (var reader = XmlReader.Create("LogRecords.xml"))
using (var writer = XmlWriter.Create("LogRecords2.xml"))
{
// Start the log file
writer.WriteStartElement("LogRecords");
while (reader.Read())
{
// When you see a record in the original file, copy it to the output
if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "LogRecord")
{
writer.WriteNode(reader.ReadSubtree(), false);
}
}
// Add your additional record(s) to the output
var serializer = new XmlSerializer(obj.GetType());
serializer.Serialize(writer, obj);
// Close the tag
writer.WriteEndElement();
}
// Replace the original file with the new file.
System.IO.File.Delete("LogRecords.xml");
System.IO.File.Move("LogRecords2.xml", "LogRecords.xml");
Another idea to consider, does the log file need to be a valid XML file (with the <LogRecords> tag at the start and finish? If you omit the root tag, you could simply append the new records at the bottom of the file (which should be very efficient). You can still read the XML in .Net by creating an XmlReader with the right ConformanceLevel. For example
var settings = new XmlReaderSettings()
{
ConformanceLevel = ConformanceLevel.Fragment
};
using (var reader = XmlReader.Create("LogRecords.xml", settings))
{
// Do something with the records here
}
Try using xml linq :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
XDocument doc = XDocument.Load(FILENAME);
LogRecord record = doc.Descendants("LogRecord").Select(x => new LogRecord()
{
Message = (string)x.Element("Message"),
SendTime = (DateTime)x.Element("SendTime"),
Sender = (string)x.Element("Sender"),
Recipient = (string)x.Element("Recipient")
}).OrderByDescending(x => x.SendTime).FirstOrDefault();
}
}
public class LogRecord
{
public string Message { get; set; }
public DateTime SendTime { get; set; }
public string Sender { get; set; }
public string Recipient { get; set; }
}
}
You can perform it by using XDocument like this;
XDocument doc = XDocument.Load("LogRecords.xml");
//Append Node
XElement logRecord = new XElement("LogRecord");
XElement message = new XElement("Message");
message.Value = "Message";
XElement sendTime = new XElement("SendTime");
sendTime.Value = "SendTime";
XElement sender = new XElement("Sender");
sender.Value = "Sender";
XElement recipient = new XElement("Recipient");
recipient.Value = "Recipient";
logRecord.Add(message);
logRecord.Add(sendTime);
logRecord.Add(sender);
logRecord.Add(recipient);
doc.Element("LogRecords").Add(logRecord);
//Append Node
doc.Save("LogRecords.xml");
I've got an XML file with multiple items, and I want to deserialize only a single specific one at a time, rather than all of them, and add it to a list.
Using the example from this site, how do I deserialize only the Product where Id=2 and add it to productList?
The Class:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
Code:
void foo()
{
string xmlString = "<Products><Product><Id>1</Id><Name>My XML product</Name></Product><Product><Id>2</Id><Name>My second product</Name></Product></Products>";
XmlSerializer serializer = new XmlSerializer(typeof(List<Product>), new XmlRootAttribute("Products"));
StringReader stringReader = new StringReader(xmlString);
List<Product> productList = (List<Product>)serializer.Deserialize(stringReader);
}
You can use the XDocument class to query Xml:
StringReader stringReader = new StringReader(xmlString);
XDocument document = XDocument.Load(stringReader);
var node = document.Descendants("Product").FirstOrDefault(p => p.Descendants("Id").First().Value == "2");
if(node != null)
{
XmlSerializer serializer = new XmlSerializer(typeof(Product));
var xmlReader = new StringReader(node.ToString());
Product result = serializer.Deserialize(xmlReader) as Product;
}
Granted, this is a quick and dirty solution that may require further analysis for certain situations.
Here this is working, you should Create XmlReader with your StringReader and read the subtree of the element. This can be achieved with ReadSubtree method.
public static void Main(string[] args)
{
string xmlString = "<Products><Product><Id>1</Id><Name>My XML product</Name></Product><Product><Id>2</Id><Name>My second product</Name></Product></Products>";
XmlSerializer serializer = new XmlSerializer(typeof(List<Product>), new XmlRootAttribute("Products"));
List<Product> productList = new List<Product>();
using (StringReader stringReader = new StringReader(xmlString))
using (XmlReader xmlReader = XmlReader.Create(stringReader))
{
xmlReader.ReadToDescendant("Products");
productList = (List<Product>)serializer.Deserialize(xmlReader.ReadSubtree());
}
}
I am trying to construct a .xml file of the form
<Orders>
<Id type="System.Int32">1</Id>
<OrderItems>
<OrderItem>
<Id type="System.Int32">321</Id>
<Product type="System.String">Coffee</Product>
</OrderItem>
</OrderItems>
<Client type="System.String">Johnny</Client>
<Orders>
For Order model:
public class Order
{
public int Id { get; set; }
public List<OrderItem> Products { get; set; }
public string Client { get; set; }
}
Here, I create the Order element
public void SaveToFile(IEnumerable<Order> elementsList)
{
XmlDocument xmlDoc = new XmlDocument();
XmlDeclaration xmlDec = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", string.Empty);
xmlDoc.PrependChild(xmlDec);
XmlElement elemRoot = xmlDoc.CreateElement("Orders");
xmlDoc.AppendChild(elemRoot);
XmlHelper<Order> xmlHelper = new XmlHelper<Order>();
foreach (var order in _orders)
{
xmlHelper.AddNodeToXmlDocument(xmlDoc, elemRoot, order);
}
xmlDoc.PreserveWhitespace = true;
xmlDoc.Save(_filePath);
}
And here, I am trying to construct the sub-elements. It works fine for Id and Client, but when I try to create the order items, I get this error at line document.AppendChild(elemRoot);
public void AddNodeToXmlDocument(XmlDocument document, XmlElement rootElement, object myObject)
{
XmlElement myObjectElement = document.CreateElement(EntityFormatter.GetObjectName(myObject));
foreach (var objectProperty in EntityFormatter.GetPropertiesAndValues(myObject))
{
if ((objectProperty.Value.GetType().FullName).ToString().Contains("System.Collections.Generic.List"))
{
Regex regex = new Regex(#"Models[.][A-Za-z]+");
Match match = regex.Match(objectProperty.Value.ToString());
var elemRoot = document.CreateElement(match.Value.Substring(7));
document.AppendChild(elemRoot);
foreach (var obj in objectProperty.Value.ToString())
{
AddNodeToXmlDocument(document, elemRoot, obj);
}
}
else
{
var elem = document.CreateElement(objectProperty.Key);
elem.SetAttribute("type", objectProperty.Value.GetType().FullName);
elem.InnerText = objectProperty.Value.ToString();
myObjectElement.AppendChild(elem);
}
}
rootElement.AppendChild(myObjectElement);
}
XML specification only allows single root element in a document. document.AppendChild(elemRoot) line in your AddNodeToXmlDocument() method throws exception because root element has been created before in the SaveToFile() method :
.....
XmlElement elemRoot = xmlDoc.CreateElement("Orders");
xmlDoc.AppendChild(elemRoot);
.....
It isn't clear what you're trying to do with the erroneous line, maybe you want to append elemRoot to the previously created root element instead :
.....
var elemRoot = document.CreateElement(match.Value.Substring(7));
document.DocumentElement.AppendChild(elemRoot);
.....