roxen.lists.roxen.general

Subject Author Date
Appendix (wikiml.pike) to: Re: User Language parser Michael Stenitzer <jenoe[dot]stenitzer[at]gmail[dot]com> 13-02-2006
// This is a ChiliMoon / Roxen module. 
// Copyright © Michael Stenitzer, 2004 - 2005, <<jenoe[at]gmx.at>>.

#include <module.h>
inherit "module";

constant cvs_version = "$Id: config.php-default,v 1.8 2003/10/08 07:04:29
tuxmonkey Exp $";
constant thread_safe = 1;
constant module_unique = 1;
constant module_type = MODULE_TAG;
constant module_name = "Tags: WikiMarkup";
constant module_doc  = "<p>This module provides a simple WikiMarkup
language</p>";

array shortcuts;
constant shortcuts_separator = ",";
int headerlimit_upper;
int headerlimit_lower;
int convert_lists;
int convert_code;
int convert_quote;
int convert_acronyms;
int convert_header;
int remap_headers;
int convert_format;
int convert_images;
string imgpath;
int c_forcedlinks;
int c_linkify;
int c_nofollow;

class VarUpper
{
  inherit Variable.IntChoice;
  array get_choice_list( )
  {
    return ({ 1, 2, 3, 4, 5, 6 })[ .. headerlimit_lower - 1 ];
  }
}

class VarLower
{
  inherit Variable.IntChoice;
  array get_choice_list( )
  {
    return ({ 1, 2, 3, 4, 5, 6 })[ headerlimit_upper - 1 .. ];
  }
}

class VarShortcuts
{
  inherit Variable.List;
  constant type="ShortcutList";

  string render_row( string prefix, mixed val, int width )
  {
    string res = "<input type=hidden name='" + prefix + "' value='" + prefix +
"' />";

    mapping m = decode_shortcuts( val );
    
    res += "<table>";
    

    res += "<tr><td>Pattern</td><td>";
    res += ("<input name='"+prefix+"pattern' value='"+
            Roxen.html_encode_string(m->pattern||"")+"' size=20/></td></tr>");
    res += ("<tr><td>URL</td><td> <input name='"+prefix+"url' value='"+
            Roxen.html_encode_string(m->url || "")+"' size=50/></td></tr>");
    res += ("<tr><td>Linktext</td><td> <input name='"+prefix+"linktext' value='"+
            Roxen.html_encode_string(m->linktext || "")+"' size=50/></td></tr>");
    return res+"</table>";
  }

  string transform_from_form( string v, mapping va )
  {
    if( v == "" ) return "";
    v = v[sizeof(path())..];
    return va[v+"pattern"]+""+va[v+"url"]+""+va[v+"linktext"];
  }

  mapping decode_shortcuts( string s )
  {
    mapping m = ([]);
    array a = s/"";
    m->pattern = a[0];
    m->url = a[1];
    if( sizeof( a ) > 2 ) 
      m->linktext =  a[2];
    if( !m->linktext || !sizeof( m->linktext ) ) 
      m_delete( m, "linktext" );
    return m;
  } 
   
  array(mapping) get_shortcuts()
  {
    return map( query(), decode_shortcuts );
  }
}


