eWorld.UI - Matt Hawley

Ramblings of Matt

Extending WikiPlex with Custom Renderers

July 30, 2009 13:22 by matthaw

Following up from my prior post on Extending WikiPlex with Custom Macros it's now time to talk about creating custom renderers. When we left off, we had created our title link macro and registered it with WikiPlex. If you had attempted to utilize the new macro in wiki content, you'd get the message "Cannot resolve macro, as no renderers were found." instead of a hyperlink. This was the expected behavior, because while you had identified your new scope via parsing, you were missing that critical link of turning the scope into actual content. Let's take a look at our scenario again to refresh what we're trying to achieve:

 

We would like to integrate WikiPlex into an existing application. The idea is to allow a user contributed area specifically for wiki content. The user should be allowed to use all out-of-the-box macros provided, but also have the ability to have inter-wiki links with the format of [Title of Page]. As you probably realized, there is currently no macro/renderer that will take that content and turn it into a inter-wiki link, so we'll have to extend WikiPlex adding this functionality.

Create a Renderer

Creating a renderer is actually the easiest portion of defining new wiki syntaxes, as it's as complicated as you need to make it. Again, a renderer simply takes in a scope (which is a contextual identifier), processes the content, and returns new content. Let's get started - so in your solution, create a class called TitleLinkRenderer and extend it from WikiPlex.Formatting.IRenderer. You'll then implement the members it requires (Id, CanExpand and Expand). The Id value is simply a string that is used as a key for static renderer registration, so it should be unique (follow the same rule of thumb for naming as the macros).

 

Next, you'll implement the CanExpand method. This method simply takes in a scope name and returns a boolean value indicating if this renderer can expand (or render) the scope successfully. As the formatter is processing all scopes, it goes through the list of renderers in the formatter and finds the first match that can expand that particular scope. There is no guarantee of the order of checking renderers, so always unregister a renderer you're overriding its implementation for. As you'll see below, the CanExpand method is fairly trivial, however should your renderer support a number of scopes, you'll need to change this code to include all of them.

public bool CanExpand(string scopeName)
{
   return scopeName == WikiScopeName.WikiLink;
}

Next, you'll implement the Expand method. This method will take in a scope name, the related input from the wiki source, and html / attribute encoding functions. The reason we're passing in html / attribute encoding functions, is so that you can utilize a consistent encoding scheme across all of the renderers. Out of the box, WikiPlex uses HttpUtility.HtmlEncode and HttpUtility.HtmlAttributeEncode, but by creating & supplying your own formatter, you can change these to use another library (like AntiXss). As previously stated, rendering is as hard as you need it to be. In the sample application example, we're just rendering a link utilizing the ASP.NET MVC UrlHelper (which is supplied via the constructor).

private const string LinkFormat = "<a href=\"{0}\">{1}</a>";

public string Expand(string scopeName, string input,
                     Func<string, string> htmlEncode, 
                     Func<string, string> attributeEncode)
{
   string url = urlHelper.RouteUrl("Default", new { slug = SlugHelper.Generate(input) });
   return string.Format(LinkFormat, attributeEncode(url), htmlEncode(input));
}

And now you have created your renderer, however it will still not be picked up when rendering your wiki content as you need to register the renderer with WikiPlex.

 

Registering a Renderer

Just as registering a macro, you have a static and a dynamic way to register your renderers. If your renderer requires only static dependencies (or no external runtime dependencies), you should opt for statically registering your renderer. To do this, have the following code in your application startup method

Renderers.Register<TitleLinkRenderer>();

When you call the WikiEngine.Render("content"), it will automatically pick up all statically defined renderers and use them when formatting your scopes. As previously stated, if you have external runtime dependencies (like in our example), a little bit of extra work is required when calling WikiEngine.Render - as you'll need to pass in a MacroFormatter instead. However, if you utilize the overload to only take in a formatter, you'll need to union the statically defined renderers with yours.

private MacroFormatter GetFormatter()
{
   var siteRenderers = new IRenderer[] {new TitleLinkRenderer(Url)};
   IEnumerable<IRenderer> allRenderers = Renderers.All.Union(siteRenderers);
   return new MacroFormatter(allRenderers);
}

