Add XElement to XML file with formatting and indenting - c#

XML
Source XML
<!-- The comment -->
<Root xmlns="http://www.namespace.com">
<FirstElement>
</FirstElement>
<SecondElement>
</SecondElement>
</Root>
Desired XML
<!-- The comment -->
<Root xmlns="http://www.namespace.com">
<FirstElement>
</FirstElement>
<SecondElement>
</SecondElement>
<ThirdElement>
<FourthElement>thevalue</FourthElement>
</ThirdElement>
</Root>
Now my output XML is
<!-- The comment -->
<Root xmlns="http://www.namespace.com">
<FirstElement>
</FirstElement>
<SecondElement>
</SecondElement><ThirdElement><FourthElement>thevalue</FourthElement></ThirdElement>
</Root>
Note that I need to load the XML with LoadOptions.PreserveWhitespace as I need to preserve all whitespaces (desired by customer).
The desired output is to put 2 newlines after the last child element of the "root" and add with the proper indent
<ThirdElement>
<FourthElement>thevalue</FourthElement>
</ThirdElement>
Any ideas how to realize this?
Code
var xDoc = XDocument.Load(sourceXml, LoadOptions.PreserveWhitespace); //need to preserve all whitespaces
var mgr = new XmlNamespaceManager(new NameTable());
var ns = xDoc.Root.GetDefaultNamespace();
mgr.AddNamespace("ns", ns.NamespaceName);
if (xDoc.Root.HasElements)
{
xDoc.Root.Elements().Last().AddAfterSelf(new XElement(ns + "ThirdElement", new XElement(ns + "FourthElement", "thevalue")));
using (var xw = XmlWriter.Create(outputXml, new XmlWriterSettings() { OmitXmlDeclaration = true })) //omit xml declaration
xDoc.Save(xw);
}

Ideally, you should explain to your client that this really isn't important.
However, if your really need to mess around with whitespace, i'd note that XText is what you need. This is another XObject that represents text nodes and can be interspersed as part of your content. This is probably a much better approach than string manipulation.
For example:
doc.Root.Add(
new XText("\n\t"),
new XElement(ns + "ThirdElement",
new XText("\n\t\t"),
new XElement(ns + "FourthElement", "thevalue"),
new XText("\n\t")),
new XText("\n"));
See this demo.

My solution is to beautify just before saving by reparsing the document.
string content = XDocument.Parse(xDoc.ToString()).ToString();
File.WriteAllText(file, content, Encoding.UTF8);

Related

Specify namespace for new XElement

I'm trying to add new elements to an existing XDocument where the new XElement either specifies the namespace or is part of the default namespace.
I already have a XmlNamespaceManager and I can select elements using that.
XmlNamespaceManager nsMgr = new XmlNamespaceManager(new NameTable());
nsMgr.AddNamespace("default", "http://www.mismo.org/residential/2009/schemas");
nsMgr.AddNamespace("customNs", "http://custom.org");
Consider the following XML
<ROOT xmlns="http://www.mismo.org/residential/2009/schemas" xmlns:customNs="http://custom.org">
<ELM_1>
<ELM_1_SUB_1>Some value</ELM_1_SUB_1>
</ELM_1>
<customNs:ELM_2>
<customNs:ELM_2_SUB_1>Another value</customNs:ELM_2_SUB_1>
</customNs:ELM_2>
</ROOT>
I'd like to write some code that would result in this:
<ROOT xmlns="http://www.mismo.org/residential/2009/schemas" xmlns:customNs="http://custom.org">
<ELM_1>
<ELM_1_SUB_1>Some value</ELM_1_SUB_1>
<ELM_1_SUB_2>ADDED TO DEFAULT NAMESPACE</ELM_1_SUB_2>
</ELM_1>
<customNs:ELM_2>
<customNs:ELM_2_SUB_1>Another value</customNs:ELM_2_SUB_1>
<customNs:ELM_2_SUB_2>ADDED TO CUSTOM NAMESPACE</customNs:ELM_2_SUB_2>
</customNs:ELM_2>
</ROOT>
I have tried the following:
var elm1 = xDoc.XPathSelectElement("/default:ROOT/default:ELM_1", nsMgr);
elm1.Add(new XElement("default:ELM_1_SUB_2", "ADDED TO DEFAULT NAMESPACE"));
var elm2 = xDoc.XPathSelectElement("/default:ROOT/customNs:ELM_2", nsMgr);
elm2.Add(new XElement("customNs:ELM_2_SUB_2", "ADDED TO CUSTOM NAMESPACE"));
The above code does select the elements I want to add to with no issue but the Add throws an XmlException: The ':' character, hexadecimal value 0x3A, cannot be included in a name..
When I try without the namespace provided in the element name I don't get an error but it doesn't do what I want.
So this:
var elm1 = xDoc.XPathSelectElement("/default:ROOT/default:ELM_1", nsMgr);
elm1.Add(new XElement("ELM_1_SUB_2", "ADDED TO DEFAULT NAMESPACE"));
Results in this:
<ROOT xmlns="http://www.mismo.org/residential/2009/schemas" xmlns:customNs="http://custom.org">
<ELM_1>
<ELM_1_SUB_1>Some value</ELM_1_SUB_1>
<ELM_1_SUB_2 xmlns="">ADDED TO DEFAULT NAMESPACE</ELM_1_SUB_2>
</ELM_1>
<customNs:ELM_2>
<customNs:ELM_2_SUB_1>Another value</customNs:ELM_2_SUB_1>
</customNs:ELM_2>
</ROOT>
What am I missing to create the desired XML output?
Create XNamespace types with the desired values.
Use them when creating an XElement.
XNamespace defaultNs = "http://www.mismo.org/residential/2009/schemas";
XNamespace customNs = "http://custom.org";
elm1.Add(new XElement(defaultNs + "ELM_1_SUB_2", "ADDED TO DEFAULT NAMESPACE"));
elm2.Add(new XElement(customNs + "ELM_2_SUB_2", "ADDED TO CUSTOM NAMESPACE"));

