How to add a line under a xml element using c# - c#

<?xml version="1.0" encoding="utf-8"?>
<configSections>
----------
----------
</configSections>
<appSettings>
<add key="Name" Value="XXX">
<add key="Age" Value="10">
<!--<add key="Number" value="5"/>--><!--uncomment it-->
<!--<add key="Class" value="10"/>-->
</appSettings>
I want to uncomment the first commented line.
That's my code so far:
foreach (XmlElement xElement in xmlDoc.DocumentElement)
{
if (xElement.Name == "appSettings")
{
foreach (XmlNode xNodes in xElement.ChildNodes)
{
if (xNodes.NodeType == XmlNodeType.Comment)
{
if (xNodes.InnerText.Contains("Number"))
{
// Now the commented line is in xNodes.InnerText.
// How can i add this line in that xml file under appSettings?
}
}
}
break;
}
}

You can use this code:
var appSettingsNode = xmlDoc.DocumentElement
.ChildNodes
.Cast<XmlNode>()
.FirstOrDefault(x => x.Name == "appSettings");
if(appSettingsNode == null)
return;
var commentedNodes = appSettingsNode.ChildNodes
.Cast<XmlNode>()
.Where(x => x.NodeType == XmlNodeType.Comment
&& (x.InnerText.Contains("Number")
|| x.InnerText.Contains("Class")))
.ToList();
foreach(var commentedNode in commentedNodes)
{
var tmpDoc = new XmlDocument();
tmpDoc.LoadXml(commentedNode.InnerText);
appSettingsNode.ReplaceChild(xmlDoc.ImportNode(tmpDoc.DocumentElement, true),
commentedNode);
// Use this instead if you want to keep the commented line:
// appSettingsNode.AppendChild(xmlDoc.ImportNode(tmpDoc.DocumentElement, true));
}
The important part is the one inside the foreach loop.
Here we load the commented node into a new XmlDocument (tmpDoc) to get it as a "real" XmlNode back. Then we simply replace the comment node (commentedNode) with our newly loaded node (tmpDoc.DocumentElement).
The rest is your original code, just beautified. Please note how I reduced the cyclomatic complexity by replacing the loops and ifs with LINQ queries.

If you want to read Name key this is how you do:
string myUsername = System.Configuration.ConfigurationManager.AppSettings["Name"];

In C# .net you would usually use System.Configuration.ConfigurationManager.AppSettings to read an appSetting from the config file.
If you are trying to read the commented out line from the web.config then you are going to have to write some custom code to do this.
This answer should give you a good head start

Related

XDocument, it says that a node is text but it is an element

