After reading this article, I have decided to update the following code (using XmlDocument
) with XmlReader
:
Rendering controls
public string Rendering(Control baseControl)
{
StringBuilder stringBuilder = new StringBuilder();
using (StringWriter stringWriter = new StringWriter(stringBuilder))
using (XhtmlTextWriter htmlWriter = new XhtmlTextWriter(stringWriter))
{
baseControl.RenderControl(htmlWriter);
return PretifyWithNewlines(stringBuilder.ToString());
}
}
Adding newline after each node
private string PretifyWithNewlines(string minifiedMarkup)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.XmlResolver = null;
try
{
xmlDocument.LoadXml("<base>" + minifiedMarkup + "</base>");
}
catch // when minifiedMarkup contains the whole HTML with DTD tag defined,
{ // it throws an exception with <base>
xmlDocument.LoadXml(minifiedMarkup);
}
return recursiveOperation(xmlDocument.ChildNodes)
.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine)
.Replace(Environment.NewLine + "<base>" + Environment.NewLine, "")
.Replace(Environment.NewLine + "</base>" + Environment.NewLine, "");
}
Recursively traverse each node and plant new element
private static string recursiveOperation(XmlNodeList xmlNodeList)
{
string result = "";
foreach (XmlNode currentNode in xmlNodeList)
{
XmlNode clonedNode = currentNode;
string interimMarkup = recursiveOperation(currentNode.ChildNodes);
try
{
clonedNode.InnerXml = interimMarkup;
}
finally
{
result += Environment.NewLine + clonedNode.OuterXml + Environment.NewLine;
}
}
return result;
}
Is there a room for optimizing the existing code?
How would I go about directly instantiating XmlTextReader
from Control
, StringWriter
or XhtmlTextWriter
object? Or do I really need to render it as a string
first then instantiate XmlTextReader
?
As per Jon Skeet's answer, here is the update. The idea is to implode a newline after each element:
Before prettify:
<div class="tag"><span>text<span class="another-span"></span></span></div><div>Text<img src="some/relative/URL/" />namely</div>
After prettify:
<div class="tag">
<span>
text
<span class="another-span"></span>
</span>
</div>
<div>
Text
<img src="some/relative/URL/" />
namely
</div>
Notice how span.another-span
keep collapsed while everything else (with child nodes) expanded. The indentation will be asserted by Visual Studio.
Is there a room for optimizing the existing code?
Absolutely. The first place I'd change has nothing to do with how you load the XML - it's string concatenation. I'd change your recursiveOperation
method to:
private static string RecursiveOperation(XmlNodeList xmlNodeList)
{
StringBuilder result = new StringBuilder();
foreach (XmlNode currentNode in xmlNodeList)
{
XmlNode clonedNode = currentNode;
// Remove try/finally block - if an exception is thrown your
// result will be lost anyway
string interimMarkup = RecursiveOperation(currentNode.ChildNodes);
clonedNode.InnerXml = interimMarkup;
result.Append(Environment.NewLine)
.Append(clonedNode.OuterXml)
.Append(Environment.NewLine);
}
return result.ToString();
}
It's possible you could optimize this further using a single StringBuilder
passed into RecursiveOperation
, but I haven't quite got to grips with your code sufficiently to say yet. (It's before the first coffee of the morning.)
In terms of the XML handling itself, you're currently doing a lot of reparsing by setting the OuterXml
node in each child (recursively). I suspect if I had a better grasp of what you were doing, it would be feasible to change the whole approach. Given that this is functionality that XmlReader
really doesn't have (it wouldn't make sense), it's not clear that you should be taking much notice of the other article at the moment.
How would I go about directly instantiating XmlTextReader from Control, StringWriter or XhtmlTextWriter object? Or do I really need to render it as a string first then instantiate XmlTextReader?
It's not clear what it would even mean to create an XmlTextReader
from any of those objects - they're not inherently a source of XML data. I think what you've got already looks reasonable to me.
If you're still concerned about the performance, you should avoid guesswork and use a profiler to measure where the time is being taken. You should set yourself a target first though, otherwise you won't know when you've finished optimizing.