Simply: The HTML5 version

A little while ago, I created a simple, stripped down theme for blog/personal site use. Over time, there were some requests to streamline things a bit and recently I’ve been getting more and more requests to rewrite it in HTML5.

So here is that version in the 5th iteration by request :

Simply: The HTML5 version.

I’m not sure if this is “better” than the XHTML version other than I had the chance to make a few fixes and clean up some things. I still have a few doubts about how much HTML5 will improve the actual browsing experience in general. If anything, HTML 5 seems to be encouraging more and more code bloat in that web designers are squeezing in more and more client-side scripting with no regard to efficiency.

And in reply to the following :

Do you have like a border-radius fetish now? Looks like you’re putting it in practically every element in your newer stuff.
– Max.

Yes.
Yes I do.

Simply: A stripped down blog theme

I’ve had a few requests lately for a stripped down and simplified version of the Mod Portal, but didn’t get a chance to create one until recently. One request from Jacob asked for it to have “not as many features”… I guess that’s a nice way of asking for something that didn’t include the kitchen sink ;)

Mod Portal was originally written as part of a design tutorial almost three years ago and really did morph into a CMS template. Although it also includes a page variation for a blog (a post index), there were a lot of added stuff in the CSS and the html that wasn’t needed for a blog.

So, here’s a theme based on Mod Portal with none of the fluff. It’s designed to have the absolute bare essentials for a blog or personal site, contains fonts and sizes that are easy to read and very lightweight in CSS and XHTML. A lot of the base still came from Mod Portal, but this one doesn’t even have any JavaScript. This is by far the simplest theme I’ve done in a while.

Simply screenshot

“DePo Masthead” is the WORST Theme available on WordPress.com!

You think I’m kidding, but when was the last time you browsed a blog with this theme and think… “hmm. I can find exactly what I’m looking for!” Not ever!

Before I go on, I’d like to make it perfectly clear that I’ve got nothing against Derek Powazek, the author. I think he’s a very talented guy who’s made a terrible, terrible, mistake.

I’m by no means the greatest designer out there, but even I can tell when something was haphazard and actively user hostile. I mean, have you tried right clicking on any of the “next/previous” post links on a page? And JavaScript to see comments?!

Wanna know how bad this theme is? Even the author doesn’t use it on his own site. Why can’t we have Derek’s other (far superior) theme, DePo Skinny, instead?

And I’m making this post with great difficulty because there are regular blogs I read that have this theme, and I don’t have the heart to tell them that their content will be recieved 1000x better if it were actually readable. I.E. Stop using this theme!

I can’t even believe the WP folks would have this in the allowed themes list. It’s like having box cutters dispensed via vending machines. You just know someone will buy one.

And the real kicker : The description of DePo…
“A classy template that draws on classic magazine design design for a simple, bold style.”

“Classy”? I think not! I’ll give it simple, as in simply unnavigable, and bold, like Yoko Ono only more painful. And classic like the first ever locomotive. A monsterous curiosity you would never EVER want to ride in.

Fits none of the descriptions...

Fits none of the descriptions...

I want to “read” your blogs and not just skim through. I don’t want to stick to feed readers alone because I’ll miss out on things like blogrolls and other extras. I can’t do that when you won’t let me. Please think about your readers!

