2005.07.10 11:11 AM

FlexWiki Notes - Starting Ordered Lists

Anytime FlexWiki encounters a line starting with a tab (or 8 spaces) followed by "1." it starts a new ordered list (i.e., <OL>). Here's an example:

        1. item 1
                1. sub item 1
                1. sub item 2
        1. item 2
        1. item 3

And here's how it is rendered:

     1.  item 1
          a.  sub item 1
          b.  sub item 1
     2.  item 2
     3.  item 3

Sometimes, though, I need to break an ordered list in the middle to accommodate some other content, maybe an image and explanation or maybe some formatted content. Unfortunately, this causes FlexWiki to start a new ordered list. Here's an example:

        1. item 1
                1. sub item 1
                1. sub item 2
        1. item 2

Some formatted content, belonging to item 2.

        1. item 3

And here's how it is rendered:

     1.  item 1
          a.  sub item 1
          b.  sub item 1
     2.  item 2

Some formatted content, belonging to item 2.

     1.  item 3

Oops. The last item should be numbered with a 3, not a 1.

The solution is to get FlexWiki to take advantage of the OL element's START attribute. This was pretty simple to do with just a few mods to the FlexWiki engine.

First, I changed the ordered list logic in the Format method of the FlexWiki.Formatting.Formatter class from this:

else if (each.StartsWith("1."))
{
  each = each.Substring(2);
  Ensure(typeof(OrderedListState));
  ((OrderedListState)(CurrentState)).SetNesting(thisNest);
  _Output.WriteListItem(each);
}

To this:

else if (Regex.IsMatch(each, @"^[0-9]{1,}\..*"))
{
  int start = Int32.Parse(each.Substring(0, each.IndexOf(".")));
  each = each.Substring(each.IndexOf(".") + 1);
  Ensure(typeof(OrderedListState), start);
  ((OrderedListState)(CurrentState)).SetNesting(thisNest);
  _Output.WriteListItem(each);
}

Now instead of looking for just "1." at the start of the line, it looks for any combination of numbers followed by a period. The number is pulled from the beginning and passed to a new version of the Ensure function:

void Ensure(Type aType, int param)
{
  if (CurrentState != null && CurrentState.GetType() == aType)
    return;  // already what's requested
  CurrentState = (State)(Activator.CreateInstance(aType, new object[]{this, param}));
}

A couple things to note about this. First, it's nearly an exact repeat of the Ensure function, so I probably should have refactored the CurrentState check into its own function to avoid repetition. Feel free. Second, I didn't bother to expand the concept of CurrentState to reflect the new param, which in this case is the starting number. The reason I didn't is because the new param doesn't change the permanent nature of our current state, which in this case is that we're in an ordered list. It only changes the state's startup behavior. What this means is that once an ordered list is started, subsequent list items can't be arbitrarily renumbered; they will always number sequentially from the first specified number. This was sufficient for my purposes.

The next thing I had to do was modify the OrderedListState class, which is an inner class of Formatter, to take the new start number on instantiation (via System.Activator.CreateInstance method) and subsequently pass it to the WriteOpenOrderedList method of the FlexWiki.Formatting.WikiOutput-derived object being targeted. Here's the original OrderedListState class:

class OrderedListState : ListState
{
  public OrderedListState(Formatter f) : base (f)
  {
  }

  override public void WriteIndent()
  {
    Output.WriteOpenOrderedList();
    listNest++;
  }

  override public void WriteOutdent()
  {
    Output.WriteCloseOrderedList();
    listNest--;
  }

  override public void Exit()
  {
    SetNesting(0);
  }

  override public void Enter()
  {
    SetNesting(1);
  }

}

Here's its replacement:

class OrderedListState : ListState
{
  int _start;

  public OrderedListState(Formatter f) : base (f)
  {
    _start = 1;
  }

  public OrderedListState(Formatter f, int start) : base (f)
  {
    _start = start;
  }

  override public void WriteIndent()
  {
    Output.WriteOpenOrderedList(_start);
    listNest++;
  }

  override public void WriteOutdent()
  {
    Output.WriteCloseOrderedList();
    listNest--;
  }

  override public void Exit()
  {
    SetNesting(0);
  }

  override public void Enter()
  {
    SetNesting(1);
  }

}

Next, I modified the abstract base class WikiOutput to include the new parameterized WriteOpenOrderedList method. I considered adding a new overloaded WriteOpenOrderedList method that took an int parameter and having the old derived implementations call the new method passing a 1, but a quick search showed there were no other calls to the method except from the OrderedListState class, so I just replaced this abstract method signature:

abstract public void WriteOpenOrderedList();

With this one:

abstract public void WriteOpenOrderedList(int start);

Finally, I modified the WriteOpenOrderedList method of the FlexWiki.Formatting.HtmlWikiOutput class, which is the only delivered FlexWiki class inheriting from WikiOutput that overrides WriteOpenOrderedList, to handle the new int parameter:

  override public void WriteOpenOrderedList(int start)
  {
    WriteLine("<ol start='" + start.ToString() + "' " + css() + ">");
  }

After some testing, I quickly discovered one more change was needed. Prior to the unordered/ordered list processing in the Formatter's Format method, there's a regex used to identify the start of preformatted text and it repeats the logic used to identify the start of lists:

// line begins with a space, it's PRE time!
else if ((each.StartsWith(" ") || Regex.IsMatch(each, "^[ \t]+[^ \t*1]")) && 
  (false == currentMultilinePropertyIsHidden))
{
  Ensure(typeof(PreState));
  _Output.Write(Regex.Replace(each, "\t", "        "));
}

This had to be changed to account for the new logic that allows ordered lists to begin with any number, not just "1.":

// line begins with a space, it's PRE time!
else if ((each.StartsWith(" ") || Regex.IsMatch(each, "^[ \t]+[^ \t*0-9]")) && 
  (false == currentMultilinePropertyIsHidden))
{
  Ensure(typeof(PreState));
  _Output.Write(Regex.Replace(each, "\t", "        "));
}

That's it. Now the ordered list shown above with the break in it can be written as this:

        1. item 1
                1. sub item 1
                1. sub item 2
        1. item 2

Some formatted content, belonging to item 2.

        3. item 3

And it will render like this:

     1.  item 1
          a.  sub item 1
          b.  sub item 1
     2.  item 2

Some formatted content, belonging to item 2.

     3.  item 3

Next up, adding support for arbitrarily indented text.

FlexWiki Notes - Multiple Namespaces
FlexWiki Notes - Getting Started
FlexWiki Notes


Comments



Post a Comment

 
  (optional)
  (no html)
 
   


TrackBack

TrackBack URL:  http://www.typepad.com/services/trackback/6a00d8341c7bd453ef00d83551987069e2

Listed below are links to weblogs that reference FlexWiki Notes - Starting Ordered Lists: