Re: [cowiki-dev] creating nested directories?

Archie Campbell <[email protected]>
Newsgroups gmane.comp.php.cowiki.devel
Message-ID <[email protected]>
Please can you fix it before I wade in and break something. The parser 
outputs entities for bad tags, so that the xml doesn't get confused.

Lines ending in <CR> i.e.

<textarea>
A
B
C
</textarea>

*are* paragraphised, as expected. Also when a line is inserted between 
characters.

Crunch time: will it be faster to create unit tests for other 
processing, or rely on eyeball scrapes of test documents?

Only you can tell.

Regards,

Archie

P.S. Have some new files. Bugfixes.

PAUL HANCHETT wrote:

> OK Archie, here's what I've got so far.  You need Pear::PHPUnit to run 
> this.  I'm doing it from inside Eclipse.
>
>     * I'm only testing basic formatting-- Bold, Italic, Underline,
>       Strikeout, Fixed, and paragraphs.  Come to think of it I don't
>       test the <p> or <b> tag...
>
>     * I've found that a bogus tag (not recognized html) causes the
>       parser to hang somehow.  Haven't traced it to see what's going
>       on.  The test is commented out in unittest.php.
>
>     * Also, lines ending only in <CR> are not seen as new paragraphs. 
>       Every other combination is OK.
>
> The stuff in amber is where the output of the parser uses &gt for > 
> and I haven't got a conversion on the test.  I'll get this done or you 
> can fix it if you want.
>
> Here are the test results from my machine:
>
>     TestCase ParserBasicFormattingTest->testEmptyString() passed
>     TestCase ParserBasicFormattingTest->testBlankLines() passed
>     TestCase ParserBasicFormattingTest->testShortLine() passed
>     TestCase ParserBasicFormattingTest->testParagraphLine() passed
>     TestCase ParserBasicFormattingTest->testTwoParagraphs() passed
>     TestCase ParserBasicFormattingTest->testCRLFLineEndings() passed
>     TestCase ParserBasicFormattingTest->testLFCRLineEndings() passed
>     TestCase ParserBoldAsteriskFormattingTest->testEmphasizeWord() passed
>     TestCase ParserBoldAsteriskFormattingTest->testEmphasizePhrase()
>     passed
>     TestCase ParserBoldAsteriskFormattingTest->testEmphasizeLine() passed
>     TestCase
>     ParserBoldAsteriskFormattingTest->testMismatchedEmphasizedPhrase()
>     passed
>     TestCase
>     ParserBoldAsteriskFormattingTest->testMismatchedEmphasizedPhrase2()
>     passed
>     TestCase
>     ParserBoldAsteriskFormattingTest->testMismatchedEmphasizedPhrase3()
>     passed
>     TestCase
>     ParserBoldAsteriskFormattingTest->testMismatchedEmphasizedPhrase4()
>     passed
>     TestCase
>     ParserBoldAsteriskFormattingTest->testEmphasizeEndsAtEoln() passed
>     TestCase ParserItalicSlashFormattingTest->testEmphasizeWord() passed
>     TestCase ParserItalicSlashFormattingTest->testEmphasizePhrase() passed
>     TestCase ParserItalicSlashFormattingTest->testEmphasizeLine() passed
>     TestCase
>     ParserItalicSlashFormattingTest->testMismatchedEmphasizedPhrase()
>     passed
>     TestCase
>     ParserItalicSlashFormattingTest->testMismatchedEmphasizedPhrase2()
>     passed
>     TestCase
>     ParserItalicSlashFormattingTest->testMismatchedEmphasizedPhrase3()
>     passed
>     TestCase
>     ParserItalicSlashFormattingTest->testMismatchedEmphasizedPhrase4()
>     passed
>     TestCase
>     ParserItalicSlashFormattingTest->testEmphasizeEndsAtEoln() passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testEmphasizeWord() passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testEmphasizePhrase() passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testEmphasizeLine() passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testMismatchedEmphasizedPhrase()
>     passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testMismatchedEmphasizedPhrase2()
>     passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testMismatchedEmphasizedPhrase3()
>     passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testMismatchedEmphasizedPhrase4()
>     passed
>     TestCase
>     ParserUnderlineUnderbarFormattingTest->testEmphasizeEndsAtEoln()
>     passed
>     TestCase ParserFixedwidthEqualsFormattingTest->testEmphasizeWord()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testEmphasizePhrase() passed
>     TestCase ParserFixedwidthEqualsFormattingTest->testEmphasizeLine()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testMismatchedEmphasizedPhrase()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testMismatchedEmphasizedPhrase2()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testMismatchedEmphasizedPhrase3()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testMismatchedEmphasizedPhrase4()
>     passed
>     TestCase
>     ParserFixedwidthEqualsFormattingTest->testEmphasizeEndsAtEoln() passed
>     TestCase ParserstrikeoutHyphenFormattingTest->testEmphasizeWord()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testEmphasizePhrase() passed
>     TestCase ParserstrikeoutHyphenFormattingTest->testEmphasizeLine()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testMismatchedEmphasizedPhrase()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testMismatchedEmphasizedPhrase2()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testMismatchedEmphasizedPhrase3()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testMismatchedEmphasizedPhrase4()
>     passed
>     TestCase
>     ParserstrikeoutHyphenFormattingTest->testEmphasizeEndsAtEoln() passed
>     TestCase ParserBoldTagFormattingTest->testEmphasizeWord() passed
>     TestCase ParserBoldTagFormattingTest->testEmphasizePhrase() passed
>     TestCase ParserBoldTagFormattingTest->testEmphasizeLine() passed
>     TestCase ParserItalicTagFormattingTest->testEmphasizeWord() passed
>     TestCase ParserItalicTagFormattingTest->testEmphasizePhrase() passed
>     TestCase ParserItalicTagFormattingTest->testEmphasizeLine() passed
>     TestCase ParserUnderlineTagFormattingTest->testEmphasizeWord() passed
>     TestCase ParserUnderlineTagFormattingTest->testEmphasizePhrase()
>     passed
>     TestCase ParserUnderlineTagFormattingTest->testEmphasizeLine() passed
>     TestCase ParserFixedwidthTagFormattingTest->testEmphasizeWord() passed
>     TestCase ParserFixedwidthTagFormattingTest->testEmphasizePhrase()
>     passed
>     TestCase ParserFixedwidthTagFormattingTest->testEmphasizeLine() passed
>     TestCase ParserBasicFormattingTest->testCROnlyLineEndings()
>     failed: Multiple lines ending in CR expected
>     <p>A</p><p>B</p><p>C</p>, actual <p>A
>     B
>
>     C</p>
>     TestCase
>     ParserBoldTagFormattingTest->testMismatchedEmphasizedPhrase()
>     failed: expected <p>this phrase <b>is mismatched</p>, actual
>     <p>this phrase &lt;b&gt;is mismatched</p>
>     TestCase
>     ParserBoldTagFormattingTest->testMismatchedEmphasizedPhrase2()
>     failed: expected <p>this phrase <b origin="html">is</b>
>     <b>mismatched in an intersting way</p>, actual <p>this phrase <b
>     origin="html">is</b> &lt;b&gt;mismatched in an intersting way</p>
>     TestCase ParserBoldTagFormattingTest->testEmphasizeEndsAtEoln()
>     failed: expected <p>A <b>Emphasize phrase</p><p>cannot
>     continue</b> across a line.</p>, actual <p>A &lt;b&gt;Emphasize
>     phrase</p><p>cannot continue&lt;/b&gt; across a line.</p>
>     TestCase
>     ParserItalicTagFormattingTest->testMismatchedEmphasizedPhrase()
>     failed: expected <p>this phrase <i>is mismatched</p>, actual
>     <p>this phrase &lt;i&gt;is mismatched</p>
>     TestCase
>     ParserItalicTagFormattingTest->testMismatchedEmphasizedPhrase2()
>     failed: expected <p>this phrase <i origin="html">is</i>
>     <i>mismatched in an intersting way</p>, actual <p>this phrase <i
>     origin="html">is</i> &lt;i&gt;mismatched in an intersting way</p>
>     TestCase ParserItalicTagFormattingTest->testEmphasizeEndsAtEoln()
>     failed: expected <p>A <i>Emphasize phrase</p><p>cannot
>     continue</i> across a line.</p>, actual <p>A &lt;i&gt;Emphasize
>     phrase</p><p>cannot continue&lt;/i&gt; across a line.</p>
>     TestCase
>     ParserUnderlineTagFormattingTest->testMismatchedEmphasizedPhrase()
>     failed: expected <p>this phrase <u>is mismatched</p>, actual
>     <p>this phrase &lt;u&gt;is mismatched</p>
>     TestCase
>     ParserUnderlineTagFormattingTest->testMismatchedEmphasizedPhrase2()
>     failed: expected <p>this phrase <u origin="html">is</u>
>     <u>mismatched in an intersting way</p>, actual <p>this phrase <u
>     origin="html">is</u> &lt;u&gt;mismatched in an intersting way</p>
>     TestCase
>     ParserUnderlineTagFormattingTest->testEmphasizeEndsAtEoln()
>     failed: expected <p>A <u>Emphasize phrase</p><p>cannot
>     continue</u> across a line.</p>, actual <p>A &lt;u&gt;Emphasize
>     phrase</p><p>cannot continue&lt;/u&gt; across a line.</p>
>     TestCase
>     ParserFixedwidthTagFormattingTest->testMismatchedEmphasizedPhrase()
>     failed: expected <p>this phrase <tt>is mismatched</p>, actual
>     <p>this phrase &lt;tt&gt;is mismatched</p>
>     TestCase
>     ParserFixedwidthTagFormattingTest->testMismatchedEmphasizedPhrase2()
>     failed: expected <p>this phrase <tt origin="html">is</tt>
>     <tt>mismatched in an intersting way</p>, actual <p>this phrase <tt
>     origin="html">is</tt> &lt;tt&gt;mismatched in an intersting way</p>
>     TestCase
>     ParserFixedwidthTagFormattingTest->testEmphasizeEndsAtEoln()
>     failed: expected <p>A <tt>Emphasize phrase</p><p>cannot
>     continue</tt> across a line.</p>, actual <p>A &lt;tt&gt;Emphasize
>     phrase</p><p>cannot continue&lt;/tt&gt; across a line.</p>
>     TestCase ParserStrikeoutTagFormattingTest->testEmphasizeWord()
>     failed: expected <p>start <strike origin="wiki">middle</strike>
>     end</p>, actual <p>start <strike origin="html">middle</strike> end</p>
>     TestCase ParserStrikeoutTagFormattingTest->testEmphasizePhrase()
>     failed: expected <p>start <strike origin="wiki">middle
>     words</strike> end</p>, actual <p>start <strike
>     origin="html">middle words</strike> end</p>
>     TestCase ParserStrikeoutTagFormattingTest->testEmphasizeLine()
>     failed: expected <p><strike origin="wiki">middle
>     words</strike></p>, actual <p><strike origin="html">middle
>     words</strike></p>
>     TestCase
>     ParserStrikeoutTagFormattingTest->testMismatchedEmphasizedPhrase()
>     failed: expected <p>this phrase <strike>is mismatched</p>, actual
>     <p>this phrase &lt;strike&gt;is mismatched</p>
>     TestCase
>     ParserStrikeoutTagFormattingTest->testMismatchedEmphasizedPhrase2()
>     failed: expected <p>this phrase <strike origin="wiki">is</strike>
>     <strike>mismatched in an intersting way</p>, actual <p>this phrase
>     <strike origin="html">is</strike> &lt;strike&gt;mismatched in an
>     intersting way</p>
>     TestCase
>     ParserStrikeoutTagFormattingTest->testEmphasizeEndsAtEoln()
>     failed: expected <p>A <strike>Emphasize phrase</p><p>cannot
>     continue</strike> across a line.</p>, actual <p>A
>     &lt;strike&gt;Emphasize phrase</p><p>cannot
>     continue&lt;/strike&gt; across a line.</p>
>
>
>     *--------------------------------------------------
>       78 tests, passed=59, failed=19, errors=0
>     --------------------------------------------------*
>

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
class.CoWikiParser.php (text/html, 7.6 KB)
<?php