void create()
{
// --------------------- Configuration Interface -----------------------

  defvar( "shortcuts", VarShortcuts( ({}), 0, "Default: Link shortcuts", 
    "You can shortcut links to important destinations like a bug database, FAQs
etc. "
    "The pattern string will be simply replaced by the URL, the Linktext is
optional "
    "and gives an easy to read link text. You can override this setting with the
"
    "attribute <i>shortcuts=&quot;...&quot;</i>" ) );


  defvar( "convert_format", 1, "Default: Convert character formats",
    TYPE_FLAG,
    "If enabled, text can be formatted with _italic text_ , *bold text* and
_*bold and italic text*_ . "
    "You can override this setting with the attribute
<i>charformat=&quot;on|off&quot;</i>.");

  defvar( "convert_lists", 1, "Default: Convert lists",
    TYPE_FLAG,
    "If enabled, lines starting with * or # will be converted into ordered and
unordered XHTML lists. "
    "You can override this setting with the attribute
<i>lists=&quot;on|off&quot;</i>.");

  defvar( "convert_code", 1, "Default: Convert code",
    TYPE_FLAG,
    "If enabled, lines starting with | will be converted into preformatted code
blocks. "
    "You can override this setting with the attribute
<i>code=&quot;on|off&quot;</i>.");

  defvar( "convert_quote", 1, "Default: Convert blockquote",
    TYPE_FLAG,
    "If enabled, lines starting with &gt; will be converted into blockquotes. "
    "You can override this setting with the attribute
<i>quote=&quot;on|off&quot;</i>.");

  defvar( "convert_header", 1, "Default: Convert headers",
    TYPE_FLAG,
    "If enabled, lines starting with a certain number of = will be converted
into a XHTML header. "
    "You can override this setting with the attribute
<i>headers=&quot;on|off&quot;</i>.");
    
  defvar( "header_upper",  VarUpper( 1, ({ }), 0,
    "Default: Upper limit for headers", "The upper limit for headers corresponds
to the highest "
    "header level allowed. (1 means H1)"))
    ->set_invisibility_check_callback( lambda( RequestID id, Variable.Variable i
)
    { 
      return ! convert_header; 
    }
  );

  defvar( "remap_headers",  1, "Default: Remap headers",
    TYPE_FLAG,
    "Your headers will automatically be remapped to the highest allowed header
(upper limit)."
    "e.g. if you you allow only H3 and lower, a wiki-header of level 1
(&quot;=&quot;) will"
    "be remapped to a H3 in XHTML." )
    ->set_invisibility_check_callback( lambda( RequestID id, Variable.Variable i
)
    { 
      return ! ( convert_header && headerlimit_upper > 1 ); 
    }
  );

  defvar( "header_lower", VarLower( 6, ({ }), 0,
    "Default: Lower limit for headers", "The lower limit for headers corresponds
to the lowest "
    "header level allowed. (6 means H6)"))
    ->set_invisibility_check_callback( lambda( RequestID id, Variable.Variable i
)
    { 
      return ! convert_header; 
    }
  );

  defvar( "convert_acronyms", 1, "Default: Convert acronyms",
    TYPE_FLAG,
    "If enabled, acronyms defined in the way acronym_(definition) will be
tagged. "
    "You can override this setting with the attribute
<i>acronym=&quot;on|off&quot;</i>.");

  defvar( "forcedlinks", 1, "Default: Allow Links",
    TYPE_FLAG,
    "If enabled, hyperlinks can be forced by embracing an Url with brackets:
[Url] or [Url Linktext]. "
    "You can override this setting with the attribute
<i>forcedlinks=&quot;on|off&quot;</i>.");

  defvar( "linkify", 1, "Default: Autoconvert Links",
    TYPE_FLAG,
    "If enabled, typical Url-patterns will be automatically converted to
hyperlinks "
    "(e.g. http://www.domain.com, www.domain.com). "
    "You can override this setting with the attribute
<i>linkify=&quot;on|off&quot;</i>.");

  defvar( "nofollow",  0, "Default: Use NOFOLLOW attribute for Urls",
    TYPE_FLAG,
    "If enabled, all links in the WikiMarkup will get
<i>rel=&quot;nofollow&quot;</i>"
    "set. This is a new spam prevention method supported by most search engines "
    "(<a
href=\"http://www.google.com/googleblog/2005/01/preventing-comment-spam.html\">Information
on preventing comment spam</a>)")
    ->set_invisibility_check_callback( lambda( RequestID id, Variable.Variable i
)
    { 
      return ! ( c_linkify || c_forcedlinks > 1 ); 
    }
  );
  
  defvar( "convert_images", 1, "Default: Convert images",
    TYPE_FLAG,
    "If enabled, images can inserted with [[image::imgfilename.jpg Alt text]]."
    "You can override this setting with the attribute
<i>images=&quot;on|off&quot;</i>.");
  
  defvar("imgpath", "/images/", "Default: Default image path",
    TYPE_LOCATION,
    "Where the images can be found. This can either be a path in your site's
virtual file system "
    "or an external URL (which might not be the best idea)."
    "You can override this setting with the attribute
<i>imgpath=&quot;path|url&quot;</i>.");

}

void start()
{ 
  // Get default values from configuration
  
  shortcuts = getvar( "shortcuts" )->get_shortcuts();
  headerlimit_lower = query( "header_lower" );
  headerlimit_upper = query( "header_upper" );
  convert_lists = query( "convert_lists" );
  convert_code = query( "convert_code" );
  convert_quote = query( "convert_quote" );
  convert_acronyms = query( "convert_acronyms" );
  convert_header = query( "convert_header" );
  convert_format = query( "convert_format" );
  remap_headers = query( "remap_headers" );
  c_forcedlinks = query( "forcedlinks" );
  c_linkify = query( "linkify" );
  c_nofollow = query( "nofollow" );
  convert_images = query( "convert_images" );
  convert_images = query( "convert_images" );
  imgpath = query( "imgpath" );
}

class TagWikimarkup
{
  inherit RXML.Tag;
  constant name = "wikimarkup";

  mapping(string:RXML.Type) req_arg_types = ([ ]);
  mapping(string:RXML.Type) opt_arg_types = ([
"shortcuts":RXML.t_text(RXML.PXml),              // [pattern,url,linktext,...]
                                               "headers":RXML.t_text(RXML.PXml),
               // [on|off]
                                              
"headerlimit-upper":RXML.t_text(RXML.PXml),      // [1..6]
                                              
"headerlimit-lower":RXML.t_text(RXML.PXml),      // [1..6]
                                              
"remap-headers":RXML.t_text(RXML.PXml),          // [on|off]
                                               "lists":RXML.t_text(RXML.PXml),  
               // [on|off]
                                               "code":RXML.t_text(RXML.PXml),   
               // [on|off]
                                               "quote":RXML.t_text(RXML.PXml),  
               // [on|off]
                                              
"acronyms":RXML.t_text(RXML.PXml),               // [on|off]
                                              
"charformat":RXML.t_text(RXML.PXml),             // [on|off]
                                              
"forcedlinks":RXML.t_text(RXML.PXml),            // [on|off]
                                               "linkify":RXML.t_text(RXML.PXml),
               // [on|off]
                                              
"nofollow":RXML.t_text(RXML.PXml),               // [on|off]
                                               "images":RXML.t_text(RXML.PXml), 
               // [on|off]
                                               "imgpath":RXML.t_text(RXML.PXml),
               // [path|url]
                                              
"separator":RXML.t_text(RXML.PXml),              // [,]
                                               "sandbox":RXML.t_text(RXML.PXml),
               //
                                               "userhelp":RXML.t_text(RXML.PXml)
]);            // [short|long]

  array(RXML.Type) result_types = ({ RXML.t_any(RXML.PXml) });

  Regexp acronym = Regexp("[^ \t\n\r]+_\([^\)]+\)");
  Regexp forcedlink = Regexp("\[\[[^ \t\[][^\]]*\]\]");
  Regexp image = Regexp("image::\[\[[^ \t\[][^\]]*\]\]");
  Regexp link_regexp =
          Regexp("[ \t\n\r\"]((((http)|(https)|(ftp))://([^ \t\n\r\"\]]+)(\.[^
\t\n\r\"\]]+)+)|"
          "(((www)|(ftp))(\.[^ \t\n\r\"\]]+)+))");
  
  mapping lists = ([ "*":"ul", "#":"ol" ]);     // available list types

  string parsewiki( string s, mapping settings, string sandbox )  // parse
wikiml line by line
  {
    if ( settings[ "userhelp" ] || settings[ "sandbox" ] )
    {
      return userdocs( settings, s, sandbox );
    }
    
    mapping(string:string) 
      block = (["current":"",                   // current blocklevel
                "next":"",                      // new blocklevel
                "currentquote":"",              // current blockquotelevel
                "nextquote":"" ]);              // new blockquotelevel

    string converted = "";
    
    // escape escaped markup-like patterns
    s = replace( s, ({ " **", " __", " **__", " __**", "[[[", "]]]" }), 
                    ({ " %*", " %_", " %*_",  " %_*",  "§%%", "%%§" }) );

    // acronyms
    if ( settings[ "convert_acronyms" ] )
      s = utf8_to_string( acronym->replace( string_to_utf8( s ), lambda( string
l )
        {
          sscanf( l, "%s_(%s)", string acr, string title );
          return "<acronym title=\"" + title + "\">" + acr + "</acronym>";
        }
      ) );
          
    // images
    if ( settings[ "convert_images" ] )
      s = imageify ( s, settings );

    s = linkify ( s, settings );
    array lines = (s - "\r" - "" ) / "\n";
    array(array(string)) wm_split;
    string wm_res;

    foreach (lines, string line )
    {
      line += " ";                              // if the markup at the end of
the line is missing a blank

      if ( settings[ "convert_format" ] )       // Convert inline formats
      {
        // strong+italic
        sscanf(line, "%{%s *_%s_* %}%s", wm_split, wm_res );
        line = sprintf("%{%s <strong><em>%s</em></strong> %}%s", wm_split,
wm_res );
        sscanf(line, "%{%s _*%s*_ %}%s", wm_split, wm_res );
        line = sprintf("%{%s <em><strong>%s</strong></em> %}%s", wm_split,
wm_res );

        // strong
        sscanf(line, "%{%s *%s* %}%s", wm_split, wm_res );
        line = sprintf("%{%s <strong>%s</strong> %}%s", wm_split, wm_res );

        // italics
        sscanf(line, "%{%s _%s_ %}%s", wm_split, wm_res );
        line = sprintf("%{%s <em>%s</em> %}%s", wm_split, wm_res );
      }

      line = getnextblock( line, block, settings );

      if ( sizeof ( block["currentquote"] ) < sizeof ( block["nextquote"] ) )
      {                                         // change in blockquotelevel
        converted += closeallblocks ( block ) + openquote( block ) +
openallblocks ( block ) + line;
      }
      else if ( sizeof ( block["currentquote"] ) > sizeof ( block["nextquote"] )
)
      {                                         // change in blockquotelevel
        converted += closeallblocks ( block ) + closequote( block ) +
openallblocks ( block ) + line;
        if ( block["next"] == "0" )
          block["current"] =  "";
      }
      else if ( block["next"] != "0" )               // not-empty lines
      {
        if ( block["next"] != "p" || block["current"] != "p" )
        {
          if ( block["current"] == "" )
            converted += openblock( block ) + line;
          else
            converted += closeblock( block ) + line;
        }
        else
          converted += line;
      }
      else if ( block["current"] != "" )
        converted += closeblock( block );
    }

    // unescape escaped markup-like patterns, 2nd part
    converted = replace( converted, ({ " %*", " %_", " %*_", " %_*", "** ", "__
", "**__ ", "__** ", "§%%", "%%§" }), 
                                    ({ " *",  " _",  " *_",  "_*",   "* ",  "_
",  "*_ ",   "_* ",   "[[",   "]]"   }) );

    // close all blocks before you go home
    block["next"] = "";
    block["nextquote"] = "";
    return converted + closeblock( block ) + closequote( block );
  }
  
  // returns the new blocktype
  string getnextblock ( string s, mapping(string:string) block, mapping settings
)
  {
    string begin="", temp="";

    if ( settings[ "convert_quote" ] )        // first remove blockquotes for 
    {                                         // parsing other block elements
      sscanf( s, "%[>]", temp );
      block["nextquote"] = temp;
      int i = 0;
      while ( s[i..i+3] == "&gt;" )			// sometimes ">" comes as entity for
validities sake 
      {
        block["nextquote"] += ">";
        s = ">" + s[4..];
        i++;
      }
    }
    s = s[sizeof(block["nextquote"])..];

    if ( s - " " - "\t" == "" )                 // emptyline
      block["next"] = "0";
    else
    {

      if ( settings[ "convert_header" ] )
      {
        sscanf( s, "%[=]", temp );               // header block
        if ( settings[ "remap_headers" ] )
          temp += "=" * ( settings[ "headerlimit_upper" ] - 1 );
        if ( sizeof( temp ) >= settings[ "headerlimit_upper" ] && sizeof( temp )
<= settings[ "headerlimit_lower" ])
        {
          begin += temp;
        }
        else
        {
          begin += "";
        }
      }

      if ( settings[ "convert_code" ] )
      {
        if ( s[0..0] == "|" )                     // code block
          begin += "|";
      }

      if ( settings[ "convert_lists" ] )
      {
        sscanf( s, "%[*#]", temp );              // list block
        begin += temp;
      }

      if ( begin[0..0] == "=" && settings[ "remap_headers" ] )   // strip markup
      {
        block["next"] = begin;
        s = s[sizeof(block["next"]) - settings[ "headerlimit_upper" ] + 1 ..];
      }
      else if ( begin != "" )
      {
        block["next"] = begin;
        s = s[sizeof(block["next"])..];
      }
      else
        block["next"] = "p";
    }
    return s;
  }

  string closeblock( mapping(string:string) block )     // close current block
  {
    if ( block["current"] == "p" )                      // close p
    {
      block["current"] = "";
      return "</p>\n" + openblock( block );
    }
    if ( block["current"][..0] == "h" )                 // close hn
    {
      string s = "</" + block["current"] + ">\n";
      block["current"] = "";
      return s + openblock( block );
    }
    if ( block["next"] == block["current"] )            // no change in block
element
    {
      if ( block["current"][..0]=="*" || block["current"][..0]=="#" )
        return "</li>\n<li>";
      else
        return "\n";
    }
    if ( block["current"] == "|" )              // close code
    {
      block["current"] = "";
      return "</code></pre>\n" + openblock( block );
    }
    if ( block["current"][..0]=="*" || block["current"][..0]=="#" )      //
change in list-level
    {
      string closelist="";
      string addclosetag="";
      for ( int i = sizeof( block["current"] ); i>0; i-=1 )
      {
        if ( block["current"] == block["next"] )
        {
          return closelist + addclosetag + "<li>";
        }
        else if( block["current"] == block["next"][..i-1] )
        {
          block["next"] = block["next"][i..];
          return closelist + addclosetag + openblock( block ); 
        }
        closelist += "</li>\n</" + lists[ block["current"][i-1..] ] + ">\n";
        block["current"] = block["current"][..i-2];
        addclosetag = "</li>\n";
      }
      return closelist + openblock( block );
    }
  }

  string openblock( mapping(string:string) block )   // open next block element
  {
    string openlist="";
    switch ( block["next"][..0] )
    {
      case "p":
        block["current"] = "p";
        return "<p>";
      case "|":
        block["current"] = "|";    
        return "<pre><code>";
      case "=":
        block["current"] = "h" + sizeof( block["next"] );
        return "<" + block["current"] + ">";
      case "*":
      case "#":
        for ( int i = 0; i < sizeof(block["next"]); i+=1 )
          openlist += "<" + lists[block["next"][i..i]] + ">\n<li>";
        block["current"] += block["next"];
        return openlist;
      default:
        return "";
    }
  }


  string closequote( mapping(string:string) block )     // close current quote
  {
    string closelist="";
    while ( sizeof( block["currentquote"] ) > sizeof( block["nextquote"] ) )
    {
      closelist += "</blockquote>\n";
      block["currentquote"] = block["currentquote"][1..];
    }
    return closelist;
  }



  string openquote( mapping(string:string) block )     // open current quote
  {
    string openlist="";
    do
    {
      openlist += "<blockquote>\n";
      block["currentquote"] += ">";
    }
    while ( sizeof( block["currentquote"] ) < sizeof( block["nextquote"] ) );
    return openlist;
  }


  string openallblocks( mapping(string:string) block )
  {
    block["current"] = block["next"];
    return openblock( ([ "current":"", "next":block["next"] ]) );
  }


  string closeallblocks( mapping(string:string) block )
  {
    return closeblock( ([ "current":block["current"], "next":"" ]) );
  }


  string imageify( string s, mapping settings )
  {
    array(array(string)) wm_split;
    string wm_res;
    string alttext, filename;

    sscanf( s, "%%s", wm_split, wm_res );
    foreach ( reverse( wm_split ), array i )
    {
      filename = ( ( i[1] / " " )[0] );
      alttext = ( i[1] / " " )[1..] * " ";
      wm_res = sprintf("%s<img " + "src=\"%s%s\" alt=\"%s\" />%s", i[0],
settings["imgpath"], filename, alttext, wm_res );
    }
    return wm_res;
  }


  string linkify( string s, mapping settings )
  {
    string fix_link(string l)
    {
      if(l[0..6] == "http://" || l[0..7] == "https://" || l[0..5] == "ftp://" )
        return l;
      
      if(l[0..3] == "ftp.")
        return "ftp://"+l;

      return "http://"+l;
    };
    
    array(array(string)) wm_split;
    string wm_res;
    string forcedlinktext, forcedurl;
    string nofollow_attrib = "rel=\"nofollow\" " * settings[ "c_nofollow" ];

    foreach( settings[ "shortcuts" ], mapping shortcut )
    {
       sscanf( s, "%{%s[[" + shortcut["pattern"] + "%s]]%}%s", wm_split, wm_res
);
       foreach ( reverse( wm_split ), array i )
       {
         forcedurl = ( ( i[1] / " " )[0] );
         forcedlinktext = ( i[1] / " " )[1..] * " ";
         if ( forcedlinktext == "" )
           forcedlinktext = 0;
         wm_res = sprintf("%s<a " + nofollow_attrib  + "href=\"%s%s\">%s</a>",
i[0], shortcut["url"], forcedurl, forcedlinktext || shortcut["linktext"] || (
shortcut["pattern"] + forcedurl ) ) + wm_res;
       }
       s = wm_res;
    }
    
    if ( settings[ "forcedlinks" ] )
    {
      s = utf8_to_string( forcedlink->replace( string_to_utf8( s ), lambda(
string l )
        {
          l = fix_link( l[2..sizeof(l) - 3] );
          string linktext = ( l / " ")[1..] * " ";
          l = (l / " ")[0];
          if ( linktext == "" )
            linktext = l - "http://"; 
          return "<a " + nofollow_attrib  + "href=\"" + l + "\">" + linktext +
"</a>"; 
        }
      ) );
    }

    Parser.HTML parser = Parser.HTML();

    if ( settings[ "linkify" ] )
    {
      parser->add_container( "a", lambda( Parser.HTML p, mapping args )
                               { return ({ p->current() }); });
      parser->_set_data_callback( lambda( Parser.HTML p, string data )
        { return ({ utf8_to_string( link_regexp->replace( string_to_utf8( data
), lambda( string link )
                     {
                       link = link[ 0..0 ] + fix_link( link[ 1.. ] );
                       return link[ 0..0 ] + "<a " + nofollow_attrib  +
"href=\"" + link[ 1.. ] + "\">" + link[ 1.. ] + "</a>";
                     }) ) }); });
    }

    return parser->finish( s )->read();
  }

  class Frame
  {
    inherit RXML.Frame;

    array do_return(RequestID id)
    {
      content = content||"";
      
      mapping flagconvert = ([ "on":1, "off":0 ]);
      mapping settings = ([]);

// --------------------- Tagspecific Configuration -----------------------

      if( args->lists )
        settings["convert_lists"] = flagconvert[ args->lists ];
      else
        settings["convert_lists"] = convert_lists;

      if( args->code )
        settings["convert_code"] = flagconvert[ args->code ];
      else
        settings["convert_code"] = convert_code;

      if( args->quote )
        settings["convert_quote"] = flagconvert[ args->quote ];
      else
        settings["convert_quote"] = convert_quote;

      if( args->acronyms )
        settings["convert_acronyms"] = flagconvert[ args->acronyms ];
      else
        settings["convert_acronyms"] = convert_acronyms;

      if( args->headers )
        settings["convert_header"] = flagconvert[ args->headers ];
      else
        settings["convert_header"] = convert_header;

      if( (int)args["headerlimit-upper"] <= 6 && (int)args["headerlimit-upper"]
>= 1 )
        settings["headerlimit_upper"] = (int)args["headerlimit-upper"];
      else
        settings["headerlimit_upper"] = headerlimit_upper;

      if( (int)args["headerlimit-lower"] <= 6 && (int)args["headerlimit-lower"]
>= 1 )
        settings["headerlimit_lower"] = (int)args["headerlimit-lower"];
      else
        settings["headerlimit_lower"] = headerlimit_lower;

      if ( settings["headerlimit_lower"] < settings["headerlimit_upper"] )
        settings["headerlimit_lower"] = settings["headerlimit_upper"];

      if( args["remap-headers"] )
        settings["remap_headers"] = flagconvert[ args["remap-headers"] ];
      else
        settings["remap_headers"] = remap_headers;

      if( args->charformat )
        settings["convert_format"] = flagconvert[ args->charformat ];
      else
        settings["convert_format"] = convert_format;
 
      if( args->forcedlinks )
        settings["forcedlinks"] = flagconvert[ args->forcedlinks ];
      else
        settings["forcedlinks"] = c_forcedlinks;
 
      if( args->linkify )
        settings["linkify"] = flagconvert[ args->linkify ];
      else
        settings["linkify"] = c_linkify;
 
      if( args->nofollow )
        settings["c_nofollow"] = flagconvert[ args->nofollow ];
      else
        settings["c_nofollow"] = c_nofollow;
 
      if( args->images)
        settings["convert_images"] = flagconvert[ args->images ];
      else
        settings["convert_images"] = convert_images;

      if( args->imgpath)
        settings["imgpath"] = args->imgpath;
      else
        settings["imgpath"] = imgpath;

      if( args->separator )
        settings["separator"] = args->separator;
      else
        settings["separator"] = shortcuts_separator;

      if( args->shortcuts != "" && args->shortcuts )
      {
        foreach( ( args->shortcuts / settings["separator"] )/3.0, array sc )
          settings["shortcuts"] += ({ ([ "pattern":sc[0], "url":sc[1],
"linktext":sc[2] ]) });
      }
      else if ( args->shortcuts )
        settings["shortcuts"] = ({ });
      else
        settings["shortcuts"] = shortcuts;

      settings["args"] = "";

      if( args->userhelp )
        settings["userhelp"] = args->userhelp;

      if( args->sandbox )
        settings["sandbox"] = args->sandbox;

      foreach( (array)args, array i )
      {
        if ( i[0] != "userhelp" && i[0] != "sandbox" )
          settings["args"] += " " + i[0] + "=\"" + i[1] + "\"";
      }

      content = parsewiki( content, settings, id->variables->_sandbox );

      return ({ content });
    }
  }
  // --------------------- WikiMarkup Userhelp & Sandbox -----------------------
 
  string userdocs( mapping settings, string s, string sandbox )
  {
    string help = "";
    string example = "";
    
    if ( settings["userhelp"] == "long" )
    {
      help = "<h2>WikiMarkup Syntax</h2>\n"
         "<h4>Paragraphs</h4>\n"
         "<p>Paragraphs are separated by empty lines (i.e. two linebreaks). \n"
         "  A single linebreak within a paragraph has no influence on the
formatting \n"
         "  of the converted XHTML (i.e. no <tag>br /</tag> is inserted).</p>\n";

      if ( settings[ "convert_format" ] ) {
        help += "<h4>Character formating: bold and italic text</h4>\n"
           "<p>If you want to format text in bold or italic fonts wrap the text
\n"
           "  in this markup: <strong>&quot; _&quot;</strong> (italic),
<strong>&quot; \n"
           "  *&quot;</strong> (strong) or <strong>&quot; _*&quot;</strong>
(strong and \n"
           "  italic). Be careful to combine the markup character always with a
blank \n"
           "  space.</p>\n";
      }

      if ( settings[ "convert_lists" ] ) {
        help += "<h4>Lists</h4>\n"
           "<p>Lists are formated by <strong>*</strong> (unordered lists) \n"
           "<strong>#</strong> (ordered lists) at the beginning of a line. Lists
may \n"
           "  be nested by joining * and # (e.g. **# is an ordered list nested
in an \n"
           "  unordered list which is again nested in an unordered list.</p>\n";
      }

      if ( settings[ "convert_code" ] ) {
        help += "<h4>Code listings</h4>\n"
           "<p>If you want to format blocks of code (codelistings) start the
lines with \n"
           "  a <strong>|</strong>.</p>\n";
      }

      if ( settings[ "convert_quote" ] ) {
        help += "<h4>Blockquote</h4>\n"
           "<p>If you want to format blockquotes start the lines with \n"
           "  a <strong>&gt;</strong>. You can nest blockquotes and use other
block formats inside blockquotes.</p>\n";
      }

      if ( settings[ "convert_header" ] ) {
        help += "<h4>Headers</h4>\n"
           "<p>Headers of a certain level are formated with the according number
of \n"
           "  <strong>=</strong> at the beginning of a line. There are <strong>"
           + ( settings[ "headerlimit_lower" ] - settings[ "headerlimit_upper" ]
+ 1 )
           + " levels of headers available (&quot;"
           + "=" * ( settings[ "remap_headers" ] || settings[
"headerlimit_upper" ] )
           + " to " + "=" * ( settings[ "remap_headers" ] * ( settings[
"headerlimit_lower" ]
                   - settings[ "headerlimit_upper" ] + 1 ) 
                   || settings[ "headerlimit_lower" ] )
           + "&quot;).</strong>.</p>\n";
      }

      if ( settings[ "convert_acronyms" ] ) {
        help += "<h4>Acronyms (and Abbreviations)</h4>\n"
           "<p>If you want to tag acronyms you can add a definition:\n"
           " <strong>acronym_(definition)</strong>.</p>\n";
      }

      if ( settings[ "linkify" ] ) {
        help +=  "<h4>Automatic link conversion (&quot;linkify&quot;)</h4>\n"
           "  <p>Textlinks of the common link-patterns <strong>http://</strong>,
\n"
           "  <strong>https://</strong>, <strong>ftp://</strong>,
<strong>www.</strong> or \n"
           "  <strong>ftp</strong>.</strong> will will automatically converted
into \n"
           "  clickable hyperlinks.\n";
      }

      if ( settings[ "forcedlinks" ] ) {
        help += "<h4>Forced Links</h4>\n"
           "  <p>If you want to force text to hyperlink conversion you can do
this with\n"
           "  the syntax <strong>[[url]]</strong> or <strong>[[url
linktext]]</strong>. \n"
           "  The text after the first blank will be the visible text on the
link, if\n"
           "  omitted the url will be shown.</p>\n";
      }

      if ( sizeof( settings[ "shortcuts" ] ) ) {
        help += "<h4>Shortcut links</h4>\n"
           "<p>You can use shortcut links to set easily an reference to certain
standard \n"
           "  link destinations.</p>\n"
           "  <p>Available shortcut links:</p>\n"
           "  <table cellspacing=\"0\" cellpadding=\"4\" border=\"1\">\n"
           "    <tr>\n"
           "     
<th>Pattern&nbsp;</th><th>Url&nbsp;</th><th>Linktext&nbsp;</th>\n"
           "    </tr>\n";
        foreach( settings[ "shortcuts" ], mapping i )
          help += "<tr><td>" + i["pattern"] + "&nbsp;</td><td>" + i["url"] +
"&nbsp;</td><td>" + i["linktext"] + "&nbsp;</td></tr>\n";
        help += "  </table>\n"
         "  <p>You can override a predefined linktext by adding a friendly name
after a\n"
         "  blank: e.g. [[Bug123 This is a link to Bug 123]].</p>\n";
      }

      help += "<h4>Escaping markup characters</h4>\n"
        "<p>If you have to escape markup characters (i.e. if you do not want to
use \n"
          "  them as markup but as simple text): </p>\n"
          "  <ul> \n";

      if ( settings[ "convert_format" ] )
          help += "  <li>Duplicate them for character formating (e.g. **, __,
**__)</li>\n";
      help +=  "  <li>Prefix them with a blank space for block formatting (*,#,=
or | \n"
        "    at the beginning of a line)</li>\n";
      if ( sizeof( settings[ "shortcuts" ] ) || settings[ "forcedlinks" ] )
         help += "  <li>If you want to use double square brackets
&quot;[[text]]&quot; triple them &quot;[[[text]]]&quot; in the markup.</li>\n";
      help += "  </ul>\n";

      if ( settings[ "convert_images" ] ) {
        help +=  "<h4>Insert images</h4>\n"
           "  <p>Images can inserted with <strong>[[image::imgfilename.jpg Alt
text]]</strong>. \n"
           "  Images should reside in the location <strong>&quot;" + settings[
"imgpath" ] + "&quot;</strong>\n";
      }

    }

    example = "This is a simple paragraph\n"
      "which ignores any linebreak. \n";
    if ( settings[ "convert_acronyms" ] )
      example += "FOAF_(Friend Of A Friend) is an acronym.\n";
 
      example += "\n";

    if ( settings[ "convert_lists" ] ) {
      example += "* this is an unordered list\n"
        "* second list item\n"
        "*# containing a nested ordered list\n"
        "*# with two items\n"
        "\n";
    }
    if ( settings[ "convert_format" ] ) {
      example += "Character formating: _italic,_ *bold* and _*bold and italic
text*_ .\n"
        "\n";
    }
    if ( settings[ "linkify" ] ) {
      example += "Automatically converted links: http://roxen.sodazitron.at and
www.chilimoon.org .\n"
        "\n";
    }
    if ( settings[ "forcedlinks" ] ) {
      example += "Forced link conversion [chilimoon.sodazitron.com], also with
friedly text on the \n"
        "link to [[chilimoon.sodazitron.com/listarchive/ Roxen Mailinglist
Archive]]. \n"
        "\n";
    }
    if ( sizeof( settings[ "shortcuts" ] ) ) {
      example += "You can also set shortcuts to some standard link
destinations:\n";
      foreach( settings[ "shortcuts" ], mapping i )
        example += "* [[" + i["pattern"] + "xyz]], \n";
      example += "where xyz is an individual identifier which will be added to
the Url.\n"
        "A friendly text can be added after a blank.\n"
        "\n";
    }
    if ( sizeof( settings[ "shortcuts" ] ) || settings[ "forcedlinks" ] ) {
      example += "Sometimes you don't want to linkify your [[[text]]].\n"
        "\n";
    }
    if ( settings[ "convert_code" ] ) {
      example += "|code = \"a\";\n"
        "|code += \" and b\"\n"
        "\n";
    }
    if ( settings[ "convert_quote" ] ) {
      example += "> Some quoted text.\n\n";
    }
    if ( settings[ "convert_header" ] ) {
      example += "Available headers:\n";
      for( int i = settings[ "headerlimit_upper" ] - settings[ "remap_headers" ]
* ( settings[ "headerlimit_upper" ] - 1 ); 
           i <= settings[ "headerlimit_lower" ] - settings[ "remap_headers" ] *
( settings[ "headerlimit_upper" ] - 1 ); 
           i++ )
        example += "=" * i + "Header " + i + "\n";
    }

    if ( settings["sandbox"] )
    {
      if ( sizeof( s - " " - "\n" - "\r" ) > 0 ) 
        example = s;
      if ( sandbox )
        example = sandbox;
      example = replace( example , ({ "<", ">", "&" }), ({ "&lt;", "&gt;",
"&amp;"}) );
      help = "<h1>Sandbox</h1>\n"
        "<form method='POST' action='#'>\n"
        "<textarea name='_sandbox' rows='20' cols='80'>" + example +
"</textarea>\n"
        "<input type='submit' value='Preview' />\n"
        "</form>\n"
        "<h3>Preview</h3>\n"
        "<div>\n"
        "<wikimarkup" + settings["args"] + ">\n"
        + example + "\n</wikimarkup>\n"
        "</div>";
    }

    if ( settings["userhelp"] == "long" || settings["userhelp"] == "short" ) {
      help += "<h2>WikiMarkup Example</h2>\n";
       
      if ( sizeof( s - " " - "\n" - "\r" ) == 0 )
      {
        example = "<wikimarkup" + settings["args"] + ">\n"
        + example + "\n</wikimarkup>\n";
      }
      else
        example = "<wikimarkup" + settings["args"] + ">" + s + "</wikimarkup>";

      help += "<table cellspacing='0' cellpadding='4' border='1'>"
        "<tr><th><h3>Markup</h3></th></tr>"
        "<tr><td><pre>" + replace( example, ({ "<", ">", "&" }),
( ) ) + "</pre>"
        "</td></tr>"
        "<tr><th><h3>Parsed Result</h3></th></tr>"
        "<tr><td>" + example + "</td></tr></table>";
    }

    return help;
  }
}

// --------------------- Documentation -----------------------

TAGDOCUMENTATION;
#ifdef manual
constant tagdoc=([
"wikimarkup":#"<desc type='cont'>
<p><short hide='hide'>
  Converts a simple WikiMarkup into XHMTL.</short>
  This tag is mostly useful for turning user freetext input from a form 
  into XHTML by using a simple Wiki-like markup language. Text that looks 
  like a link will be converted to a XHTML-link (except mailto links).</p>
<h2>Usage of the <tag>wikimarkup</tag>/ container</h2>
<p>The container can be used in a very simple manner - without any required
  attributes - to transform its contents from WikiMarkup into XHTML.</p>
<p>To control the translation of certain markup, you could either enable or 
  disable feaures in the administration interface's &quot;Default&quot;-tab
  or override those default values by setting the according tag-attributes.</p>
<p>For the webmaster's convenience there is an help screen for the user 
  editing and writing in WikiMarkup. This help text can be shown with the
  <i>userhelp</i> attribute and shows the appropriate syntax for all enabled
  features (via attributes or default values).</p>
<attr name='charformat' value='on|off'><p>Enables or disables simple character 
  formating markup: <strong>&quot; _&quot;</strong> (italic), <strong>&quot; 
  *&quot;</strong> (strong) or <strong>&quot; _*&quot;</strong> (strong and 
  italic).</p>
</attr>
<attr name='lists' value='on|off'><p>Enables or disables list markup: 
  <strong>*</strong> (unordered lists) and <strong>#</strong> (ordered lists)
  at the beginning of a line.</p>
</attr>
<attr name='code' value='on|off'><p>Enables or disables codelisting markup: 
  <strong>|</strong> at the beginning of a line.</p>
</attr>
<attr name='quote' value='on|off'><p>Enables or disables blockqote markup: 
  <strong>&gt;</strong> at the beginning of a line.
</p></attr>
<attr name='headers' value='on|off'><p>Enables or disables header markup: 
  a certain number <strong>=</strong> at the beginning of a line indicates the
  level of the XHMTL header.</p>
</attr>
<attr name='headerlevel-upper' value='[1-6]' default='1'><p>Sets the highest
  available level of headers. Be aware that a higher header level corresponds 
  with a lower number, 1 equals to H1 and is the default value.
  Higher levels will be ignored (i.e. parsed as simple paragraphs). If there is 
  a conflict with the headerlevel-lower (i.e. headerlevel-lower has a lower 
  number than headerlevel-upper) headerlevel-upper takes precedence.</p>
</attr>
<attr name='headerlevel-lower' value='[1-6]' default='6'><p>Sets the lowest
  available level of headers. Lower levels will be ignored (i.e. parsed as
  simple paragraphs).</p>
</attr>
<attr name='remap-headers' value='on|off'><p>Enables or disables header
remapping: 
  Your headers will automatically be remapped to the highest allowed header 
  (headerlevel-upper). E.g. if you you allow only H3 and lower, a wiki-header 
  of level 1 (&quot;=&quot;) will be remapped to a H3 in XHTML. This attribute
  is only useful, if you have headerlevel-upper different from 1.</p>
</attr>
<attr name='acronyms' value='on|off'><p>Enables or disables tagging of acronyms:
  <strong>acronym_(definition)</strong>.
</p></attr>
<attr name='linkify' value='on|off'><p>Enables or disables automatic conversion
  of text similar to common link-patterns to hyperlink. Note: there is no
automatic
  concersion of email links.</p>
</attr>
<attr name='forcedlinks' value='on|off'><p>Enables or disables a forced text to
  hyperlink conversion: all text embraced by &quot;<strong>[[]]</strong>&quot;
double 
  squared brackets will be transformed into hyperlinks. </p>
</attr>
<attr name='nofollow' value='on|off'><p>If enabled, all links in the WikiMarkup 
  will get <i>rel=&quot;nofollow&quot;</i> set. This is a new spam prevention 
  method supported by most search engines 
  (<a
href=\"http://www.google.com/googleblog/2005/01/preventing-comment-spam.html\">
  Information on preventing comment spam</a> ) </p>
 </attr>
<attr name='shortcuts' value=''
default='string-pattern,url,string-linktext[,...]'>
  <p>Sets or overrides shortcut definitions. All definitions should be provided 
  as triples &quot;pattern,url,linktext&quot; concencated by an seperator-string 
  (default: &quot;,&quot;). The last value (linktext) can be an empty string (or 
  omitted in the last definition set). Several sets can be concencated by an 
  seperator-string (defaul: &quot;,&quot;).</p>
  <p>For information on the usage of shortcut urls see the WikiMarkup
section.</p>
</attr>
<attr name='images' value='on|off'>
  <p>Enables or disables the insertion of images in the form of 
  &quot;<strong>[[image::filename.jpg Alt text]]</strong>&quot;.</p>
</p></attr>
<attr name='imgpath' value='' default='path|url'>
  <p>Sets the default path for image insertion to an path or external url.</p>
</attr>
<attr name='userhelp' value='long|short'>
  <p>With this attribute you can provide syntax information and an example of
usage
  for a Wiki-Editor. With the attribute value <strong>&quot;short&quot;</strong> 
  you will only get an example with unparsed and parsed WikiMarkup. With the 
  attribute value <strong>&quot;long&quot;</strong> additional syntax information
  will be shown. </p>
  <p>Examples and syntax will only be provided on the enabled features
  (from the &quot;Default&quot;-tab in the administration interface or set by tag
  attributes).</p>
  <p>If the content of the container is empty, a default example is shown.</p>
</attr>
<attr name='sandbox'>
  <p>The sandbox is a helpfult tool to experiment with WikiMarkup. You'll have
  a form where you can edit and preview your WikiMarkup.</p>
  <p>If the content of the container is empty, a default example is shown
initially.</p>
  <p>The default examples will only show the enabled features (from the 
  &quot;Default&quot;-tab in the administration interface or set by tag
  attributes).</p>
</attr>
<!-- <ex><wikimarkup userhelp='long' headers='on' headerlimit-upper='1'
headerlimit-lower='6' lists='on' charformat='on' linkify='on' forcedlinks='on'
code='on' shortcuts='Bug,http://community.roxen.com,Roxens
BugCrunch'></wikimarkup></ex> -->
<h2>WikiMarkup Syntax</h2>
<h4>Paragraphs</h4>
<p>Paragraphs are separated by empty lines (i.e. two linebreaks). 
  A single linebreak within a paragraph has no influence on the formatting 
  of the converted XHTML (i.e. no &lt;br /&gt; is inserted).</p>

<h4>Character formating: bold and italic text</h4>
<p>If you want to format text in bold or italic fonts wrap the text 
  in this markup: <strong>&quot; _&quot;</strong> (italic), <strong>&quot; 
  *&quot;</strong> (strong) or <strong>&quot; _*&quot;</strong> (strong and 
  italic). Be careful to combine the markup character always with a blank 
  space.</p>

<h4>Lists</h4>
<p>Lists are formated by <strong>*</strong> (unordered lists) 
<strong>#</strong> (ordered lists) at the beginning of a line. Lists may 
  be nested by joining * and # (e.g. **# is an ordered list nested in an 
  unordered list which is again nested in an unordered list.</p>

<h4>Code listings</h4>
<p>If you want to format blocks of code (codelistings) start the lines with 
  a <strong>|</strong>.</p>

<h4>Blockqotes</h4>
<p>If you want to format blockquotes start the lines with a 
  <strong>&gt;</strong>. You can nest blockquotes and use other block formats
inside blockquotes.</p>

<h4>Headers</h4>
<p>Headers of a certain level are formated with the according number of 
  <strong>=</strong> at the beginning of a line. The highest level of headers  
allowed is <strong>H1 (&quot;=&quot;)</strong>. The highest lowest   of headers
allowed is <strong>H6 (&quot;======&quot;)</strong>.</p>

<h4>Acronyms</h4>
<p>If you want to tag acronyms you you can add a definition:
  <strong>acronym_(definition)</strong>.</p>

<h4>Automatic link conversion (&quot;linkify&quot;)</h4>
  <p>Textlinks of the common link-patterns <strong>http://</strong>, 
  <strong>https://</strong>, <strong>ftp://</strong>, <strong>www.</strong> or 
  <strong>ftp</strong>.</strong> will will automatically converted into 
  clickable hyperlinks.

<h4>Forced Links</h4>
  <p>If you want to force text to hyperlink conversion you can do this with
  the syntax <strong>[[url]]</strong> or <strong>[[url linktext]]</strong>. 
  The text after the first blank will be the visible text on the link, if
  omitted the url will be shown.</p>

<h4>Shortcut links</h4>
<p>You can use shortcut links to set easily an reference to certain standard
link destinations.
  Shortcut syntax has the form [[ABCxyz]] where ABC is a defined pattern to
match with and xyz 
  is an individual identifier which will be added to your Url.</p>
  <p>Example shortcut link:</p>
  <xtable>
    <row>
      <h>Pattern&nbsp;</h><h>Url&nbsp;</h><h>Linktext&nbsp;</h>
    </row>
    <row><c>Bug&nbsp;</c><c>http://community.roxen.com&nbsp;</c><c>Roxens
BugCrunch&nbsp;</c></row>
  </xtable>
  <p>This example can be written in your text as [[Bug123]]. You can override a
predefined linktext 
  by adding a friendly name after a blank: e.g. [[Bug123 This is a link to Bug
123]].</p>

<h4>Inserting images</h4>
<p>You can insert images in the form [[image::filename.jpg Alt text]].</p>

<h4>Escaping markup characters</h4>
<p>If you have to escape markup characters (i.e. if you do not want to use 
  them as markup but as simple text): </p>
  <ul> 
  <li>Duplicate them for character formating (e.g. **, __, **__)</li>
  <li>Prefix them with a blank space for block formatting (*,#,= or | 
    at the beginning of a line)</li>
  <li>If you want to use double square brackets &quot;[[text]]&quot; triple them 
    &quot;[[[text]]]&quot; in the markup.</li>
  </ul>
<h2>WikiMarkup Example</h2>
<xtable>
  <row><c>
<pre>&lt;wikimarkup 
  linkify=\"on\" 
  charformat=\"on\" 
  headerlimit-lower=\"6\" 
  headers=\"on\" 
  code=\"on\" 
  quote=\"on\" 
  lists=\"on\" 
  forcedlinks=\"on\" 
  headerlimit-upper=\"1\" 
  shortcuts=\"Bug,http://community.roxen.com,Roxen\'s BugCrunch\"&gt;
This is a simple paragraph
which ignores any linebreak. 
FOAF_(Friend Of A Friend) is an acronym.

* this is an unordered list
* second list item
*# containing a nested ordered list
*# with two items

Character formating: _italic,_ *bold* and _*bold and italic text*_ .

Automatically converted links: http://roxen.sodazitron.at and www.chilimoon.org .

Forced link conversion [[chilimoon.sodazitron.com]], also with friedly text on
the 
link to [[chilimoon.sodazitron.com/listarchive/ Roxen Mailinglist Archive]]. 

You can also set shortcuts to some standard link destinations: 
[[Bugxyz]], where xyz is an individual identifier which will be added to the Url.
A friendly text can be added after a blank.

|code = \"a\";
|code += \" and b\"

> quoted text.

Available headers:
=Header 1
==Header 2
===Header 3
====Header 4
=====Header 5
======Header 6
&lt;/wikimarkup&gt;
</pre></c></row><row><c><p>This is a simple paragraph which ignores any
linebreak. <acronym title=\"Friend Of A Friend\">FOAF</acronym> is an
acronym.</p>
<ul>
<li> this is an unordered list </li>
<li> second list item <ol>

<li> containing a nested ordered list </li>
<li> with two items </li>
</ol>
</li>
</ul>
<p>Character formating: <em>italic,</em> <strong>bold</strong> and
<em><strong>bold and italic text</strong></em> . </p>

<p>Automatically converted links: <a
href='http://roxen.sodazitron.at'>http://roxen.sodazitron.at</a> and <a
href='http://www.chilimoon.org'>http://www.chilimoon.org</a> . </p>
<p>Forced link conversion <a
href='http://chilimoon.sodazitron.com'>chilimoon.sodazitron.com</a>, also with
friedly text on the  link to <a
href='http://chilimoon.sodazitron.com/listarchive/'>Roxen Mailinglist
Archive</a>.  </p>
<p>You can also set shortcuts to some standard link destinations: <a
href='http://community.roxen.com/xyz'>Roxen's BugCrunch</a>, where xyz is an
individual identifier which will be added to the Url. A friendly text can be
added after a blank. </p>

<pre><code>code = \"a\"; 
code += \" and b\" </code></pre>
<blockqote>quoted text.</blockqote>
<p>Available headers: </p>
<h1>Header 1 </h1>
<h2>Header 2 </h2>
<h3>Header 3 </h3>
<h4>Header 4 </h4>
<h5>Header 5 </h5>
<h6>Header 6 </h6>
</c></row></xtable>
<h2>Security notes</h2>
<p>You might want to use the <tag>noparse</tag>/ container inside the 
  <tag>wikimarkup</tag>/ container for security reasons.</p>
</desc>", ]);
#endif