<?php
/******************************************************************************
 *  SiteBar 3 - The Bookmark Server for Personal and Team Use.                *
 *  Copyright (C) 2003-2006  Ondrej Brablc <http://brablc.com/mailto?o>       *
 *                                                                            *
 *  This program is free software; you can redistribute it and/or modify      *
 *  it under the terms of the GNU General Public License as published by      *
 *  the Free Software Foundation; either version 2 of the License, or         *
 *  (at your option) any later version.                                       *
 *                                                                            *
 *  This program is distributed in the hope that it will be useful,           *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
 *  GNU General Public License for more details.                              *
 *                                                                            *
 *  You should have received a copy of the GNU General Public License         *
 *  along with this program; if not, write to the Free Software               *
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA *
 ******************************************************************************/

require_once('./inc/tree.inc.php');
require_once('./inc/converter.inc.php');

$SB_loader_title = array();
$SB_loader_default = array();

class SB_LoaderInterface extends SB_Converter
{
    var $importedLinks = 0;
    var $importedFolders = 0;
    var $level = null;
    var $childs = array();
    var $root;

    function SB_LoaderInterface($useEngine=true, $charSet=null)
    {
        $this->SB_Converter($useEngine, $charSet);
    }

    function getAttributeMap() { die('Abstract class.'); }
    function getNodeTag() { die('Abstract class.'); }
    function getLinkTag() { die('Abstract class.'); }

    function load(&$lines, &$root)
    {
        $this->root =& $root;

        $xml_parser = xml_parser_create($this->charSet);
        xml_set_object($xml_parser, $this);
        xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, 0);
        xml_parser_set_option($xml_parser, XML_OPTION_SKIP_WHITE,   1);

        $data = implode("\n", $lines);
        unset($lines);

        $vals = array();
        $index = array();

        if (!xml_parse_into_struct($xml_parser, $data, $vals, $index))
        {
            $this->error("XML error: %s at line %d col %d", array
            (
                xml_error_string(xml_get_error_code($xml_parser)),
                xml_get_current_line_number($xml_parser),
                xml_get_current_column_number($xml_parser)
            ));
            return;
        }
        xml_parser_free($xml_parser);

        $xmlTree = $this->xmlGetChildren($vals, $i = 0);
        $this->parseTree($this->root, $xmlTree);

        return true;
    }

    function getMappedAttributes(&$tag, &$attrs)
    {
        static $map = null;

        if ($map==null)
        {
            $map = $this->getAttributeMap();
        }

        $mappedAttrs = array();

        if (isset($map[$tag]))
        {
            foreach ($attrs as $attname => $attvalue)
            {
                if (isset($map[$tag][$attname]))
                {
                    $mappedAttrs[$map[$tag][$attname]] = $attvalue;
                }
            }
        }

        return $mappedAttrs;
    }

    function createNode($xmlTag)
    {
        return new SB_Tree_Node($this->getMappedAttributes($xmlTag['tag'], $xmlTag['attributes']));
    }

    function createLink($xmlTag)
    {
        return new SB_Tree_Link($this->getMappedAttributes($xmlTag['tag'], $xmlTag['attributes']));
    }

    function parseTree(&$parent, $xmlTree)
    {
        foreach ($xmlTree as $index => $value)
        {
            $node = null;

            switch ($value['tag'])
            {
                case $this->getNodeTag():

                    $node = $this->createNode($value);

                    if ($node!=null)
                    {
                        $node->setParent($parent);

                        if (isset($value['children']))
                        {
                            $this->parseTree($node, $value['children']);
                        }
                        break;
                    }

                case $this->getLinkTag():

                    $link = $this->createLink($value);
                    $parent->addLink($link);
                    break;

                default:
                    if (isset($value['children']))
                    {
                        $this->parseTree($parent, $value['children']);
                    }
            }

            if ($node != null)
            {
                $parent->addNode($node);
            }
        }
    }

    function xmlGetChildren($vals, &$i)
    {
        $children = array();
        if (isset($vals[$i]['value']))
        {
            $children[] = $vals[$i]['value'];
        }
        while (++$i < count($vals))
        {
            switch ($vals[$i]['type'])
            {
                case 'cdata':
                    $children[] = $vals[$i]['value'];
                    break;
                case 'complete':
                    $children[] = array
                    (
                        'tag'        => $vals[$i]['tag'],
                        'attributes' => isset($vals[$i]['attributes'])?
                                        $vals[$i]['attributes'] : array(),
                        'value'      => isset($vals[$i]['value'])?
                                        $vals[$i]['value'] : null,
                    );
                    break;
                case 'open':
                    $children[] = array
                    (
                        'tag'        => $vals[$i]['tag'],
                        'attributes' => isset($vals[$i]['attributes'])?
                                        $vals[$i]['attributes'] : array(),
                        'children'   => $this->xmlGetChildren($vals, $i),
                    );
                    break;
                case 'close':
                    return $children;
            }
        }
    }
}