/**
 *
 * $Id: class.CoWikiParser.php,v 1.13 2005/01/16 23:25:09 dgorski Exp $
 *
 * This file is part of coWiki. coWiki is free software under the terms of
 * the GNU General Public License (GPL). Read the LICENSE file. If you did
 * not receive a copy of the license and are not able to obtain it through
 * the internet, please send a note to <[email protected]> so we can mail
 * you a copy immediately.
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @copyright   (C) Daniel T. Gorski, {@link http://www.develnet.org}
 * @license     http://www.gnu.org/licenses/gpl.html
 * @version     $Revision: 1.13 $
 *
 */

/**
 * coWiki - coWiki parser class
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @since       coWiki 0.3.0
 * @author      Archie Campbell <[email protected]>
 * @since       coWiki 0.4.0
 *
 * @todo        [D11N]  Complete documentation
 */
class CoWikiParser extends WikiParser {
    /**
     * Get instance
     *
     * @access  public
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     * @todo    [D11N]  Check return type
     */
    public function getInstance() {
        if (!self::$Instance) {
            self::$Instance = new CoWikiParser;
        }
        return self::$Instance;
    }

    protected function __construct() {}

    // --- Element creators -----------------------------------------------

    /**
     * Create a <link>-element. This method overwrites the parent one.
     *
     * @access  protected
     * @param   string
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function createLinkElement($sStr, $bSquare = false) {

        $aStr = explode(($bSquare?"][":")("),$sStr);

        $sRef   = false;
        $sAlias = false;

        if (isset($aStr[0])) {
            $sRef = trim($this->restoreTokens($aStr[0]));
        }
        if (isset($aStr[1])) {
            $sAlias = trim($this->restoreTokens($aStr[1]));
        }

        // Check if reference string is an URI, if so return the <link>
        // element
        $sPattern = '^(http://|https://|ftp://|mailto:|news:).*';

        if (preg_match('=' . $sPattern . '=', $sRef)) {
            return $this->createUriLinkElement($sRef, $sAlias, $bSquare);
        }

        // Obviously the reference string is not an URI. Check if it has
        // a reference to an other web. Means: "webname|documentname"
        $aWebRef = explode('|', $sRef);

        // Do we have a reference to an other web?
        if (sizeof($aWebRef) > 1) {
            $sWeb = trim($aWebRef[0]);
            $sRef = trim($aWebRef[1]);
            return $this->createGlobalLinkElement($sWeb, $sRef, $sAlias, $bSquare);
        }

        // The reference string was not an URI and it has no reference
        // to an other web -> Look up in database.
        return $this->createLocalLinkElement($sRef, $sAlias, $bSquare);
    }

    /**
     * If a link is an URI
     *
     * @access  protected
     * @param   string
     * @param   string
     * @return  string
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function createUriLinkElement($sRef, $sAlias, $bSquare) {
        $sStr =  '<link origin='.($bSquare?'"square"':'"round"').
			' href="'. $sRef . '">';

        if ($sAlias && $sAlias != '') {
            $sStr .= $sAlias ;
        }

        $sStr .= '</link>';

        return $sStr;
    }

    /**
     * If a link references to an other web (global)
     *
     * @access  protected
     * @param   string
     * @param   string
     * @param   string
     * @return  string
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function createGlobalLinkElement($sWeb, $sRef, $sAlias, $bSquare) {
        $Context = RuntimeContext::getInstance();

        $sWebStr = unescape($sWeb);
        $sRefStr = unescape($sRef);

        // Find web by its name
        $WebNode = $Context->getDocumentDAO()->getWebByName($sWebStr);

        // Found web
        if (is_object($WebNode)) {

            // If document reference string is empty ((web|)), just link
            // to the web directly
            if ($sRefStr == '') {

                $Node = $WebNode;

            } else {

                // Otherwise find a document by its name (in the
                // referenced web)
                $Node = $Context->getDocumentDAO()->getNodeByName(
                            $sRefStr,
                            $WebNode->get('treeId'),
                            'node_id, name'
                        );
            }

            // Found document
            if (is_object($Node)) {

                // Remember resolved reference
                $this->addReferencedNode($Node);

                // Create element
                $sStr = '<link origin='.($bSquare?'"square"':'"round"').
				' idref="' . $Node->get('id') . '">';
                $sStr .=    $sAlias;
                $sStr .= '</link>';
            }
        }

        if (!is_object($WebNode) || !is_object($Node)) {
            $sStr = '<link origin='.($bSquare?'"square"':'"round"').
			    ' strref="' . $sWeb . '¦' . $sRef . '">';
            $sStr .=    $sAlias;
            $sStr .= '</link>';
        }

        return $sStr;
    }

    /**
     * If a link references within a web only (local)
     *
     * @access  protected
     * @param   string
     * @param   string
     * @return  string
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function createLocalLinkElement($sRef, $sAlias, $bSquare) {
        $Context = RuntimeContext::getInstance();

        // Get current directory/document object
        $Node = $Context->getCurrentNode();

        $aData   = false;
        $sRefStr = unescape($sRef);

        // Find document by its name (in the local web)
        $NewNode = $Context->getDocumentDAO()->getNodeByName(
                    $sRefStr,
                    $Node->get('treeId'),
                    'node_id, name'
                );

        if (is_object($NewNode)) {

            // Remember resolved reference
            $this->addReferencedNode($NewNode);

            // Create element
            $sStr = '<link origin='.($bSquare?'"square"':'"round"').
			    ' idref="' . $NewNode->get('id') . '">';
            $sStr .=    $sAlias;
            $sStr .= '</link>';
        }

        if (!is_object($NewNode)) {
            $sStr = '<link origin='.($bSquare?'"square"':'"round"').
			    ' strref="' . $sRef . '">';
            $sStr .=    $sAlias;
            $sStr .= '</link>';
        }

        return $sStr;
    }

} // of class

/*
    Moments lost though time remains
    I am so proud of what we were
    No pain remains, no feeling
    Eternity awaits

    Grant me wings that I might fly
    My restless soul is longing
    No pain remains, no feeling
    Eternity awaits
*/

?>
class.CoWikiReverseParser.php (text/html, 7 KB)
<?php

/**
 *
 * $Id: class.CoWikiReverseParser.php,v 1.15 2005/01/16 23:25:09 dgorski Exp $
 *
 * This file is part of coWiki. coWiki is free software under the terms of
 * the GNU General Public License (GPL). Read the LICENSE file. If you did
 * not receive a copy of the license and are not able to obtain it through
 * the internet, please send a note to <[email protected]> so we can mail
 * you a copy immediately.
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @copyright   (C) Daniel T. Gorski, {@link http://www.develnet.org}
 * @license     http://www.gnu.org/licenses/gpl.html
 * @version     $Revision: 1.15 $
 *
 */

/**
 * coWiki - coWiki reverse parser class
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @since       coWiki 0.3.0
 * @author      Archie Campbell <[email protected]>
 * @since       coWiki 0.4.0
 *
 * @todo        [D11N]  Complete documentation
 */
class CoWikiReverseParser extends WikiReverseParser {
    protected
        $aWebList = array();

    /**
     * Get instance
     *
     * @access  public
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     * @todo    [D11N]  Check return type
     */
    public function getInstance() {
        if (!self::$Instance) {
            self::$Instance = new CoWikiReverseParser;
        }
        return self::$Instance;
    }