Include XML CDATA in an element

UPDATE: Added more detail per request
I am trying to create an xml configuration file for my application. The file contains a list of criteria to search and replace in an html document. The problem is, I need to search for character strings like &nbsp. I do not want my code to read the decoded item, but the text itself.
Admitting to being very new to XML, I did make some attempts at meeting the requirements. I read a load of links here on Stackoverflow regarding CDATA and ATTRIBUTES and so on, but the examples here (and elsewhere) seem to focus on creating one single line in an xml file, not multiple.
Here is one of many attempts I have made to no avail:
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE item [
<!ELEMENT item (id, replacewith)>
<!ELEMENT id (#CDATA)>
<!ELEMENT replacewith (#CDATA)>
]>
]>
<item id=" " replacewith=" ">Non breaking space</item>
<item id="‑" replacewith="-">Non breaking hyphen</item>
This document gives me a number of errors, including:
In the DOCTYPE, I get errors like <!ELEMENT id (#CDATA)>. In the CDATA area, Visual Studio informs me it is expecting a ',' or '|'.
]> gives me an error of invalid token at the root of the document.
And of course, after the second <item entry, I get an error stating XML document cannot contain multiple root level elements.
How can I write an xml file that includes multiple items and allows me to store and retrieve the text within the element, rather than the interpreted characters?
If it helps any, I am using .Net, C#, and Visual Studio.
EDIT:
The purpose of this xml file is to provide my code with a list of things to search and replace in an html file. The xml file simply contains a list of what to search for and what to replace with.
Here is the file I have in place right now:
<?xml version="1.0" encoding="utf-8" ?>
<Items>
<item id="‑" replacewith="-">Non breaking hyphen</item>
<item id=" " replacewith=" ">Non breaking hyphen</item>
</Items>
Using the first as an example, I want to read the text &#8209 but instead when I read this, I get - because that is what the code represents.
Any help or pointers you can give would be helpful.
To elaborate on my comment: XML acts like HTML due to the reserved characters. An ampersand prefixes keywords or character codes to translate into a literal string when read in with any type of parser (browser, XML reader, etc).
The easiest way to escape the values to make sure they are read back in as the literal that you want is to put them in as if you were encoding it for web. For example, to create your XML document, I did this:
XmlDocument xmlDoc = new XmlDocument();
XmlElement xmlItem;
XmlAttribute xmlAttr;
XmlText xmlText;
// Declaration
XmlDeclaration xmlDec = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
XmlElement xmlRoot = xmlDoc.DocumentElement;
xmlDoc.InsertBefore(xmlDec, xmlRoot);
// Items
XmlElement xmlItems = xmlDoc.CreateElement(string.Empty, "Items", string.Empty);
xmlDoc.AppendChild(xmlItems);
// Item #1
xmlItem = xmlDoc.CreateElement(string.Empty, "item", string.Empty);
xmlAttr = xmlDoc.CreateAttribute(string.Empty, "id", string.Empty);
xmlAttr.Value = "‑";
xmlItem.Attributes.Append(xmlAttr);
xmlAttr = xmlDoc.CreateAttribute(string.Empty, "replacewith", string.Empty);
xmlAttr.Value = "-";
xmlItem.Attributes.Append(xmlAttr);
xmlText = xmlDoc.CreateTextNode("Non breaking hyphen");
xmlItem.AppendChild(xmlText);
xmlItems.AppendChild(xmlItem);
// Item #2
xmlItem = xmlDoc.CreateElement(string.Empty, "item", string.Empty);
xmlAttr = xmlDoc.CreateAttribute(string.Empty, "id", string.Empty);
xmlAttr.Value = " ";
xmlItem.Attributes.Append(xmlAttr);
xmlAttr = xmlDoc.CreateAttribute(string.Empty, "replacewith", string.Empty);
xmlAttr.Value = " ";
xmlItem.Attributes.Append(xmlAttr);
xmlText = xmlDoc.CreateTextNode("Non breaking hyphen");
xmlItem.AppendChild(xmlText);
xmlItems.AppendChild(xmlItem);
// For formatting
StringBuilder xmlBuilder = new StringBuilder();
XmlWriterSettings xmlSettings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ",
NewLineChars = "\r\n",
NewLineHandling = NewLineHandling.Replace
};
using (XmlWriter writer = XmlWriter.Create(xmlBuilder, xmlSettings))
{
xmlDoc.Save(writer);
}
xmlOutput.Text = xmlBuilder.ToString();
Notice that I put in your id values with what you are expecting. Now, look at how it gets encoded:
<?xml version="1.0" encoding="utf-16"?>
<Items>
<item id="&#8209;" replacewith="-">Non breaking hyphen</item>
<item id=" " replacewith="&nbsp;">Non breaking hyphen</item>
</Items>
The only difference between yours and this one is that the ampersand was encoded as & and the rest remained as a string literal. This is normal behavior for XML. When you read it back in, it will come back as the literal ‑ and .