ASP.Net BBCode (C#)

Update

This code has now been superceeded by a better alternative that will allow you to use an off-the-shelf WYSIWYG and still allow custom tags.

This problem comes up if you find yourself creating a forum from scratch or implementing some sort of comment system and want to make sure you can introduce some formatting functionality without compromising security.

Well, there are plenty of regular expressions examples out there, but few deal directly with BBCode and of those, most don’t go beyond the basic Bold, Italic, and Strike formatting plus HTML links, images etc…

This example not only formats the above basic stuff, but also does quotes, alignment, Google search links, Wikipedia article links, as well embedded videos for several video sharing sites. You can always add more tags by following the same pattern. All the content is formatted into paragraphs (<p>) for proper validation. It checks for nested quotes up to a specified depth.

There is no extensive input cleanup to prevent XSS attacks through tags. I’m just showing the basics like tag stripping which can be circumvented by clever attakers, so it’s up to you to implement a more thorough system. The reason I’m excluding it is because there are already many, many, many examples out there that do a wonderful job at it.

This does set a limited set of formatting options.

To make everything easier to read in the code file, I used multiple Regex replacements instead of one super duper pattern. This also makes adding quick tags for something else much simpler.

A sample of the rendered markup :

  • [b]Bold[/b] = Bold
  • [i]Italic[/i] = Italic
  • [del]Strike[/de] = Strike
  • [color=blue]Blue[/blue] = Blue
  • [color=#FA9A99]Pinkish[/blue] = Pinkish
  • [size=2]Larger text[/size] (between 2 & 5) = Larger text
  • [google]once in a blue moon[/google] = once in a blue moon
  • [wikipedia]Captain Haddock[/wikipedia] = Captain Haddock (Remember, Wikipedia articles are case sensitive)
  • [img]http://www.google.com/logos/Logo_40blk.gif[/img] =
  • [img=Google Logo]http://www.google.com/logos/Logo_40blk.gif[/img] = Google Logo

One major difference than other BBCode functions is the ability to embed YouTube, Metacafe and LiveVideo media players. You only need to specify the URL within the tags.

E.G. For youtube :


For LiveVideo
[livevideo]http://www.livevideo.com/video/Megalis/E7C2EEE8A7C740379F40B5ECA56ACE8A/momma-sophia-s-dream.aspx[/livevideo]

I don’t think WordPress supports embedding Metacafe videos at the time of this post, but if you want to include those videos :
[metacafe]http://www.metacafe.com/watch/685732/the_diet/[/metacafe]

To find the exact URL of the LiveVideo page, click on “Get Codes” link underneath the video player.
I tried to keep this as simple as possible for users as they just need to wrap the url around [tag][/tag] markers.

You can embed quotes following a similar convention to phpBB. But there are slight differences as this was for a custom application.

[quote]This is a quote[/quote]

This is a quote

[quote=Author]This is a quote[/quote]

Author wrote


This is a quote

And so on…

This version also deals with Headers
([h#]Header[/h#] becomes <h#>Header</h#> from 1 to 6.)

[h3]Header[/h3]

[h4]Header[/h4]

And so on…

Of course, you would want to apply special formatting via CSS to keep the look of the page consistent with the rest of the site.

This particular excerpt was written for a .Net 3.5 app, but this portion should work on 2.0+ with no alterations since it doesn’t use anything unique to the newer framework.

This class is by no means meant to be a comprehensive bbcode plugin, but it should get you on the way to create your own custom tags.

Once again, this code has no usage restrictions. I’m just including a disclaimer like all other code samples I’ve posted here. You don’t have to ask me permission to use it for any purpose and I only ask that you abide by the disclaimer.

THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

The tag function

/// <summary>
/// Converts the input plain-text BBCode to HTML output and replacing carriage returns
/// and spaces with <br /> and   etc...
/// Recommended: Use this function only during storage and updates.
/// Keep a seperate field in your database for HTML formatted content and raw text.
/// An optional third, plain text field, with no formatting info will make full text searching
/// more accurate.
/// E.G. BodyText(with BBCode for textarea/WYSIWYG), BodyPlain(plain text for searching),
/// BodyHtml(formatted HTML for output pages)
/// </summary>
public static string ConvertToHtml(string content)
{
    // Clean your content here... E.G.:
    // content = CleanText(content);

    // Basic tag stripping for this example (PLEASE EXTEND THIS!)
    content = StripTags(content);

    content = MatchReplace(@"\[b\]([^\]]+)\[\/b\]", "<strong>$1</strong>", content);
    content = MatchReplace(@"\[i\]([^\]]+)\[\/i\]", "<em>$1</em>", content);
    content = MatchReplace(@"\[u\]([^\]]+)\[\/u\]", "<span style=""text-decoration:underline"">$1</span>", content);
    content = MatchReplace(@"\[del\]([^\]]+)\[\/del\]", "<span style=""text-decoration:line-through"">$1</span>", content);

    // Colors and sizes
    content = MatchReplace(@"\[color=(#[0-9a-fA-F]{6}|[a-z-]+)]([^\]]+)\[\/color\]", "<span style=""color:$1;"">$2</span>", content);
    content = MatchReplace(@"\[size=([2-5])]([^\]]+)\[\/size\]", "<span style=""font-size:$1em; font-weight:normal;"">$2</span>", content);

    // Text alignment
    content = MatchReplace(@"\[left\]([^\]]+)\[\/left\]", "<span style=""text-align:left"">$1</span>", content);
    content = MatchReplace(@"\[right\]([^\]]+)\[\/right\]", "<span style=""text-align:right"">$1</span>", content);
    content = MatchReplace(@"\[center\]([^\]]+)\[\/center\]", "<span style=""text-align:center"">$1</span>", content);
    content = MatchReplace(@"\[justify\]([^\]]+)\[\/justify\]", "<span style=""text-align:justify"">$1</span>", content);

    // HTML Links
    content = MatchReplace(@"\[url\]([^\]]+)\[\/url\]", "<a href=""$1"">$1</a>", content);
    content = MatchReplace(@"\[url=([^\]]+)]([^\]]+)\[\/ur\l]", "<a href=""$1"">$2</a>", content);

    // Images
    content = MatchReplace(@"\[img\]([^\]]+)\[\/img\]", "<img src=""$1"" alt="""" />", content);
    content = MatchReplace(@"\[img=([^\]]+)]([^\]]+)\[\/img\]", "<img src=""$2"" alt=""$1"" />", content);

    // Lists
    content = MatchReplace(@"\[*\]([^\[]+)", "<li>$1</li>", content);
    content = MatchReplace(@"\[list\]([^\]]+)\[\/list\]", "<ul>$1</ul><p>", content);
    content = MatchReplace(@"\[list=1\]([^\]]+)\[\/list\]", "</p><ol>$1</ol><p>", content);

    // Headers
    content = MatchReplace(@"\[h1\]([^\]]+)\[\/h1\]", "<h1>$1</h1>", content);
    content = MatchReplace(@"\[h2\]([^\]]+)\[\/h2\]", "<h2>$1</h2>", content);
    content = MatchReplace(@"\[h3\]([^\]]+)\[\/h3\]", "<h3>$1</h3>", content);
    content = MatchReplace(@"\[h4\]([^\]]+)\[\/h4\]", "<h4>$1</h4>", content);
    content = MatchReplace(@"\[h5\]([^\]]+)\[\/h5\]", "<h5>$1</h5>", content);
    content = MatchReplace(@"\[h6\]([^\]]+)\[\/h6\]", "<h6>$1</h6>", content);

    // Horizontal rule
    content = MatchReplace(@"\[hr\]", "<hr />", content);

    // Set a maximum quote depth (In this case, hard coded to 3)
    for (int i = 1; i < 3; i++)
    {
        // Quotes
        content = MatchReplace(@"\[quote=([^\]]+)@([^\]]+)|([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 <a href=""" + QuoteUrl("$3") + """>wrote</a> on $2</cite><hr /><p>$4</p></blockquote></div></p><p>", content);
        content = MatchReplace(@"\[quote=([^\]]+)@([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 wrote on $2</cite><hr /><p>$3</p></blockquote></div><p>", content);
        content = MatchReplace(@"\[quote=([^\]]+)]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><cite>$1 wrote</cite><hr /><p>$2</p></blockquote></div><p>", content);
        content = MatchReplace(@"\[quote\]([^\]]+)\[\/quote\]", "</p><div class=""block""><blockquote><p>$1</p></blockquote></div><p>", content);
    }

    // The following markup is for embedded video -->

    // YouTube
    content = MatchReplace(@"\http:\/\/([a-zA-Z]+.)youtube.com\/watch\?v=([a-zA-Z0-9_\-]+)\[\/youtube\]",
        "<object width=""425"" height=""344""><param name=""movie"" value=""http://www.youtube.com/v/$2""></param><param name=""allowFullScreen"" value=""true""></param><embed src=""http://www.youtube.com/v/$2"" type=""application/x-shockwave-flash"" allowfullscreen=""true"" width=""425"" height=""344""></embed></object>", content);

    // LiveVideo
    content = MatchReplace(@"\[livevideo\]http:\/\/([a-zA-Z]+.)livevideo.com\/video\/([a-zA-Z0-9_\-]+)\/([a-zA-Z0-9]+)\/([a-zA-Z0-9_\-]+).aspx\[\/livevideo\]",
        "<object width=""445"" height=""369""><embed src=""http://www.livevideo.com/flvplayer/embed/$3"" type=""application/x-shockwave-flash"" quality=""high"" width=""445"" height=""369"" wmode=""transparent""></embed></object>", content);

    // LiveVideo (There are two types of links for LV)
    content = MatchReplace(@"\[livevideo\]http:\/\/([a-zA-Z]+.)livevideo.com\/video\/([a-zA-Z0-9]+)\/([a-zA-Z0-9_\-]+).aspx\[\/livevideo\]",
        "<object width=""445"" height=""369""><embed src=""http://www.livevideo.com/flvplayer/embed/$2&autostart=0"" type=""application/x-shockwave-flash"" quality=""high"" width=""445"" height=""369"" wmode=""transparent""></embed></object>", content);

    // Metacafe
    content = MatchReplace(@"\[metacafe\]http\:\/\/([a-zA-Z]+.)metacafe.com\/watch\/([0-9]+)\/([a-zA-Z0-9_]+)/\[\/metacafe\]",
        "<object width=""400"" height=""345""><embed src=""http://www.metacafe.com/fplayer/$2/$3.swf"" width=""400"" height=""345"" wmode=""transparent"" pluginspage=""http://www.macromedia.com/go/getflashplayer"" type=""application/x-shockwave-flash""></embed></object>", content);

    // LiveLeak
    content = MatchReplace(@"\[liveleak\]http:\/\/([a-zA-Z]+.)liveleak.com\/view\?i=([a-zA-Z0-9_]+)\[\/liveleak\]",
        "<object width=""450"" height=""370""><param name=""movie"" value=""http://www.liveleak.com/e/$2""></param><param name=""wmode"" value=""transparent""></param><embed src=""http://www.liveleak.com/e/59a_1231807882"" type=""application/x-shockwave-flash"" wmode=""transparent"" width=""450"" height=""370""></embed></object>", content);

    // < -- End video markup

    // Google and Wikipedia page links
    content = MatchReplace(@"\[google\]([^\]]+)\[\/google\]", "<a href=""http://www.google.com/search?q=$1"">$1", content);
    content = MatchReplace(@"\[wikipedia\]([^\]]+)\[\/wikipedia\]", "<a href=""http://www.wikipedia.org/wiki/$1"">$1</a>", content);

    // Put the content in a paragraph
    content = "</p><p>" + content + "</p>";

    // Clean up a few potential markup problems
    content = content.Replace("t", "    ")
        .Replace("  ", "  ")
        .Replace("<br />", "")
        .Replace("<p><br />", "</p><p>")
        .Replace("</p><p><blockquote>", "<blockquote>")
        .Replace("</blockquote></blockquote></p>", "")
        .Replace("<p></p>", "")
        .Replace("<p><ul></ul></p>", "<ul>")
        .Replace("<p></p></ul>", "")
        .Replace("<p><ol></ol></p>", "<ol>")
        .Replace("<p></p></ol>", "")
        .Replace("<p><li>", "</li><li><p>")
        .Replace("</p></li></p>", "");

    return content;
}

StripTags and Match Replace functions

/// <summary>
/// Strip any existing HTML tags
/// </summary>
///
<param name="content">Raw input from user</param>
/// <returns>Tag stripped storage safe text</returns>
public static string StripTags(string content)
{
	return MatchReplace(@"< [^>]+>", "", content, true, true, true);
}

public static string MatchReplace(string pattern, string match, string content)
{
	return MatchReplace(pattern, match, content, false, false, false);
}

public static string MatchReplace(string pattern, string match, string content, bool multi)
{
	return MatchReplace(pattern, match, content, multi, false, false);
}

public static string MatchReplace(string pattern, string match, string content, bool multi, bool white)
{
	return MatchReplace(pattern, match, content, multi, white);
}

/// <summary>
/// Match and replace a specific pattern with formatted text
/// </summary>
///
<param name="pattern">Regular expression pattern</param>
///
<param name="match">Match replacement</param>
///
<param name="content">Text to format</param>
///
<param name="multi">Multiline text (optional)</param>
///
<param name="white">Ignore white space (optional)</param>
/// <returns>HTML Formatted from the original BBCode</returns>
public static string MatchReplace(string pattern, string match, string content, bool multi, bool white, bool cult)
{
	if (multi && white && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
	else if (multi && white)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.IgnoreCase);
	else if (multi && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant);
	else if (white && cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant);
	else if (multi)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.Multiline);
	else if (white)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
	else if (cult)
		return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);

	// Default
	return Regex.Replace(content, pattern, match, RegexOptions.IgnoreCase);
}

Enjoy!

Addendum…
Robert Beal, in particular has created a wonderful HtmlUtility class for C# 3.0+ that will only allow certain tags and tag attributes. If your visitors make use of extensive HTML tags, then that is a better option than my system. If you want to implement a feedback system for guest writers with strong HTML support, Robert’s example is highly recommended.

My example is really only for people who post in plain text most of the time, would only post formatted text and videos semi-rarely.

In fact, it’s probably best to avoid extensive HTML support for ordinary comments, as that will only encourage users to abuse the system. You’re better off using this for something minimal.