    /**
     * Class constructor
     *
     * @access  protected
     * @return  void
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function __construct() {
        $Context = RuntimeContext::getInstance();

        // If link references point to an other web, we will need
        // some web data. Get the names of all webs.
        $Node = $Context->getDocumentDAO()->getWebComposite();

        // Iterate through children
        $It = $Node->getItems()->iterator();

        while ($Obj = $It->next()) {
            $this->aWebList[$Obj->get('id')] = $Obj->get('name');
        }
    }

    /**
     * Process link
     *
     * @access  protected
     * @param   string
     * @return  string
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     */
    protected function processLink(&$sStr) {

        $sStr = preg_replace_callback(
            '=<link strref\="(.*)">(.*)</link>=Umsi',
            array(&$this, 'buildStrRefLink'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<link href\="(.*)">(.*)</link>=Umsi',
            array(&$this, 'buildHyperRefLink'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<link idref\="(.*)">(.*)</link>=Umsi',
            array(&$this, 'buildIdRefLink'),
            $sStr
        );

        return $sStr;
    }

    /**
     * Build id ref link
     *
     * @access  protected
     * @param   array
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     * @todo    [D11N]  Check description
     */
    protected function buildIdRefLink(&$aMatches) {
        $Context = RuntimeContext::getInstance();

        // Get current directory/document object
        $Node = $Context->getCurrentNode();

        // Get referenced node (faster: get required fields only)
        $RefNode = $Context->getDocumentDAO()->getNodeById(
                      $aMatches[1], 'tree_id, parent_id, name'
                   );

        // If no referenced document was found
        if (!is_object($RefNode)) {
	    if ($aMatches[0]=='square') {
                return '['.'['.$aMatches[1].']['.$aMatches[2].']'.
			__('I18N_DOC_DELETED_DOCUMENT').']';

	    } else {
		return '['.'('.$aMatches[1].')('.$aMatches[2].')'.
			__('I18N_DOC_DELETED_DOCUMENT').']';

	    }
        }

        // Normalize!
        $aMatches[2] = unescape($aMatches[2]);

        $sPrefix = '';

        // Link reference in an other web?
        if ($Node->get('treeId') != $RefNode->get('treeId')) {
            if (isset($this->aWebList[$RefNode->get('treeId')])) {

                $sPrefix =  escape($this->aWebList[$RefNode->get('treeId')]);
                $sPrefix .= '|';

            } else {

                $sPrefix = 'UNKNOWN|';
            }

            // Special treatment if links point to a web
            if ($RefNode->get('isWeb')) {

                // Link directly to a web with an alias
                if ($aMatches[2] != '') {
		    if( $aMatches[0]=='square' ) {
                        return '[['.$sPrefix.']['.escape($aMatches[2]).']]';
		    } else {
                        return '(('.$sPrefix.')('.escape($aMatches[2]).'))';
		    }
                }

                // Link directly to a web without alias
		if ($aMatches[0]=='square') {
		    return '[['.$sPrefix.']]';
		} else {
		    return '(('.$sPrefix.'))';
		}
            }
        }

        // ---

        // Generate normal link without alias
        if ($aMatches[2] == '') {
	    if ($aMatches[0]=='square') {
		return
		'[['
                  .$sPrefix
                  .escape($RefNode->get('name'))
		.']]';
	    } else {
		return
		'(('
                  .$sPrefix
                  .escape($RefNode->get('name'))
		.'))';
	    }
        }

        // Generate normal link with an alias
	if ($aMatches[0] == 'square' ) {
	    return '['
                 .'['
                    .$sPrefix
                    .$this->noopDelimiters(escape($RefNode->get('name')))
                 .']'
                 .'['
                    .escape($aMatches[2])
                 . ']'
               .']';
	} else {
	    return '('
                 .'('
                    .$sPrefix
                    .$this->noopDelimiters(escape($RefNode->get('name')))
                 .')'
                 .'('
                    .escape($aMatches[2])
                 . ')'
               .')';
	}
    }

    /**
     * Build hyper ref link
     *
     * @access  protected
     * @param   array
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     * @todo    [D11N]  Check description
     */
    protected function buildHyperRefLink(&$aMatches) {
        if (trim($aMatches[2]) == '') {
	    if( $aMatches[0] == 'square' ) {
		return '[[' . $aMatches[1] . ']]';
	    } else {
		return '((' . $aMatches[1] . '))';
	    }
        }
	if( $aMatches[0] == 'square' ) {
	    return '[[' . $aMatches[1] . '][' . $aMatches[2] . ']]';
	} else {
	    return '((' . $aMatches[1] . ')(' . $aMatches[2] . '))';
	}
    }

} // of class

?>
class.WikiParser.php (text/html, 24.2 KB)
<?php

/**
 *
 * $Id: class.WikiParser.php,v 1.36 2005/02/14 22:23:53 dgorski Exp $
 *
 * This file is part of coWiki. coWiki is free software under the terms of
 * the GNU General Public License (GPL). Read the LICENSE file. If you did
 * not receive a copy of the license and are not able to obtain it through
 * the internet, please send a note to <[email protected]> so we can mail
 * you a copy immediately.
 *
 * <pre>
 * Helping hands: Matt Ho <[email protected]>
 * </pre>
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @copyright   (C) Daniel T. Gorski, {@link http://www.develnet.org}
 * @license     http://www.gnu.org/licenses/gpl.html
 * @version     $Revision: 1.36 $
 *
 */

/**
 * coWiki - Wiki parser class
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @since       coWiki 0.3.0
 * @author      Archie Campbell <[email protected]>
 * @since       coWiki 0.4.0
 */
class WikiParser extends Object {

    protected static
        $Instance = null;
    protected $aToc = array(),
	      $aRows = array(),
              $iRow = 0;
    protected $aEmph = array();
    protected $RefNodes = null;
    // --------------------------------------------------------------------

    /**
     * Collect referenced nodes to where the document is linking to
     *
     * @access  protected
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function addReferencedNode($Obj) {
        $this->RefNodes->add($Obj);
    }

    // --------------------------------------------------------------------

    /**
     * Get referenced nodes
     *
     * @access  public
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    public function getReferencedNodes() {
        return $this->RefNodes;
    }

    /**
     * Get instance
     *
     * @access  public
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    public function getInstance() {
        if (!self::$Instance) {
            self::$Instance = new WikiParser;
        }
        return self::$Instance;
    }

    // --------------------------------------------------------------------

    /**
     * @access  protected
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function __construct() {
        $this->resetEmphasis();
    }

    // --- Helper methods -------------------------------------------------

    /**
     * @access  protected
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function restoreTokens($sStr){ return $sStr; }

    /**
     * Generate "toc" (Document Table of Contents)
     *
     * @access  protected
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function buildToc(&$i, &$nDepth) {
        $sStr = '';

        // Iterate though all toc-entries
        while (isset($this->aToc[$i]['DEPTH'])
               && $this->aToc[$i]['DEPTH'] == $nDepth) {

	    $sAlias = $this->aToc[$i]['TEXT'];
            $sStr .= '<li>';
            $sStr .=    '<link topicref="'.($i+1).'">'.$sAlias.'</link>';

            $i++;

            // Recurse (indent) if toc-entry is nested
            if (isset($this->aToc[$i]['DEPTH'])) {

                if ($this->aToc[$i]['DEPTH'] > $nDepth) {
                    $sStr .= '<ul>';
                    $sStr .=   $this->buildToc($i, $this->aToc[$i]['DEPTH']);
                    $sStr .= '</ul>';
                }
            }

            $sStr .= '</li>';
        }

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * parse
     *
     * @access  public
     * @param   string  The wiki source string
     * @return  string  Wiki XML
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    public function parse($sStr) {

	$sRet = '';
	$sContent = '';
	$bList = false;
	$bTable = false;
	$bBegin = false;
	$aList = array();
	$aTable = array();

        // {{{ DEBUG }}}
        Logger::info('Start parsing wiki document.');

        // Init reference collection
        $this->RefNodes = new Vector;

        $this->resetEmphasis();

        // ---

        $sStr = trim(escape($sStr));

        // Replace possible tabulators
        $sStr = str_replace("\t", '    ', $sStr);

        // Replace possible \r\n or \n\r with \n
        $sStr = str_replace("\r\n", "\n", $sStr);
        $sStr = str_replace("\n\r", "\n", $sStr);

	$this->aRows = explode( "\n",$sStr."\n" );


        $this->iRow = 0;
	$sRow = $this->aRows[$this->iRow];
	$bBegin = true;

	$t = 0; $l = -1;

	while( true ) {
	    $sContent = '';
	    $aMatches = array();
	    //table of contents alone ^<toc/>$
	    if( $bBegin && preg_match( '=^&lt;toc(/?)&gt;$=',
					$sRow, $aMatches )) {
		$sRet .= "\n\t\t\n";
   	    //plugins
	    }else if( $bBegin && preg_match(
			'=^&lt;plugin +([A-Za-z0-9_.]+)( +([^&]*))?(/?)&gt;=i',
				    $sRow, $aMatches )) {
	        if(isset($aMatches[3])){
		    $sRet .= '<plugin name="'.
				$aMatches[1].'" '.$aMatches[3].'/>';
		}else{
		    $sRet .= '<plugin name="'.$aMatches[1].'"/>';
		}
	    //headings
	    }else if( $bBegin && preg_match( '=^(\+{1,})\s*(.*)$=s',
				    $sRow, $aMatches )) {
                $sContent = $this->processContent($aMatches[2]);
		if( preg_match( '=<link\s+strref\="([^>]+)">
						([^<]*)</link>=USx',
				$sContent, $aLinkMatches ) ) {
		    $sAlias = !empty($aLinkMatches[2])
                               ? $aLinkMatches[2]
                               : $aLinkMatches[1]
                               ;
		} else {
		    $sAlias = $sContent;
		}
		$i = sizeof($this->aToc);
		$this->aToc[$i]['TEXT'] = $sAlias;
		$this->aToc[$i]['DEPTH'] = strlen( $aMatches[1] );		
		$sTag = 'h'.strlen($aMatches[1]);
		$sRet .= '<'. $sTag .'>'.
			 $sContent .
			 '</'. $sTag .'>';
	    //end of table
	    } else if( $bBegin && ($t > 0) && 
			    preg_match( '=^&lt;/table&gt;=i',
						$sRow, $aMatches  )) {
	        $sParam = trim($aTable[$t][0]);
		if( $sParam == '') {
		    $sRet .= "\n".'<table>'.$this->buildTable($aTable[$t]).
		             '</table>'."\n";
		} else {
		    $sRet .= "\n".'<table '.trim(unescape($sParam)).'>'.
			         $this->buildTable($aTable[$t]).'</table>'."\n";
		}
		if( --$t == 0 ){
  		    $bTable = false;
		}
	    //beginning of table
	    } else if( $bBegin &&
			preg_match( '=^&lt;table([^>]*)&gt;$=Ui',
					$sRow, $aMatches )) {
		$bTable = true;
		$aTable[++$t] = array();
		array_push( $aTable[$t], $aMatches[1] );
	    //beginning of list
	    } else if( $bBegin && 
			preg_match( '=^([\s*#]{0,}[*#]) ([^\n]*)$=',
				    $sRow, $aMatches )) {
	        $bList = true;
		$aList[++$l] = array();
		$aList[$l]['TEXT'] = $this->processContent( $aMatches[2] );
		$aList[$l]['DEPTH'] = strlen( $aMatches[1] );
		$aList[$l]['TYPE'] = (substr($aMatches[1],-1)=='*'?
							      'ul':'ol');
	    //horizontal rule
	    } else if ($bBegin && 
			preg_match( '=^-{3,}(\s|$)=',
				    $sRow, $aMatches ) ) {
	        $sRet .= '<hr/>'."\n";
		/*
	    //noop 
	    } else if (preg_match( '=((.*)&lt;noop&gt;)=i',
					$sRow, $aMatches )) {
		$sContent = $this->processContent( $aMatches[2], false )
				. '<noop>';
		$sRow = substr( $sRow, strlen($aMatches[1]) );
		do {
		    if (preg_match( '=(.*)&lt;/noop&gt;=' , 
				    $sRow, $aMatches )) {
  		        $sContent .= $aMatches[1] . '</noop>';
			break;
		    } else {
		        $sContent .= $sRow;
		    }
		    if(++$this->iRow<sizeof($this->aRows)){
		        $sRow = $this->aRows[$this->iRow];
		    } else {
		        break;
		    }
		} while(true);
		$sRet .= $sContent;
		*/
	    } else {
		//end of list
		if($bList) {
		    $d = 0;
		    $sListType = $aList[$d]['TYPE'];
		    $nDepth = $aList[$d]['DEPTH'];
		    $sContent .= "\n".'<list>'.
				    '<'.$sListType.'>'.
				      $this->buildList($d,$nDepth,$aList).
				    '</'.$sListType.'>'.
		             '</list>'."\n";
    		    $bList = false;
		    $aList = array();
		    $l = -1;
		    if($bTable){
		        array_push( $aTable[$t], $sContent );
		    }else{
			$sRet .= $sContent;
		    }
		//more table
		}else if($bTable) {
		    array_push( $aTable[$t], $sRow );
		    $sRow = '';
		//non table/list row
		}else{
		    $sContent = $this->processContent( $sRow );
		    if( $sContent != '' ){
		      $sRet .= '<p>'.$sContent.'</p>';
		    }
		    $sRow = '';
		}
	    }
	    if(isset($aMatches[0])) {
		$sRow = substr( $sRow, strlen( $aMatches[0] ) );
		$bBegin = false;
	    }
	    if ( trim($sRow) == '' ) {
		if( ++$this->iRow < sizeof($this->aRows) ) {
		    $sRow = $this->aRows[$this->iRow];
		    $bBegin = true;
		} else {
		    break;
		}
	    }
	}
	