How to write xsd:schema tag using XmlDocument

I'm trying to write a XML-document programatically.
I need to add <xsd:schema> tag to my document.
Currently I have:
var xmlDoc = new XmlDocument();
var root = xmlDoc.CreateElement("root");
xmlDoc.AppendChild(root);
var xsdSchemaElement = xmlDoc.CreateElement("schema");
xsdSchemaElement.Prefix = "xsd";
xsdSchemaElement.SetAttribute("id", "root");
root.AppendChild(xsdSchemaElement);
However, this renders to:
<root>
<schema id="root" />
</root>
How do I get the tag to be <xsd:schema>?
Already tried var xsdSchemaElement = xmlDoc.CreateElement("xsd:schema"); which simply ignores the xsd:.
Edit #1
Added method
private static XmlSchema GetTheSchema(XmlDocument xmlDoc)
{
var schema = new XmlSchema();
schema.TargetNamespace = "xsd";
return schema;
}
which is called like xmlDoc.Schemas.Add(GetTheSchema(xmlDoc)); but does not generate anything in my target XML.
Using LINQ-to-XML, you can nest XElements and XAttributess in a certain hierarchy to construct an XML document. As for namespace prefix, you can use XNamespace.
Notice that every namespace prefix, such as xsd in your case, has to be declared before it is used, something like xmlns:xsd = "http://www.w3.org/2001/XMLSchema".
XNamespace xsd = "http://www.w3.org/2001/XMLSchema";
var doc =
new XDocument(
//root element
new XElement("root",
//namespace prefix declaration
new XAttribute(XNamespace.Xmlns+"xsd", xsd.ToString()),
//child element xsd:schema
new XElement(xsd + "schema",
//attribute id
new XAttribute("id", "root"))));
Console.WriteLine(doc.ToString());
output :
<root xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:schema id="root" />
</root>

How to add xmlns attribute to the root element?

I have to write xml file like the fallowing
<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Status>Enabled</Status>
</VersioningConfiguration>
please any one help me to write like above.
LINQ to XML makes this trivial - you just specify the namespace for the element, and it will include the xmlns="..." automatically. You can give it an alias, but that's slightly harder. To produce the exact document you've shown, you just need:
XNamespace ns = "http://s3.amazonaws.com/doc/2006-03-01/";
var doc = new XDocument(
new XElement(ns + "VersioningConfiguration",
new XElement(ns + "Status", "Enabled")));
Console.WriteLine(doc);
LINQ to XML is by far the best XML API I've used, particularly in its handling of namespaces. Just say no to XmlDocument :)
XNamespace Name = "http://s3.amazonaws.com/doc/2006-03-01/";
XDocument doc=new XDocument();
XElement X1=new XElement(Name+"VersioningConfiguration","" );
XElement X2=new XElement(Name+"Status","Enabled");
X1.Add(X2);
doc.Add(X1);

