How to save XmlDocument with multiple indentation settings? - c#

I need to save one XmlDocument to file with proper indentation (Formatting.Indented) but some nodes with their children have to be in one line (Formatting.None).
How to achieve that since XmlTextWriter accept setting for a whole document?
Edit after #Ahmad Mageed's resposne:
I didn't know that XmlTextWriter settings can be modified during writing. That's good news.
Right now I'm saving xmlDocument (which is already filled with nodes, to be specific it is .xaml page) this way:
XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.UTF8);
writer.Formatting = Formatting.Indented;
xmlDocument.WriteTo(writer);
writer.Flush();
writer.Close();
It enables indentation in all nodes, of course. I need to disable indentation when dealing with all <Run> nodes.
In your example you write to XmlTextWriter "manually".
Is there an easy way to crawl through all xmlDocument nodes and write them to XmlTextWriter so I can detect <Run> nodes? Or do I have to write some kind of recursive method that will go to every child of current node?

What do you mean by "since XmlTextWriter accept setting for a whole document?" The XmlTextWriter's settings can be modified, unlike XmlWriter's once set. Similarly, how are you using XmlDocument? Please post some code to show what you've tried so that others have a better understanding of the issue.
If I understood correctly, you could modify the XmlTextWriter's formatting to affect the nodes you want to appear on one line. Once you're done you would reset the formatting back to be indented.
For example, something like this:
XmlTextWriter writer = new XmlTextWriter(...);
writer.Formatting = Formatting.Indented;
writer.Indentation = 1;
writer.IndentChar = '\t';
writer.WriteStartElement("root");
// people is some collection for the sake of an example
for (int index = 0; index < people.Count; index++)
{
writer.WriteStartElement("Person");
// some node condition to turn off formatting
if (index == 1 || index == 3)
{
writer.Formatting = Formatting.None;
}
// write out the node and its elements etc.
writer.WriteAttributeString("...", people[index].SomeProperty);
writer.WriteElementString("FirstName", people[index].FirstName);
writer.WriteEndElement();
// reset formatting to indented
writer.Formatting = Formatting.Indented;
}
writer.WriteEndElement();
writer.Flush();

Related

C# - Allow any order of elements in XSD file after generating it with XMLSchemaInference

I'm writing a code which is generating XSD file from XML data sample by using C# XMLSchemaInference class within the following code
XmlReader reader = XmlReader.Create(xml);
XmlSchemaSet schemaSet = new XmlSchemaSet();
XmlSchemaInference schema = new XmlSchemaInference();
schema.Occurrence = XmlSchemaInference.InferenceOption.Relaxed; //Setting minimum occurences to 0 (nullable elements)
schemaSet = schema.InferSchema(reader);
XmlWriter writer;
foreach (XmlSchema xmls in schemaSet.Schemas())
{
var dest = Path.ChangeExtension(Path.Combine(xsdDirectory, Path.GetFileName(xml)), "xsd");
writer = XmlWriter.Create(dest);
xmls.Write(writer);
writer.Close();
}
reader.Close();
However, the result XSD contains <<xs:sequence>> elements which wont allow XML elements to be in a different order. Is there a way of trustworthy replacing the <<xs:sequence>> for <<xs:all>> in places where it is mandatory? If there is an element which can occur more times it can be seen in that XML data sample. I'm surprised that I'm not able to find this option in the official tool XmlSchemaInference.

How to add data to an existing xml file in c#