	if( preg_match( '=^(.*)\n\t\t\n(.*)$=s', $sRet, $aMatches )) {
   	    $t = 0;
	    if (sizeof($this->aToc) > 0) {
		$sToc = $this->buildToc($t, $this->aToc[$t]['DEPTH']);
		if ($sToc != '') {
		    $sToc = '<toc><ul>' . $sToc . '</ul></toc>';
		} else {
		    $sToc = '<toc/>';
		}
	    }
	    return $aMatches[1] . $sToc . $aMatches[2];
	}

        // {{{ DEBUG }}}
        Logger::info('Finished parsing wiki document.');

	return $sRet;
    }

    /**
     * reset Emphasis
     *
     * @access  protected
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function resetEmphasis() {
        $this->aEmph = array(
		       "b" => false,
		       "u" => false,
		       "i" => false,
		       "f" => false,
		       "s" => false,
		       "j" => '',
		       "p" => ''
		);
    }

    /**
     * processContent
     *
     * @access  protected
     * @return  string
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function processContent( $sStr ) {
        $sRet = '';
       	$aMatches = array();
	$this->resetEmphasis();

	$sStr = ltrim( $sStr );

	while( trim( $sStr ) != '' ) {
	    //noop
	    if (preg_match( '=^&lt;noop&gt;=is',
					$sStr, $aMatches )) {
	        //greedily munch all until </noop>
	        $sStr = substr( $sStr, strlen( $aMatches[0] ) );
	        $sContent = '';
	        while( !preg_match( '=^&lt;/noop&gt;=is', $sStr, $aMatches ) ) {
		    preg_match( '=([^&]|(\&(?![lg])))*=is', $sStr, $aMatches );
		    $sContent .= $aMatches[0];
		    $sStr = substr( $sStr, strlen( $aMatches[0] ) );
		    if ( $sStr == "\n" ) {
			if ( ++$this->iRow < sizeof($this->aRows) ) {
			    $sStr = $this->aRows[$this->iRow];
			} else {
			    $sStr = '';
			    break;
			}
		    }
	        }
	        $sRet .= '<noop>' . $sContent . '</noop>';
	    //paragraphs
	    } else if (preg_match( '=^&lt;(/??)p&gt;=i', $sStr, $aMatches) ) {
	    //justification
 	    } else if( preg_match( '=^&lt;(/?)(left|center|right)&gt;=i',
 				  $sStr, $aMatches )) {
	        $sRet .= '<'.$aMatches[1].$aMatches[2].'>';
	      /*
 	        if( $aMatches[1] == '/' &&
 		    $this->aEmph['j'] == $aMatches[2]{0} ) {
		    $sRet .= '</'. $aMatches[2] .'>';
 		    $this->aEmph['j'] = '';
		} else if ( $aMatches[1] == '' &&
 			    $this->aEmph['j'] == '' ) {
 		    $sRet .= '<'. $aMatches[2] .'>';
 		    $this->aEmph['j'] = $aMatches[2]{0};
 		}
	      */
	    //pre code posting q start at BOL, are greedy until 
	    // closing tag anywhere on a line
	    } else if (preg_match( '=^&lt;(pre|code|posting|q|rem)&gt;=i',
				    $sStr, $aMatch ) ) {
	        $sStr = substr( $sStr, strlen($aMatch[0]) );
		do {
		    if (preg_match( '=(.*)&lt;/'.$aMatch[1].'&gt;=i',
				    $sStr, $aMatches )) {
			$sRet .= '<'.$aMatch[1].'>'.$sContent . 
				$aMatches[1].'</'.$aMatch[1].'>';
			break;
		    } else {
			$sContent .= $sStr ."\n";
		    }
		    if( ++$this->iRow < sizeof($this->aRows) ){
		        $sStr = $this->aRows[$this->iRow];
		    }else{
		        break;
		    }
		} while(true);
		if( $this->iRow == sizeof($this->aRows) ) {
		    $sRet .= '<'.$aMatch[1].'>'.
				$sContent .
				 '</'.$aMatch[1].'>';
		    break;
		}
	    //line-break
	    }else if( preg_match( '=^&lt;br([ /]+)&gt;=is', $sStr, $aMatches )) {
	        $sRet .= '<br/>';
	    //variables
	    }else if( preg_match( '=^%([A-Z0-9_]+)%=U', $sStr, $aMatches )) {
	        $sRet .= '<var name="'.$aMatches[1].'"/>';
	    //links (()())
	    }else if( preg_match( '=^\(\(([^\(\)]+|[^\(\)]+' .
				    '\([^\(\)]+\)[^\(\)]*|' .
			            '[^\(\)]+\)\([^\(\)]+)\)\)=Usx',
				  $sStr, $aMatches )) {
	        $sRet .= $this->createLinkElement( $aMatches[1] );
	    //links [[][]]
	    }else if( preg_match( '=^\[\[([^\[\]]+|[^\[\]]+' .
				    '\[[^\[\]]+\][^\[\]]*|' .
			            '[^\[\]]+\]\[[^\[\]]+)\]\]=Usx',
				  $sStr, $aMatches )) {
	        $sRet .= $this->createLinkElement( $aMatches[1], true );
	      /*
	        $aLink = explode( ')(', $aMatches[1] );
		$aLink[0] = str_replace( '|', '¦', $aLink[0] );
	        $sRet .= '<link strref="' . trim($aLink[0]) . '">';
		if ( isset( $aLink[1]) ) {
		    $sRet .= trim($aLink[1]);
		} else {
		    $sRet .= trim($aLink[0]);
		}
	        $sRet .= '</link>';
	      */
		/*
	    //WikiWords
	    }else if( preg_match( '=^([A-Z][a-z]+([A-Z][a-z]+)+)=s',
				    $sStr, $aMatches )) {
	      $sLink = trim(preg_replace( '=([A-Z])=', ' \1',
						$aMatches[1] ));
		$sRet .= '<link strref="' . $sLink . '">' .
				 $sLink . '</link>';
		*/
		/*
	    //urls
	    }else if( preg_match( '=^&lt;url(\s+)(
                   (http://|https://|ftp://|mailto:|news:)
                   ([-_A-Z0-9\S]*)
                   (\*\s|\=\s|&quot;|&lt;|&gt;|<|>|\(|\)|\s|$)??
				    )&gt;=six',
		    $sStr, $aMatches ) ) {
		*/
	    //URIs
	    }else if( preg_match(
		'=^(http://|https://|ftp://|mailto:|news:)
                   ([-_A-Z0-9\S]*)
                   (\*\s|\=\s|&quot;|&lt;|&gt;|<|>|\(|\)|\s|$)??
                 =six',
				  $sStr, $aMatches )) {
	        $sRet .= '<uri strref="' . 
				$aMatches[1].$aMatches[2] . '"/>';
	    //subscript, superscript
	    }else if( preg_match( '=^&lt;(/??)(sub|sup)&gt;=is',
				  $sStr, $aMatches ) ) {
	        if( $aMatches[1] == '/' &&
		    $this->aEmph['p'] == $aMatches[2] ) {
		    $sRet .= '</' . $aMatches[2] . '>';
		    $this->aEmph['p'] = '';
		}else if ( $aMatches[1] == '' &&
			   $this->aEmph['p'] == '' ) {
		    $sRet .= '<' . $aMatches[2] . '>';
		    $this->aEmph['p'] = $aMatches[2];
		}
	    //emphasis						
	    }else if( preg_match( '#^&lt;(/??)(strike|tt|b|u|i)&gt;#is',
	 	      $sStr, $aMatches ) ) {
	        switch($aMatches[2]{0}){
		    case 's':
		    case 'S':
		        if( $aMatches[1]=='/' ) {
			    $sRet .= "\x11";
			} else {
			    $sRet .= "\x06";
			}
			break;
		    case 't':
		    case 'T':
		        if( $aMatches[1]=='/' ) {
			    $sRet .= "\x12";
			} else {
			    $sRet .= "\x07";
			}
			break;
		    case 'b':
		    case 'B':
		        if( $aMatches[1]=='/' ) {
			    $sRet .= "\x13";
			} else {
			    $sRet .= "\x08";
			}
			break;
		    case 'u':
		    case 'U':
		        if( $aMatches[1]=='/' ) {
			    $sRet .= "\x14";
			} else {
			    $sRet .= "\x09";
			}
			break;
		    case 'i':
		    case 'I':
		        if( $aMatches[1]=='/' ) {
			    $sRet .= "\x15";
			} else {
			    $sRet .= "\x10";
			}
		    break;
	        }
	    //emphasis						
	    }else if( preg_match( '#^([-=\*_/]{2})#',
	 	      $sStr, $aMatches ) ) {
	        if(preg_match( '#^['.$aMatches[1]{0}.']{1,}#',
			substr($sStr,strlen($aMatches[0])), $aHead) ) {
		    $aMatches[0] .= $aHead[0];
		    $sRet .= $aMatches[0];
		} else {
		    switch($aMatches[1]{0}){
			case '-':
			    $sRet .= "\x01";
			break;
			case '=':
			    $sRet .= "\x02";
			break;
			case '*':
			    $sRet .= "\x03";
			break;
			case '_':
			    $sRet .= "\x04";
			break;
			case '/':
			    $sRet .= "\x05";
			break;
		    }
	        }
	    //unknown tags
	    }else if( preg_match( '#^&lt;[^>&]*&gt;#', $sStr, $aMatches ) ) {
	        $sRet .= $aMatches[0];
            }else if( preg_match( '#^((([-=\*_/]{1})(?!\3))|[^-=\*_/&]|(&(?![lg])))*#',
				    $sStr, $aMatches ) ) {
	        $sRet .= $aMatches[0];
	    }
	    if( isset($aMatches[0]) ) {
	        $sStr = substr( $sStr, strlen($aMatches[0]) );
	    }
	}
	//process emphasis tokens
	$sStr = $sRet;
	$sRet = '';
	$p = 0;
	//while found wiki tokens (count)
	  //convert in pairs or replace (as we go)
	$this->replaceWikiTokens("\x01","-","strike",$sStr);
	$this->replaceWikiTokens("\x02","=","tt",$sStr);
	$this->replaceWikiTokens("\x03","*","b",$sStr);
	$this->replaceWikiTokens("\x04","_","u",$sStr);
	$this->replaceWikiTokens("\x05","/","i",$sStr);
	//while found html tokens (paired)
	  //correspond or replace (suitably as we go)
	$this->replaceHtmlTokens("\x06","\x11","strike",$sStr);
	$this->replaceHtmlTokens("\x07","\x12","tt",$sStr);
	$this->replaceHtmlTokens("\x08","\x13","b",$sStr);
	$this->replaceHtmlTokens("\x09","\x14","u",$sStr);
	$this->replaceHtmlTokens("\x10","\x15","i",$sStr);

	return $sStr;
    }

    protected function replaceWikiTokens( $char,$wiki,$sHtml, &$sStr ) {
        $sRet = '';
	$open = false;
 	$c = substr_count($sStr,$char);
	if(!$c) { return; }
        while( $c ) {
 	    if($sStr{0} == $char){
   	        if($c >= 2 || ($c == 1 && $open) ) {
 		    $sRet .= '<'.($open?'/':'').$sHtml.
				    ($open?'':' origin="wiki"').'>';
		    $open = !$open;
		    $c--;
		} else {
		    $sRet .= str_repeat($wiki,2);
		    $c--;
		}
	    } else {
	        $sRet .= $sStr{0};
	    }	    
	    $sStr = substr($sStr,1);
	}
	$sRet .= $sStr;
	$sStr = $sRet;
    }

    protected function replaceHtmlTokens( $start, $end, $sHtml, &$sStr ) {
        $sRet = '';
	$open = false;
	$s = substr_count($sStr,$start);
	$e = substr_count($sStr,$end);
	if(!$s && !$e) { return; }
	while( $s || $e ) {
	    if($sStr{0} == $start){
	        if($e > 0){
		    $sRet .= '<'.$sHtml.' origin="html">';
		    $open = true;
		} else {
		    $sRet .= '&lt;'.$sHtml.'&gt;';
		}
		$s--;
	    } else if($sStr{0} == $end){
	        if($open){
		    $sRet .= '</'.$sHtml.'>';
		    $open = false;
		} else {
 		    $sRet .= '&lt;/'.$sHtml.'&gt;';
		}
		$e--;
	    } else {
	        $sRet .= $sStr{0};
	    }
	    $sStr = substr($sStr,1);
	}
	$sRet .= $sStr;
	$sStr = $sRet;	
    }

    /**
     * &build table
     *
     * @access  protected
     * @return  string
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function buildTable( &$aTable ) {

	$sRet = '';
	$sContent = '';
	$sRow = '';
	$bPriorRow = false;
	$aMatches = array();

	$j = 0;
	$v = sizeof($aTable);

	while( ++$j < $v ) {

	  /*
            if (!strlen(trim($aTable[$j]))) {
                return '<tr valign="top"><td colspan="1">'.$sStr.'</td></tr>';
            }
	  */

	    //rows expected to hold table syntax (pipes), may instead
	    //make room for ongoing content (tables, lists, etc)
	    $sRow = $aTable[$j];

	    //find table syntax within a (long?) row
	    while( strlen($sRow) && preg_match(
		    '=^([\|!][-+]?)( {0,1}[^\|!\n]*)?=',
		    $sRow,
		    $aMatches) ) {

		//found exploded row begins with pipe syntax, poss params
		$sRow = substr( $sRow, strlen($aMatches[0]) );
		$nType = 0;
		//tables made of cells, headers, rows, caption
		if ($aMatches[1]=="|") {
		    $sRet .= '<td';
		    $nType = 1;
		}else if ($aMatches[1]=="!") {
		    $sRet .= '<th';
		    $nType = 2;
		}else if ($aMatches[1]=="|-") {
		    if ($bPriorRow) {
		        $sRet .= "</tr>\n<tr";
		    } else {
			$sRet .= '<tr';
		    }
		    $nType = 3;
		    $bPriorRow = true;
		}else if ($aMatches[1]=="|+") {
		    $sRet .= '<caption>'.trim($aMatches[2])."</caption>\n";
		    continue 1;
		}

		//content can be large, room for it here
		$sContent = '';

		if (isset($aMatches[2]) && $aMatches[2]{0}==' ') {
		  //left a space to denote no params
		    $sRet .= '>';
		    $sContent .= substr($aMatches[2],1);
		    //expect double pipe, bang or newline
		} elseif (isset($aMatches[2])) {
		  //got params. look for pipe syntax that blocked last preg
		    $sRet .= ' '.unescape($aMatches[2]).'>';
		    if (preg_match('=^\|([^\|!\n]*)=',$sRow,$aMatches)) {
		      //got rest of pipe syntax (...|text)
			$sContent .= $aMatches[1];
			$sRow = substr( $sRow, strlen($aMatches[0]) );
		    }
		} else {
		    $sRet .= '>';
		}

		//look ahead now.
		if (trim($sRow) == '') {
		  //look ahead to close our content
		    while( ++$j<$v ){
			$sRow = $aTable[$j];
			//pipe syntax (will force $sContent) to continue
			if ($sRow{0} == '|' || $sRow{0} == '!') {
			    $sRet .= $this->processContent($sContent);
			    switch($nType){
			      case 1:
			        $sRet .= "</td>\n";
			        break;
			      case 2:
			        $sRet .= "</th>\n";
			        break;
			    }
			    --$j;
			    continue 3;
			} else {
			    //non-pipe (ever-increasing) content
			    $sContent .= $sRow;
			}
		    }

		    $j = $v;
		    $sRet .= $this->processContent($sContent);
		    switch($nType){
		      case 1:
		        $sRet .= "</td>\n";
		        break;
		      case 2:
		        $sRet .= "</th>\n";
		        break;
		    }
		    continue 2;
		} elseif ( ( $sRow{0} == "|" || $sRow{0} == "!") && 
			($sRow{1} == "|" || $sRow{1} == "!") ) {
		    $sRow = substr($sRow, 1);
		    $sRet .= $this->processContent($sContent);
		    switch($nType){
		      case 1:
			$sRet .= "</td>\n";
			break;
		      case 2:
			$sRet .= "</th>\n";
			break;
		    }
		    continue 1;
	        }
	    }
	}
	if( $bPriorRow ) {
	    return $sRet."</tr>";
	} else {
	    return $sRet;
	}
    }

    /**
     * &build list
     *
     * @access  protected
     * @return  string
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     */
    protected function buildList(&$i, &$nDepth, &$aList) {
        $sStr = '';

        // Iterate though all list-entries
        while (isset($aList[$i]['DEPTH'])
               && $aList[$i]['DEPTH'] == $nDepth) {

            $sStr .= '<li>';
            $sStr .=    $aList[$i]['TEXT'];

            $i++;

            // Recurse (indent) if list-entry is nested
            if (isset($aList[$i]['DEPTH'])) {

                if ($aList[$i]['DEPTH'] > $nDepth) {

                    // Correct items that are indented too deep
                    $aList[$i]['DEPTH'] = $nDepth + 1;
                
                    $sListType = $aList[$i]['TYPE'];

                    $sStr .=  '<'.$sListType.'>';
                    $sStr .=    $this->buildList(
                                    $i,
                                    $aList[$i]['DEPTH'],
				    $aList
                                );
                    $sStr .=  '</'.$sListType.'>';
                }
            }

            $sStr .= '</li>';
        }

        return $sStr;
    }

    // --------------------------------------------------------------------

} // of class

/*
    Prospero:  That cross you wear around your neck; is it only a decoration,
               or are you a true Christian believer?

    Francesca: Yes, I believe - truly.

    Prospero:  Then I want you to remove it at once! - and never to wear it
               within this castle again! Do you know how a falcon is trained
               my dear? Her eyes are sown shut. Blinded temporarily she
               suffers the whims of her God patiently, until her will is
               submerged and she learns to serve - as your God taught and
               blinded you with crosses.

    Francesca: You had me take off my cross because it offended ...

    Prospero:  It offended no-one. No - it simply appears to me to be
               discourteous to ... to wear the symbol of a deity long dead.
               My ancestors tried to find it. And to open the door that
               seperates us from our Creator.

    Francesca: But you need no doors to find God. If you believe ...

    Prospero:  Believe?! If you believe you are gullible. Can you look
               around this world and believe in the goodness of a god who
               rules it? Famine, Pestilence, War, Disease and Death!
               They rule this world.

    Francesca: There is also love and life and hope.

    Prospero:  Very little hope I assure you. No. If a god of love and life
               ever did exist ... he is long since dead. Someone ...
               something rules in his place.
*/

?>
class.WikiReverseParser.php (text/html, 17.6 KB)
<?php

/**
 *
 * $Id: class.WikiReverseParser.php,v 1.17 2005/04/09 23:34:07 dgorski Exp $
 *
 * This file is part of coWiki. coWiki is free software under the terms of
 * the GNU General Public License (GPL). Read the LICENSE file. If you did
 * not receive a copy of the license and are not able to obtain it through
 * the internet, please send a note to <[email protected]> so we can mail
 * you a copy immediately.
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @copyright   (C) Daniel T. Gorski, {@link http://www.develnet.org}
 * @license     http://www.gnu.org/licenses/gpl.html
 * @version     $Revision: 1.17 $
 *
 */

/**
 * coWiki - Wiki reverse parser class
 *
 * @package     parse
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @since       coWiki 0.3.0
 * @author      Archie Campbell <[email protected]>
 * @since       coWiki 0.4.0
 *
 * @todo        [D11N]  Complete documentation
 */
class WikiReverseParser extends Object {
    protected static
        $Instance = null,
	$Context = null,
	$DocDAO = null;
    private
        $rParser = null;
    private
    $ignoreToc = false,
        $aList = array(),
      $nDepth = 0,
      $aType = array(),
      $aTable = array(),
      //      $nTable = null;
      $sLink = array(),
      $fLink = array(),
        $aState = array(0),
        $sContent = '',
        $aRow = array(),
      $nListIndent = null;

    // --------------------------------------------------------------------

    /**
     * Get instance
     *
     * @access  public
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     * @todo    [D11N]  Check return type
     */
    public function getInstance() {
        if (!self::$Instance) {
            self::$Instance = new WikiReverseParser;
        }
        return self::$Instance;
    }

    // --------------------------------------------------------------------

    /**
     * Parse
     *
     * @access  protected
     * @param   string
     * @return  void
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     */
    protected function __construct() {
	$this->Context = RuntimeContext::getInstance();
        $this->DocDAO = $this->Context->getDocumentDAO();
    }


    // --------------------------------------------------------------------

    // FIX: THIS METHOD DO NOT COVER ALL POSSIBLE OCCURANCES OF DELIMITERS
    // YET! THIS HAS TO BE CHECKED AND FIXED.

    /**
     * If a string reference contains delimiters, escape them with <noop>
     *
     * @access  protected
     * @param   string
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check return type
     */
    protected function noopDelimiters(&$sStr) {

        // Escape leading delimiters
        if (substr($sStr, 0, 2) == '()') {
            $sStr = '&lt;noop&gt;()&lt;/noop&gt;'.substr($sStr, 2);
        } else if (substr($sStr, 0, 1) == '(') {
            $sStr = '&lt;noop&gt;(&lt;/noop&gt;'. substr($sStr, 1);
        }

        // Escape trailing delimiters
        if (substr($sStr, -2) == '()') {
            $sStr = substr($sStr, 0, -2).'&lt;noop&gt;()&lt;/noop&gt;';
        } else if (substr($sStr, -1) == ')') {
            $sStr = substr($sStr, 0, -1).'&lt;noop&gt;)&lt;/noop&gt;';
        }

        // Escape double closing
        $sStr = str_replace('))', '&lt;noop&gt;))&lt;/noop&gt;', $sStr);

        // Replace web/document delimiter
        return str_replace('¦', '|', $sStr);
    }

    // --------------------------------------------------------------------

