What I really like about JsonReader in Json.NET is that you always know the path of the current JsonReader position. For example, we have a json like this:
{
"name" :
{
"first": "John",
"last": "Smith"
}
}
If we are standing or "John" element, JsonReader.Path would be "name.first"
Is there a way to achieve something similar with XmlReader? Maybe use XPath? For example, we have a xml like this:
<root>
<name>
<first>John/<first>
<last>Smith</last>
</name>
</root>
I want to get "/root/name/first" while standing on "John" and "/root/name/last" while standing on "Smith"
It seems like there is no way to do this using standard .NET functionality, so I came up with my own class.
internal sealed class XmlReaderWrapperWithPath : IDisposable
{
private const string DefaultPathSeparator = ".";
private readonly Stack<string> _previousNames = new Stack<string>();
private readonly XmlReader _reader;
private readonly bool _ownsReader;
public XmlReaderWrapperWithPath(XmlReader reader, bool ownsReader)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
_ownsReader = ownsReader;
_reader = reader;
PathSeparator = DefaultPathSeparator;
}
public bool Read()
{
var lastDepth = Depth;
var lastName = Name;
if (!_reader.Read())
{
return false;
}
if (Depth > lastDepth)
{
_previousNames.Push(lastName);
}
else if (Depth < lastDepth)
{
_previousNames.Pop();
}
return true;
}
public string Name
{
get
{
return _reader.Name;
}
}
public string Value
{
get
{
return _reader.Value;
}
}
private int Depth
{
get
{
return _reader.Depth;
}
}
public string Path
{
get
{
return string.Join(PathSeparator, _previousNames.Reverse());
}
}
public string PathSeparator { get; set; }
#region IDisposable
public void Dispose()
{
if (_ownsReader)
{
_reader.Dispose();
}
}
#endregion
}
Note that this class does not form XPath (so no paths for attributes), but this was enough for my needs. Hope this helps someone.
I use XmlDocument and the XmlNode class to work with xml Data.
While standing on a Node is in this case you have the XmlNode in this case x. There is no such methode to get the path to the node. But this code do it.
XmlDocument doc = new XmlDocument();
doc.LoadXml("<root>" +
"<name>" +
"<first>John</first>" +
"<last>Smith</last>" +
"</name>" +
"</root>");
XmlNodeList liste = doc.FirstChild.SelectNodes("*/first");
XmlNode x = liste[0]; //Some Node
string path = "";
while (!x.Name.Equals("document"))
{
path = x.Name + "\\" + path;
x = x.ParentNode;
}
Related
Hello in a C# WCF Service application i want to return an array of 5 strings in a method. The above code isn't returning any errors but when i launch the Service in debug mode it only shows the first string on the Array.
Here's the IService side :
[OperationContract]
string[] NaviresXml();
Here's the Service side :
public string[] NaviresXml()
{
try
{
XMLReader x = new XMLReader(FilePath);
return new string[] { x.ReadXmlDocument_Navires() };
}
catch (Exception ex)
{
throw new Exception(ex.Message + "\n" + ex.StackTrace);
}
}
And the XMLReader Class :
public class XMLReader
{
public string XmlFilePath { get; set; }
public XMLReader(string XmlFilePath)
{
this.XmlFilePath = XmlFilePath;
}
public string ReadXmlDocument_Navires()
{
XmlDocument xmlDoc1 = new XmlDocument();
xmlDoc1.Load(XmlFilePath);
XmlNodeList itemNodes = xmlDoc1.GetElementsByTagName("Navire");
if (itemNodes.Count > 0)
{
foreach (XmlElement node in itemNodes)
return "Navire" + node.Attributes["Type"].Value + "Nom" + node.Attributes["Nom"].Value;
}
return null;
}
}
When i launch the Service i can see only the first string but not the others.
enter image description here
What is wrong with this code?
I've tried to do it without the XMLReader Class and put the code directly in the Service side but this didn't worked.
Move the return statement outside your loop.
StringBuilder stringContent = new StringBuilder();
if (itemNodes.Count > 0)
{
foreach (XmlElement node in itemNodes)
stringContent.Append("Navire" + node.Attributes["Type"].Value + "Nom" + node.Attributes["Nom"].Value);
}
return stringContent.ToString();
When a return statement is executed, function execution stops and jumps out of the executing function, even if there are other statements in the body of the function. The code after return is not executed.
So you need to put the return out of the loop like this:
public class XMLReader
{
public string XmlFilePath { get; set; }
public XMLReader(string XmlFilePath)
{
this.XmlFilePath = XmlFilePath;
}
public string ReadXmlDocument_Navires()
{
XmlDocument xmlDoc1 = new XmlDocument();
xmlDoc1.Load(XmlFilePath);
XmlNodeList itemNodes = xmlDoc1.GetElementsByTagName("Navire");
StringBuilder res = new StringBuilder();
if (itemNodes.Count > 0)
{
foreach (XmlElement node in itemNodes)
res.append("Navire" + node.Attributes["Type"].Value + "Nom" + node.Attributes["Nom"].Value);
}
return res.ToString();
}
return null;
}
Im using unity3d and I want to add some attributes the android manifest that unity generated, this is my code, modified from this comment https://stackoverflow.com/a/54894488/2126254
public class ModifyUnityAndroidAppManifest : IPostGenerateGradleAndroidProject
{
public void OnPostGenerateGradleAndroidProject(string basePath)
{
var androidManifest = new AndroidManifest(GetManifestPath(basePath));
XmlAttribute ReplaceBackupAttr = androidManifest.GenerateAttribute(
"tools", "replace", "android:allowBackup", androidManifest.ToolsXmlNamespace);
XmlAttribute AllowBackupAttr = androidManifest.GenerateAttribute(
"android", "allowBackup", "true", androidManifest.AndroidXmlNamespace);
androidManifest.SetAttribute(ReplaceBackupAttr);
androidManifest.SetAttribute(AllowBackupAttr);
androidManifest.Save();
}
public int callbackOrder { get { return 1; } }
private string _manifestFilePath;
private string GetManifestPath(string basePath)
{
... // irrelevnat
}
}
internal class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public readonly string ToolsXmlNamespace = "http://schemas.android.com/apk/res/tools";
public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
nsMgr.AddNamespace("tools", ToolsXmlNamespace);
}
public string Save()
{
return SaveAs(m_Path);
}
public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
}
internal class AndroidManifest : AndroidXmlDocument
{
internal readonly XmlElement ApplicationElement;
public AndroidManifest(string path) : base(path)
{
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}
internal XmlAttribute GenerateAttribute(string prefix, string key, string value, string XmlNamespace)
{
XmlAttribute attr = CreateAttribute(prefix, key, XmlNamespace);
attr.Value = value;
return attr;
}
internal void SetAttribute(XmlAttribute Attribute)
{
ApplicationElement.Attributes.Append(Attribute);
}
}
My issue is that after I add the 2 attributes (replace and allowBackup), the tools Namespace is also appended to the end of the tag
<application android:theme="#style/UnityThemeSelector" android:icon="#mipmap/app_icon" android:label="#string/app_name" android:isGame="true" android:banner="#drawable/app_banner" tools:replace="android:allowBackup" android:allowBackup="true" xmlns:tools="http://schemas.android.com/apk/res/tools">
How can I fix this?
I tried setting XmlNamespace to null, but this causes the prefix (tools,android) not to be printed.
Figured it out, I had "http://schemas.android.com/tools"; in the manifest tag, but "http://schemas.android.com/apk/res/tools"; for the "tools" attribute, so the c# write added it the application tag.
Is there an easy way to construct class from a XML. The constructed class will be used to serialize and deserialize XML.
I have an XML with lots of properties and elements defined. Do I need to manually create my class based on that XML? Or Is there a utility tool available to generate class from XML
Thanks,
Esen
Further on Willem's post:
This will generate the XSD (not dataset)
xsd.exe myCustom.xml
This generates the C# class:
xsd.exe myCustom.xsd /c
There's a round about way:
Using xsd.exe, you can first create a schema (xsd) from your xml file, which can then be used as input for xsd.exe to generate classes from the schema.
i.e. (from the command prompt):
xsd.exe myXmlFile.xml
to output myXmlFile.xsd
and next
xsd.exe myXmlFile.xsd
to generate classes from the xsd file.
#Willem van Rumpt: solution helped me to generate class. But in some case when I try to instantiate the dataset, I end up receiving this exception "Same Table cannot be the child table in two nested relations..."
I have tried different solution using xmldocument object to navigate each nodes and generate my class that can be used to serialize and deserialize xml file. Thought to post it here so that it would be helpful to someone who is looking for similar solution. Please post your optimized solution if you have one.
namespace Utility1
{
public static class XMLHelper
{
private enum XMLType
{
Element,
Attribute
}
public static string GenerateXMLClass(string xmlstring)
{
XmlDocument xd = new XmlDocument();
xd.LoadXml(xmlstring);
XmlNode rootNode = xd.DocumentElement;
var xmlClassCollection = new Dictionary<string, XMLClass>();
var xmlClass = new XMLClass();
xmlClassCollection.Add(rootNode.Name, xmlClass);
CollectAttributes(ref xmlClass, rootNode);
CollectElements(ref xmlClass, rootNode);
CollectChildClass(ref xmlClassCollection, rootNode);
var clsBuilder = new StringBuilder();
clsBuilder.AppendLine("[XmlRoot(\"" + rootNode.Name + "\")]");
foreach (var cls in xmlClassCollection)
{
clsBuilder.AppendLine("public class " + cls.Key);
clsBuilder.AppendLine("{");
foreach (var element in cls.Value.Elements)
{
if (XMLType.Element == element.XmlType)
clsBuilder.AppendLine("[XmlElement(\"" + element.Name + "\")]");
else
clsBuilder.AppendLine("[XmlAttribute(\"" + element.Name + "\")]");
clsBuilder.AppendLine("public " + element.Type + element.Name + "{get;set;}");
}
clsBuilder.AppendLine("}");
}
return clsBuilder.ToString();
}
private static void CollectAttributes(ref XMLClass xmlClass, XmlNode node)
{
if (null != node.Attributes)
{
foreach (XmlAttribute attr in node.Attributes)
{
if (null == xmlClass.Elements.SingleOrDefault(o => o.Name == attr.Name))
xmlClass.Elements.Add(new Element("string ", attr.Name, XMLType.Attribute));
}
}
}
private static bool IsEndElement(XmlNode node)
{
if ((null == node.Attributes || node.Attributes.Count <= 0) &&
(null == node.ChildNodes || !node.HasChildNodes || (node.ChildNodes.Count == 1 && node.ChildNodes[0].NodeType == XmlNodeType.Text)))
{
return true;
}
return false;
}
private static void CollectElements(ref XMLClass xmlClass, XmlNode node)
{
foreach (XmlNode childNode in node.ChildNodes)
{
if (null == xmlClass.Elements.SingleOrDefault(o => o.Name == childNode.Name))
{
var occurance = node.ChildNodes.Cast<XmlNode>().Where(o => o.Name == childNode.Name).Count();
var appender = " ";
if (occurance > 1)
appender = "[] ";
if(IsEndElement(childNode))
{
xmlClass.Elements.Add(new Element("string" + appender, childNode.Name, XMLType.Element));
}
else
{
xmlClass.Elements.Add(new Element(childNode.Name + appender, childNode.Name, XMLType.Element));
}
}
}
}
private static void CollectChildClass(ref Dictionary<string, XMLClass> xmlClsCollection, XmlNode node)
{
foreach (XmlNode childNode in node.ChildNodes)
{
if (!IsEndElement(childNode))
{
XMLClass xmlClass;
if (xmlClsCollection.ContainsKey(childNode.Name))
xmlClass = xmlClsCollection[childNode.Name];
else
{
xmlClass = new XMLClass();
xmlClsCollection.Add(childNode.Name, xmlClass);
}
CollectAttributes(ref xmlClass, childNode);
CollectElements(ref xmlClass, childNode);
CollectChildClass(ref xmlClsCollection, childNode);
}
}
}
private class XMLClass
{
public XMLClass()
{
Elements = new List<Element>();
}
public List<Element> Elements { get; set; }
}
private class Element
{
public Element(string type, string name, XMLType xmltype)
{
Type = type;
Name = name;
XmlType = xmltype;
}
public XMLType XmlType { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
}
}
thanks,
Esen
i need to extract information from incoming (e.g. xml) data based on a given template.
The template may be XML or plain text (comma separated). For each type of message there exists a template, e.g.
<SomeMessage>
<Id>$id</Id>
<Position>
<X>$posX</X>
<Y>$posY</Y>
<Z>$posZ</Z>
</Position>
</SomeMessage>
The incoming data for example is:
<SomeMessage>
<Id>1</Id>
<Position>
<X>0.5f</X>
<Y>1.0f</Y>
<Z>0.0f</Z>
</Position>
</SomeMessage>
Now i need to extract information about $id, $posX, etc.
Parser p = new Parser(templateString);
int id = p.Extract("id", incomingString);
float posx = p.Extract("posX", incomingString);
I need something like the difference of incomingData and template and then extract the information at the appropiate position. Because there exist several tempaltes which contain different information and may be extended in the future i am looking for a general approach.
The template in this case may also be
$id,$posX,$posY,$posZ
and the incoming data would be then
1,0.5f,1.0f,0.0f
The latter case may be eaiser to parse, but i need a solution which is able the handle both (xml template as well as non xml).
You could create a parsing class having a property for each field:
class Parser
{
public string PositionX { get; set; }
public string PositionY { get; set; }
public string PositionZ { get; set; }
public Parser(XmlNode item)
{
this.PositionX = GetNodeValue(item, "Position/X");
this.PositionY = GetNodeValue(item, "Position/X/Y");
this.PositionZ = GetNodeValue(item, "Position/X/Y/Z");
}
}
I can supply a routine that can generate such parsing classes from sample xml if your interested, when arrays do not concern. GetNodeValue is a method that uses an xpath query and returns the value for the xpath (basicly XmlNode.SelectSingleNode with some added parsing added to it).
It would probably be a good idea to use a Interface and 2 different templates for each occasion. Note that the returned Message is not completed but it gives you an idea. With the static XElement.Parse you can parse well formed XML strings for easier usage.
public interface IParser
{
Message Parse(String Payload);
}
// Position Class
public class Position
{
public int X { get; private set; }
public int Y { get; private set; }
public int Z { get; private set; }
public Position(int X, int Y, int Z)
{
this.X = X;
this.Y = Y;
this.Z = Z;
}
}
// Message Class
public class Message
{
public String ID { get; private set; }
public Position Position { get; private set; }
public Message(String ID, Position Position)
{
this.ID = ID;
this.Position = Position;
}
}
// Parser Class
public class XMLParser : IParser
{
public Message Parse(string Payload)
{
var result = XElement.Parse(Payload);
return new Message(result.Elements().ElementAt(0).Value, new Position(X,Y,Z);
}
}
For each template create a parser definition file of the format:
Parser Type (XML or CSV)
Variable1, path
variable2, path
etc
for xml path might be someMessage,Position,x.
for csv you might forget the path and just list the variables in order.
Then when you read in your template file your pick up the parser type and the path to each of the variables. If you have a lot of hierarchical information then you'll have to apply a bit of imagination to this but it should be fine for the simple case you've given.
For anything over CSV you will probably have to use a parser, but XML/XPATH is pretty simple to find the basics for.
using System;
using System.IO;
using System.Xml;
class TemplateParse {
XmlDocument xdoc;
string GetPath(XmlNode node, string val, string path){
if(node.HasChildNodes){
if(node.ChildNodes.Count == 1 && node.FirstChild.NodeType == XmlNodeType.Text)
return (node.FirstChild.Value == val) ? path + "/" + node.Name : String.Empty;
foreach(XmlNode cnode in node.ChildNodes){
if(cnode.NodeType != XmlNodeType.Element) continue;
string result = GetPath(cnode, val, path + "/" + node.Name);
if(result != String.Empty) return result;
}
}
return "";
}
public TemplateParse(string templateXml){
xdoc = new XmlDocument();
xdoc.LoadXml(templateXml);
}
public string Extract(string valName, string data){
string xpath = GetPath((XmlNode)xdoc.DocumentElement, "$" + valName, "/");
var doc = new XmlDocument();
doc.LoadXml(data);
return doc.SelectSingleNode(xpath).InnerText;
// var value = doc.SelectSingleNode(xpath).InnerText;
// var retType = typeof(T);
// return (T)retType.InvokeMember("Parse", System.Reflection.BindingFlags.InvokeMethod, null, null, new []{value});
}
}
class Sample {
static void Main(){
string templateString = File.ReadAllText(#".\template.xml");
string incomingString = File.ReadAllText(#".\data.xml");
var p = new TemplateParse(templateString);
string[] names = new [] { "id", "posX", "posY", "posZ" };
foreach(var name in names){
var value = p.Extract(name, incomingString);
Console.WriteLine("{0}:{1}", name, value);
}
}
}
OUTPUT
id:1
posX:0.5f
posY:1.0f
posZ:0.0f
I have a xml-file which I want to read. How can I do it? I would not load whole xml file at runtime
(XmlDocument _xd = XmlDocument.Load(path))
I want to do it with Readers, but I can not achieve with it.
At the same time I want to add nodes to this xml-file with writers. How do these work with XDocument or at c# 3.5.
Kind Regards.
I think this is useful.
Here's an example:
using System;
using System.Xml;
namespace ReadXml1
{
class Class1
{
static void Main(string[] args)
{
// Create an isntance of XmlTextReader and call Read method to read the file
XmlTextReader textReader = new XmlTextReader("C:\\books.xml");
textReader.Read();
// If the node has value
while (textReader.Read())
{
// Move to fist element
textReader.MoveToElement();
Console.WriteLine("XmlTextReader Properties Test");
Console.WriteLine("===================");
// Read this element's properties and display them on console
Console.WriteLine("Name:" + textReader.Name);
Console.WriteLine("Base URI:" + textReader.BaseURI);
Console.WriteLine("Local Name:" + textReader.LocalName);
Console.WriteLine("Attribute Count:" + textReader.AttributeCount.ToString());
Console.WriteLine("Depth:" + textReader.Depth.ToString());
Console.WriteLine("Line Number:" + textReader.LineNumber.ToString());
Console.WriteLine("Node Type:" + textReader.NodeType.ToString());
Console.WriteLine("Attribute Count:" + textReader.Value.ToString());
}
}
}
}
And here's an example for XMLWriters:
using System.Xml;
class Program
{
class Employee
{
int _id;
string _firstName;
string _lastName;
int _salary;
public Employee(int id, string firstName, string lastName, int salary)
{
this._id = id;
this._firstName = firstName;
this._lastName = lastName;
this._salary = salary;
}
public int Id { get { return _id; } }
public string FirstName { get { return _firstName; } }
public string LastName { get { return _lastName; } }
public int Salary { get { return _salary; } }
}
static void Main()
{
Employee[] employees = new Employee[4];
employees[0] = new Employee(1, "David", "Smith", 10000);
employees[1] = new Employee(3, "Mark", "Drinkwater", 30000);
employees[2] = new Employee(4, "Norah", "Miller", 20000);
employees[3] = new Employee(12, "Cecil", "Walker", 120000);
using (XmlWriter writer = XmlWriter.Create("employees.xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("Employees");
foreach (Employee employee in employees)
{
writer.WriteStartElement("Employee");
writer.WriteElementString("ID", employee.Id.ToString());
writer.WriteElementString("FirstName", employee.FirstName);
writer.WriteElementString("LastName", employee.LastName);
writer.WriteElementString("Salary", employee.Salary.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}
Here is an example of loading xml file with XDocument, adding a child node and saving it to the use :
// Load XML file :
XDocument xdoc = XDocument.Load(path);
// Parse XML :
//XDocument xdoc = XDocument.Parse("<YourRootElement><ChildElement>Child 1</ChildElement></YourRootElement>");
// Add Child Node to loaded xml :
xdoc.Element("YourRootElement").Add(
new XElement("ChildElement", "Child 2"));
// Save XML to file :
xdoc.Save(path);
EDIT : Use XDocument.Parse method to load XML from memory.
Didn't you consider also using LINQ? Like this (just pseudo-like, you would have to look it up on the web, but just to have an idea).
XDocument xmlDoc = //load or parse with XDocument.Load(..) or XDocument.Parse(...)
List<MyObject> myObj = (from myObject in xmlDoc.Descendants("myObjectTag")
select new MyObject
{
Name = (string)myObject.Attribute("name"),
...
}
).toList<MyObject>();
VoilĂ , here's a blog post I just found by quickly googling: * click *