While writing a post on a side-project I noticed that my
code snippets were not always well presented. The challenge is to keep the code
clear and easy for the interested reader to copy. A glance back over some posts
shows a couple of approaches. Sometimes the code is just copied and pasted from
my IDE and therefore looks nicely formatted with colour coding and sometimes I
have gone the way of wrapping the code as HTML inside <pre> tags – which works
best when the code lines (or indentation) would otherwise result in line wraps
that obscure the meaning.
I did come across a useful tool here http://codeformatter.blogspot.co.uk/
which is great but the colour coding is lost and I thought about a Sunday
afternoon mini-project (sad, but the Rugby World Cup has not started yet and it
is raining) to take code directly from Visual studio or Android Studio or what
have you and present it within <pre> tags with colours intact. I expect
there are loads of tools out there that do this but…
First thing I did was look at some long ago VB.NET code I
wrote to create a sub-classed RichTextBox control that could output HTML. This
could certainly do the business but it did it laboriously and I did not fancy
the long task of converting that code to C#.
A search led me here https://code.msdn.microsoft.com/windowsdesktop/Converting-between-RTF-and-aaa02a6e#content
to a WPF project written by Matthew Manela that does the trick with some
succinct code – relying somewhat on the capabilities of the WPF version of the
RichTextBox. So I added references for PresentationCore and PresentationFramework
to my Windows Forms project and snaffled two of the classes – namely HtmlFromXamlConverter and RtfToHtmlConverter which
did the job.
The project is a simple form with a RichTextBox to copy the
code into, a button to trigger the translation and a regular multi-line TextBox
to receive the output. I added some pre-processing code to remove any
indentation common to all of the lines in a code snippet and just wrapped the
HTML output in <pre> and <code> tags with a bit of styling boldly
stolen and then modified from the code formatter at blogspot.
private const string preTag = "<pre style=\"border:1px dashed #CCCCCC;width:99%;height:auto;overflow:auto;background:#f0f0f0;background-image:URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOXt6mD1Fn2KnEXzUUf4viYqwWOhisanrHWF9gRJxS1Nh8k0v0Xh_WqwM-4CSdRxP1mO3-MFSVRCp4iW44mrmi3xpJgykKQvKmovZtt7X1KW8Z86wOUR54ANT1no5TAkwROZ6REg/s320/codebg.gif);padding:0px;margin:0px;text-align:left;font-size:110%;line-height:1;color:#000000;\"><code style = \"word-wrap:normal;\" >";
private const string preTail = "</code></pre>";
........ and then .......
private void preProcessRtf()
{
if(rtbInput.Lines.Count() > 0)
{
int blCount = 0;
rtbInput.Rtf = rtbInput.Rtf.Replace(new string('\t', 1), " "); // first convert any tabs to spaces
string firstLine = rtbInput.Lines[0];
// count the number of spaces at the start of the first line
while (firstLine.Substring(blCount, 1) == " ")
{
blCount++;
}
if(blCount > 0)
{
string tstString = "\n" + new string(' ', blCount);
rtbInput.Rtf = rtbInput.Rtf.Replace(tstString, "\n");
rtbInput.SelectionStart = 0;
rtbInput.SelectionLength = blCount;
rtbInput.Cut(); // to remove the first line leading spaces
}
tbOutput.Text = preTag + tidyHtml(RtfToHtmlConverter.ConvertRtfToHtml(rtbInput.Rtf)) + preTail;
}
}
And that would have
been that except for the fact that the Google Blogger web site changes your
HTML during the publish process – and of course adds some styles.
So it made sense to
post-process the result to tidy up the HTML.
private string tidyHtml(string rawHtml)
{
StringBuilder sb = new StringBuilder(rawHtml);
int sPos = sb.IndexOf("<p");
int ePos;
while (sPos > -1)
{
ePos = sb.IndexOf(">", sPos);
if(ePos > sPos)
{
sb.Remove(sPos, ++ePos - sPos);
}
sPos = sb.IndexOf("<p", sPos);
}
sb.Replace("</p>", "\r");
sb.Replace("background-color:#ffffff", "");
sPos = sb.IndexOf("<span style=\"\">");
while (sPos > -1)
{
sb.Remove(sPos, 15);
sPos = sb.IndexOf("</span>", sPos);
if (sPos > -1)
{
sb.Remove(sPos, 7);
sPos = sb.IndexOf("<span style=\"\">", sPos);
}
}
sb.Replace(" ", " ");
return sb.ToString();
}
Afterwards I added some event
one liners to make copying and pasting a tad more intuitive. Yes, there are
probably a dozen edge cases where this will product imperfect results but this
is not code to run a business on – just a utility written in thirty odd lines
of C#. In any case, it is time for a pre-dinner G&T.
Let us hope it improves my
presentation in future.
If anyone wants a copy (executable
or source as VS2015 project) to play with – just let me know.
Clearly the HTML tidy up routine could wreck some actual HTML embedded in the code so I added a simplified routine just to handle that.
private void processHTML()
{
if (rtbInput.Lines.Count() > 0)
{
int blCount = 0;
rtbInput.Rtf = rtbInput.Rtf.Replace(new string('\t', 1), " ");
rtbInput.Rtf = rtbInput.Rtf.Replace("&", "&");
rtbInput.Rtf = rtbInput.Rtf.Replace("\"", """);
rtbInput.Rtf = rtbInput.Rtf.Replace("'", "'");
rtbInput.Rtf = rtbInput.Rtf.Replace("<", "<");
rtbInput.Rtf = rtbInput.Rtf.Replace(">", ">");
string firstLine = rtbInput.Lines[0];
while (firstLine.Substring(blCount, 1) == " ")
{
blCount++;
}
if (blCount > 0)
{
string tstString = new string(' ', blCount);
List<string> sl = new List<string>();
for (int tl=0; tl < rtbInput.Lines.Count(); tl++)
{
sl.Add(rtbInput.Lines[tl]);
if (rtbInput.Lines[tl].Length >= blCount)
{
if(rtbInput.Lines[tl].Substring(0, blCount) == tstString)
{
sl[tl] = rtbInput.Lines[tl].Substring(blCount);
}
}
}
rtbInput.Lines = sl.ToArray();
}
rtbInput.Refresh();
tbOutput.Text = preTag + rtbInput.Text + preTail;
}
}
Which would produce HTML results like:
<table style="width: 100%; border: solid 1px #ccc;">
<thead><tr><th colspan="3" style="text-align: center;">High Water</th></tr><tr><th colspan="3" style="text-align: center;">POR
<asp:TextBox ID="tbPOR" runat="server" Width="90px" Text="Dover" ReadOnly="True"></asp:TextBox>
</th></tr></thead>
<tbody>
<tr><td valign="middle">Springs</td><td>
<asp:RadioButton ID="rbSPlus" runat="server"
ToolTip="Adjust HW Springs time" Text="+" Checked="True" GroupName="rbSpring" CssClass="rbBut" />
<asp:RadioButton ID="rbSMinus" runat="server"
ToolTip="Adjust HW Springs time" Text="-" GroupName="rbSpring" CssClass="rbBut" />
</td><td valign="middle">
<asp:TextBox ID="tbHWs" runat="server" CssClass="smlTb timepicker">00:00</asp:TextBox>
</td></tr>
<tr><td valign="middle">Neaps</td><td>
<asp:RadioButton ID="rbNPlus" runat="server"
ToolTip="Adjust HW Neaps time" Text="+" Checked="True" GroupName="rbNeap" CssClass="rbBut" />
<asp:RadioButton ID="rbNMinus" runat="server"
ToolTip="Adjust HW Neaps time" Text="-" GroupName="rbNeap" CssClass="rbBut" />
</td><td valign="middle">
<asp:TextBox ID="tbHWn" runat="server" CssClass="smlTb timepicker">00:00</asp:TextBox>
</td></tr>
</tbody>
</table>
And would would process itself as:
private void processHTML()
{
if (rtbInput.Lines.Count() > 0)
{
int blCount = 0;
rtbInput.Rtf = rtbInput.Rtf.Replace(new string('\t', 1), " ");
rtbInput.Rtf = rtbInput.Rtf.Replace("&", "&");
rtbInput.Rtf = rtbInput.Rtf.Replace("\"", """);
rtbInput.Rtf = rtbInput.Rtf.Replace("'", "'");
rtbInput.Rtf = rtbInput.Rtf.Replace("<", "<");
rtbInput.Rtf = rtbInput.Rtf.Replace(">", ">");
string firstLine = rtbInput.Lines[0];
while (firstLine.Substring(blCount, 1) == " ")
{
blCount++;
}
if (blCount > 0)
{
string tstString = new string(' ', blCount);
List<string> sl = new List<string>();
for (int tl=0; tl < rtbInput.Lines.Count(); tl++)
{
sl.Add(rtbInput.Lines[tl]);
if (rtbInput.Lines[tl].Length >= blCount)
{
if(rtbInput.Lines[tl].Substring(0, blCount) == tstString)
{
sl[tl] = rtbInput.Lines[tl].Substring(blCount);
}
}
}
rtbInput.Lines = sl.ToArray();
}
rtbInput.Refresh();
tbOutput.Text = preTag + rtbInput.Text + preTail;
}
}