I'm using this c# code to write data to xml file:
Employee[] employees = new Employee[2];
employees[0] = new Employee(1, "David", "Smith", 10000);
employees[1] = new Employee(12, "Cecil", "Walker", 120000);
using (XmlWriter writer = XmlWriter.Create("employees.xml"))
{
writer.WriteStartDocument();
writer.WriteStartElement("Employees");
foreach (Employee employee in employees)
{
writer.WriteStartElement("Employee");
writer.WriteElementString("ID", employee.Id.ToString());
writer.WriteElementString("FirstName", employee.FirstName);
writer.WriteElementString("LastName", employee.LastName);
writer.WriteElementString("Salary", employee.Salary.ToString());
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
Now suppose I restart my application and I want to add new data to the xml file without losing the existed data, using the same way will overwrite the data on my xml file, I tried to figure out how to do that and I searched for a similar example but I couldn't come to anything , any ideas ??
Perhaps you should look at some examples using datasets and xml:
http://www.codeproject.com/Articles/13854/Using-XML-as-Database-with-Dataset
or use System.Xml.Serialization.XmlSerializer, when you dont't have amount of records.
Example using XmlDocument
XmlDocument xd = new XmlDocument();
xd.Load("employees.xml");
XmlNode nl = xd.SelectSingleNode("//Employees");
XmlDocument xd2 = new XmlDocument();
xd2.LoadXml("<Employee><ID>20</ID><FirstName>Clair</FirstName><LastName>Doner</LastName><Salary>13000</Salary></Employee>");
XmlNode n = xd.ImportNode(xd2.FirstChild,true);
nl.AppendChild(n);
xd.Save(Console.Out);
Using an xml writer for small amounts of data is awkward. You would be better of using an XDocument that you either initialize from scratch for the first run, or read from an existing file in subsequent runs.
Using XDocument you can manipulate the XML with XElement and XAttribute instances and then write the entire thing out to a file when you want to persist it.

Xml within an Xml

I basically want to know how to insert a XmlDocument inside another XmlDocument.
The first XmlDocument will have the basic header and footer tags.
The second XmlDocument will be the body/data tag which must be inserted into the first XmlDocument.
string tableData = null;
using(StringWriter sw = new StringWriter())
{
rightsTable.WriteXml(sw);
tableData = sw.ToString();
}
XmlDocument xmlTable = new XmlDocument();
xmlTable.LoadXml(tableData);
StringBuilder build = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(build, new XmlWriterSettings { OmitXmlDeclaration = true }))
{
writer.WriteStartElement("dataheader");
//need to insert the xmlTable here somehow
writer.WriteEndElement();
}
Is there an easier solution to this?
Use importNode feature in your document parser.
You can use this code based on CreateCDataSection method
// Create an XmlCDataSection from your document
var cdata = xmlTable.CreateCDataSection("<test></test>");
XmlElement root = xmlTable.DocumentElement;
// Append the cdata section to your node
root.AppendChild(cdata);
Link : http://msdn.microsoft.com/fr-fr/library/system.xml.xmldocument.createcdatasection.aspx
I am not sure what you are really looking for but this can show how to merge two xml documents (using Linq2xml)
string xml1 =
#"<xml1>
<header>header1</header>
<footer>footer</footer>
</xml1>";
string xml2 =
#"<xml2>
<body>body</body>
<data>footer</data>
</xml2>";
var xdoc1 = XElement.Parse(xml1);
var xdoc2 = XElement.Parse(xml2);
xdoc1.Descendants().First(d => d.Name == "header").AddAfterSelf(xdoc2.Elements());
var newxml = xdoc1.ToString();
OUTPUT
<xml1>
<header>header1</header>
<body>body</body>
<data>footer</data>
<footer>footer</footer>
</xml1>
You will need to write the inner XML files in CDATA sections.
Use writer.WriteCData for such nodes, passing in the inner XML as text.
writer.WriteCData(xmlTable.OuterXml);
Another option (thanks DJQuimby) is to encode the XML to some XML compatible format (say base64) - note that the encoding used must be XML compatible and that some encoding schemes will increase the size of the encoded document (base64 adds ~30%).

How to append new item into serialized xml data without deserialize old data?

Normally, I use the following code to serialize object to XML file. Everyday, I have about 100-1000 new items to be added into this list in different period of time.
var xmlSerializer = new XmlSerializer(typeof(List<TestModel>));
xmlSerializer.Serialize(stream, list);
How to append new item into serialized xml data without deserialize old data?
Thanks,
You can serialize objecto to memory and append to existing file. Also take a look at MS article Efficient Techniques for Modifying Large XML Files which shows two techniques both applicable in your situation.
There is no way of doing what you want using only the XmlSerializer that I can think of, but with a little bit of extra work this is possible.
A simple approach to this would be to serialize the list for the first item(s) of the day - as your existing code does. When new data comes in, you can now open the saved xml using an XmlDocument and append the serialization of one single item at a time.
One thing to note is that if the resulting xml is extremely big, the XmlDocument may grow very large (and may be slow or even cause OutOfMemoryExceptions as Pavel Kyments notes in a comment), in this case you may want to investigate XmlReader and XmlWriter to append the xml serially. However the overall approach would remain the same (open->serialize your new item->append the generated xml->resave)
[EDIT - changed code sample to show chained XmlReader/XmlWriter, rather than XmlDocument approach]
Something along these lines:
public static void AppendToXml(
Stream xmlSource, // your existing xml - could be from a file, etc
Stream updatedXmlDestination, // your target xml, could be a different file
string rootElementName, // the root element name of your list, e.g. TestModels
TestModel itemToAppend) // the item to append
{
var writerSettings = new XmlWriterSettings {Indent = true, IndentChars = " " };
using (var reader = XmlReader.Create(xmlSource))
using (var writer = XmlWriter.Create(updatedXmlDestination, writerSettings))
{
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.XmlDeclaration:
break;
case XmlNodeType.Element:
writer.WriteStartElement(reader.Prefix, reader.LocalName, reader.NamespaceURI);
if (reader.HasAttributes)
{
while (reader.MoveToNextAttribute())
{
writer.WriteAttributeString(reader.Prefix, reader.LocalName, reader.NamespaceURI, reader.Value);
}
}
if (reader.IsEmptyElement)
writer.WriteEndElement();
break;
case XmlNodeType.EndElement:
if (reader.Name == rootElementName)
{
var serializer = new XmlSerializer(typeof(TestModel));
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(writer, itemToAppend, ns);
}
writer.WriteEndElement();
break;
case XmlNodeType.Text:
writer.WriteRaw(SecurityElement.Escape(reader.Value));
break;
case XmlNodeType.CDATA:
writer.WriteCData(reader.Value);
break;
}
}
}
}
Note: you may want to add support for other node types (omitted here for brevity), such as Whitespace, Comments, Processing Instructions, etc. These all follow the same pattern as CDATA above: put a case in, call the appropriate writer method.
With this updated approach - you never have more than a small amount in memory at any given time.
I don't think it is possible. You want to perform random access to a block of data that is serialized and deserialized, so it has to be accessed sequentially.
Maybe you can modify directly the XML document, which will be faster, but you'll be losing the facilities of using a serialized/deserialized object tree, which is much more easy to manipulate (add/remove objects, ...)

