Hello,

here is my first try at a PHP class which can generate QooxDoo JavaScript from QxBuilder XML files. It is still very basic and incomplete, but you can see where I am going.

Suggestions are very much appreciated. Here are two example conversions:

<?xml version="1.0" encoding="utf-8"?>
<qx:widgets xmlns:qx="http://qooxdoo.sourceforge.net";> <qx:splitPane id="bg_horizonatlSplitpane"
                     orientation="horizontal"
                     top="0" left="0"
                     width="100%" height="100%"
                     oneTouchExpandable="false"
                     dividerLocation="100"
                     minimumDividerLocation="10"
                     firstWidget="bg_treepane"
                     useFirstChildAs="firstWidget"
                     useSecondChildAs="secondWidget">
<qx:label html="Left Pane"/> <qx:splitPane id="bg_verticalSplitpane"
                     orientation="vertical"
                     top="0" left="0"
                     width="100%" height="100%"
                     oneTouchExpandable="false"
                     dividerLocation="100"
                     minimumDividerLocation="10"
                     useFirstChildAs="firstWidget"
                     useSecondChildAs="secondWidget">
<qx:label html="Top Pane"/> <qx:label html="Bottom Pane"/> </qx:splitPane>
       </qx:splitPane>
</qx:widgets>

becomes

var qx_1 = bg_horizonatlSplitpane = new QxSplitPane("horizontal");

var qx_2 = bg_treepane = new QxLabel("Left Pane");
window.application.add(qx_2);
var qx_3 = bg_verticalSplitpane = new QxSplitPane("vertical");
window.application.add(qx_3);
var qx_4 = new QxLabel("Top Pane");
window.application.add(qx_4);
var qx_5 = new QxLabel("Bottom Pane");
window.application.add(qx_5);
qx_3.set({top:0,left:0,width:"100%",height:"100%",oneTouchExpandable:false,dividerLocation:100,minimumDividerLocation:10,firstWidget:qx_4,secondWidget:qx_5});
qx_1.set({top:0,left:0,width:"100%",height:"100%",oneTouchExpandable:false,dividerLocation:100,minimumDividerLocation:10,firstWidget:qx_2,secondWidget:qx_3});
window.application.add(qx_1);


and

<?xml version="1.0" encoding="utf-8"?>
<qx:widgets xmlns:qx="http://qooxdoo.sourceforge.net";> <qx:menuBar id="app_menubar" width="100%"> <qx:menuBarButton height="16" label="File" useFirstChildAs="menu">
               <qx:menu>
                   <qx:menuButton label="Open"/>
                   <qx:menuButton label="Save"/>
                   <qx:menuButton label="Close"/>
               </qx:menu>
           </qx:menuBarButton>
<qx:menuBarButton height="16" label="Edit" useFirstChildAs="menu">
               <qx:menu>
                   <qx:menuButton label="Copy"/>
                   <qx:menuButton label="Cut"/>
                   <qx:menuButton label="Paste"/>
               </qx:menu>
           </qx:menuBarButton>
       </qx:menuBar>

</qx:widgets>

becomes

var qx_1 = app_menubar = new QxMenuBar();
var qx_2 = new QxMenuBarButton("File");
var qx_3 = new QxMenu();
window.application.add(qx_3);
var qx_4 = new QxMenuButton("Open");
var qx_5 = new QxMenuButton("Save");
var qx_6 = new QxMenuButton("Close");
qx_3.add(qx_4,qx_5,qx_6);
qx_2.set({height:16,menu:qx_3});
var qx_7 = new QxMenuBarButton("Edit");
var qx_8 = new QxMenu();
window.application.add(qx_8);
var qx_9 = new QxMenuButton("Copy");
var qx_10 = new QxMenuButton("Cut");
var qx_11 = new QxMenuButton("Paste");
qx_8.add(qx_9,qx_10,qx_11);
qx_7.set({height:16,menu:qx_8});
qx_1.add(qx_2,qx_7);
qx_1.set({width:"100%"});
window.application.add(qx_1);

Judge which one is nice to code ;-)

Since the QooxDoo API does not always model a parent-child relationship ( because many times, child widgets are attached
by properties or methods rather than the generic "add" method),
there needs to be quite a bit of ugly hacking - still unfinished.

I will update the PHP QxBuilder while I am working on my app. Anyone
like to contribute?

Greetings,

Christian

<?php