I am reading an XML that contains a tag like this:
<source><bpt id="1"><donottranslate></bpt><ph id="2">($ T_353_1 Parent ID $)</ph><ept id="1"></donottranslate></ept></source>
When reading source node I get that this node type is Text, but it should be Element.
This is an XML that I am receiving and I cannot change it.
Do you know how can I get this sorted out?
This is my code:
XDocument doc = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
foreach (var elUnit in doc.Descendants("trans-unit"))
{
if (elUnit.AttributeString("translate").ToString() == "no")
{
foreach (var elSource in elUnit.Elements("source"))
{
string text = "";
foreach (var node in elSource.DescendantNodes().Where(n => XmlNodeType.Text == n.NodeType).ToList())
{
//When reading that "source" node, it enters inside this code
Thanks
First check whether your XML is wellformed
http://www.w3schools.com/xml/xml_validator.asp
http://chris.photobooks.com/xml/default.htm
I could get this to work
//using System.Xml.Linq;
var str = "<source><bpt id=\"1\"><donottranslate></bpt>" +
"<ph id=\"2\">($ T_353_1 Parent ID $)</ph>" +
"<ept id=\"1\"></donottranslate></ept></source>";
XElement element = XElement.Parse(str);
Console.WriteLine(element);
The output is this
<source>
<bpt id="1"><donottranslate></bpt>
<ph id="2">($ T_353_1 Parent ID $)</ph>
<ept id="1"></donottranslate></ept>
</source>
Please provide some code sample for more help if this example if not suffient.
Finally, I solved this checking if the node is correct or not:
if (System.Security.SecurityElement.IsValidText(text.XmlDecodeEntities()))

Deleting an element from XML document and saving over old file

*EDIT: ok, so I got the program to delete, the line foreach (var elem in doc.Document.Descendants("Profiles")) needed "Profile" instead. BUT now in my XML doc there is an empty Profile Element for each one deleted, so If all the items in the xml example(at bottom of question) get deleted I'm left with this:*
<?xml version="1.0" encoding="utf-8"?>
<Profiles>
<Profile />
<Profile />
</Profiles>
=========================ORIGINAL QUESTION BELOW=================================
I'm using the following code to remove a element and it's children from an XML file, BUT it isn't removing them from the file upon saving. Could someone let me know why this is incorrect?
public void DeleteProfile()
{
var doc = XDocument.Load(ProfileFile);
foreach (var elem in doc.Document.Descendants("Profiles"))
{
foreach (var attr in elem.Attributes("Name"))
{
if (attr.Value.Equals(this.Name))
elem.RemoveAll();
}
}
doc.Save(ProfileFile,
MessageBox.Show("Deleted Successfully");
}
EDIT: an example of the XML format below
<?xml version="1.0" encoding="utf-8"?>
<Profiles>
<Profile Name="MyTool">
<ToolName>PC00121</ToolName>
<SaveLocation>C:\Users\13\Desktop\TestFolder1</SaveLocation>
<Collections>True.True.True</Collections>
</Profile>
<Profile Name="TestProfile">
<ToolName>PC10222</ToolName>
<SaveLocation>C:\Users\14\Desktop\TestFolder2</SaveLocation>
<Collections>True.False.False</Collections>
</Profile>
</Profiles>
I assume you want to remove a profile given its name:
private static void RemoveProfile(string profileFile, string profileName)
{
XDocument xDocument = XDocument.Load(profileFile);
foreach (var profileElement in xDocument.Descendants("Profile") // Iterates through the collection of "Profile" elements
.ToList()) // Copies the list (it's needed because we modify it in the foreach (when the element is removed)
{
if (profileElement.Attribute("Name").Value == profileName) // Checks the name of the profile
{
profileElement.Remove(); // Removes the element
}
}
xDocument.Save(profileFile);
}
If you have only an empty element is because you use RemoveAll() (removes the descendants and attributes of an element) instead of Remove() (removes the element it self from its parent).
You can even remove the if by replacing it by a where in the LINQ query:
foreach (var profileElement in (from profileElement in xDocument.Descendants("Profile") // Iterates through the collection of "Profile" elements
where profileElement.Attribute("Name").Value == profileName // Checks the name of the profile
select profileElement).ToList()) // Copies the list (it's needed because we modify it in the foreach (when the element is removed)
profileElement.Remove(); // Removes the element
xDocument.Save(profileFile);
....
foreach (var elem in doc.Document.Descendants("Profiles"))
{
foreach (var attr in elem.Attributes("Name"))
{
if (attr.Value.Equals(this.Name))
TempElem = elem;
}
}
TempElem.Remove();
...
I'm dumb lol, this solves everything

xpath to select elements with last child value

This is my xml file
<profiles>
<profile id='8404'>
<name>john</name>
<name>mark</name>
</profile>
<profile id='8405'>
<name>john</name>
</profile>
</profiles>
and I want to select profiles where last "name" child value= john, the result should contains the profile with id=8405 only
what it the xpath that can evaluate this?
here is my trial:
var filterdFile = profilefileXML.XPathSelectElements("/profiles/profile[name[last()]='john']");
but it doesn't make sense.
Updated:
My trail is correct, there was only a syntax error in it. Thanks all
You can apply multiple indexing operations with successive [...]:
var doc = XDocument.Parse(xml); //the xml from your question
var node = doc.XPathSelectElement("/profiles/profile[name='john'][last()]");
Console.WriteLine(node.Attribute("id").Value); //outputs 8405
This will return the last profile element that contains the element name with a value of john.
If you on the other hand want to return all elements which last name element has a value of john, your XPath should work already:
var nodes = doc.XPathSelectElements("/profiles/profile[name[last()]='john']");
foreach (var node in nodes)
{
Console.WriteLine(node.Attribute("id").Value);
}
You can also try LINQ
XDocument xDoc = XDocument.Load("data.xml");
var matches = xDoc.Descendants("profile")
.Where(profile => XElement.Parse(profile.LastNode.ToString()).Value == "john");
And you can access the xml data with a foreach
foreach(XElement xEle in lastEle)
{
var xAttribute = xEle.Attribute("id");
if (xAttribute != null)
{
var id = xAttribute.Value;
}
var lastName = XElement.Parse(xEle.LastNode.ToString()).Value;
}

remove sections of XML document with Linq

How would i using Linq remove all section where their element contains parameter with {} ? In my example i want to remove section with {SecName1}
Source document:
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>Test Layout1</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
<ReceiptLayout>
<Name>{SecName1}</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
</ReceiptLayoutMaintenanceRequest>
Wanted output
<ReceiptLayoutMaintenanceRequest>
<ReceiptLayoutName>Test Layout1</ReceiptLayoutName>
<ActionName>Add</ActionName>
<ReceiptLayoutForMaintenance>
<Name>AAA</Name>
<Description>ReciptDesc</Description>
<PrinterName>Emulator - Receipt</PrinterName>
<ReceiptLayout>
<Name>AAA</Name>
<Description>$</Description>
<TemplateName>DefaultTemplate</TemplateName>
<LayoutParameters />
</ReceiptLayout>
</ReceiptLayoutForMaintenance>
thanks
This removes any ReceiptLayout node which has a child Name that starts and ends with brackets and produces your desired output:
XDocument doc = XDocument.Load(#"test.xml"); //load xml
var nodesToRemove = doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.ToList();
foreach (var node in nodesToRemove)
node.Remove();
This can be shortened into one Linq statement, personally I prefer to keep Linq query and modification (removal) separate though:
doc.Descendants("ReceiptLayout")
.Where(x => x.Element("Name").Value.StartsWith("{")
&& x.Element("Name").Value.EndsWith("}"))
.Remove();
var doc = XDocument.Parse(xml);
doc.Descendants()
.Where(n => !n.HasElements && Regex.IsMatch(n.Value, "[{].*?[}]"))
.Select(n=>n.Parent) // because you want to remove the section not the node
.Remove();
xml = doc.ToString();
A variant of BrokenGlass code using the let keyword
var doc = XDocument.Load(#"test.xml");
var list = from p in doc.Descendants("ReceiptLayout")
let q = p.Element("Name")
let r = q != null ? q.Value : string.Empty
where r.StartsWith("{") && r.EndsWith("}")
select p;
list.Remove();
This is "premature optimization" :-) I "cache" the p.Element("Name").Value. Ah... And I check if really there is a Name Element so that everything doesn't crash if there isn't one :-)

Generic method for reading config sections

Am trying to implement a generic way for reading sections from a config file. The config file may contain 'standard' sections or 'custom' sections as below.
<configuration>
<configSections>
<section name="NoteSettings" type="System.Configuration.NameValueSectionHandler"/>
</configSections>
<appSettings>
<add key="AutoStart" value="true"/>
<add key="Font" value="Verdana"/>
</appSettings>
<NoteSettings>
<add key="Height" value="100"/>
<add key="Width" value="200"/>
</NoteSettings>
The method that I tried is as follows :
private string ReadAllSections()
{
StringBuilder configSettings = new StringBuilder();
Configuration configFile = ConfigurationManager.OpenExeConfiguration(Application.ExecutablePath);
foreach (ConfigurationSection section in configFile.Sections)
{
configSettings.Append(section.SectionInformation.Name);
configSettings.Append(Environment.NewLine);
if (section.GetType() == typeof(DefaultSection))
{
NameValueCollection sectionSettings = ConfigurationManager.GetSection(section.SectionInformation.Name) as NameValueCollection;
if (sectionSettings != null)
{
foreach (string key in sectionSettings)
{
configSettings.Append(key);
configSettings.Append(" : ");
configSettings.Append(sectionSettings[key]);
configSettings.Append(Environment.NewLine);
}
}
}
configSettings.Append(Environment.NewLine);
}
return configSettings.ToString();
}
Assuming that all custom sections will have only KEY-VALUE
Is such an implementation possible? And if yes, is there a 'cleaner' and more elegant solution than this one?
The above method also reads 'invisible' sections like mscorlib, system.diagnostics. Is this avoidable?
System.Data.Dataset returns a dataset which could not be cast to a NameValueCollection. How can this be handled?
Corrections/suggestions welcome.
Thanks.
Since configuration file is XML file, you can use XPath queries for this task:
Configuration configFile = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location);
XmlDocument document = new XmlDocument();
document.Load(configFile.FilePath);
foreach (XmlNode node in document.SelectNodes("//add"))
{
string key = node.SelectSingleNode("#key").Value;
string value = node.SelectSingleNode("#value").Value;
Console.WriteLine("{0} = {1}", key, value);
}
If you need to get all {key, value} pair then you need to define triplets of XPath queries:
1 - main query for selecting nodes with similar structure. 2, 3 - queries for extracting key and value nodes from nodes retrieved by first query. In your case it's enough to have common query for all nodes, but it's easy to maintain support for different custom sections.
Read your config into a XmlDocument then use XPath to find the elements your looking for?
Something like;
XmlDocument doc = new XmlDocument();
doc.Load(HttpContext.Current.Server.MapPath("~/web.config"));
XmlNodeList list = doc.SelectNodes("//configuration/appSettings");
foreach (XmlNode node in list[0].ChildNodes)
...
You can read a custom section as follows:
var sectionInformation = configuration.GetSection("mysection").SectionInformation;
var xml = sectionInformation.GetRawXml();
var doc = new XmlDocument();
doc.LoadXml(xml);
IConfigurationSectionHandler handler = (IConfigurationSectionHandler)Type.GetType(sectionInformation.Type).GetConstructor(new Type[0]).Invoke(new object[0]);
var result = handler.Create(null, null, doc.DocumentElement);
When you've specified the NameValueSectionHandler as the type attribute for a section and call to Configuration.GetSection(string), you will receive a DefaultSection instance as the return type.
string SectionInformation.SectionInformation.GetRawXml() is the key in this case to get into your data.
I answered another similar question with a valid way to do it using System.Configuration that you can reference to get all the details and a code snippet.
NameValueSectionHandler can i use this section type for writing back to the app

Categories

Resources