html to XSLT conversion using C#

I am trying to change a html page to a xslt page using C#,
for example if i have something like
#companyname#
i have to convert it into
<xsl:value-of select="test/companyname" />
I have a xsl file which has all these values. I dont want to replace the values here as they are to be further processed before replacing the original values.
The problem i am facing here is i have a trouble identifying(to replace the xml construct) if the value is in the attribute level of the tag or in the value level of the tag.
I am trying to use the regular expressions on it . Can someone help??
Html Agility Pack is the way to go. Don't forget to add the reference to it. This code illustrates one way of using HTML Agility Pack to create an XSLT which is what I think you want to do.
HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(#"<html>" +
"<a href='#compantnameURL1#'>#companyname1#</a>" +
"<a href='#compantnameURL2#'>#companyname2#</a>" +
"</html>");
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = (" ");
settings.Encoding = Encoding.UTF8;
using (XmlWriter writer = XmlWriter.Create(Console.Out, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement("xsl", "stylesheet", "http://www.w3.org/1999/XSL/Transform");
writer.WriteStartElement("template", "http://www.w3.org/1999/XSL/Transform");
writer.WriteAttributeString("match", "/");
writer.WriteElementString("apply-templates", "http://www.w3.org/1999/XSL/Transform", "");
writer.WriteEndElement();
writer.WriteStartElement("template", "http://www.w3.org/1999/XSL/Transform");
writer.WriteAttributeString("match", "test/");
foreach (HtmlNode link in doc.DocumentNode.SelectNodes("//a"))
{
HtmlAttribute att = link.Attributes["href"];
writer.WriteStartElement("a");
writer.WriteStartElement("attribute", "http://www.w3.org/1999/XSL/Transform");
writer.WriteStartElement("value-of", "http://www.w3.org/1999/XSL/Transform");
writer.WriteAttributeString("select", att.Value);
writer.WriteEndElement();
writer.WriteEndElement();
writer.WriteStartElement("value-of", "http://www.w3.org/1999/XSL/Transform");
writer.WriteAttributeString("select", link.InnerText);
writer.WriteEndElement();
writer.WriteEndElement();
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
I'm not aware of a component that will get you all to XSLT, but the HTML Agility Pack is wonderful for any sort of HTML manipulation. The parser will provide a complete object tree with attributes, tags, styles, etc clearly defined, and it's easily queryable with XSLT.
Also, for a good discussion of parsing HTML with regex, see the first answer on this post.

Categories

Resources