Now, when you call WikiEngine.Render, you'll utilize the overload that takes in an IFormatter as a parameter. After you've set all of this up, try re-loading your page and you should see your syntax of [Title Link] be converted into the html <a href="/title-link">Title Link</a>.

 

Summary

You now have a new fully functioning macro syntax. Obviously, this example is trivial - but I guarantee if you embed WikiPlex into your application and need any cross-page linking, you'll utilize this macro & renderer. Again, the possibilities are endless with what you can do, so long as you have a syntax, regex, and rendering code - you can allow your users to simply include expansive macros.

 

Download WikiPlex now!

 

kick it on DotNetKicks.com



Extending WikiPlex with Custom Macros

July 24, 2009 18:47 by matthaw

One of the very nice features of WikiPlex is the ability to extend it to your hearts desire. The API is completely open, and the entry points off of WikiEngine are merely wrappers. This means that, if you really wanted to, you could create your own parser and formatter - but the majority of extending WikiPlex will be done via macros and renderers. A summary of the extension points include:

  1. Macros encapsulate rules for matching segments of content.
  2. Macro rules are regular expressions that map matches to scopes.
  3. Scopes are contextual identifiers.
  4. Renderers take scopes and expand them into a HTML formatted representation of the macro.
Scenario

We would like to integrate WikiPlex into an existing application. The idea is to allow a user contributed area specifically for wiki content. The user should be allowed to use all out-of-the-box macros provided, but also have the ability to have inter-wiki links with the format of [Title of Page]. As you probably realized, there is currently no macro/renderer that will take that content and turn it into a inter-wiki link, so we'll have to extend WikiPlex adding this functionality.

 

Create a Macro

When creating a macro, you're going to have to dust off that copy of RegexBuddy you probably don't have installed anymore. Why? Well, as previously stated, macro rules are regular expressions - and unless you're a regex guru, you won't be able to do this ad-hoc without a great tool. Let's get started - so in your solution, create a class called TitleLinkMacro, and extend it from WikiPlex.Compilation.Macros.IMacro. You'll then implement the members it requires (Id and Rules). The Id value is simply a string that is used as a key for static macro registration and macro compilation, so it should be unique (rule of thumb, give it the name of your class but with spaces). Now, it's time to define your macro rules.

 

As you may have noticed, I kept "rule" plural. The reason, is that the majority of macros you will create need to have an initial "escaped" rule. This rule basically stops the regex from matching within code blocks, between curly braces, and possibly between square brackets. Since our macro utilizes square brackets, we'll use the escape rule of CurlyBraceEscape.

 

Next, you'll define your regex (with extreme caution!) utilizing capturing groups to identify scopes. If you take a look at the code, you may not think that the scope identification is zero based - don't be fooled, it really is! Identifying an index 0 scope indicates the full match for that rule. When creating your capturing groups, you can have any number, allowing for fine granularity when rendering. So, let's take a look at the sample project's macro's rules.

public IList<MacroRule> {
   get {
      return new List<MacroRule> {
         new MacroRule(EscapeRegexPatterns.CurlyBraceEscape),
         new MacroRule(@"(?i)(\[)(?!\#|[a-z]+:)((?>[^\]]+))(\])",
                 new Dictionary<int, string> {
                    { 1, ScopeName.Remove },
                    { 2, WikiScopeName.WikiLink },
                    { 3, ScopeName.Remove }
                 })
      };
   }
}

As you can see, the regular expression is indicating that I should match 3 scopes per overall match. The scope "Remove" does just that. It removes the captured content when rendering. The WikiLink scope name is one that was created specifically for the sample. For the non-regex savvy developer, the regex reads:

  1. Use case insensitive matching from this point on
  2. Match the character "["
  3. Match any character until "]" is found
    1. But do not match if the character is preceded
      1. by the "#" character
      2. or by any character between a-z, one or more times, followed by a ":" character
  4. Match the character "]"

Our scope to step matching then is

  1. Remove "["
  2. WikiLink any content
  3. Remove "]"