/**
 * Server-Side QxBuilder Class
 *
 * @author Christian Boulanger
 * @version 0.1
 * @todo -implement QxEventListener delegate
 * 
 * Careful, this class behaves differently from the Javascript QxBuilder
 * 
 * Additional parse instructions via attributes:
 * forceAdd= "true"             forces adding of previously created widgets
 *                              to parent widget
 * args="attr1[,attr2[,..]]"    specifies which attributes to provide as
 *                              arguments to the constructor function.
 *                              this is needed because some QxWidget
 *                              require constructor arguments to be correctly
 *                              built.
 * useFirstChildAs="attr1"      Use the first/second child node as an attribute 
argument
 * useSecondChildAs="attr2      and don't add it to the parent. useful to keep a
 *                              logical parent-child order when the qooxdoo API 
doesn't
 *                              work that way (for example, in menus). property
 *                              will be set to the parent after the child 
widget has been
 *                              created.
 * 
 * 
 * useage:
 *  $qxbuilder          = new QxBuilder ( $xml  ); // or use QxBuilder ( $path, 
"file" );
 *      $this->output   = $qxbuilder->build();
 *      
 **/
 
 // dependencies
 require_once "XML/XPath.php";
 
 // constants
 define ( "QX_BUILDER_EVAL_ATTR" , 
"color,backgroundColor,border,menu,manager,group" );
 
 class QxBuilder
 {
    /**
     * debug mode
     **/
    var $debug = false;
 
    /**
     * prefix for anonymous variable counter
     **/
    var $varprefix = "qx_";
    
        /**
         * counter for anonymous javascript variables
         **/
        var $varcounter = 0;
        
        /**
         * constructor arguments for common QxWidget Types
         * needs to be updated from the QooxDoo API.
         * you can manually specify the constructor arguments by
         * providing an "args" attribute with the names of attributes
         * to use as arguments, for example args="html,icon"
         * var @array
         **/
        
        var $qxConstructorArgs = array (
                QxLabel                 => "html",
                QxSplitPane             => "orientation",
        QxMenuBarButton => "label",
        QxMenuButton    => "label,icon"
        );
    
    /**
     * parent objects
     * @var array
     **/
     
    var $_parent = array();
    
    /**
     * constructor
     * @param mixed $xml xml string or path to xml file
     * @param string type default string
     **/
    
    function QxBuilder ( $xml, $type="string" )
    {   
        switch ( $type ){
            
            case "file":
                $this->xml = file_get_contents ( $xml );
                break;
            
            default:
                $this->xml = $xml;
        }
    }
 
    /**
     * builds and returns javascript
     * @return string javascript code
     **/
 
    function build ()
    {
        $xmldoc = new XML_XPath();
        $xmldoc->load($this->xml);
        $nodes  = $xmldoc->evaluate('./*');
        $parent = "window.application";        
        return $this->_buildNode ( $nodes, $parent );
    }
    
        /**
         * remembers  javascript widget objects created on the client
         **/
        function _registerWidget ( $name )
        {
                if ( $this->_checkIfCreated ( $name ) ) return;
                $_SESSION['qx_widgets_created'][] = $name;
        }
        
        /**
         * checks if widget has been created
         **/
        function _checkIfCreated ( $name )
        {
                return ( @array_search( $name, $_SESSION['qx_widgets_created'] 
) !== false  );
        }

        /**
         * checks if argument / value need quotations marks (strings)
         * or not (numbers, objects)
         * @param mixed $arg
         * @return bool true if string
         **/
        function _isString ( $arg )
        {
                return ! ( is_numeric ( $arg ) or
                        $arg == "false" or $arg == "true" or
                        substr ( $arg, 0, 2 ) == "Qx" or
                        $this->_checkIfCreated( $arg ) );
        }

    /**
     * gets current anonymous variable 
     **/
    
    function _getCurrentVar()
    {
        return $this->varprefix . $this->varcounter;
    }

    /**
     * gets current anonymous variable and increase count
     * register widget as created
     **/
    
    function _getNewVar()
    {
        $id = $this->varprefix . ++$this->varcounter;
        $this->_registerWidget($id);
        return $id;
    }

    /**
     * gets next anonymous variable without increasing count
     **/
    
    function _getNextVar($offset=1)
    {
        $id =  $this->varprefix .  ( $this->varcounter + $offset );
        return $id;        
    }

        /**
         * converts a qooxdooXML node into javascript
         * @param PEAR_XML_RESULTSET Object $nodes
         * @param string $parent name of parent object
         **/
        
        function _buildNode ( &$nodes, $parent )
        {
                $output         = "";
                $objectsToAdd   = array();
        $nodes->rewind();
        
                
                while($nodes->next()){
                        
                        $nodeName       = $nodes->nodeName();
                        if ( $nodeName == "#comment" ) continue;
                        
                        $className      = "Qx" . strtoupper ( substr ( 
$nodeName,0,1 ) ) . substr ( $nodeName, 1);
                        $varName        = $this->_getNewVar();
                        $attributes = $nodes->getAttributes();
                        $constrArgs     = either (  $attributes['args'],
                                                                        
$this->qxConstructorArgs[$className],
                                                                        "" );
                        $args           = "";
                        
            // use first/second child as attribute argument (see class doc)
            
            if ( $attr = $attributes['useFirstChildAs'] ) {
                $firstChild        = $this->_getNextVar(); 
                $attributes[$attr] = $firstChild;
                $this->_parent[$firstChild] = "window.application";
                unset ( $attributes['useFirstChildAs'] );
            }
            
            if ( $attr = $attributes['useSecondChildAs'] ) {
                $secondChild       = $this->_getNextVar(2); 
                $attributes[$attr] = $secondChild;
                $this->_parent[$secondChild] = "window.application";
                unset ( $attributes['useSecondChildAs'] );
            }
            
                        // constructor arguments

                        if ( count ( $constrArgs = explode ( ",", $constrArgs ) 
) ) {
                                foreach ( $constrArgs as $i => $key ) {
                    $value = $attributes[$key];
                    if ( ! empty ( $value ) ) { 
                        if ( $this->_isString ( $value  ) ) {
                            $constrArgs [$i] = '"' . $value  . '"';
                        } else {
                            $constrArgs [$i] = $value;
                        }
                        unset ( $attributes[$key] );
                    } else {
                        unset ( $constrArgs[$i] );
                    }
                                }
                                $args = implode (",", $constrArgs );
                        }
                
                        if ( $this->debug ) {
                                $output .= "$parent.debug ('$varName : 
$className(" . addslashes($args) . ")');\n";
                        }
            
                        switch ( $className ) {

                                case "QxScript":
                                        $code   = preg_replace ( 
"/(<!--)|(-->)|(<\/?qx:[^>]*>)/","", $nodes->toString() );
                                        $output .= $code . "\n";
                                        break;
                                
                                case "QxEventListener":
                                        // todo: delegates
                                        $code   = preg_replace ( 
"/(<!--)|(-->)|(<\/?qx:[^>]*>)/","", $nodes->toString() );
                                        $event  = either ( $attributes['args'], 
"event" );
                                        $type   = $attributes['type'];
                                        $output .= 
"$parent.addEventListener('$type',function($event){\n$code\n});\n";
                                        break;                  
                        
                                default:
                                
                                        // create instance
                                        if ( $id = $attributes['id'] ) {
                                                // pass to global variable
                                                $instance = "var $varName = $id 
= new $className($args);\n";
                                                $this->_registerWidget($id);
                                        } else {
                                                $instance = "var $varName = new 
$className($args);\n";
                                        }
                    
                    $output .= $instance;
                    
                    // add to parent
                    if ( $this->_parent[$varName] ) {
                        $output .= $this->_parent[$varName] . 
".add($varName);\n";
                    } else {
                        $objectsToAdd[] = $varName;
                    }
                                        
                    // add children
                    
                    if ( !PEAR::isError ( $children = $nodes->childNodes() ) ) 
{        
                                                $output .= $this->_buildNode ( 
$children, $varName);
                                        }

                                        // add attributes
                                        $attSet                 = array();
                                        $eventListeners = "";
                                        
                                        if ( count ( $attributes ) ){
                                                foreach ( $attributes as $key 
=> $value ){
                                                        
                                                        // key types
                                                        if ( $key == "id" or 
$key == "args" ) {
                                                                continue;
                                                        } elseif ( $key == 
"forceAdd" ) {
                                                                if ( $value == 
"true" and count ( $objectsToAdd ) ) {
                                    $output .= "$parent.add(" . implode (",", 
$objectsToAdd ) . ");\n";
                                    $objectsToAdd = array();
                                                                }
                                continue;
                                                        } elseif ( ereg( 
"on[A-Z]", substr ($key,0,2) ) ) {
                                                                $type = 
strtolower ( substr ( $key,2 ) );
                                                                $eventListeners 
.= "$varName.addEventListener('$type',function(event)\{$value});\n";
                                                                continue;
                                                        } elseif ( strstr( 
QX_BUILDER_EVAL_ATTR, $key ) ) {
                                                                $attSet[] = 
"$key:$value";
                                                                continue;
                                                        }
                                                        
                                                        // value type
                                                        if ( $this->_isString ( 
$value ) ) {
                                                                $attSet[] = 
"$key:\"$value\"";
                                                        } else {
                                                                $attSet[] = 
"$key:$value";
                                                        }
                                                }
                                                if ( $this->debug ) {
                                                        foreach ( $attSet as 
$attr ) {
                                                                $output .= 
"$varName.debug('$attr')\n";
                                                                $output .= 
"$varName.set(\{$attr});\n";
                                                        }
                                                } elseif ( count ( $attSet ) ) {
                                                        $output .= 
"$varName.set({" . implode ( ",",$attSet ) . "});\n" ;
                                                }
                                                if ( $eventListeners ) $output 
.= $eventListeners;
                                        }

                                        break;
                        }
                }
                if ( count ( $objectsToAdd ) ) {
                        $output .= "$parent.add(" . implode (",", $objectsToAdd 
) . ");\n";
            $objectsToAdd = array();
                }
                return $output;
        }
    

}

if ( ! function_exists ( "either" ) ){
        /**
         * avoids if statements such as if($a) $c=$a; else $c=$b;
         *
         * @argument mixed 
         * @argument mixed ...
         * @return first non-false argument, otherwise false
         */ 
        
        function &either()
        {
                $arg_list = func_get_args();
                foreach($arg_list as $i => $arg)
                        if ( $arg ) return $arg_list[$i];
                return false;
        }
}
?>

Reply via email to