Read an existing xml file and apply changes - c#

I'm trying to learn how to read a XML file and do changes using C#.
Problem:
The XMLfile already exists. I would like to read the file and search for a specific element which has another element with an attribute:
<ELement>
<Element2 Attribute = "Value" />
</Element>
The problem is if this element "Element2" does not exist in the xml file I would like to create it under the same path.
Here is my actual code.
private void UpdateConfig(string configPath, string serverName)
{
string oldServername = null;
XElement config = XElement.Load(configPath);
CreateConfigBackup(configPath);
try
{
//which could be Null if the element does not exist in xml file.
oldServername = config.Element("WebGUI").Element("ServerIP").Attribute("Value").Value.ToString();
oldServername = oldServername.Split(':').FirstOrDefault();
// Config updaten
config.Element("WebGUI").Element("ServerIP").Attribute("Value").SetValue(serverName);
}
else
{
XElement ELM = new XElement("ServerIP");
ELM.SetAttributeValue("Value",serverName);
config.Element("WebGUI").Add(ELM);
}
}
catch (Exception ex)
{
if (oldServername == null)
{
MessageBox.Show(configPath+ "enthält nicht das Element Server in der Web-Konfigurationsdatei.");
}
}
SaveConfig(config, configPath);
}

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.

Delete an element with specific node name from XML file using XDocument and XmlDocument class

