XDocument with inline schema. SchemaInfo is null - c#

OK, so here we go. I've got a set of XML documents that I'm loading into my app. In my little test I've created a reader that validates against the schema specified by the XML document. Using the following code it works quite nicely.
// Set the validation settings.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
// Create the XmlReader object.
XmlReader reader = XmlReader.Create("xml/note.xml", settings);
// Load the XDocument from the reader
XDocument loadedDoc = XDocument.Load(reader);
Now my XML document gets loaded correctly and any validation errors that occur are handled by the callback.
However, if I want to get schema information about an element by calling GetSchemaInfo() on said element, I'm given null. This question here talks about using an overloaded Validate method, but that doesn't really apply to this situation, unless I'm missing something.
Is it possible to get the schema info loaded into the XDoc using an inline schema or should I be doing this another way?

Check out my answer to my own question.
The first paragraph after the code block is what is important to you, but basically, the SchemaInfo will be there, but it is not added until after the validation callback.
The workaround I used was basically this (NOTE: this code was tested and works when loading an XML directly and calling XDocument.Validate on an XmlSchemaSet, but the premise should be the same or similar with XmlReader and inline schemas):
List<XElement> errorElements = new List<XElement>();
serializedObject.Validate((sender, args) =>
{
var exception = (args.Exception as XmlSchemaValidationException);
if (exception != null)
{
var element = (exception.SourceObject as XElement);
if (element != null)
errorElements.Add(element);
}
});
foreach element in errorElements
{
var si = element.GetSchemaInfo;
// do something with SchemaInfo
}
I was only trying to capture SchemaInfo for elements, hence the as cast and null check, but this should work for other node types like Attributes (not tested though, so i could be wrong).
If you are looking to use a specific callback method instead of an anonymous delegate, your errorElements will need to be a class level field and you can do what you need to do with it after the validation is complete (again, I will add in the untested tag).
public class SomeClass
{
List<XElement> errorElements = new List<XElement>();
public void Load()
{
// Set the validation settings.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
// Create the XmlReader object.
XmlReader reader = XmlReader.Create("xml/note.xml", settings);
// Load the XDocument from the reader
XDocument loadedDoc = XDocument.Load(reader);
// do something with errorElements
}
public void ValidationCallBack(object sender, ValidationEventArgs e)
{
var exception = (args.Exception as XmlSchemaValidationException);
if (exception != null)
{
var element = (exception.SourceObject as XElement);
if (element != null)
errorElements.Add(element);
}
}
}
In response to your comment about wanting the IXSchemaInfo for all nodes, the Schema info is added to the XElement after the validation regardless of whether the node failed or passed, so your requirement would actually be easier since you do not need to keep a list of failed nodes. You should be able to do this:
public void Load()
{
// Set the validation settings.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessInlineSchema;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationFlags |= XmlSchemaValidationFlags.ReportValidationWarnings;
settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);
// Create the XmlReader object.
XmlReader reader = XmlReader.Create("xml/note.xml", settings);
// Load the XDocument from the reader
XDocument loadedDoc = XDocument.Load(reader);
foreach (var node in loadedDoc.Descendants())
{
var si = node.GetSchemaInfo();
}
}
The above example will only access the XElements in your XDocument but it is just to illustrate my point. Once the loading is complete and the xml is validate, the schema info should be there.

Related

Reading XML comments in C#