Keeping Redundant Namespace Prefixes On XML Elements in C#

I'm trying to write an XML file that will be picked up and parsed by another service. In order for this to happen the XML must be formatted in a very specific way, namely:
<?xml version="1.0"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<Feedback:MfgUnitID></Feedback:MfgUnitID>
<Feedback:MachineId></Feedback:MachineId>
<Feedback:OperationCode></Feedback:OperationCode>
<Feedback:ItemSeqNum></Feedback:ItemSeqNum>
<Feedback:OperDispositionCd></Feedback:OperDispositionCd>
<Feedback:ItemId></Feedback:ItemId>
<Feedback:ParentItemId></Feedback:ParentItemId>
<Feedback:ItemEndSize>1821</Feedback:ItemEndSize>
<Feedback:ItemDispositionCd></Feedback:ItemDispositionCd>
<Feedback:OperStartDate></Feedback:OperStartDate>
<Feedback:OperEndDate></Feedback:OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
with data of course between the innermost elements. Here's the issue though, no matter what I do, I can't get any of the C# classes to keep the semicolons on the innermost nodes. As far as I know these need to stay, so is there a way in C# to force it to format the nodes this way? I've tried all of the create methods that I could find in the XMLDocument class. I can get the outer nodes formatted fine, but the inner ones just keep creating problems.
Edit, sorry here's the code that makes the inner nodes.
private void AppendFile(string filename, string[] headers, Dictionary<string, string> values)
{
XmlDocument doc = new XmlDocument();
doc.Load(filename);
XmlNode node = doc.GetElementsByTagName(headers[headers.Length - 2]).Item(0);
string[] hPieces = headers[headers.Length - 1].Split(':');
XmlElement appendee = doc.CreateElement(hPieces[0].Trim(), hPieces[1].Trim(), hPieces[0].Trim());
node.AppendChild(appendee);
foreach (KeyValuePair<string, string> pair in values)
{
string[] ePieces = pair.Key.Split(':');
//XmlElement element = doc.CreateElement(ePieces[0].Trim(), string.Empty, ePieces[1].Trim());
//XmlText text = doc.CreateTextNode(pair.Value);
XmlNode innerNode = doc.CreateNode(XmlNodeType.Element, ePieces[1].Trim(), ePieces[0].Trim());
node.InnerText = pair.Value;
// element.AppendChild(text);
appendee.AppendChild(innerNode);
}
doc.Save(filename);
}
The data for the inner nodes comes in as key value pairs in the dictionary. Where the keys contain the intended name.
Edit2: This is what the file output looks like
<?xml version="1.0" encoding="utf-8"?>
<Feedbacks:Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:XMLFeedback xmlns:Feedback="Feedback">
<MfgUnitID></MfgUnitID>
<MachineId></MachineId>
<OperationCode</OperationCode>
<ItemSeqNum></ItemSeqNum>
<OperDispositionCd></OperDispositionCd>
<ItemId></ItemId>
<ParentItemId></ParentItemId>
<ItemEndSize></ItemEndSize>
<ItemDispositionCd></ItemDispositionCd>
<OperStartDate></OperStartDate>
<OperEndDate></OperEndDate>
</Feedback:XMLFeedback>
</Feedbacks:Elements>
</Feedbacks:Feedbacks>
You can accompish this easily with XLinq:
using System.Xml.Linq;
XNamespace ns1 = "Feedbacks";
XNamespace ns2 = "Feedback";
var doc = new XElement("Feedbacks",
new XAttribute(XNamespace.Xmlns+"Feedbacks", ns1));
doc.Add(new XElement(ns1 + "Elements",
new XElement(ns2 + "Feedback",
new XAttribute(XNamespace.Xmlns+"Feedback", ns2),
new XElement(ns2 + "Unit"))));
Gives
<Feedbacks xmlns:Feedbacks="Feedbacks">
<Feedbacks:Elements>
<Feedback:Feedback xmlns:Feedback="Feedback">
<Feedback:Unit />
</Feedback:Feedback>
</Feedbacks:Elements>
</Feedbacks>
Although I believe that your own output should be valid XML, relying on the parent namespcae.

Categories

Resources