> as (generally) specified in extended creole
- can be inline, or act as block on its own line
@par Usage:
@verbatim
$wiki = new SimpleWiki($raw_text);
$html = $wiki->get_html();
@endverbatim
@par
Or for iterative usage:
@verbatim
$wiki = new SimpleWiki(); //once
$wiki->prepare($raw_text);
$html = $wiki->get_html();
@endverbatim
or:
@verbatim
$wiki = new SimpleWiki();
$html = $wiki->get_html($raw_text);
@endverbatim
@par
For auto_quicktoc, register the prepared event before getting html:
@par
@verbatim
$wiki = new SimpleWiki($markup);
$wiki->register_events(array('onemit' => array($wiki,'auto_quicktoc')));
$html = $wiki->get_html();
@endverbatim
*/
#==========================================================================
#-----------------------------[ SIMPLE WIKI ]------------------------------
#==========================================================================
/**
Facade, and default method classes, macros, events and symlinks
public methods.
$wiki = new SimpleWiki($markup) - create object to process markup text
note that SimpleWiki registers a number of default behaviours with SimpleWikiEmitter
*/
class SimpleWiki
{
protected $_parser;
protected $_allow_html = TRUE;
protected $_emitter;
protected $_footnotes;
protected $_footnotereferences;
protected $_working_parser;
/**@{ @name constructor */
/** The constructor for Simplewiki. The constructor does the following:
- invokes internal instances of the parser and emitter
- registers standard callbacks, macros, and symlinks
@param string $text the markup text to be processed.
@return void
@sa prepare\n
register_class_callbacks\n
register_property_callbacks\n
register_macro_callbacks\n
register_symlinks
@par Specific operations:
*/
public function __construct($text = NULL)
{
/// instantiate and save new parser
$this->_parser = new Parser($text);
/// instantiate and save new emitter
$this->_emitter = new Emitter();
/// register standard class callbacks
$this->register_class_callbacks(
array(
'span' => array(
'subscript'=> array($this,'callback_span_subscript'),
'superscript'=> array($this,'callback_span_superscript'),
'footnote'=> array($this,'callback_span_footnote'),
'comment'=> array($this,'callback_span_comment')),
'link' => array(
'newwin' => array($this,'callback_link_newwin')),
'image' => array(
'lframe'=> array($this,'callback_image_frame'),
'rframe'=> array($this,'callback_image_frame')),
'paragraph' => array(
'nop'=> array($this,'callback_paragraph_nop'),
'div'=> array($this,'callback_paragraph_div')),
'blockdef' => array(
'lframe'=> array($this,'callback_blockdef_frame'),
'rframe'=> array($this,'callback_blockdef_frame')),
'preformatted' => array(
'html'=> array($this,'callback_pre_html')),
'code' => array(
'html'=> array($this,'callback_code_html'))
)
);
/// register standard macro callbacks
$this->register_macro_callbacks(
array(
'quicktoc' => array($this,'macro_quicktoc')
)
);
/// register standard symlinks
$this->register_symlinks(array('Anchor'=>'','Local'=>''));
}
public function __clone()
{
$this->_parser = clone $this->_parser;
$this->_emitter = clone $this->_emitter;
$this->_working_parser = NULL;
}
/**@}*/
/**@{ @name settings methods*/
/** Get or set permission to allow html in markup. ...from within "" elements.
@param boolean $bool true allows html (default), false disallows
@return boolean
@todo trace and document how allow_html is enforced
*/
public function allow_html($bool = NULL)
{
if (!is_null($bool))
$this->_allow_html = $bool;
return $this->_allow_html;
}
/** Set callbacks for class methods.
@param array $callbacks [$nodetype][$classname]=>$methodref\n
$methodref is typically array(objectref,'methodname')
@return void
$classname can be any valid class string
Callback methods are passed a DocNode object, and are expected to manipulate
and return that object
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa DocNode::$type for $nodetype options
@sa Emitter::register_class_callouts
*/
public function register_class_callbacks($callbacks)
{
$emitter = $this->_emitter;
$emitter->register_class_callouts($callbacks);
}
/** Set callbacks for property methods.
@param array $callbacks [$nodetype][$propertyname]=>$methodref\n
$methodref is typically array(objectref,'methodname')
@return void
$propertyname can be any valid class string
Callback methods are passed a DocNode object, and are expected to manipulate
and return that object
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa DocNode::$type for $nodetype options
@sa Emitter::register_property_callouts
*/
public function register_property_callbacks($callbacks)
{
$emitter = $this->_emitter;
$emitter->register_property_callouts($callbacks);
}
/** Set specific symlinks for link and image markup.
@param array $symlinks [$symlink]=>$value\n
$value should be an absolute or relative html link (href/src)
@return void
$symlink can be any string of letters with a leading uppercase letter.
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa register_symlink_handler
@sa Emitter::register_symlinks
*/
public function register_symlinks($symlinks)
{
$emitter = $this->_emitter;
$emitter->register_symlinks($symlinks);
}
/** Set symlink handler, to handle any number of symlinks.
@param method $handler typically in the form array(objectref,'methodname')
@return void
$handler is handed a DocNode object, and is expected to manipulate
and return that object.
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa register_symlinks
@sa Emitter::register_symlink_handler
*/
public function register_symlink_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_symlink_handler($handler);
}
/**
Set rawlink handler. The rawlink handler is called for any links that are not anchors, external links (with recognized prototcol), or symlinks.
@param method $handler typically in the form array(objectref,'methodname')
@return void
*/
public function register_rawlink_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_rawlink_handler($handler);
}
/**
Set charfilter handler. The charfilter handler is called by the emitter where normally htmlspecialchars
would be applied (the default), allowing clients to substitute alternate or custom character filters.
@param method $handler typically in the form array(objectref,'methodname')
@return void
*/
public function register_charfilter_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_charfilter_handler($handler);
}
/**
Set blockdef handler (and other tag type handlers). The blockdef handler is called for any known blockdef, with a param of the blockdef node.
@param method $handler typically in the form array(objectref,'methodname')
@return void
*/
public function register_blockdef_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_blockdef_handler($handler);
}
public function register_vblockdef_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_vblockdef_handler($handler);
}
public function register_itagdef_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_itagdef_handler($handler);
}
public function register_vtagdef_handler($handler)
{
$emitter = $this->_emitter;
$emitter->register_vtagdef_handler($handler);
}
/** Registers macro callbacks.
@param array $callbacks in the form [$macroname]=>$methodref\n
$methodref is typically array(objectref,'methodname')
@return void
$macro callbacks are handed a DocNode object, and are expected to manipulate
and return that object.
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa Emitter::register_macro_callouts
*/
public function register_macro_callbacks($callbacks)
{
$emitter = $this->_emitter;
$emitter->register_macro_callouts($callbacks);
}
/** Registers events.
@param array $callbacks in the form [$eventname]=>$methodref\n
$methodref is typically array(objectref,'methodname')
@return void
$eventname can be 'onemit' or 'onafteremit'
$event callbacks are handed a DocNode object, and are expected to manipulate
and return that object.
This method is a facade for the emitter object (Emitter), to which the callbacks are passed directly.
@sa Emitter::register_events
*/
public function register_events($callbacks)
{
$emitter = $this->_emitter;
$emitter->register_events($callbacks);
}
/**@}*/
/**@{ @name operational methods*/
/** Prepare the markup for processing.
@param string $raw (markup text)
@return object $this (so the method can be chained)
This method is a facade for the parser object (Parser), to which the markup is passed directly.
@sa Parser::prepare
*/
public function prepare($raw)
{
$this->_parser->prepare($raw);
return $this;
}
/** Generate html from the markup text, as passed to the constructor, or to prepare.
@sa __construct, prepare
*/
public function get_html($markup = NULL)
{
if ($markup) $this->prepare($markup);
global $profiles;
$profile = array();
$profile['parse']['start'] = microtime(true);
$dom = $this->_parser->parse();
$profile['parse']['end'] = microtime(true);
$profiles[] = $profile;
$profile = array();
$profile['emit']['start'] = microtime(true);
$retval = $this->_emitter->emit($dom);
$profile['emit']['end'] = microtime(true);
$profiles[] = $profile;
return $retval;
}
/**@}*/
/**@{ @name accessor methods */
/** Get or set the instance of the parser used by Simplewiki.
The parser method allows the caller to set a different instance of the parser,
or to obtain the current instance of the parser.
@param object $parser class Parser
@return object, class Parser
@sa Parser
*/
public function parser($parser = NULL)
{
if (!is_null($parser))
$this->_parser = $parser;
return $this->_parser;
}
/** Get or set the instance of the emitter used by Simplewiki.
The emitter method allows the caller to set a different instance of the emitter,
or to obtain the current instance of the emitter.
@param $emitter object, optional, class Emitter
@sa Emitter
*/
public function emitter($emitter = NULL )
{
if (!is_null($emitter))
$this->_emitter = $emitter;
return $this->_emitter;
}
/** Get metadata from prepared markup.
Gets the metadata from the metadata section of the parsed markup.
@return object
@sa Parser::metadata
*/
public function get_metadata()
{
return $this->_parser->metadata();
}
/** Get marker data from prepared markup.
@return object
@sa Parser::markerdata
*/
public function get_markerdata()
{
return $this->_parser->markerdata();
}
/** Get markup from prepared markup.
@return string
@sa Parser::preprocessed_markup
*/
public function get_preprocessed_markup()
{
return $this->_parser->preprocessed_markup();
}
/**@}*/
#-----------------------------[ STANDARD CLASS CALLBACKS ]-----------------------------#
/**@{ @name standard class callbacks
Standard class callbacks are registered in the constructor (__construct) with register_class_callbacks
*/
/** Standard callback |:p nop:| (no paragraph).
@param object $node class DocNode
@return object DocNode
*/
public function callback_paragraph_nop($node)
{
$node->opentag_head = '';
$node->opentag_tail = '';
$node->closetag = '';
$node->decoration = new StdClass;
return $node;
}
/** Standard callback |:p div:| (paragraph to div).
@param object $node class DocNode
@return object DocNode
*/
public function callback_paragraph_div($node)
{
$node->opentag_head = 'opentag_tail = '>';
$node->closetag = '
';
unset($node->decoration->classes[array_search('div',$node->decoration->classes)]);
return $node;
}
/** Standard callback %c html% code to html.
@param object $node class DocNode
@return object DocNode
*/
public function callback_code_html($node)
{
if ($this->_allow_html)
{
$node->opentag_head = '';
$node->opentag_tail = '';
$node->closetag = '';
$node->escapecontent = FALSE;
$node->decoration = new StdClass;
}
return $node;
}
/** Standard callback (:div frame:) frame.
@param object $node class DocNode
@return object DocNode
*/
public function callback_blockdef_frame($node)
{
if ($node->blocktag != 'div') return $node;
$node->decoration->classes[] = 'frame';
return $node;
}
/** Standard callback left or right frame. \%i lframe%{{... or \%i lframe%{{...
@param object $node class DocNode
@return object DocNode
*/
public function callback_image_frame($node)
{
$lframeindex = array_search('lframe',$node->decoration->classes);
$rframeindex = array_search('rframe',$node->decoration->classes);
if ($lframeindex === FALSE) // must be rframe
{
unset($node->decoration->classes[$rframeindex]);
$orientation = 'rframe';
}
else // must be lframe;
{
unset($node->decoration->classes[$lframeindex]);
$orientation = 'lframe';
}
$opentag_head = $node->opentag_head;
$opentag_tail = $node->opentag_tail;
$opentag_head = "" . $opentag_head;
$opentag_tail .= "
{$node->caption}
";
$node->opentag_head = $opentag_head;
$node->opentag_tail = $opentag_tail;
return $node;
}
/** Standard callback block html. |:pre html:|{{{... pre to html.
@param object $node class DocNode
@return object DocNode
*/
public function callback_pre_html($node)
{
if ($this->_allow_html)
{
$node->opentag_head = '';
$node->opentag_tail = '';
$node->closetag = '';
$node->escapecontent = FALSE;
$node->decoration = new StdClass;
}
return $node;
}
/** Standard callback \%s comment%...%%.
@param object $node class DocNode
@return object DocNode
*/
public function callback_span_comment($node)
{
$node->opentag_head = '';
$node->opentag_tail = '';
$node->closetag = '';
$node->decoration = new StdClass;
$node->elementcontent = '';
return $node;
}
/** Standard callback \%s footnote%...%%.
@param object $node class DocNode
@return object DocNode
*/
public function callback_span_footnote($node)
{
$footnotes = $this->_footnotes;
if (empty($footnotes)) // inititalize footnote system
{
$footnotes = new StdClass;
$footnotes->count = 0;
$footnotes->list = array();
if (empty($this->_working_parser))
$this->_working_parser = new Parser('');
$this->register_events(
array('onafteremit' =>
array($this,'render_footnotes')));
}
// set aside footnote
$footnote = $footnotes->list[] = $node;
$count = $footnote->id = ++$footnotes->count;
// generate markup for link
$parser = $this->_working_parser;
$markup =
'%s superscript%[[#footnotemarker'
. $count
. ']][[#footnote'
. $count
. '|['
. $count.']]]%%';
$dom = $parser->prepare($markup)->parse();
// replace footnote body with reference in body of text
$span = $dom->children[0]->children[0]; // document/paragraph
$span->parent = $footnote->parent;
$footnote->parent = NULL;
$this->_footnotes = $footnotes;
// create lookups for referenced footnotes
$footnote->rendered = FALSE;
$footnotereference = @$footnote->decoration->attributes['footnotereference'];
if (!empty($footnotereference))
{
$this->_footnotereferences[$footnotereference][] = $footnote;
}
// fix and return footnote link span
$span->elementcontent = $this->_emitter->emit_children($span);
$span = $this->callback_span_superscript($span); // set html elements
return $span;
}
/** Standard callback \%s superscript%.
@param object $node class DocNode
@return object DocNode
*/
public function callback_span_superscript($node)
{
$node->opentag_head = 'opentag_tail = '>';
$node->closetag = '';
unset($node->decoration->classes[array_search('superscript',$node->decoration->classes)]);
return $node;
}
/** Standard callback \%s subscript%.
@param object $node class DocNode
@return object DocNode
*/
public function callback_span_subscript($node)
{
$node->opentag_head = 'opentag_tail = '>';
$node->closetag = '';
unset($node->decoration->classes[array_search('subscript',$node->decoration->classes)]);
return $node;
}
/** Standard callback \%l newwin%[[...
@param object $node class DocNode
@return object DocNode
*/
public function callback_link_newwin($node)
{
$node->decoration->attributes['target'] = '_blank';
$node->decoration->attributedelimiters['target'] = '"';
unset($node->decoration->classes[array_search('newwin',$node->decoration->classes)]);
return $node;
}
/**@}*/
#-----------------------------[ DEFAULT MACROS ]----------------------------------#
/**@{@name standard macros */
/** quicktoc macro.
enclosing div is given class 'quicktoc-platform'\n
to suppress heading inclusion, give header element the attribute toc=no
*/
public function macro_quicktoc($node)
{
$caption = $node->caption;
if (!$caption) $caption = 'Table of Contents';
# move to root of document
$document = $this->_parser->get_selected_ancestor($node,array('document'));
# collect all headings
$contents = array();
$contents = $this->macro_quicktoc_assemble_headings($document,$contents);
# set data for content line items
$contentheadings = array();
$count = 0;
foreach ($contents as $heading)
{
// assign id
$count++;
$sessionid = 'heading' . $count;
$headingid = @$heading->decoration->attributes['id'];
if (is_null($headingid))
{
$headingid = $heading->decoration->attributes['id'] = $sessionid;
$heading->decoration->attributedelimiters['id'] = '"';
}
$heading->decoration->attributes['contentsid'] = $sessionid;
$heading->decoration->attributedelimiters['contentsid'] = '"';
$contentheading = new StdClass;
$contentheading->id = $headingid;
// assign text
$contentheading->text = $this->_emitter->emit_node_text($heading);
// assign level
$contentheading->level = $heading->level;
$contentheading->nesting = $heading->nesting;
$contentheadings[] = $contentheading;
}
# generate markup for table of contents
if (!empty($contentheadings))
{
$markup = '';
// calculate relative depth, beginning with 1
$contentdepth = 1;
$previouscontentdepth = $contentdepth;
$contentdepthstack = array();
$flooroffset = 0; // lowest depth, controlled by nesting
$flooroffsetstack = array();
// make sure there is no change for first item
// - markup requires starting depth of 1
$previouslevel = $contentheadings[0]->level;
$previouslevelstack = array();
$previousnestinglevel = $contentheadings[0]->nesting;
// process collected elements
foreach ($contentheadings as $contentheading)
{
// calculate depth
$level = $contentheading->level;
$nestinglevel = $contentheading->nesting;
if ($nestinglevel > $previousnestinglevel)
{ // save state
array_push($flooroffsetstack,$flooroffset);
array_push($contentdepthstack,$contentdepth);
array_push($previouslevelstack,$previouslevel);
// set floor
$flooroffset = $contentdepth;
}
elseif ($nestinglevel < $previousnestinglevel)
{ // restore state
if (!empty($flooroffsetstack))
{
$flooroffset = array_pop($flooroffsetstack);
$contentdepth = array_pop($contentdepthstack);
$previouslevel = array_pop($previouslevelstack);
}
}
if ($level > $previouslevel)
$contentdepth++;
elseif ($level < $previouslevel)
$contentdepth--;
$contentdepth = min($level,$contentdepth);
$contentdepth = max($contentdepth,1);
$previouslevel = $level;
$previousnestinglevel = $nestinglevel;
$previouscontentdepth = $contentdepth;
// generate markup
$markup .=
str_repeat('*',$contentdepth + $flooroffset)
. '[[#'
. $contentheading->id
. '|'
. $contentheading->text
. "]]\n";
}
// enclose markup
$caption = preg_replace('/\\n/','',$caption); // to allow \\ (newline) markup in caption
$markup =
"(:div id=quicktoc-platform:)\n
(:div id=quicktoc-header:)\n
|:p div id=quicktoc-caption quicktoc-closed:|%c html%{{{" . $caption . "}}}\n
(:divend:)\n
(:div id=quicktoc-body:)\n"
. $markup . "\n
(:divend:)\n
(:divend:)\n";
# generate html for table of contents
// allow html
$allowhtml = $this->allow_html();
$this->allow_html(TRUE);
$wiki = new SimpleWiki($markup);
$wiki->register_symlinks($this->emitter()->symlinks());
$wiki->register_symlink_handler($this->emitter()->symlink_handler());
$node->output = $wiki->get_html();
$this->allow_html($allowhtml);
}
return $node;
}
/**@}*/
protected function macro_quicktoc_assemble_headings($node,$contents)
{
static $nesting = -1;
$nesting++;
$isheading = false;
if ($node->type == DocNode::HEADING)
{
$notoc = @$node->decoration->attributes['toc'] == 'no';
if ($notoc)
{
unset($node->decoration->attributes['toc']);
}
else
{
$isheading = TRUE;
$node->nesting = $nesting;
$contents[] = $node;
}
}
if (!$isheading)
{
$children = $node->children;
if (!empty($children))
{
foreach ($children as $child)
{
$contents = $this->macro_quicktoc_assemble_headings($child,$contents);
}
}
}
$nesting--;
return $contents;
}
#----------------------[ NATIVE EVENT CALLBACKS ]--------------------------#
/**@{ @name standard event methods */
# triggered at onafteremit event...
public function render_footnotes($document)
{
$footnotes = $this->_footnotes->list;
$markup = '';
foreach ($footnotes as $footnote)
{
if ($footnote->rendered) continue; // has been rendered as reference to other
// render base footnote
$id = $footnote->id;
$markup .=
'* [[#footnotemarker' . $id . '|^]][' . $id .'][[#footnote' . $id . ']]';
$footnotename = @$footnote->decoration->attributes['footnotename'];
if (!empty($footnotename)) // possibly referenced by others
{
$references = @$this->_footnotereferences[$footnotename];
if (!empty($references)) // add references
{
foreach ($references as $reference)
{
$ref = $reference->id;
$markup .=
' [[#footnotemarker'
. $ref
. '|^]]['
. $ref
.'][[#footnote'
. $ref
. ']]';
$reference->rendered=true;
}
}
}
$elementcontent = $footnote->elementcontent;
$elementcontent = preg_replace('/\\n/','',$elementcontent); // to allow \\ (newline) markup in footnotes
$markup .=
' %c html%{{{'
. $elementcontent
. "}}}\n";
}
// wrap footnote block
$markup =
"\n|:b divider:|\n----\n"
. "(:div footnoteblock:)\n======Footnotes:======\n"
. $markup
. "(:divend:)\n";
// allow html
$allowhtml = $this->allow_html();
$this->allow_html(TRUE);
$wiki = new SimpleWiki($markup);
$wiki->register_symlinks($this->emitter()->symlinks());
$wiki->register_symlink_handler($this->emitter()->symlink_handler());
$document->closetag .= $wiki->get_html();
$this->allow_html($allowhtml);
return $document;
}
// $wiki->register_events(array('onemit' =>array($wiki,'auto_quicktoc')));
public function auto_quicktoc($document)
{
$markup = '<>';
if (empty($this->_working_parser))
$this->_working_parser = new Parser('');
$parser = $this->_working_parser;
$dom = $parser->prepare($markup)->parse();
$tocnode = $dom->children[0];
$tocnode->parent = $document;
array_unshift($document->children,$tocnode);
return $document;
}
}