I have got some XML files that contain comments above the nodes. When I am reading the file in, as part of the process I would like to get the comment out as well. I know you can write a comment to the file using XmlComment, but not sure how to read them back out.
My XML looks similar to this:
<Objects>
<!--Comment about node-->
<GUID-bf2401c0-ef5e-4d20-9d20-a2451a199362>
<info job="SAVE" person="Joe" />
<info job="SAVE" person="Sally" />
</GUID-bf2401c0-ef5e-4d20-9d20-a2451a199362>
<!--Another Comment about node-->
<GUID-bf2401c0-ef5e-4d20-9d20-a5844113284112>
<info job="SAVE" person="John" />
<info job="SAVE" person="Julie" />
</GUID-bf2401c0-ef5e-4d20-9d20-a5844113284112>
Try this:
XmlReaderSettings readerSettings = new XmlReaderSettings();
readerSettings.IgnoreComments = false;
using (XmlReader reader = XmlReader.Create("input.xml", readerSettings))
{
XmlDocument myData = new XmlDocument();
myData.Load(reader);
// etc...
}
To read comments:
XmlReader xmlRdr = XmlReader.Create("Test.XML");
// Parse the file
while (xmlRdr.Read())
{
switch (xmlRdr.NodeType)
{
case XmlNodeType.Element:
// You may need to capture the last element to provide a context
// for any comments you come across... so copy xmlRdr.Name, etc.
break;
case XmlNodeType.Comment:
// Do something with xmlRdr.value
Using System.Xml.Linq:
var doc = XElement.Load(fileName);
var comments = doc.DescendantNodes().OfType<XComment>();
foreach (XComment comment in comments)
...
They are a part of the child nodes of the containing node as all other nodes: http://msdn.microsoft.com/en-us/library/system.xml.xmlcomment.aspx
I know the question is very old, but yesterday I had the same problem. So here is my solution:
XmlReaderSettings settings = new XmlReaderSettings();
settings.IgnoreWhitespace = false;
settings.IgnoreComments = false;
XmlReaderSettings settings2 = new XmlReaderSettings();
settings2.IgnoreWhitespace = false;
settings2.IgnoreComments = false;
XmlReader xmlreaderOriginalCfg = XmlReader.Create(#"C:\...xml", settings);
XmlReader xmlreaderVerificationCfg = XmlReader.Create(#"C:\....xml", settings);
XmlDocument myData = new XmlDocument();
myData.Load(xmlreaderOriginalCfg);
XmlDocument myData2 = new XmlDocument();
myData2.Load(xmlreaderVerificationCfg);
XmlNode parentNode = myData.SelectSingleNode("/configuration/appSettings");
foreach (XmlComment comment in myData2.SelectNodes("//comment()"))
{
XmlComment importedCom = myData.CreateComment(comment.Value);
parentNode.AppendChild(importedCom);
foreach (XmlNode node in myData2.DocumentElement.SelectNodes("/configuration/appSettings/add"))
{
XmlNode imported = myData.ImportNode(node, true);
parentNode.AppendChild(imported);
}
}
myData.Save(this.pathNew);
Maybe it helps somebody
I stored your XML into a file, here is the code sample.
XmlDocument document = new XmlDocument();
document.Load("test.xml");
foreach (XmlComment comment in document.SelectNodes("//comment()"))
{
Console.WriteLine("Comment: \"{0}\".", comment.Value);
}
Some sample code on how to access comments hope this helps
using System;
using System.IO;
using System.Xml;
public class Sample {
public static void Main() {
XmlDocument doc = new XmlDocument();
doc.LoadXml(#"<Objects><!--Comment about node--><othernode/><!--Some more comment--></Objects>");
XmlNode root = doc.FirstChild;
if (root.HasChildNodes)
{
for (int i=0; i<root.ChildNodes.Count; i++)
{
if( root.ChildNodes[i] is XmlComment)
Console.WriteLine(root.ChildNodes[i].InnerText);
}
}
}
}

xmldocument and nested schemas

Using c# and .net 3.5 I'm trying to validate an xml document against a schema that has includes.
The schemas and there includes are as below
Schema1.xsd -> include another.xsd
another.xsd -> include base.xsd
When i try to add the Schema1.xsd to the XmlDocument i get the following error.
Type 'YesNoType' is not declared or is not a simple type.
I believe i'm getting this error because the base.xsd file is not being included when i load the Schema1.xsd schema.
I'm trying to use the XmlSchemaSet class and I'm setting the XmlResolver uri to the location of the schemas.
NOTE : All schemas live under the same directory E:\Dev\Main\XmlSchemas
Here is the code
string schemaPath = "E:\\Dev\\Main\\XmlSchemas";
XmlDocument xmlDocSchema = new XmlDocument();
XmlSchemaSet s = new XmlSchemaSet();
XmlUrlResolver resolver = new XmlUrlResolver();
Uri baseUri = new Uri(schemaPath);
resolver.ResolveUri(null, schemaPath);
s.XmlResolver = resolver;
s.Add(null, XmlReader.Create(new System.IO.StreamReader(schemaPath + "\\Schema1.xsd"), new XmlReaderSettings { ValidationType = ValidationType.Schema, XmlResolver = resolver }, new Uri(schemaPath).ToString()));
xmlDocSchema.Schemas.Add(s);
ValidationEventHandler valEventHandler = new ValidationEventHandler
(ValidateNinoDobEvent);
try
{
xmlDocSchema.LoadXml(xml);
xmlDocSchema.Validate(valEventHandler);
}
catch (XmlSchemaValidationException xmlValidationError)
{
// need to interogate the Validation Exception, for possible further
// processing.
string message = xmlValidationError.Message;
return false;
}
Can anyone point me in the right direction regarding validating an xmldocument against a schema with nested includes.
I also have a nested schema case and I don't find any error in validating.My code looks like follwoing.
private string strLogger = null;
public bool ValidateXml(string path2XMLFile, string path2XSDFile)
{
bool isValidFile = false;
try
{
XmlReaderSettings settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add(null, path2XSDFile);
settings.ValidationEventHandler += new ValidationEventHandler(settings_ValidationEventHandler);
XmlReader reader = XmlReader.Create(path2XMLFile, settings);
while (reader.Read()) ;
if (String.IsNullOrEmpty(strLogger))
{
isValidFile = true;
}
}
catch (Exception ex)
{
LoggingHandler.Log(ex);
}
return isValidFile;
}
private void settings_ValidationEventHandler(object sender, ValidationEventArgs e)
{
strLogger += System.Environment.NewLine + "Validation Error Message = [" + e.Message + "], " + "Validation Error Severity = [" + e.Severity + "], " + System.Environment.NewLine;
}
I think that what you need to do is to merge the schemas:
http://asp.dotnetheaven.com/howto/doc/Xml/MultipleSchemas.aspx
If they're nested, that means that you'll need to start at the bottom of the hierarchy and load them in that order. I'm not 100% sure because the samples I was able to find don't have, strictly speaking, nested structures, but rather complementary structures. Good luck.

XmlReader - I need to edit an element and produce a new one

I am overriding a method which has an XmlReader being passed in, I need to find a specific element, add an attribute and then either create a new XmlReader or just replace the existing one with the modified content. I am using C#4.0
I have investigated using XElement (Linq) but I can't seem to manipulate an existing element and add an attribute and value.
I know that the XmlWriter has WriteAttributeString which would be fantastic but again I am not sure how it all fits together
I would like to be able to do something like --- This is pseudo-code!
public XmlReader DoSomethingWonderful(XmlReader reader)
{
Element element = reader.GetElement("Test");
element.SetAttribute("TestAttribute","This is a test");
reader.UpdateElement(element);
return reader;
}
XmlReader/Writer are sequential access streams. You will have to read in on one end, process the stream how you want, and write it out the other end. The advantage is that you don't need to read the whole thing into memory and build a DOM, which is what you'd get with any XmlDocument-based approach.
This method should get you started:
private static void PostProcess(Stream inStream, Stream outStream)
{
var settings = new XmlWriterSettings() { Indent = true, IndentChars = " " };
using (var reader = XmlReader.Create(inStream))
using (var writer = XmlWriter.Create(outStream, settings)) {
while (reader.Read()) {
switch (reader.NodeType) {
case XmlNodeType.Element:
writer.WriteStartElement(reader.Prefix, reader.Name, reader.NamespaceURI);
writer.WriteAttributes(reader, true);
//
// check if this is the node you want, inject attributes here.
//
if (reader.IsEmptyElement) {
writer.WriteEndElement();
}
break;
case XmlNodeType.Text:
writer.WriteString(reader.Value);
break;
case XmlNodeType.EndElement:
writer.WriteFullEndElement();
break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
writer.WriteProcessingInstruction(reader.Name, reader.Value);
break;
case XmlNodeType.SignificantWhitespace:
writer.WriteWhitespace(reader.Value);
break;
}
}
}
}
This is not quite as clean as deriving your own XmlWriter, but I find that it's much easier.
[EDIT]
An example of how you would open two streams at once might be something like this:
using (FileStream readStream = new FileStream(#"c:\myFile.xml", FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write)) {
using (FileStream writeStream = new FileStream(#"c:\myFile.xml", FileMode.OpenOrCreate, FileAccess.Write)) {
PostProcess(readStream, writeStream);
}
}
I fixed it using the following duct tape coding
public XmlReader FixUpReader(XmlReader reader)
{
reader.MoveToContent();
string xml = reader.ReadOuterXml();
string dslVersion = GetDSLVersion();
string Id = GetID();
string processedValue = string.Format("<ExampleElement dslVersion=\"{1}\" Id=\"{2}\" ", dslVersion, Id);
xml = xml.Replace("<ExampleElement ", processedValue);
MemoryStream ms = new MemoryStream(System.Text.Encoding.ASCII.GetBytes(xml));
XmlReaderSettings settings = new XmlReaderSettings();
XmlReader myReader = XmlReader.Create(ms);
myReader.MoveToContent();
return myReader;
}
I feel dirty for doing it this way but it is working....
You can't easily do this with XmlReader - at least, not without reading the whole XML document in from the reader, futzing with it and then creating a new XmlReader from the result. That defeats a lot of the point of using XmlReader though - namely the ability to stream large documents.
You could potentially derive from XmlReader, forwarding most method calls to the existing reader but intercepting them where appropriate to add extra attributes etc... but I suspect that code would be really quite complex and fragile.
I would rather prefer to load the xml into XmlDocument object and use Attributes collection to modify the value and call Save method to update this value. Below code works for me.
public static void WriteElementValue ( string sName, string element, string value)
{
try
{
var node = String.Format("//elements/element[#name='{0}']", sName);
var doc = new XmlDocument { PreserveWhitespace = true };
doc.Load(configurationFileName);
var nodeObject = doc.SelectSingleNode(node);
if (nodeObject == null)
throw new XmlException(String.Format("{0} path does not found any matching
node", node));
var elementObject = nodeObject[element];
if (elementObject != null)
{
elementObject.Attributes["value"].Value = value;
}
doc.Save(configurationFileName);
}
catch (Exception ex)
{
throw new ExitLevelException(ex, false);
}
}
I've also observed when you use XmlWriter or XmlSerializer the whitespaces were not correctly preserved, this could be annoying sometimes
string newvalue = "10";
string presentvalue = "";
string newstr = "";
XmlReader xmlr = XmlReader.Create(new StringReader(str));
while (xmlr.Read())
{
if (xmlr.NodeType == XmlNodeType.Element)
{
if (xmlr.Name == "priority")
{
presentvalue = xmlr.ReadElementContentAsString();
newstr = str.Replace(presentvalue, newvalue);
}
}
}
//newstr can be written back to file... that is the edited xml

Possible to validate xml against xsd using code at runtime?

I have xml files that I read in at runtime, is is possible to validate the xml against an xsd file at runtime? using c#
Try this:
public void ValidateXmlDocument(
XmlReader documentToValidate, string schemaPath)
{
XmlSchema schema;
using (var schemaReader = XmlReader.Create(schemaPath))
{
schema = XmlSchema.Read(schemaReader, ValidationEventHandler);
}
var schemas = new XmlSchemaSet();
schemas.Add(schema);
var settings = new XmlReaderSettings();
settings.ValidationType = ValidationType.Schema;
settings.Schemas = schemas;
settings.ValidationFlags =
XmlSchemaValidationFlags.ProcessIdentityConstraints |
XmlSchemaValidationFlags.ReportValidationWarnings;
settings.ValidationEventHandler += ValidationEventHandler;
using (var validationReader = XmlReader.Create(documentToValidate, settings))
{
while (validationReader.Read())
{
}
}
}
private static void ValidationEventHandler(
object sender, ValidationEventArgs args)
{
if (args.Severity == XmlSeverityType.Error)
{
throw args.Exception;
}
Debug.WriteLine(args.Message);
}
I GOT CODE TOO! I use this in my tests:
public static bool IsValid(XElement element, params string[] schemas)
{
XmlSchemaSet xsd = new XmlSchemaSet();
XmlReader xr = null;
foreach (string s in schemas)
{ // eh, leak 'em.
xr = XmlReader.Create(
new MemoryStream(Encoding.Default.GetBytes(s)));
xsd.Add(null, xr);
}
XDocument doc = new XDocument(element);
var errored = false;
doc.Validate(xsd, (o, e) => errored = true);
if (errored)
return false;
// If this doesn't fail, there's an issue with the XSD.
XNamespace xn = XNamespace.Get(
element.GetDefaultNamespace().NamespaceName);
XElement fail = new XElement(xn + "omgwtflolj/k");
fail.SetAttributeValue("xmlns", xn.NamespaceName);
doc = new XDocument(fail);
var fired = false;
doc.Validate(xsd, (o, e) => fired = true);
return fired;
}
This one takes in the schemas as strings (file resources within the assembly) and adds them to a schema set. I validate and if its not valid I return false.
If the xml isn't found to be invalid, I do a negative check to make sure my schemas aren't screwed up. Its not guaranteed foolproof, but I have used this to find errors in my schemas.
simpler solution..
try
{
XmlReaderSettings Xsettings = new XmlReaderSettings();
Xsettings.Schemas.Add(null, "personDivideSchema.xsd");
Xsettings.ValidationType = ValidationType.Schema;
XmlDocument document = new XmlDocument();
document.Load("person.xml");
XmlReader reader = XmlReader.Create(new StringReader(document.InnerXml), Xsettings);
while (reader.Read());
}
catch (Exception e)
{
Console.WriteLine(e.Message.ToString());
}

Xml validation using XSD schema

The following code helps me validate an XML file with an XSD schema.
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, xsdFilePath);
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += new System.Xml.Schema.ValidationEventHandler(settings_ValidationEventHandler);
XmlDocument document = new XmlDocument();
document.Load(xmlFilePath);
XmlReader rdr = XmlReader.Create(new StringReader(document.InnerXml), settings);
while (rdr.Read())
{
}
isValid = true;
The ValidationEventHandler also tells me what the errors are, but doesn't tell me on 'where' or 'on which line' they are located. Is there any way to get the line number where the XML fails to be validated?
Would not this do what you are after ?
Create an XmlReaderSettings object and enable warnings through that object.
Unfortunately, there seems to be no way to pass your own XmlReaderSettings object to XmlDocument.Validate().
Instead, you can use a validating XmlReader and an XmlNodeReader to validate an existing XmlDocument (using a XmlNodeReader with StringReader rather than an XmlDocument)
XmlDocument x = new XmlDocument();
x.LoadXml(XmlSource);
XmlReaderSettings settings = new XmlReaderSettings();
settings.CloseInput = true;
settings.ValidationEventHandler += Handler;
settings.ValidationType = ValidationType.Schema;
settings.Schemas.Add(null, ExtendedTreeViewSchema);
settings.ValidationFlags =
XmlSchemaValidationFlags.ReportValidationWarnings |
XmlSchemaValidationFlags.ProcessIdentityConstraints |
XmlSchemaValidationFlags.ProcessInlineSchema |
XmlSchemaValidationFlags.ProcessSchemaLocation ;
StringReader r = new StringReader(XmlSource);
using (XmlReader validatingReader = XmlReader.Create(r, settings)) {
while (validatingReader.Read()) { /* just loop through document */ }
}
And the handler:
private static void Handler(object sender, ValidationEventArgs e)
{
if (e.Severity == XmlSeverityType.Error || e.Severity == XmlSeverityType.Warning)
System.Diagnostics.Trace.WriteLine(
String.Format("Line: {0}, Position: {1} \"{2}\"",
e.Exception.LineNumber, e.Exception.LinePosition, e.Exception.Message));
}
ValidationEventArgs.Message includes line/column in its text.
ValidationEventArgs.Exception has fields for line and column.

Categories