I am trying to delete an element with specific node name. Using the following code but receive an error like "Name cannot begin with the '2' character, hexadecimal value 0x32." As I understand this method is not correct for the relevant xml format.
How can I delete Table with specific User_Name info. should delete specific table When I try to delete Administrator User
RemoveElement("Accounts.xml", "User", "Test1");
private static void RemoveElement(string xmlFile, string elementName, string elementAttribute)
{
XDocument xDocument = XDocument.Load(xmlFile);
foreach (var profileElement in xDocument.Descendants("Table").ToList())
{
if (profileElement.Attribute(elementAttribute).Value == elementName)
{
profileElement.Remove();
}
}
xDocument.Save(xmlFile);
}
Here is the Xml file;
`<?xml version="1.0" encoding="utf-8"?>
<Accounts>
<Table>
<User>Administrator</User>
<Domain>Localhost</Domain>
<Password>Test</Password>
<Account_Type>Windows</Account_Type>
</Table>
<Table>
<User>Test1</User>
<Domain>demo</Domain>
<Password>empty</Password>
<Account_Type>Domain</Account_Type>
</Table>
</Accounts>`
Original code snippet doesn't work because name of user is not an attribute of user but a value. Also, you can replace .Descendants("Table") with .Descendants(elementName) to avoid unnecessary if statement.
I think, the most elegant way to achieve needed functionality is to use Linq to Xml:
XDocument xDocument = XDocument.Load(xmlFile);
xDocument.Descendants(elementName)
.Where(e => e.Value == elementAttribute)
.ToList()
.ForEach(e => e.Remove());
xDocument.Save(xmlFile);
As for your second question: I believe that you remove first element in this line
listViewMevcutKullaniciListesi.Items.RemoveAt(listViewMevcutKullaniciListesi.SelectedIndices[0]);
When you call listViewMevcutKullaniciListesi.SelectedIndices[0] for the second time you obviously get an Exception.
Also, listViewMevcutKullaniciListesi.SelectedIndices[0].ToString() don't give you selected item but just it's number.
Yeah Found it how you can delete when you get info from texBox
xDoc.Load("Accounts.xml");
foreach (XmlNode node in xDoc.SelectNodes("Accounts/Table"))
{
if (node.SelectSingleNode("User").InnerText == textBox1.Text)
{
node.ParentNode.RemoveChild(node);
}
}
xDoc.Save("Accounts.xml");
But I Want to get info from listview. I receive an error When I try to use following code.
Error : InvalidArgument=Value of '0' is not valid for 'index'.\r\nParameter name: index
listViewMevcutKullaniciListesi.Items.RemoveAt(listViewMevcutKullaniciListesi.SelectedIndices[0]);
xDoc.Load("Accounts.xml");
foreach (XmlNode node in xDoc.SelectNodes("Accounts/Table"))
{
if (node.SelectSingleNode("User").InnerText == listViewMevcutKullaniciListesi.SelectedIndices[0].ToString())
{
node.ParentNode.RemoveChild(node);
}
}
xDoc.Save("Accounts.xml");
Here is the answer with XmlDocument class;
You are calling the method like
`RemoveElementWithXmlDocument("Accounts.xml", "Accounts/Table", "User", listViewMevcutKullaniciListesi.SelectedItems[0].Text);`
method;
private static void RemoveElementWithXmlDocument(string xmlFile, string nodeName, string elementName, string elementAttribute)
{
xDoc.Load(xmlFile);
try
{
foreach (XmlNode node in xDoc.SelectNodes(nodeName))
{
if (node.SelectSingleNode(elementName).InnerText == elementAttribute)
{
node.ParentNode.RemoveChild(node);
}
}
xDoc.Save(xmlFile);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
And also I wanna use XDocument class for same structure But I receive an exception like "Object reference not set to an instance of an object" in foreach loop when the ran profileElement.Remove(); line. If I comment out to this line never get the exception but I need this line for removing relevant node from xml. So, As I understand I missing something in XDocument. need your help
RemoveElementWithXDocument("Accounts.xml", "Table", "User", listViewMevcutKullaniciListesi.SelectedItems[0].Text);
method for XDocument
private static void RemoveElementWithXDocument(string xmlFile, string nodeName, string elementName, string elementAttribute)
{
XDocument xDocument = XDocument.Load(xmlFile);
try
{
foreach (XElement profileElement in xDocument.Descendants(nodeName))
{
if (profileElement.Element(elementName).Value == elementAttribute)
{
profileElement.Remove();
}
}
xDocument.Save(xmlFile);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}

how to read & write xml file in C# not rely on the tag name?

Thank you very much for reading my question.
the bottom is the sample of my xml file.please refer that.
i did some xml files before, but by "CMarkXml". "IntoElement, OutofElement", is very clear.
but when C#...i was lost..
1: how to read & write my xml file without using the tag name. i see some articles about operation on xml file by c#, but all assumed that known the tag name.
2: if without tag name, it is very difficult or not recommend. then how to read & write my xml file by XmlDocument? (sorry, but no Ling please, i am very faint with that...).
3: my idear is, for the xml file, get out some section, we still could parse the section by xmldocument.
4: for the write/modify the xml file, of course, should contain delete some section, delete some "leaf", change the attributes...
Thank you very much for reading the long question, and any help i will very appreciate. If you have a good sample code but not continent paste them here, could you send it to "erlvde#gmail.com"?
<root>
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
....(other <bb>)
</b>
</root>
Read your xml into XmlDocument:
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml("XML HERE");
Access child nodes:
xmlDocument.ChildNodes[1]
But it's also true that it's very error prone
You can also check if you have child nodes at all:
xmlDocument.HasChildNodes
And get number of child nodes:
xmlDocument.ChildNodes.Count
It looks to me like your elements names contain identifiers. If that is the case, and you have control over the XML schema, I would highly recommend changing your XML to contain elements and/or attributes indicating your identifiers and then use the built in XmlSerializer class for serializing to and from XML. It has many modifiers available, such as XmlElement and XmlAttribute among many others, for formatting the output.
Here is a tutorial to get you started.
If possible, change your XML to something like following which would make it far simpler to manipulate...again if changing the schema is a possibility.
<root>
<a>i belong to a</a>
<b>
<bb id="1">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb id="2">
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
<bb>
</b>
</root>
Edit this edit reflects the changes you made to your XML
Here is a simple console application which will serialize an object to an XML file and then rehydrate it.
Expected XML
<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<a>i belong to a</a>
<b>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
<bb>
<bb>1</bb>
<bb>2</bb>
<bb>3</bb>
<bb>4</bb>
<bb>5</bb>
</bb>
</b>
</root>
Simple Console Application Demonstration
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var items = new root
{
a = "i belong to a",
b = new List<bb>
{
new bb
{
bbClassProperty = new List<int>
{
1,
2,
3,
4,
5
}
},
new bb
{
bbClassProperty= new List<int>
{
1,
2,
3,
4,
5
}
}
}
};
XmlSerializer serializer = new XmlSerializer(typeof(root));
using (var textWriter = new StreamWriter(#"C:\root.xml"))
{
serializer.Serialize(textWriter, items);
textWriter.Close();
}
using (var stream = new StreamReader(#"C:\root.xml"))
{
var yourObject = serializer.Deserialize(stream);
}
Console.Read();
}
}
#region [Classes]
public class root
{
public string a { get; set; }
public List<bb> b { get; set; }
}
public class bb
{
[XmlElement("bb")]
public List<int> bbClassProperty { get; set; }
}
#endregion
}
Look into the ChildNodes (and similar) properties and methods on your XmlElement object. These will let you iterate over the children of a node and you can then ask that node for its name.
If you have a XmlNode object, you can use XMLNode.FirstChild to get the child, if it has any. You can also use XMLNode.NextSibling to get the next Node of the same parent node.
Why can't you use the names of the nodes? It's the easiest and most common way. Especially if you use XPath or similar.
XPath is also the answer to your second question.
U can use the class XML reader, a simple example is given here.
using System;
using System.Xml;
class Program
{
static void Main()
{
// Create an XML reader for this file.
using (XmlReader reader = XmlReader.Create("perls.xml"))
{
while (reader.Read())
{
// Only detect start elements.
if (reader.IsStartElement())
{
// Get element name and switch on it.
switch (reader.Name)
{
case "perls":
// Detect this element.
Console.WriteLine("Start <perls> element.");
break;
case "article":
// Detect this article element.
Console.WriteLine("Start <article> element.");
// Search for the attribute name on this current node.
string attribute = reader["name"];
if (attribute != null)
{
Console.WriteLine(" Has attribute name: " + attribute);
}
// Next read will contain text.
if (reader.Read())
{
Console.WriteLine(" Text node: " + reader.Value.Trim());
}
break;
}
}
}
}
}
}
The input file text is:
<?xml version="1.0" encoding="utf-8" ?>
<perls>
<article name="backgroundworker">
Example text.
</article>
<article name="threadpool">
More text.
</article>
<article></article>
<article>Final text.</article>
</perls>
Output
Start element.
Start element.
Has attribute name: backgroundworker
Text node: Example text.
Start element.
Has attribute name: threadpool
Text node: More text.
Start element.
Text node:
Start element.
Text node: Final text.enter code here
You can use the following code to if the file does not contain the headers, in the example above.
XmlReaderSettings settings = new XmlReaderSettings();
settings.ConformanceLevel = ConformanceLevel.Fragment;
reader = XmlReader.Create(filePath, settings)
Would something like this help?
void Iterate(XmlNode parent) {
//do something with
//parent.Name
//parent.Value
//parent.Attributes
foreach(XmlNode child in parent.ChildNodes) {
Iterate(child);
}
}
XmlDocument document = new XmlDocument();
document.Load(filename);
XmlNode parent = document.DocumentElement;
Iterate(parent);
You could also store it like that (sorry for any syntactical error, didn't run it)
public class Document {
public Element DocumentElement { set; get; }
private void Load(string fileName) {
XmlDocument document = new XmlDocument();
document.Load(fileName);
DocumentElement = new Element(this, null);
DocumentElement.Load(document.DocumentElement);
}
}
public class Element {
public string Name { set; get; }
public string Value { set; get; }
//other attributes
private Document document = null;
private Element parent = null;
public Element Parent { get { return parent; } }
public List<Element> Children { set; get; }
private int order = 0;
public Element(Document document, Element parent) {
Name = "";
Value = "";
Children = new List<LayoutElement>();
this.document = document;
this.parent = parent;
order = parent != null ? parent.Children.Count + 1 : 1;
}
private Element GetSibling(bool left) {
if(parent == null) return null;
int add = left ? -1 : +1;
Element sibling = parent.Children.Find(child => child.order == order + add);
return sibling;
}
public Element GetLeftSibling() {
return GetSibling(true);
}
public Element GetRightSibling() {
return GetSibling(false);
}
public void Load(XmlNode node) {
Name = node.Name;
Value = node.Value;
//other attributes
foreach(XmlNode nodeChild in node.Children) {
Element child = new Element(document, this);
child.Load(nodeChild);
Children.Add(child);
}
}
}
Document document = new Document();
document.Load(fileName);
For changing/deleting right now you could iterate the tree and find elements by name, but since name is not unique, you would affect many elements at once. You could add an unique id in every tag like
<bb id="bb1"/>
Then read it in Load function like
id = ((XmlElement)node).GetAttribute("id");
and use this id to iterate through the tree. Sorry I don't have time right now to provide something more detailed.

Error loading contents from a XML file into a dropdownlist

private void BindCountry()
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("countries.xml"));
foreach (XmlNode node in doc.SelectNodes("//country"))
{
usrlocationddl.Items.Add(new ListItem(node.InnerText, node.Attributes["codes"].InnerText));
}
}
The above code am using for loading countries List from an xml file into a dropdown list. But while doing so am encountering a Null Reference error.
Object reference not set to an
instance of an object.
Contents of the xml file:
<countries>
<country code="AF" iso="4">Afghanistan</country>
<country code="AL" iso="8">Albania</country>
</countries>
Where in the code should I be changing, so that I can escape the error.
I suspect the problem is that you've got a country with no "codes" attribute. You could avoid that like this:
private void BindCountry()
{
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath("countries.xml"));
foreach (XmlNode node in doc.SelectNodes("//country"))
{
XmlAttribute attr = node.Attributes["codes"];
if (attr != null)
{
usrlocationddl.Items.Add(new ListItem(node.InnerText, attr.Value));
}
}
}
If that doesn't help, I suggest you write a simple console application to try to load the XML and write out each entry you'd select - that should make it easier to work out what's going wrong.

Categories