/******************************************************************************/

class SB_Loader extends SB_ErrorHandler
{
    var $root;
    // Content of the whole file - memory should not be problem
    var $lines = array();
    // Were bookmarks loaded?
    var $success = false;

    var $importedLinks = 0;
    var $importedFolders = 0;

    var $useEngine = true;
    var $charSet = null;

    function SB_Loader($useEngine=true, $charSet=null)
    {
        if (!$charSet)
        {
            $charSet = 'utf-8';
        }

        $this->useEngine = $useEngine;
        $this->charSet = $charSet;
    }

    function loadFile($filename, $type=null)
    {
        if (!($fp = fopen($filename, 'r')))
        {
            $this->error('Can\'t open file!');
            return;
        }

        $line = '';

        // PHP sucks again. fgets does not work as supposed,
        // we have to go to lower level and do end of line
        // detection ourselves.
        // We support all combinations of CR and LF for line ends.
        while (!feof($fp))
        {
            $line .= fread($fp,1024);

            while (preg_match("/(.*?)(\n\r|\r\n|\r|\n)(.*)/s", $line, $reg))
            {
                $this->lines[] = $reg[1];
                $line = $reg[3];
            }
        }
        fclose( $fp);

        // Last line
        $this->lines[] = $line;

        if (!count($this->lines))
        {
            $this->error('File is empty!');
            return;
        }

        $this->load($type);
    }

    function loadArray(&$array, $type=null)
    {
        $this->lines = $array;
        $this->load($type);
    }

    function load($type=null)
    {
        if (!$type)
        {
            if (stristr($this->lines[0], 'NETSCAPE-Bookmark-file-1'))
            {
                $type = 'netscape';
            }
            elseif (stristr($this->lines[0], 'Opera Hotlist version 2.0'))
            {
                $type = 'opera';
            }
            else
            {
                for ($i=0; $i<count($this->lines); $i++)
                {
                    if (preg_match('/<(\w+)\b.*>/', $this->lines[$i], $reg))
                    {
                        switch ($reg[1])
                        {
                            case 'xbel': $type = 'xbel'; break;
                            case 'feed': $type = 'atom'; break;
                            case 'opml': $type = 'opml_link'; break;
                            case 'rdf':  $type = 'rdf'; break;
                            case 'rss':  $type = 'rss'; break;
                        }
                    }
                }
            }
        }

        $loaderFile = './inc/loaders/'.$type.'.inc.php';
        if (is_file($loaderFile))
        {
            $this->root = new SB_Tree_Node();
            require_once($loaderFile);
            eval(sprintf('$loaderObj = new SB_Loader_%s();',
                $type, $this->useEngine, $this->charSet));

            if ($loaderObj->load($this->lines, $this->root))
            {
                $this->success = true;
                $this->importedLinks = $loaderObj->importedLinks;
                $this->importedFolders = $loaderObj->importedFolders;
            }
        }
        else
        {
            $this->error('Unknown bookmark file format!');
            return;
        }
    }
}
?>