    /**
     * Helper functions
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    protected function appendRow($sStr){
	array_push($this->aRow,$sStr);
	$this->clearContent();
    }
    protected function clearContent(){
	$this->sContent = '';
    }
    protected function clearRow() {
        if($this->sContent!=''){
	    $this->appendRow($this->sContent);
	}
    }
    protected function textListItem( $sData ) {
	$this->aList[sizeof($this->aList)-1]['TEXT'] .= $sData;
    }
    protected function pushTableItem( $sData ) {
        array_push($this->aTable[sizeof($this->aTable)-1],$sData);
    }


    /**
     * Parse
     *
     * @access  public
     * @param   string
     * @return  mixed
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    public function parse($sStr) {

      //return $this->dummy($sStr);

        if (!function_exists('xml_parser_create')) {
            return false;
        }

        $this->rParser = @xml_parser_create();

        if (!$this->rParser) {
            return false;
        }

        xml_parser_set_option($this->rParser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($this->rParser, XML_OPTION_SKIP_WHITE, 1);

        // Replace possible tabulators
        $sStr = str_replace("\t", '    ', $sStr);

        // Replace possible \r\n or \n\r with \n
        $sStr = str_replace("\r\n", "\n", $sStr);
        $sStr = str_replace("\n\r", "\n", $sStr);

	// Prepare XML, add root element
	$sStr = '<document>' . $sStr . '</document>';

	xml_set_default_handler( $this->rParser, 
				array(&$this,'xml_default_handler') );
	xml_set_element_handler( $this->rParser,
				array(&$this,'xml_start_handler'),
				array(&$this,'xml_end_handler') );
	xml_set_character_data_handler( $this->rParser,
				array(&$this,'xml_cdata_handler') );

	if(!xml_parse( $this->rParser, $sStr )){
	  echo xml_error_string(xml_get_error_code($this->rParser));
	}

	return implode("\n",$this->aRow);

    }

    /**
     * xml element start handler
     *
     * @access  protected
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    protected function xml_start_handler( &$parser, $sName, $aAttrib ) {
        if($this->ignoreToc){ return; }
        switch($sName){
	case 'document':
	    $this->aRow = array();
	    break;
	case 'toc':
	    $this->clearRow();
	    $this->ignoreToc = true;
	    break;
	case 'plugin':
	    $this->clearRow();
	    $sStr = '<plugin '.$aAttrib['name'];
	    if(sizeof($aAttrib)>1){
	        foreach($aAttrib as $attr => $value){
		    if($attr != 'name'){
		        $sStr .= ' '.$attr.'='.$value;
		    }
		}
	    }
	    $sStr .= '>';
	    $this->appendRow($sStr);
  	    break;
	//table syntax
	case 'table':
	    $this->clearRow();
	    array_push($this->aState,6);/*WRP_TABL*/
	    array_push($this->aTable,array($aAttrib));//[][0]
	    break;
	case 'caption':
	    array_push($this->aState,7);/*WRP_CAPN*/
	    $this->clearContent();
	    break;
	case 'tr':
   	    array_push($this->aState,8);/*WRP_TROW*/
	    $sStr = '|-';
	    if(sizeof($aAttrib)){
  	        foreach($aAttrib as $attr => $value){
		    $sStr .= $attr.'="'.$value.'" ';
 		}
	    }
	    $this->pushTableItem( $sStr );
	    break;
	case 'th':
	    array_push($this->aState,9);/*WRP_THED*/
	    $sStr = '!';
	    if(sizeof($aAttrib)){
  	        foreach($aAttrib as $attr => $value){
		    $sStr .= $attr.'="'.$value.'" ';
 		}
	    }
	    $sStr .= ' ';
	    $this->pushTableItem( $sStr );	    
	    break;
	case 'td':
	    array_push($this->aState,10);/*WRP_TCEL*/
	    $sStr = '|';
	    if(sizeof($aAttrib)){
  	        foreach($aAttrib as $attr => $value){
		    $sStr .= $attr.'="'.$value.'" ';
 		}
	    }
	    $sStr .= ' ';
	    $this->pushTableItem( $sStr );	    
	    break;
	//list syntax
	case 'list':
	    $this->clearRow();
	    array_push($this->aState,1);/*WRP_LIST*/
	    break;
	case 'ol':
	case 'ul': 
	    $this->nDepth++;
	    array_push($this->aType,$sName);
	    break;
	case 'li':
	    switch(end($this->aState)){
	    case 1:
	        if(strlen($this->sContent)){
		    $this->textListItem($this->sContent);
	        }
		array_push( $this->aList, 
			    array(  'DEPTH'=>$this->nDepth,
				    'TYPE'=>(end($this->aType)=='ol'?
							         "#":"*"),
				    'TEXT'=>''  ));
		break;
	    default:
	      echo $sName . "insanity";
	      exit;
	    }
	    $this->clearContent();
	    break;
        //paragraphs
	case 'p':
	    $this->clearRow();
	    break;
	//noop
	case 'noop':
	    $this->sContent .= '<noop>';
	    array_push($this->aState,5);
	    break;
        //justification
	case 'left':
	case 'right':
	case 'center':
	    $this->sContent .= '<'.$sName.'>';
	    break;
        //pre, code, posting, quote
	case 'pre':
	case 'code':
	case 'posting':
	case 'q':
	case 'rem':
	    $this->clearRow();
	    array_push($this->aState,4);
	    break;
	//line-break
	case 'br':
	    $this->clearRow();
	    break;
        //variables
	case 'var':
	    $this->sContent .= "%".$aAttrib['name']."%";
	    break;
	//headings
	case 'h1':
	case 'h2':
	case 'h3':
	case 'h4':
	case 'h5':
	case 'h6':
	    array_push($this->aState,3);
            break;
	//wiki links
	case 'uri':
	    $this->sContent .= $aAttrib['strref'];
	    break;
	case 'link':
	    array_push($this->aState,2);
	    if( isset($aAttrib['strref']) ) {
		$this->fLink = array(&$this, 'buildStrRefLink');
		$this->sLink[1] = $aAttrib['strref'];
	    }else if( isset($aAttrib['href']) ) {
		$this->fLink = array(&$this, 'buildHyperRefLink');
		$this->sLink[1] = $aAttrib['href'];
	    }else if( isset($aAttrib['idref']) ) {
		$this->fLink = array(&$this, 'buildIdRefLink');
		$this->sLink[1] = $aAttrib['idref'];
	    }
	    $this->sLink[0] = $aAttrib['origin'];
    	    break;
	//subscript & superscript
	case 'sub':
	case 'sup':
	    $this->sContent .= '<'.$sName.'>';
	    break;
	//horizontal rule
	case 'hr':break;
	//emphasis (strike,tt,b,u,i)
	//WRP_ST WRP_TT WRP_B WRP_U WRP_I
	//11     12     13    14    15
	case 'strike':
	    if(isset($aAttrib['origin']) && $aAttrib['origin']=='html'){
	        array_push($this->aState,11);
	        $this->sContent .= "<strike>";
	    }else{
		$this->sContent .= "--";
	    }
	    break;
	case 'tt':
	    if(isset($aAttrib['origin']) && $aAttrib['origin']=='html'){
	        array_push($this->aState,12);
	        $this->sContent .= "<tt>";
	    }else{
		$this->sContent .= "==";
	    }
	    break;
	case 'b':
	    if(isset($aAttrib['origin']) && $aAttrib['origin']=='html'){
	        array_push($this->aState,13);
	        $this->sContent .= "<b>";
	    }else{
		$this->sContent .= "**";
	    }
	    break;
	case 'u':
	    if(isset($aAttrib['origin']) && $aAttrib['origin']=='html'){
	        array_push($this->aState,14);
	        $this->sContent .= "<u>";
	    }else{
		$this->sContent .= "__";
	    }
	    break;
	case 'i':
	    if(isset($aAttrib['origin']) && $aAttrib['origin']=='html'){
	        array_push($this->aState,15);
	        $this->sContent .= "<i>";
	    }else{
		$this->sContent .= "//";
	    }
	    break;
	default:
            echo $sName . " start" . "\n";
	}
    }

    /**
     * xml element end handler
     *
     * @access  protected
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    protected function xml_end_handler( &$parser, $sName ) {
        if($this->ignoreToc){
	    if($sName == 'toc'){
		$this->appendRow("<toc>");
		$this->ignoreToc = false;
		return;
	    }
	}
        switch($sName){
	case 'document': 
	    $this->clearRow();
	    break;
	case 'plugin':
	    break;
	//table syntax
	case 'table':
	    if(array_pop($this->aState)!=6) {
	        echo "insanity in table";
	        exit;
	    }
	    $aTable = array_pop($this->aTable);
	    $sStr = '<table';
	    foreach($aTable[0] as $attr => $value ){
	        $sStr .= " ".$attr.'="'.$value.'"';
	    }
	    $sStr .= (sizeof($aTable)==1?'/>':'>');
	    $this->appendRow( $sStr );
	    for ($i = 1; $i < sizeof($aTable) ; $i++){
	        $this->appendRow( $aTable[$i] );
	    }
	    $this->appendRow('</table>');
	    break;
        //caption
	case 'caption':
            if(array_pop($this->aState)!=7) {
	        echo "insanity in caption";
	        exit;
	    }
	    $this->pushTableItem( "|+ ".$this->sContent );
	    $this->clearContent();
	    break;
	case 'tr':
	    if(array_pop($this->aState)!=8) {
  	        echo "insanity in trow";
	        exit;
	    }
  	    break;
	case 'th':
	    if(array_pop($this->aState)!=9) {
	        echo "insanity in thead";
		exit;
	    }
	    if($this->sContent != ''){
	        $this->pushTableItem($this->sContent);
		$this->clearContent();
	    }
	    break;
	case 'td':
	    if(array_pop($this->aState)!=10) {
	        echo "insanity in tcell";
		exit;
	    }
	    if($this->sContent != ''){
	        $this->pushTableItem($this->sContent);
		$this->clearContent();
	    }
	    break;
	//list syntax
	case 'list':
	    if(array_pop($this->aState)!=1) {
	        echo "insanity in list";
	        exit;
	    };
	    foreach($this->aList as $li){
		$sStr = str_repeat($li['TYPE'],$li['DEPTH']).
		  " ".$li['TEXT'];
	      switch(end($this->aState)){
	      case 9:
	      case 10:
		  $this->pushTableItem( $sStr );
		  break;
	      default:
		  $this->appendRow( $sStr );
	      }
	    }
	    $this->clearContent();
	    $this->aList = array();
	    break;
	case 'ol':
	case 'ul':
	    $this->nDepth--;
	    array_pop($this->aType);
	    break;
	case 'li':
	    $this->textListItem($this->sContent);
	    $this->clearContent();
	    break;
        //paragraphs
	case 'p':
	    $this->appendRow( "\n".$this->sContent );
	    break;
	//noop
	case 'noop':
	    $this->sContent .= '</noop>';
	    array_pop($this->aState);
	    break;
        //justification
	case 'left':
	case 'right':
	case 'center':
	    $this->sContent .= '</'.$sName.'>';
	    break;
        //pre, code, posting, quote
	case 'pre':
	case 'code':
	case 'posting':
	case 'q':
	case 'rem':
	    $this->sContent = '<'.$sName.'>'.
				$this->sContent.
			      '</'.$sName.'>';
	    array_pop($this->aState);
	    if(end($this->aState)!=1){
	        $this->clearRow();
	    }
	    break;
	//line-break
	case 'br':
	    $this->appendRow( '<br/>' );
	    break;
        //variables
	case 'var':
	    break;
	//headings
	case 'h1':
	case 'h2':
	case 'h3':
	case 'h4':
	case 'h5':
	case 'h6':
	    $this->appendRow( str_repeat("+",substr($sName,1,1)).
				" ". $this->sContent);
	    array_pop( $this->aState );
	    break;
	//wiki links
	case 'uri':
	    break;
	case 'link':
	    if(array_pop($this->aState)!=2){
	        echo "insanity in link";
		exit;
	    }
	    $sStr = call_user_func($this->fLink, $this->sLink);
            $this->sContent .= $sStr;
	    break;
	//subscript & superscript
	case 'sub':
	case 'sup':
	    $this->sContent .= '</'.$sName.'>';
	    break;
	//horizontal rule
	case 'hr':
	    $this->appendRow("---");
	    break;
	//emphasis (strike,tt,b,u,i)
	case 'strike':
	    if(end($this->aState)==11){
 	        array_pop($this->aState);
		$this->sContent .= "</strike>";
	    } else {
	        $this->sContent .= "--";
	    }
	    break;
	case 'tt':
	    if(end($this->aState)==12){
 	        array_pop($this->aState);
		$this->sContent .= "</tt>";
	    } else {
	        $this->sContent .= "==";
	    }
	    break;
	case 'b':
	    if(end($this->aState)==13){
 	        array_pop($this->aState);
		$this->sContent .= "</b>";
	    } else {
	        $this->sContent .= "**";
	    }
	    break;
	case 'u':
	    if(end($this->aState)==14){
 	        array_pop($this->aState);
		$this->sContent .= "</u>";
	    } else {
	        $this->sContent .= "__";
	    }
	    break;
	case 'i':
	    if(end($this->aState)==15){
 	        array_pop($this->aState);
		$this->sContent .= "</i>";
	    } else {
	        $this->sContent .= "//";
	    }
	    break;
	default:
	    echo $sName . " end" . "\n";
        }
    }

    /**
     * xml element default handler
     *
     * @access  protected
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    protected function xml_default_handler( &$parser, $sData ) {
        if(!$this->ignoreToc){
            $this->sContent .= html_entity_decode($sData);
	}
    }

    /**
     * xml cdata handler
     *
     * @access  protected
     *
     * @author  Archie Campbell <[email protected]>
     * @since   coWiki 0.4.0
     *
     */
    protected function xml_cdata_handler( &$parser, $sData ) {
        if($this->ignoreToc){ return; }
        if( trim($sData) != '' ) {
	    switch( end($this->aState) ){
	    case 1:/*WRP_LIST*/
	    case 3:/*WRP_HEAD*/
	    case 4:/*WRP_PCPQ*/
	    case 5:/*WRP_NOOP*/
	    case 7:/*WRP_CAPT*/
	    case 9:/*WRP_THED*/
		$this->sContent .= $sData;
 		break;
	    case 2:/*WRP_LINK*/
	        $this->sLink[2]=trim($sData);
	        break;
	    default:/*text*/
	        $this->sContent .= $sData;
	    }
        }
    }

    // --------------------------------------------------------------------

    /**
     * Build str ref link
     *
     * @access  protected
     * @param   array
     * @return  mixed
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     *
     * @todo    [D11N]  Check description
     */
    protected function buildStrRefLink(&$aMatches) {
        if ($aMatches[1] == $aMatches[2] || trim($aMatches[2]) == '') {
	    if( $aMatches[0] == 'square' ) {
		return '[[' . $this->noopDelimiters($aMatches[1]) . ']]';
	    } else {
		return '((' . $this->noopDelimiters($aMatches[1]) . '))';
	    }
        }

	if($aMatches[0] == 'square' ) {
	    return '[' . $this->noopDelimiters($aMatches[1]) . ']'
                .'[' . $aMatches[2] . ']]';
	} else {
	    return '((' . $this->noopDelimiters($aMatches[1]) . ')'
                .'(' . $aMatches[2] . '))';
	}
    }

} // of class