Defining macro rules is a fairly straight forward process, just keep in mind that the order of the macro rules is important! You should also realize that if you wish to allow nesting of rules (for example, italicize bolded text) the italics and bold macro rules cannot be apart of the same macro. This, again, is because the macro rules are combined to build a large regular expression - and each rule is treated as an "or" statement.

 

Registering a Macro

After you have created your macro, you need to register it with WikiPlex. You have two ways of doing this - statically and dynamically. When statically registering macros, you simply need to have the following code in your application startup method

Macros.Register<TitleLinkMacro>();

When you call the WikiEngine.Render("content"), it will automatically pick up your macro, compile it, and use it when parsing the wiki content. Dynamically loading your macros is useful when you require a different set of macros to be executed than what is normally registered. For example, in CodePlex - we have a different set of allowed macros between editing a project's wiki content and editing your personal statement. Dynamically loading is achieved by utilizing one of the WikiEngine.Render overloads:

WikiEngine.Render("content", new IMacro[] { new WikiTitleLinkMacro() });

The only caveat of this approach, is that it will only use that macro when parsing your wiki content.

 

Summary

This is just the tip of the extensibility for WikiPlex. Creating your own macro is a great. Simply be cautious of your regular expression as it could have negative side effects (catastrophic backtracking) that will bring your site to a screeching halt! In the next installment, we'll take this scenario to the next step by creating a renderer for your macro.

 

Download WikiPlex now!

 

kick it on DotNetKicks.com



Categories: .NET | CodePlex | WikiPlex
Actions: E-mail | Permalink | Comments (4) | Comment RSSRSS comment feed

WikiPlex – An Embedded Wiki Engine

July 16, 2009 11:04 by matthaw

What and Why?

I'd like to introduce you to WikiPlex, which is CodePlex's wiki engine that we have re-written and made open source under the MS-PL license. I'm also happy to announce that our first public release is now available!

 

CodePlex previously had a decent wiki engine that was written eon's ago. On the average, that wiki engine worked relatively well, but had a very problematic performance bug that would cause rendering slowness occasionally. So, instead of attempting to fix the bug, we decided to re-write the entire thing with the intensions of making it available to everyone! This time, we chose a different approach for parsing the wiki markup (utilizing regular expressions) which has proven to give us a performance boost as well as a relatively simpler architecture!

 

The main question you may be asking yourself is - Why use WikiPlex over a different solution? Here's the simple answer: WikiPlex is great if you already have a .NET application you'd like to embed a wiki interface into. Be it as simple as allowing users to host their own homepage content, item descriptions, or comments - the possibilities are endless!

 

Usage

WikiPlex was built in a way that it can easily be added into your infrastructure. Whether your using dependency injection or not, the code is as simple as the following:

var engine = new WikiPlex.WikiEngine();
string output = engine.Render("This is my wiki source!");

If you take a look at the overloads for Render, you'll see that you have a lot of flexibility as far as rendering various wiki segments differently at runtime. (I'll describe the extensibility in the future)

public interface IWikiEngine
{
   string Render(string wikiContent);
   string Render(string wikiContent, IFormatter formatter);
   string Render(string wikiContent, IEnumerable<IMacro> macros);
   string Render(string wikiContent, IEnumerable<IMacro> macros, IFormatter formatter);
}

Supported Macros

The following are the macros supported out of the box for WikiPlex. If you'd like to see the description and usage, please visit the markup guide.

  • Text Formatting
    • Bold
    • Italics
    • Underline
    • Strikethrough
    • Superscript
    • Subscript
  • Headings
  • Images
  • Links
  • Tables
  • Left and Right Aligned Text
  • Ordered and Unordered Lists
  • RSS / Atom Feeds
  • Source Code Blocks (both syntax highlighted and not)
  • Silverlight
  • Videos (Flash, Quicktime, Real, Soapbox, Windows Media, and YouTube)

Enjoy! And don't forget to download WikiPlex now!

 

kick it on DotNetKicks.com





Copyright © 2000 - 2024 , Excentrics World