Monday, August 31, 2015

A Side, Side Project


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.

Which got the code I had to write down to:
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("    ", "&nbsp;&nbsp;&nbsp;&nbsp;");     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.

Addendum:

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("&", "&amp;");         rtbInput.Rtf = rtbInput.Rtf.Replace("\"", "&quot;");         rtbInput.Rtf = rtbInput.Rtf.Replace("'", "&apos;");         rtbInput.Rtf = rtbInput.Rtf.Replace("<", "&lt;");         rtbInput.Rtf = rtbInput.Rtf.Replace(">", "&gt;");         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("&", "&amp;");
        rtbInput.Rtf = rtbInput.Rtf.Replace("\"", "&quot;");
        rtbInput.Rtf = rtbInput.Rtf.Replace("'", "&apos;");
        rtbInput.Rtf = rtbInput.Rtf.Replace("<", "&lt;");
        rtbInput.Rtf = rtbInput.Rtf.Replace(">", "&gt;");
        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;
        
    }

}

No comments: