I am trying to create an application that imports an XML file into a TextBox, with the goal to edit the content.
After editing, the user should be able to save the content of the file, but at the same time to validate it. For example,
<Person id="22">
<Name gg="u">John</Name>
<Surname>Jones</Surname>
<PhoneNo>333333333111</PhoneNo>
<Country>Germany</Country>
</Person>
If the user edits the start tag "Name", but forgets to edits the end tag, it should throw an exception.
I have tried
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(MyTextBox.Text);
xmlDoc.Save(fileName);
and
XmlElement DocRoot = xmlDoc.CreateElement("root");
DocRoot.InnerText = MyTextBox.Text;
xmlDoc.AppendChild(DocRoot);
xmlDoc.Save(fileName);
None worked. I am grateful for any help, thank you!
I have though of this solution and it seems to work :) As per the xsd question, I have a generic XML.
try
{
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(MyTextBox.Text);
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true
};
XmlWriter writer = XmlWriter.Create(fileName, settings);
xmlDoc.Save(writer);
MessageBox.Show("File overwritten to: " + fileName);
}
catch (Exception ex)
{
MessageBox.Show("The textbox content is wrong. ");
}
Seems that you are trying to check if the XML text is well formed and not really if it is valid against a certain definition.
To check if the XML text is well formed, you can try to parse it and verify if it contains any errors. Here is a function that attempts to do that:
class Program
{
static void Main(string[] args)
{
var result = ValidateXml("<Person id=\"22\"><Name>John<Name></Person>");
if (!result.IsValid)
{
Console.WriteLine($"Line number: {result.Exception.LineNumber}");
Console.WriteLine($"Line position: {result.Exception.LinePosition}");
Console.WriteLine($"Message: {result.Exception.Message}");
}
// OUTPUT:
// Line number: 1
// Line position: 35
// Message: The 'Name' start tag on line 1 position 28 does not match the end tag of 'Person'.Line 1, position 35.
}
static ValidationResult ValidateXml(string xml)
{
using (var xr = XmlReader.Create( new StringReader(xml)))
{
try
{
while (xr.Read())
{
}
return ValidationResult.ValidResult;
}
catch (XmlException exception)
{
return new ValidationResult(exception);
}
}
}
public class ValidationResult
{
public static ValidationResult ValidResult = new ValidationResult();
private ValidationResult()
{
IsValid = true;
}
public ValidationResult(XmlException exception)
{
IsValid = false;
Exception = exception;
}
public bool IsValid { get; }
public XmlException Exception { get;}
}
}
Related
So I am trying to validate a xml file against a xsd file using XmlSchemaSet and I tried implementing the following solution in my project and it finds all the errors in the xml file but the line number it gets is always 1 for some reason. Here is the code that deals with the issue:
xmlValidate class:
public class xmlValidate
{
private IList<string> allValidationErrors = new List<string>();
public IList<string> AllValidationErrors
{
get
{
return this.allValidationErrors;
}
}
public void checkForErrors(object sender, ValidationEventArgs error)
{
if (error.Severity == XmlSeverityType.Error || error.Severity == XmlSeverityType.Warning)
{
this.allValidationErrors.Add(String.Format("<br/>" + "Line: {0}: {1}", error.Exception.LineNumber, error.Exception.Message));
}
}
}
Main function:
public string validate(string xmlUrl, string xsdUrl)
{
XmlDocument xml = new XmlDocument();
xml.Load(xmlUrl);
xml.Schemas.Add(null, xsdUrl);
string xmlString = xml.OuterXml;
XmlSchemaSet xmlSchema = new XmlSchemaSet();
xmlSchema.Add(null, xsdUrl);
if (xmlSchema == null)
{
return "No Schema found at the given url.";
}
string errors = "";
xmlValidate handler = new xmlValidate();
XmlReaderSettings settings = new XmlReaderSettings();
settings.CloseInput = true;
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += new ValidationEventHandler(handler.checkForErrors);
settings.Schemas.Add(xmlSchema);
settings.ValidationFlags = XmlSchemaValidationFlags.ProcessInlineSchema
| XmlSchemaValidationFlags.ProcessSchemaLocation
| XmlSchemaValidationFlags.ReportValidationWarnings
| XmlSchemaValidationFlags.ProcessIdentityConstraints;
StringReader sr = new StringReader(xmlString);
using (XmlReader vr = XmlReader.Create(sr, settings))
{
while (vr.Read()) { }
}
if (handler.AllValidationErrors.Count > 0)
{
foreach (String errorMessage in handler.AllValidationErrors)
{
errors += errorMessage;
}
return errors;
}
return "No Errors!";
}
Does anyone see my issue? Thank you in advance!
Could it be, that you load your XML without formatting?
Try with XmlDocument xml = new XmlDocument { PreserveWhitespace = true }
I guess that could be important for getting the right line number but I did not check to be honest.
I have a class that I deserialize with:
public static bool FileDeserializer<T>(string xmlFileName, out T element, out string strError)
{
try
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (TextReader reader = new StreamReader(xmlFileName))
{ element = (T)xmlSerializer.Deserialize(reader); }
strError = string.Empty;
return true;
}
catch (Exception exc)
{
strError = "XmlFileDeserializer exception: " + exc;
element = default(T);
return false;
}
}
and serialize with
public static bool FileSerializer<T>(T value, string strFilename, out string strError)
{
try
{
var serializer = new XmlSerializer(typeof(T));
using (var xmlWriter = XmlWriter.Create(strFilename))
{ serializer.Serialize(xmlWriter, value); }
strError = string.Empty;
return true;
}
catch (Exception exc)
{
strError = "XmlFileSerializer exception: " + exc;
return false;
}
}
Now everything works fine with serialize/deserialize.
But when I try to read it to modify it:
with visual studio it's on a single line (so very hard to read)
with an other editor (xml explorer) i get the message:
The document does not have a specified schema. Click to apply one.
So I have searched for documentation but didn't understand how to put that in my code.
Thanks
---ADD---
Here is the xml in question sorry for not having added it before
<?xml version="1.0" encoding="utf-8"?><CfgData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><DialogFileIniDir>C:\temp</DialogFileIniDir><HorSplitterPos>204</HorSplitterPos><VerSplitterPos>358</VerSplitterPos><GridStep>10</GridStep><Scale>0</Scale><TraceThickness>3</TraceThickness><ShadowsEnabled>false</ShadowsEnabled><Theme>LIGHT</Theme><LightForeground><A>255</A><R>0</R><G>0</G><B>0</B><ScA>1</ScA><ScR>0</ScR><ScG>0</ScG><ScB>0</ScB></LightForeground><LightBackgound><A>255</A><R>245</R><G>245</G><B>245</B><ScA>1</ScA><ScR>0.913098633</ScR><ScG>0.913098633</ScG><ScB>0.913098633</ScB></LightBackgound><LightTraceOkColor><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></LightTraceOkColor><LightTraceCancelColor><A>255</A><R>255</R><G>0</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>0</ScG><ScB>1</ScB></LightTraceCancelColor><LightTraceEndColor><A>255</A><R>0</R><G>0</G><B>255</B><ScA>1</ScA><ScR>0</ScR><ScG>0</ScG><ScB>1</ScB></LightTraceEndColor><LightHeaderUnselectedBG><A>255</A><R>0</R><G>206</G><B>209</B><ScA>1</ScA><ScR>0</ScR><ScG>0.6172066</ScG><ScB>0.637596846</ScB></LightHeaderUnselectedBG><LightHeaderSelectedBG><A>255</A><R>0</R><G>0</G><B>255</B><ScA>1</ScA><ScR>0</ScR><ScG>0</ScG><ScB>1</ScB></LightHeaderSelectedBG><LightHeaderStartEndBG><A>255</A><R>105</R><G>105</G><B>105</B><ScA>1</ScA><ScR>0.141263291</ScR><ScG>0.141263291</ScG><ScB>0.141263291</ScB></LightHeaderStartEndBG><LightHeaderFG><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></LightHeaderFG><LightBlockBG><A>255</A><R>220</R><G>220</G><B>220</B><ScA>1</ScA><ScR>0.7156935</ScR><ScG>0.7156935</ScG><ScB>0.7156935</ScB></LightBlockBG><LightBlockFG><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></LightBlockFG><DarkForeground><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></DarkForeground><DarkBackgound><A>255</A><R>19</R><G>56</G><B>53</B><ScA>1</ScA><ScR>0.00651209056</ScR><ScG>0.0395462364</ScG><ScB>0.0356013142</ScB></DarkBackgound><DarkTraceOkColor><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></DarkTraceOkColor><DarkTraceCancelColor><A>255</A><R>255</R><G>0</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>0</ScG><ScB>1</ScB></DarkTraceCancelColor><DarkTraceEndColor><A>255</A><R>0</R><G>0</G><B>255</B><ScA>1</ScA><ScR>0</ScR><ScG>0</ScG><ScB>1</ScB></DarkTraceEndColor><DarkHeaderUnselectedBG><A>255</A><R>220</R><G>220</G><B>220</B><ScA>1</ScA><ScR>0.7156935</ScR><ScG>0.7156935</ScG><ScB>0.7156935</ScB></DarkHeaderUnselectedBG><DarkHeaderSelectedBG><A>255</A><R>0</R><G>0</G><B>255</B><ScA>1</ScA><ScR>0</ScR><ScG>0</ScG><ScB>1</ScB></DarkHeaderSelectedBG><DarkHeaderStartEndBG><A>255</A><R>105</R><G>105</G><B>105</B><ScA>1</ScA><ScR>0.141263291</ScR><ScG>0.141263291</ScG><ScB>0.141263291</ScB></DarkHeaderStartEndBG><DarkHeaderFG><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></DarkHeaderFG><DarkBlockBG><A>255</A><R>220</R><G>220</G><B>220</B><ScA>1</ScA><ScR>0.7156935</ScR><ScG>0.7156935</ScG><ScB>0.7156935</ScB></DarkBlockBG><DarkBlockFG><A>255</A><R>255</R><G>255</G><B>255</B><ScA>1</ScA><ScR>1</ScR><ScG>1</ScG><ScB>1</ScB></DarkBlockFG></CfgData>
I'm using this code to save and restore the XML values but I'm in trouble . Rescue usually works the problem and when I try to load the XML . I get this exception that in the image.
line 105 : string text = el.Attribute("Text").Value;
void SaveData() {
XDocument xmlDocument = new XDocument(new XElement("Pages"));
List<XElement> xmlPages = new List<XElement>();
foreach(KeyValuePair<string, string> doc in documents)
xmlDocument.Root.Add(
new XElement("Page",
new XAttribute("nodeName", GetNodeName(doc.Key)),
new XAttribute("pageGuid", doc.Key),
new XAttribute("Rtf", doc.Value)));
xmlDocument.Root.Add(
new XElement("TextEdit",
new XAttribute("Text", textBox1.Text)));
xmlDocument.Save(GetPathToFile());
}
void LoadData() {
try {
XDocument xmlDocument = XDocument.Load(GetPathToFile());
rootNode.Nodes.Clear();
documents.Clear();
foreach(XElement el in xmlDocument.Root.Elements()) {
string nodeName = el.Attribute("nodeName").Value;
string pageGuid = el.Attribute("pageGuid").Value;
string rtf = el.Attribute("Rtf").Value;
string text = el.Attribute("Text").Value;
rootNode.Nodes.Add(new DataNode(nodeName, pageGuid));
documents.Add(pageGuid, rtf);
textBox1.Text = text;
}
} catch(Exception ex) {
MessageBox.Show("No data loaded. Check XML file" + ex.ToString());
}
treeList1.RefreshDataSource();
}
The exception is clear: There is not such attribute el.Attribute("Text"), so you can't try to get it's value. Check for attribute existence before getting it's value.
After research could solve the case.
Solution:
void LoadData() {
try {
XDocument xmlDocument = XDocument.Load(GetPathToFile());
rootNode.Nodes.Clear();
documents.Clear();
foreach(XElement el in xmlDocument.Root.Elements()) {
switch(el.Name.LocalName) {
case "Page":
string nodeName = el.Attribute("nodeName").Value;
string pageGuid = el.Attribute("pageGuid").Value;
string rtf = el.Attribute("Rtf").Value;
rootNode.Nodes.Add(new DataNode(nodeName, pageGuid));
documents.Add(pageGuid, rtf);
break;
case "Text":
textEdit1.Text = el.Attribute("text").Value;
break;
}
}
} catch(Exception ex) {
MessageBox.Show("No data loaded. Check XML file");
}
treeList1.RefreshDataSource();
}
so I'm a total noob to C# is there any way to make this work?
It would also be a great help if someone could explain why my system doesn't work, and why another version would.
using System;
using System.Xml;
public class XMLManager
{
private XmlTextReader reader;
private XmlDocument document;
private XmlNodeList nodeList;
public void OpenFile(string file)
{
try
{
reader = new XmlTextReader(file);
reader.WhitespaceHandling = WhitespaceHandling.None;
reader.MoveToContent();
document = new XmlDocument();
document.Load(reader);
nodeList = document.SelectNodes(#"Settings/Settings");
}
catch (System.IO.FileNotFoundException)
{
}
}
public void CloseFile()
{
if (reader != null)
{
((IDisposable)reader).Dispose();
reader.Close();
reader = null;
}
document = null;
nodeList = null;
}
public string Get(string attrib)
{
for (int i = 0; i < nodeList.Count; i++)
{
reader.MoveToAttribute(i);
if (reader.Name == attrib)
{
return reader.Value;
}
}
return null;
}
}
Edit: Sorry for my bad formatting, this is my first time posting on Stack Overflow.
You are making multiple mistakes here.
First of all, you don't need a reader to read xml content into an XmlDocument.
Second, while trying to get the attributes, you are trying to proceed to the attributes using the reader which is obviously not having the context of the selected nodes.
Here is your updated XmlManager, but I have to note, there is a logical error too, which is, when the GetAttribute is invoked, you are searching all the Settings/Settings nodes and if find the attribute in any of them, return it. If the xml file contains only one Settings/Settings node, SelectSingleNode is better. I assume the following format:
<Settings>
<Settings attr1="attr1val" attr2="attr2val" />
</Settings>
Note: I also removed CloseFile method because it is no longer required.
public class XMLManager
{
private XmlDocument document;
private XmlNodeList nodeList;
public void OpenFile(string file)
{
document = new XmlDocument();
document.Load(file);
nodeList = document.SelectNodes(#"Settings/Settings");
}
public string Get(string attrib)
{
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Attributes[attrib] != null)
{
return nodeList[i].Attributes[attrib].Value;
}
}
return null;
}
}
Overall, you're doing way too much work.
If you have XML in a file, load it directly into an XML (DOM) object using XmlDocument.Load( strFileName );
To iterate all nodes matching an XPath query, see how I run through them.
try {
string strFileName = HttpContext.Current.Server.MapPath("\\data.xml");
XmlDocument xml = new XmlDocument();
xml.Load( strFileName );
foreach (XmlElement ndRow in xml.SelectNodes("//row")) {
string strTemp = ndRow.GetAttribute("foo");
}
} catch (Exception ex) {
Response.Write(ex.Message);
}
There is an error in XML document (8, 20). Inner 1: Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it.
OK, I understand this error.
How I get it, however, is what perplexes me.
I create the document with Microsoft's Serialize tool. Then, I turn around and attempt to read it back, again, using Microsoft's Deserialize tool.
I am not in control of writing the XML file in the correct format - that I can see.
Here is the single routine I use to read and write.
private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }
public StoredMsgs Operation(string from, string message, FileAccess access) {
StoredMsgs list = null;
lock (objLock) {
ErrorMessage = null;
try {
if (!File.Exists(xmlPath)) {
var root = new XmlRootAttribute(rootName);
var serializer = new XmlSerializer(typeof(StoredMsgs), root);
if (String.IsNullOrEmpty(message)) {
from = "Code Window";
message = "Created File";
}
var item = new StoredMsg() {
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
using (var stream = File.Create(xmlPath)) {
list = new StoredMsgs();
list.Add(item);
serializer.Serialize(stream, list);
}
} else {
var root = new XmlRootAttribute("MessageHistory");
var serializer = new XmlSerializer(typeof(StoredMsgs), root);
var item = new StoredMsg() {
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
using (var stream = File.Open(xmlPath, FileMode.Open, FileAccess.ReadWrite)) {
list = (StoredMsgs)serializer.Deserialize(stream);
if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write)) {
list.Add(item);
serializer.Serialize(stream, list);
}
}
}
} catch (Exception error) {
var sb = new StringBuilder();
int index = 0;
sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
var err = error.InnerException;
while (err != null) {
index++;
sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
err = err.InnerException;
}
ErrorMessage = sb.ToString();
}
}
return list;
}
Is something wrong with my routine? If Microsoft write the file, it seems to me that it should be able to read it back.
It should be generic enough for anyone to use.
Here is my StoredMsg class:
[Serializable()]
[XmlType("StoredMessage")]
public class StoredMessage {
public StoredMessage() {
}
[XmlElement("From")]
public string From { get; set; }
[XmlElement("Date")]
public string Date { get; set; }
[XmlElement("Message")]
public string Message { get; set; }
}
[Serializable()]
[XmlRoot("MessageHistory")]
public class MessageHistory : List<StoredMessage> {
}
The file it generates doesn't look to me like it has any issues.
I saw the solution here:
Error: The XML declaration must be the first node in the document
But, in that case, it seems someone already had an XML document they wanted to read. They just had to fix it.
I have an XML document created my Microsoft, so it should be read back in by Microsoft.
The problem is that you are adding to the file. You deserialize, then re-serialize to the same stream without rewinding and resizing to zero. This gives you multiple root elements:
<?xml version="1.0"?>
<StoredMessage>
</StoredMessage
<?xml version="1.0"?>
<StoredMessage>
</StoredMessage
Multiple root elements, and multiple XML declarations, are invalid according to the XML standard, thus the .NET XML parser throws an exception in this situation by default.
For possible solutions, see XML Error: There are multiple root elements, which suggests you either:
Enclose your list of StoredMessage elements in some synthetic outer element, e.g. StoredMessageList.
This would require you to load the list of messages from the file, add the new message, and then truncate the file and re-serialize the entire list when adding a single item. Thus the performance may be worse than in your current approach, but the XML will be valid.
When deserializing a file containing concatenated root elements, create an XML writer using XmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment and iteratively walk through the concatenated root node(s) and deserialize each one individually as shown, e.g., here. Using ConformanceLevel.Fragment allows the reader to parse streams with multiple root elements (although multiple XML declarations will still cause an error to be thrown).
Later, when adding a new element to the end of the file using XmlSerializer, seek to the end of the file and serialize using an XML writer returned from XmlWriter.Create(TextWriter, XmlWriterSettings)
with XmlWriterSettings.OmitXmlDeclaration = true. This prevents output of multiple XML declarations as explained here.
For option #2, your Operation would look something like the following:
private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }
const string rootName = "MessageHistory";
static readonly XmlSerializer serializer = new XmlSerializer(typeof(StoredMessage), new XmlRootAttribute(rootName));
public MessageHistory Operation(string from, string message, FileAccess access)
{
var list = new MessageHistory();
lock (objLock)
{
ErrorMessage = null;
try
{
using (var file = File.Open(xmlPath, FileMode.OpenOrCreate))
{
list.AddRange(XmlSerializerHelper.ReadObjects<StoredMessage>(file, false, serializer));
if (list.Count == 0 && String.IsNullOrEmpty(message))
{
from = "Code Window";
message = "Created File";
}
var item = new StoredMessage()
{
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write))
{
file.Seek(0, SeekOrigin.End);
var writerSettings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true, // Optional; remove if compact XML is desired.
};
using (var textWriter = new StreamWriter(file))
{
if (list.Count > 0)
textWriter.WriteLine();
using (var xmlWriter = XmlWriter.Create(textWriter, writerSettings))
{
serializer.Serialize(xmlWriter, item);
}
}
}
list.Add(item);
}
}
catch (Exception error)
{
var sb = new StringBuilder();
int index = 0;
sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
var err = error.InnerException;
while (err != null)
{
index++;
sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
err = err.InnerException;
}
ErrorMessage = sb.ToString();
}
}
return list;
}
Using the following extension method adapted from Read nodes of a xml file in C#:
public partial class XmlSerializerHelper
{
public static List<T> ReadObjects<T>(Stream stream, bool closeInput = true, XmlSerializer serializer = null)
{
var list = new List<T>();
serializer = serializer ?? new XmlSerializer(typeof(T));
var settings = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment,
CloseInput = closeInput,
};
using (var xmlTextReader = XmlReader.Create(stream, settings))
{
while (xmlTextReader.Read())
{ // Skip whitespace
if (xmlTextReader.NodeType == XmlNodeType.Element)
{
using (var subReader = xmlTextReader.ReadSubtree())
{
var logEvent = (T)serializer.Deserialize(subReader);
list.Add(logEvent);
}
}
}
}
return list;
}
}
Note that if you are going to create an XmlSerializer using a custom XmlRootAttribute, you must cache the serializer to avoid a memory leak.
Sample fiddle.