Partial deserialisation of an XML file - c#

I have a master XML schema that has three classes in
<jobs>
<job>
<id>123-123-123-123</id>
<aspect1>
<...></...>
</aspect1>
<aspect2>
<...></...>
</aspect2>
<aspect3>
<...></...>
</aspect3>
</job>
</jobs>
The receiver classes are all set up fine (if all three aspects elements are filled with their respective properties, it's read in without an issue). The issue comes if (say) aspect2 is missing from the object when I get a XmlException thrown.
My XML reader is a simple affair
public static Jobs JobData;
public static bool ProcessData()
{
try
{
var s = new XmlSerializer(typeof(Jobs));
var r = new StreamReader(jobsapp.Singleton.AppContext.Assets.Open("schemaJobsV101.xml"));
JobData = (Jobs)s.Deserialize(r);
r.Close();
return true;
}
catch (FileNotFoundException)
{
Console.Write("file not found");
return false;
}
catch (InvalidOperationException)
{
Console.WriteLine("invalid operation");
return false;
}
}
Is there a way to read the XML file to allow me to have just one element (aspect1, 2 or 3)?

Related

How can I grab extract custom XML from web.config for use in another class?

I currently have an xml file named PrioritizationSettings.config and I need to consolidate this into Web.config.
I have moved this directly into the Web.config as it is the same across all configurations.
I noticed that the project is using this old file path that no longer exists because I moved the XML directly into Web.config.
public static PrioritizationSettings LoadPrioritizeSettings()
{
return LoadPrioritizeSettings(AppDomain.CurrentDomain.BaseDirectory + "__Configs\\PrioritizationSettings.config");
}
I would like to be able to access the PrioritizationSettings inside of Web.config from here. So that instead of passing the entire XML file, I can just pass in the section of XML that now exists in Web.Config
Is there another way to do this without using ConfigurationManager.GetSection()? I have looked at this an I fear it may be far more involving. I just need to extract the XML.
This appears to be doing what I would like.
public static PrioritizationSettings LoadPrioritizeSettings()
{
return LoadPrioritizeSettings(AppDomain.CurrentDomain.BaseDirectory + "Web.config");
}
I now pass the entire Web.config file in. Inside of the LoadPrioritizeSettings I have the following code:
public static PrioritizationSettings LoadPrioritizeSettings(string configFile)
{
XmlReader xmlReader;
try { xmlReader = XmlReader.Create(configFile); }
catch (Exception ex) { throw ex; }
if (xmlReader == null)
return null;
XmlDocument xmlDoc = new System.Xml.XmlDocument();
xmlReader.Read();
xmlDoc.Load(xmlReader);
xmlReader.Close();
XmlNode xmlConfiguration = xmlDoc["configuration"];
if (xmlConfiguration == null)
throw new Exception("The root element (PrioritizationSettings) of the config file could not be found.");
XmlNode xmlPrioritizeSettings = xmlConfiguration["PrioritizationSettings"];
return prioritizeSettings;
}
So I am able to get the PrioritizationSetting node from the web.config.

C# validate XML against generated Xsd generated class

In our project, we have classes generated from XSD's. Currently we are validating the XML against the XSD by the XSD file path.
There are several XSD's and we select the right one by a number stored in the Database just like:
"C:/Projects/XSD/Reports/Report_1.7.xsd"
"C:/Projects/XSD/Reports/Report_1.8.xsd"
Because I become a little bit nervous when it starts to have file paths in a project like this. Is there a best practice for this use-case? Something like validating the xml against the generated C# class directly. My current Code:
private static string GetXsdPath(SchemaType aSchemaType, string aTransferRevision)
{
var lFileBeginnName = XsdStrategies.XsdService.GetXsdName(aSchemaType);
var lDirectoryName = XsdStrategies.XsdService.GetDirectoryName(aSchemaType);
string lRoot = HttpContext.Current.Server.MapPath("~");
string lFullRootPath = Path.GetFullPath(Path.Combine(lRoot, #"../"));
return string.Format(
CultureInfo.CurrentCulture, #"{0}/Reports/{1}/Report_V{2}.xsd",
lFullRootPath,
lDirectoryName,
aTransferRevision);
}
public bool IsValidXml(string aXmlContent, string aXsdFilePath, XNamespace aNamespaceName)
{
try
{
if (aNamespaceName == null)
{
this.Logger.AddLogEntry(LogLevel.Error, "Namespace is null.");
return false;
}
var lXdoc = XDocument.Parse(aXmlContent);
var lSchemas = new XmlSchemaSet();
lSchemas.Add(aNamespaceName.NamespaceName, aXsdFilePath);
// xDoc Validate throws an excption if xml not conforms xsd.
lXdoc.Validate(lSchemas, null);
}
catch (XmlSchemaValidationException lEx)
{
this.Logger.AddLogEntry(LogLevel.Error, $"The Xml is not valid against the Xsd: {lEx}");
return false;
}
catch (XmlSchemaException lEx)
{
this.Logger.AddLogEntry(LogLevel.Error, $"Therse is something wrong in the Schema-Version from Xml and Xsd: {lEx}");
return false;
}
catch (XmlException lEx)
{
this.Logger.AddLogEntry(LogLevel.Error, $"A generic Error occured durring Xml against Xsd validation: {lEx}");
return false;
}
return true;
}
I would suggest you store the XSD data directly in the database rather than just the path, you're very much correct that this is a bad idea.
You can store the XSD data in something like an NVarChar data type in MS SQL Server, for example.

XmlReader ReadStartElement causes XmlException

I'm writing a file reader using the XmlReader in a Silverlight project. However, I'm getting some errors (specifically around the XmlReader.ReadStartElement method) and it's causing me to believe that I've misunderstood how to use it somewhere along the way.
Basically, here is a sample of the format of the Xml I am using:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<root>
<EmptyElement />
<NonEmptyElement Name="NonEmptyElement">
<SubElement Name="SubElement" />
</NonEmptyElement>
</root>
And here is a sample of some code used in the same way as how I am using it:
public void ReadData(XmlReader reader)
{
// Move to root element
reader.ReadStartElement("root");
// Move to the empty element
reader.ReadStartElement("EmptyElement");
// Read any children
while(reader.ReadToNextSibling("SubEmptyElement"))
{
// ...
}
// Read the end of the empty element
reader.ReadEndElement();
// Move to the non empty element
reader.ReadStartElement("NonEmptyElement"); // NOTE: This is where I get the error.
// ...
}
So, essentially, I am simply trying to read each element and any contained children. The error I get at the highlighted point is as follows:
Error Description
[Xml_InvalidNodeType]
Arguments: None,10,8
Debugging resource strings are unavailable. Often the key and arguments provide sufficient information to diagnose the problem. See http://go.microsoft.com/fwlink/?linkid=106663&Version=4.0.51204.0&File=System.Xml.dll&Key=Xml_InvalidNodeType
Error Stack Trace
at System.Xml.XmlReader.ReadStartElement(String name)
at ----------------
Any advice or direction on this would be greatly appreciated.
EDIT
Since this reader needs to be fairly generic, it can be assumed that the Xml may contain elements that are children of the EmptyElement. As such, the attempt at reading any SubEmptyElements should be valid.
<SubElement/> is not a sibling of <EmptyElement>, so <NonEmptyElement> is going to get skipped entirely, and your call to ReadEndElement() will read the end element </root>. When you try to subsequently read "NonEmptyElement", there are no elements left, and you'll get an XmlException: {"'None' is an invalid XmlNodeType. Line 8, position 1."}
Note also that since <EmptyElement/> is empty, when you ReadStartElement("EmptyElement"), you'll read the whole element, and you won't need to use ReadEndElement().
I'd also recommend that you configure your reader settings to IgnoreWhitespace (if you're not already doing so), to avoid any complications introduced by reading (insignificant) whitespace text nodes when you aren't expecting them.
Try moving the Read of NonEmptyElement up:
public static void ReadData(XmlReader reader)
{
reader.ReadStartElement("root");
reader.ReadStartElement("EmptyElement");
reader.ReadStartElement("NonEmptyElement");
while (reader.ReadToNextSibling("SubEmptyElement"))
{
// ...
}
reader.ReadEndElement(/* NonEmptyElement */);
reader.ReadEndElement(/* root */);
// ...
}
If you just want to skip anything in <EmptyElement>, regardless of whether or not its actually empty, use ReadToFollowing:
public static void ReadData(XmlReader reader)
{
reader.ReadStartElement("root");
reader.ReadToFollowing("NonEmptyElement");
Console.WriteLine(reader.GetAttribute("Name"));
reader.ReadStartElement("NonEmptyElement");
Console.WriteLine(reader.GetAttribute("Name"));
while (reader.ReadToNextSibling("SubEmptyElement"))
{
// ...
}
reader.ReadEndElement(/* NonEmptyElement */);
reader.ReadEndElement(/* root */);
// ...
}
Update: Here's a fuller example with a clearer data model. Maybe this is closer to what you're asking for.
XMLFile1.xml:
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<root>
<Person Type="Homeless"/>
<Person Type="Developer">
<Home Type="Apartment" />
</Person>
<Person Type="Banker">
<Home Type="Apartment"/>
<Home Type="Detached"/>
<Home Type="Mansion">
<PoolHouse/>
</Home>
</Person>
</root>
Program.cs:
using System;
using System.Xml;
namespace ConsoleApplication6
{
internal class Program
{
public static void ReadData(XmlReader reader)
{
reader.ReadStartElement("root");
while (reader.IsStartElement("Person"))
{
ReadPerson(reader);
}
reader.ReadEndElement( /* root */);
}
public static void ReadPerson(XmlReader reader)
{
Console.WriteLine(reader.GetAttribute("Type"));
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement("Person");
while (reader.IsStartElement("Home"))
{
ReadHome(reader);
}
if (!isEmpty)
{
reader.ReadEndElement( /* Person */);
}
}
public static void ReadHome(XmlReader reader)
{
Console.WriteLine("\t" + reader.GetAttribute("Type"));
bool isEmpty = reader.IsEmptyElement;
reader.ReadStartElement("Home");
if (!isEmpty)
{
reader.Skip();
reader.ReadEndElement( /* Home */);
}
}
private static void Main(string[] args)
{
var settings = new XmlReaderSettings { IgnoreWhitespace = true };
using (var xr = XmlReader.Create("XMLFile1.xml", settings))
{
ReadData(xr);
}
Console.ReadKey();
}
}
}

Writing Logs to an XML File with .NET

I am storing logs in an xml file...
In a traditional straight text format approach, you would typically just have a openFile... then writeLine method...
How is it possible to add a new entry into the xml document structure, like you would just with the text file approach?
use an XmlWriter.
example code:
public class Quote
{
public string symbol;
public double price;
public double change;
public int volume;
}
public void Run()
{
Quote q = new Quote
{
symbol = "fff",
price = 19.86,
change = 1.23,
volume = 190393,
};
WriteDocument(q);
}
public void WriteDocument(Quote q)
{
var settings = new System.Xml.XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent= true
};
using (XmlWriter writer = XmlWriter.Create(Console.Out, settings))
{
writer.WriteStartElement("Stock");
writer.WriteAttributeString("Symbol", q.symbol);
writer.WriteElementString("Price", XmlConvert.ToString(q.price));
writer.WriteElementString("Change", XmlConvert.ToString(q.change));
writer.WriteElementString("Volume", XmlConvert.ToString(q.volume));
writer.WriteEndElement();
}
}
example output:
<Stock Symbol="fff">
<Price>19.86</Price>
<Change>1.23</Change>
<Volume>190393</Volume>
</Stock>
see
Writing with an XmlWriter
for more info.
One of the problems with writing a log file in XML format is that you can't just append lines to the end of the file, because the last line has to have a closing root element (for the XML to be valid)
This blog post by Filip De Vos demonstrates quite a good solution to this:
High Performance writing to XML Log files (edit: link now dead so removed)
Basically, you have two XML files linked together using an XML-include thing:
Header file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log [
<!ENTITY loglines SYSTEM "loglines.xml">
]>
<log>
&loglines;
</log>
Lines file (in this example, named loglines.xml):
<logline date="2007-07-01 13:56:04.313" text="start process" />
<logline date="2007-07-01 13:56:25.837" text="do something" />
<logline date="2007-07-01 13:56:25.853" text="the end" />
You can then append new lines to the 'lines file', but (most) XML parsers will be able to open the header file and read the lines correctly.
Filip notes that: This XML will not be parsed correctly by every XML parser on the planet. But all the parsers I have used do it correctly.
The big difference is the way you are thinking about your log data. In plain text files you are indeed just adding new lines. XML is a tree structure however, and you need to think about like such. What you are adding is probably another NODE, i.e.:
<log>
<time>12:30:03 PST</time>
<user>joe</user>
<action>login</action>
<log>
Because it is a tree what you need to ask is what parent are you adding this new node to. This is usually all defined in your DTD (Aka, how you are defining the structure of your data). Hopefully this is more helpful then just what library to use as once you understand this principle the interface of the library should make more sense.
Why reinvent the wheel? Use TraceSource Class (System.Diagnostics) with the XmlWriterTraceListener.
Sorry to post a answer for old thread. i developed the same long time ago. here i like to share my full code for logger saved log data in xml file date wise.
logger class code
using System.IO;
using System.Xml;
using System.Threading;
public class BBALogger
{
public enum MsgType
{
Error ,
Info
}
public static BBALogger Instance
{
get
{
if (_Instance == null)
{
lock (_SyncRoot)
{
if (_Instance == null)
_Instance = new BBALogger();
}
}
return _Instance;
}
}
private static BBALogger _Instance;
private static object _SyncRoot = new Object();
private static ReaderWriterLockSlim _readWriteLock = new ReaderWriterLockSlim();
private BBALogger()
{
LogFileName = DateTime.Now.ToString("dd-MM-yyyy");
LogFileExtension = ".xml";
LogPath= Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) + "\\Log";
}
public StreamWriter Writer { get; set; }
public string LogPath { get; set; }
public string LogFileName { get; set; }
public string LogFileExtension { get; set; }
public string LogFile { get { return LogFileName + LogFileExtension; } }
public string LogFullPath { get { return Path.Combine(LogPath, LogFile); } }
public bool LogExists { get { return File.Exists(LogFullPath); } }
public void WriteToLog(String inLogMessage, MsgType msgtype)
{
_readWriteLock.EnterWriteLock();
try
{
LogFileName = DateTime.Now.ToString("dd-MM-yyyy");
if (!Directory.Exists(LogPath))
{
Directory.CreateDirectory(LogPath);
}
var settings = new System.Xml.XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
StringBuilder sbuilder = new StringBuilder();
using (StringWriter sw = new StringWriter(sbuilder))
{
using (XmlWriter w = XmlWriter.Create(sw, settings))
{
w.WriteStartElement("LogInfo");
w.WriteElementString("Time", DateTime.Now.ToString());
if (msgtype == MsgType.Error)
w.WriteElementString("Error", inLogMessage);
else if (msgtype == MsgType.Info)
w.WriteElementString("Info", inLogMessage);
w.WriteEndElement();
}
}
using (StreamWriter Writer = new StreamWriter(LogFullPath, true, Encoding.UTF8))
{
Writer.WriteLine(sbuilder.ToString());
}
}
catch (Exception ex)
{
}
finally
{
_readWriteLock.ExitWriteLock();
}
}
public static void Write(String inLogMessage, MsgType msgtype)
{
Instance.WriteToLog(inLogMessage, msgtype);
}
}
Calling or using this way
BBALogger.Write("pp1", BBALogger.MsgType.Error);
BBALogger.Write("pp2", BBALogger.MsgType.Error);
BBALogger.Write("pp3", BBALogger.MsgType.Info);
MessageBox.Show("done");
may my code help you and other :)
Without more information on what you are doing I can only offer some basic advice to try.
There is a method on most of the XML objects called "AppendChild". You can use this method to add the new node you create with the log comment in it. This node will appear at the end of the item list. You would use the parent element of where all the log nodes are as the object to call on.
Hope that helps.
XML needs a document element (Basically top level tag starting and ending the document).
This means a well formed XML document need have a beginning and end, which does not sound very suitable for logs, where the current "end" of the log is continously extended.
Unless you are writing batches of self contained logs where you write everything to be logged to one file in a short period of time, I'd consider something else than XML.
If you are writing a log of a work-unit done, or a log that doesn't need to be inspected until the whole thing has finished, you could use your approach though - simply openfile, write the log lines, close the file when the work unit is done.
For editing an xml file, you could also use LINQ. You can take a look on how here:
http://www.linqhelp.com/linq-tutorials/adding-to-xml-file-using-linq-and-c/