?>
class.FrontHtmlTransformer.php (text/html, 23.2 KB)
<?php

/**
 *
 * $Id: class.FrontHtmlTransformer.php,v 1.39 2005/02/05 05:27:18 dgorski Exp $
 *
 * This file is part of coWiki. coWiki is free software under the terms of
 * the GNU General Public License (GPL). Read the LICENSE file. If you did
 * not receive a copy of the license and are not able to obtain it through
 * the internet, please send a note to <[email protected]> so we can mail
 * you a copy immediately.
 *
 * @package     render
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @copyright   (C) Daniel T. Gorski, {@link http://www.develnet.org}
 * @license     http://www.gnu.org/licenses/gpl.html
 * @version     $Revision: 1.39 $
 *
 */

/**
 * The FrontHtmlTransformer is a low-end replacement for a XSLT processor.
 * Its main purpose is to transform and render the internal coWiki documents
 * (that are stored as simple XML) into HTML. This class is a Singelton.
 * You can not instantiate this class directly (with $foo = new class), but
 * have to get its instance:
 *
 * Example:
 *   <code>
 *      // This won't work
 *      $Trans = new FrontHtmlTransformer();
 *
 *      // This is the right way
 *      $Trans = FrontHtmlTransformer::getInstance();
 *   </code>
 *
 * @package     render
 * @subpackage  class
 * @access      public
 *
 * @author      Daniel T. Gorski, <[email protected]>
 * @since       coWiki 0.3.0
 */
class FrontHtmlTransformer extends Object {
    protected static
        $Instance = null;

    protected
        $Context     = null,
        $DocDAO      = null,
        $Response    = null,
        $Registry    = null,
        $Utility     = null,
        $nTopicCount = 0,
        $aNodeBackup = array(),
        $bChangedRef = false,
        $bRepair     = true;

    // --------------------------------------------------------------------