How can I find the position where a string is malformed XML (in C#)?

I'm writing a lightweight XML editor, and in cases where the user's input is not well formed, I would like to indicate to the user where the problem is, or at least where the first problem is. Does anyone know of an existing algorithm for this? If looking at code helps, if I could fill in the FindIndexOfInvalidXml method (or something like it), this would answer my question.
using System;
namespace TempConsoleApp
{
class Program
{
static void Main(string[] args)
{
string text = "<?xml version=\"1.0\"?><tag1><tag2>Some text.</taagg2></tag1>";
int index = FindIndexOfInvalidXml(text);
Console.WriteLine(index);
}
private static int FindIndexOfInvalidXml(string theString)
{
int index = -1;
//Some logic
return index;
}
}
}
I'd probably just cheat. :) This will get you a line number and position:
string s = "<?xml version=\"1.0\"?><tag1><tag2>Some text.</taagg2></tag1>";
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
try
{
doc.LoadXml(s);
}
catch(System.Xml.XmlException ex)
{
MessageBox.Show(ex.LineNumber.ToString());
MessageBox.Show(ex.LinePosition.ToString());
}
Unless this is an academic exercise, I think that writing your own XML parser is probably not the best way to go about this. I would probably check out the XmlDocument class within the System.Xml namespace and try/catch exceptions for the Load() or LoadXml() methods. The exception's message property should contain info on where the error occurred (row/col numbers) and I suspect it'd be easier to use a regular expression to extract those error messages and the related positional info.
You should be able to simply load the string into an XmlDocument or an XmlReader and catch XmlException. The XmlException class has a LineNumber property and a LinePosition property.
You can also use XmlValidatingReader if you want to validate against a schema in addition to checking that a document is well-formed.
You'd want to load the string into an XmlDocument object via the load method and then catch any exceptions.
public bool isValidXml(string xml)
{
System.Xml.XmlDocument xDoc = null;
bool valid = false;
try
{
xDoc = new System.Xml.XmlDocument();
xDoc.loadXml(xmlString);
valid = true;
}
catch
{
// trap for errors
}
return valid;
}

Categories