    /**
     * Get the unique instance of the class (This class is implemented as
     * Singleton).
     *
     * @access  public
     * @return  Object  The class instance
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    public function getInstance() {
        if (!self::$Instance) {
            self::$Instance = new FrontHtmlTransformer;
        }
        return self::$Instance;
    }

    // --------------------------------------------------------------------

    /**
     * Class constructor
     *
     * @access  protected
     * @return  void
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function __construct() {
        $this->Context = RuntimeContext::getInstance();
        $this->DocDAO = $this->Context->getDocumentDAO();
        $this->Response = $this->Context->getResponse();
        $this->Registry = $this->Context->getRegistry();
        $this->Utility  = $this->Context->getUtility();
    }

    // --- Simple HTML transformer for front page display -----------------

    /**
     * Return the transformed (converted) HTML
     *
     * @access  public
     * @param   object    The node (document object) you are working on
     * @param   boolean   Determines whether to 'repair' and store document
     *                    references. Repairing means lookup of new
     *                    documents by their name (title).
     * @return  string    The transformed HTML
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    public function &transform($Node, $bRepair = true) {

        $this->nTopicCount = 1;
        $this->bChangedRef = false;
        $this->bRepair = $bRepair;

        // Set <meta keywords=...>
        $this->Registry->set('META_KEYWORDS', $Node->get('keywords'));

        $sStr = $Node->get('content');

        // Look for new and lost documents, gather the names of existing
        // ones
        $sStr = preg_replace_callback(
            '=<link (strref|idref)\="([^"]*)">(.*)</link>=Usi',
            array($this, '_checkDocReferences'),
            $sStr
        );

        // If we have found a new or lost document, we have to save
        // the modified source
        if ($this->bChangedRef && $this->bRepair) {
            $Node->set('content', $sStr);

            // Store modified source
            $this->Context->getDocumentDAO()->storeContentOnly($Node);
        }

        // --- Now the transforming starts --------------------------------

        // Transform remarks
        $sStr = preg_replace_callback(
            '=<rem>(.*)</rem>=Usi',
            array($this, '_transformRemark'),
            $sStr
        );

        // Transform preformatted text
        $sStr = preg_replace_callback(
            '=<pre>(.*)</pre>=Usi',
            array($this, '_transformPre'),
            $sStr
        );

        // Transform code
        $sStr = preg_replace_callback(
            '=<code>(.*)</code>=Usi',
            array($this, '_transformCode'),
            $sStr
        );

        // Transform posting
        $sStr = preg_replace_callback(
            '=<posting>(.*)</posting>=Usi',
            array($this, '_transformPosting'),
            $sStr
        );

        // Transform missing document (link)
        $sStr = preg_replace_callback(
            '=<link origin\="[^"]*" strref\="([^"]*)">(.*)</link>=Usi',
            array($this, '_transformMissingDocument'),
            $sStr
        );

        // Transform exisisting document (link)
        $sStr = preg_replace_callback(
            '=<link origin\="[^"]*" idref\="([^"]*)">(.*)</link>=Usi',
            array($this, '_transformExistingDocument'),
            $sStr
        );

        // Transform link with URI
        $sStr = preg_replace_callback(
            '=<link origin\="[^"]*" href\="([^"]*)">(.*)</link>=Usi',
            array($this, '_transformUri'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<link topicref\="([^"]*)">(.*)</link>=Usi',
            array($this, '_transformTopic'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<uri strref\="(.*)"/>=Usi',
            array($this, '_transformUri'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<plugin name\="([^"]+)"(.*)/>=Usi',
            array($this, '_transformPlugin'),
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<h([1-6])>(.*)</h\1>=Usi',
            array($this, '_transformHeading'),
            $sStr
        );

        $sStr = preg_replace(
            '=<var name\="([^"]*)"/>=Usi',
            '{%\1%}',
            $sStr
        );

        $sStr = preg_replace_callback(
            '=<q>(.*)</q>=Usi',
            array($this, '_transformQuote'),
            $sStr
        );

	$sStr = preg_replace_callback(
	    '=<(left|center|right)>(.*)</\1>=Usi',
	    array($this, '_transformJustification'),
	    $sStr
	);

        // Get rid of remaining XML elements
        $aArr = array(
                  '<toc>', '</toc>',
                  '<noop>', '</noop>',
                  '<list>', '</list>',
                  '<rem>', '</rem>'
                );
        $sStr = str_replace($aArr, '', $sStr);

        $sStr = $this->finish($sStr, $Node);

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Check document references callback.
     *
     * @access  protected
     * @param   array   RegEx matches defined in preg_replace_callback().
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_checkDocReferences(&$aMatches) {
        $sAlias = $aMatches[3];

        // Remove <noop>s if any
        $sRef = str_replace('<noop>', '', $aMatches[2]);
        $sRef = str_replace('</noop>', '', $sRef);

        if ($aMatches[1] == 'strref') {
            // Check if 'strref' is a reference to an other web.
            // Means: "webname¦documentname"
            $aWebRef = explode('¦', $sRef);

            // Do we have a reference to an other web?
            if (sizeof($aWebRef) > 1) {
                $sGlobalStrRef = $this->_checkGlobalStrRef(
                          $aWebRef[0],
                          $aWebRef[1],
                          $sAlias
                       );
                return $sGlobalStrRef;
            }

            // If the reference does not point to an other web
            $sLocalStrRef = $this->_checkLocalStrRef($sRef, $sAlias);
            return $sLocalStrRef;
        }

        if ($aMatches[1] == 'idref') {

            $Node = $this->DocDAO->getNodeById(
                        $sRef, 'node_id, tree_id, name'
                    );

            if (is_object($Node)) {

                // Save document node for further use
                $this->aNodeBackup[$Node->get('id')] = $Node;

                $sStr =  '<link idref="'.$Node->get('id').'">';
                    // Do we have an alias?
                    if ($Node->get('name') != $sAlias) {
                        $sStr .= $sAlias;
                    }
                $sStr .= '</link>';

            }

            // Referenced document not found, try history
            if (!is_object($Node)) {
                $Node = $this->DocDAO->getHistNodeForId($sRef, 'name');

                if (is_object($Node)) {
                    $this->bChangedRef = true;

                    $sStr =  '<link strref="'.$Node->get('name').'">';
                        // Do we have an alias?
                        if ($Node->get('name') != $sAlias) {
                            $sStr .= $sAlias;
                        }
                    $sStr .= '</link>';
                }
            }

            // "Deleted document"
            if (!is_object($Node)) {
                $this->bChangedRef = true;
                $sStr = '['.'('.$sRef.')('.$sAlias.')'.
			    __('I18N_DOC_DELETED_DOCUMENT').']';
            }
        }

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Check references to an other web (global) callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_checkGlobalStrRef(&$sWeb, &$sRef, &$sAlias) {

        $sWebStr = trim(unescape($sWeb));
        $sRefStr = trim(unescape($sRef));

        // Find web node by its name
        $WebNode = $this->DocDAO->getWebByName($sWebStr);

        // Found web
        if (is_object($WebNode)) {

            // Find document by its name (in the referenced web)
            $Node = $this->DocDAO->getNodeByName(
                        $sRefStr,
                        $WebNode->get('treeId'),
                        'node_id, name'
                    );

            // Found document
            if (is_object($Node)) {

                // Save document node for further use
                $this->bChangedRef = true;
                $this->aNodeBackup[$Node->get('id')] = $Node;

                $sStr =  '<link idref="'.$Node->get('id').'">';

                // Do we have an alias?
                if ($Node->get('name') != $sAlias) {
                    $sStr .= $sAlias;
                }

                $sStr .= '</link>';
            }
        }

        // Did not found web, keep the link as it is
        if (!is_object($WebNode) || !is_object($Node)) {
            $sStr =   '<link strref="'. $sWeb . '|' . $sRef.'">';
            $sStr .=      $sAlias;
            $sStr .=  '</link>';
        }

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Check references within a web only (local) callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_checkLocalStrRef(&$sRef, &$sAlias) {

        // Get current directory/document object
        $Node = $this->Context->getCurrentNode();

        $sRefStr = trim(unescape($sRef));

        $Node = $this->DocDAO->getNodeByName(
            $sRefStr,
            $Node->get('treeId'),
            'node_id, tree_id, name'
        );

        // Save document info for further use
        if (is_object($Node)) {

            // Save document node for further use
            $this->bChangedRef = true;
            $this->aNodeBackup[$Node->get('id')] = $Node;

            $sStr = '<link idref="'.$Node->get('id').'">';
                // Do we have an alias?
                if ($Node->get('name') != $sAlias) {
                    $sStr .= $sAlias;
                }
            $sStr .= '</link>';
        }

        if (!is_object($Node)) {
            // Keep the link as it is
            $sStr   =   '<link strref="'.$sRef.'">';
            $sStr   .=      $sAlias;
            $sStr   .=  '</link>';
        }

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on missing documents callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformMissingDocument(&$aMatches) {

        // Get current directory/document object
        $Node = $this->Context->getCurrentNode();

        // Check if 'strref' is a reference to an other web.
        // Means: "webname|documentname"
        $aWebRef = explode('|', $aMatches[1]);

        // Do we have a reference to an other web?
        if (sizeof($aWebRef) > 1) {
            $sStr =  '<span class="error">[';
            $sStr .=    __('I18N_DOC_UNRESOLVED_WEB_REFERENCE').': ';
            $sStr .=    $aMatches[1];
            $sStr .= ']</span>';
            return $sStr;
        }

        // Remove <noop>s if any
        $aMatches[1] = str_replace('<noop>', '', $aMatches[1]);
        $aMatches[1] = str_replace('</noop>', '', $aMatches[1]);

        // Missing local document, provide a link to edit
        $sQuery = 'cmd=' . CMD_NEWDOC . '&newdocname=' .
                  urlencode(unescape($aMatches[1]));
        if (is_object($Node)) {
            $sQuery .= '&node=' . $Node->get('parentId') .
                       '&refnode='.$Node->get('id');
        }

        $sStr  = '<a href="';
        $sStr .= $this->Response->getControllerHref($sQuery);
        $sStr .=  '">';

        // Do we have an alias?
        $sStr .=    ($aMatches[2] == '') ? $aMatches[1] : $aMatches[2];
        $sStr .= '</a>';
        $sStr .= '<strong class="missing">?</strong>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on existing documents callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformExistingDocument(&$aMatches) {

        $sStr =  '<a href="';
        $sStr .=    $this->Response->getControllerHref('node='.$aMatches[1]);
        $sStr .=  '">';

        // Possible alias
        if (isset($aMatches[2]) && $aMatches[2] != '') {
            $sStr .= $aMatches[2];
        } else {
            $sStr .= escape($this->aNodeBackup[$aMatches[1]]->get('name'));
        }

        $sStr .= '</a>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on remarks callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformRemark(&$aMatches) {
         $sStr = '';
         return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on preformatted text callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformPre(&$aMatches) {
        $sStr = '<pre>' . $aMatches[1] . '</pre>';
        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on code callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformCode(&$aMatches) {

        if ($this->Registry->get('COLOR_CODE_COLORIZE')) {
            return '<pre class="code">'
                      .$this->Utility->colorizeCode($aMatches[1])
                   .'</pre>';
        }

        $sStr = '<pre class="code">' . $aMatches[1] . '</pre>';
        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Act on posting callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformPosting(&$aMatches) {
        $sStr = '<pre>'
                  .$this->Utility->colorizeQuote($aMatches[1])
               .'</pre>';
        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Execute plugins callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformPlugin(&$aMatches) {

        // Reset Layouter attributes
        $this->Context->getLayouter()->init();

        // Set plugin parameters
        if (isset($aMatches[2]) && $aMatches[2] != '') {
            $this->Context->setPluginParam($aMatches[2]);
        }

        // Load plugin
        $sStr = $this->Context->getPluginLoader()->load('Custom'.$aMatches[1]);

        // Clean plugin data
        $this->Context->cleanPluginParams();

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Created (URI) links and obfuscate them callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformUri(&$aMatches) {
        $sStr1 = $aMatches[1];

        if (isset($aMatches[2]) && $aMatches[2] != '') {
            $sStr2 = $aMatches[2];
        } else {

            // Because extremely long URIs won't wrap in browser output
            // and shred the output horizontally, we'll shorten them a bit.
            // If you change it, also change the PrintHtmlTransformer.

            // FIX: This behaviour might me toggleable or configureable in
            // length via the core.conf file.

            $sStr2 = shortenUrl($aMatches[1]);
        }

        $sTarget = ' target="_blank"';

        // No new window for mailto: URIs
        if (substr($sStr1, 0, 7) == 'mailto:') {
            $sTarget = '';
        }

        // obfuscate email if set in config and if no ftp link
        if ($this->Registry->get('RUNTIME_EMAIL_OBFUSCATE')
             && substr($sStr1, 0, 7) == 'mailto:') {

            $sStr1 = obfuscateEmail($sStr1);
            $sStr2 = obfuscateEmail($sStr2);
        }

		    $sStr = '<a'.$sTarget.' href="'.$sStr1.'">'.$sStr2.'</a>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Create a topic callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformTopic(&$aMatches) {
        $sUri = $this->Response->getControllerAction();

        $sStr =  '<a href="' . $sUri . '#A' . $aMatches[1] . '">';
        $sStr .=     $aMatches[2];
        $sStr .= '</a>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Create a header (title) callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformHeading(&$aMatches) {
        $sStr =  '<a name="A'. ($this->nTopicCount++) .'"></a>';
        $sStr .= '<h'.$aMatches[1].'>'.$aMatches[2].'</h'.$aMatches[1].'>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Create an inteded text callback.
     *
     * @access  protected
     * @return  string  Converted string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &_transformQuote(&$aMatches) {
        $sStr =  '<blockquote>';
        $sStr .= $aMatches[1];
        $sStr .= '</blockquote>';

        return $sStr;
    }

    // --------------------------------------------------------------------

    protected function &_transformJustification(&$aMatches) {
        $sStr =  '<div width="100%" style="text-align:'.$aMatches[1].'">';
        $sStr .= $aMatches[2];
        $sStr .= '</div>';
      /*
        $sStr =  '<p width="100%" style="text-align:'.$aMatches[1].'">';
        $sStr .= $aMatches[2];
        $sStr .= '</p>';
      */
        return $sStr;
    }

    // --------------------------------------------------------------------

    /**
     * Manipulate the finished string, if necessary
     *
     * @access  protected
     * @return  string  The manipulated string.
     *
     * @author  Daniel T. Gorski, <[email protected]>
     * @since   coWiki 0.3.0
     */
    protected function &finish(&$sStr, $Node) {
        return $sStr;
    }

} // of class

/*
    Rachel:   Do you like our owl?
    Deckard:  Is it artificial?
    Rachel:   Of course it is.
    Deckard:  Must be expensive.
    Rachel:   Very.
    Rachel:   I'm Rachel.
    Deckard:  Deckard.
    Rachel:   Its seems you feel our work is not a benefit to the public.
    Deckard:  Replicants are like any other machines. They are either a
              benefit or a hazard. If they're a benefit, it's not my problem.
    Rachel:   May I ask you a personal question?
    Deckard:  Sure.
    Rachel:   Have you ever retired a human, by mistake?
    Deckard:  No.
    Rachel:   But in your position that is a risk?
    Tyrell:   Is this to be an empathy test? Capilary dilation of the so
              called blush response ... fluctuation of the pupil ...
              involuntary dilation of the iris ...
    Deckard:  We call it Voight-Kampff for short.
    Rachel:   Mr. Deckard, Dr. Elden Tyrell.
    Tyrell:   Demonstrate it. I want to see it work.
    Deckard:  Were is the subject?
    Tyrell:   I want to see it work on a person. I want to see a negative
              before I provide you with a positive.
    Deckard:  What's that gonna prove?
    Tyrell:   Indulge me.
    Deckard:  On you?
    Tyrell:   Try her.
*/

?>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.