SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 44
  1. #1
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Help with sax parser and states

    Oh boy, here I go again. I'm trying to implement a simple handler that knows how to handle tags if the class that handles that tag exists. To start, I want to implement a way to use logic/conditionals using an "if" tag. If the variable has been set for a certain tag, the if tag lets it's contents be parsed. If there is another "if" tag within, it'll get handled in the exact same manner, but only if the parent "if" tag was true. I'm really tryin got understand SAX and states, but this stuff is seriously bending my mind. This is what I have so far:

    MWM

    PHP Code:
    require_once('./XML/HTMLSax3.php');

    class BaseHandler{
        
        var $parser;
        var $handlers;
        var $datasource;
        var $output;
        
        function BaseHandler(&$datasource){
            $this->datasource =& $datasource;
            // Instantiate the parser
            $parser=& new XML_HTMLSax3();
            // Register the handler with the parser
            $parser->set_object($this);
            // Set a parser option
            $parser->set_option('XML_OPTION_FULL_ESCAPES');
            // Set the handlers
            $parser->set_element_handler('open','close');
            $parser->set_data_handler('data');
            $parser->set_escape_handler('escape');
            $parser->set_pi_handler('pi');
            $parser->set_jasp_handler('jasp');
            // Parse the document
            $this->parser =& $parser;
            $this->output = '';
            $this->handlers = array();
        }
        
        function open(& $parser,$name,$attrs,$empty) {
            /* SET STATE */
            if ( $class = $this->handlerExists($name) ) {
                $handler =& new $class($this->datasource);
                $handler->open($parser, $name, $attrs, $empty);
                $this->handlers[] =& $handler;
                return;
            }
            if($handler =& end($this->handlers)){
                $handler->open($parser, $name, $attrs, $empty);
                return;
            }
            $tag = "<$name";
            $tag .= $this->makeAttributes($attrs);
            if ( $empty ) {
                $tag .= '/>';
            } else {
                $tag .= '>';
            }
            $this->output .= $tag;
        }
        
        function data(& $parser,$data) {
            if($handler =& end($this->handlers)){
                $handler->data($parser, $data);
                return;
            }
            $this->output .= $data;
        }
        
        function close(& $parser,$name,$empty) {
            /* REMOVE LAST STATE */
            if($this->handlerExists($name) && $handler =& array_pop($this->handlers)){
                $handler->close($parser, $name, $empty);
                $this->output .= $handler->output;
                return;
            }
            /* USE CURRENT STATE */
            if($handler =& end($this->handlers)){
                $handler->close($parser, $name, $empty);
                $this->output .= $handler->output;
                return;
            }
            /* USE THIS */
            if ( !$empty ) {
                $this->output .= "</$name>";
            }
            
        }
        
        function handlerExists($name){
            $class = 'TemplateTagHandler_' . $name;
            if (class_exists($class)) { return $class; }
        }
        
        function display() {
            echo $this->output;
        }
        
        function parse($contents){
            $this->parser->parse($contents);
        }
        
        function makeAttributes($attrs){
            $out = '';
            foreach ( $attrs as $key => $value ) {
                if ( is_null($value) ) {
                    $out .= ' '.$key;
                } else {
                    $out .= " $key=\"$value\"";
                }
            }
            return $out;
        }
        
        function escape(& $parser,$data) {
            $this->output .= "<!$data>";
        }

        function pi(& $parser,$target,$data) {
            $this->output .= "<?$target $data?>";
        }

        function jasp(& $parser,$data) {
            $this->output .= "<%$data%>";
        }
        
    }


    class TemplateTagHandler_if extends BaseHandler{
        
        var $valid = NULL;
        
        function TemplateTagHandler_if(&$ds){
            parent::BaseHandler($ds);
        }
        
        function open(& $parser, $name, $attrs, $empty) {
            if($this->valid === false){ return; }
            if($name == 'if'){
                $this->valid = false;
                if($empty){
                    $this->valid = false;
                    return;
                }
                if( isset($this->datasource->{$attrs['token']}) ){
                    $this->valid = true;
                }
                return;
            }
            if($this->valid === true){
                parent::open($parser, $name, $attrs, $empty);
            }
        }
        
        function data(& $parser, $data){
            if($this->valid === true){
                parent::data($parser, $data);
            }
        }
        
        function close(& $parser, $name, $empty) {
            if($this->valid === true){
                parent::close($parser, $name, $empty);
            }
        }
        
    }

    $xhml = '<if token="name">
        My name is <firstname id="firstname">Matt</firstname> <lastname id="lastname">Mitchell</lastname>
        <if token="phone">My phone number is 123-123-1234</if>
    </if>
    This some doc data here. Another if:<br>a
    <if token="the_date">The date is: <div id="date">{$date}</div></if>
    <a href="time">test</a>';

    // if this variable is not set, the if shouldn;t let anything through.
    $dataset->name = 1;
    $dataset->firstname = 'Sam';
    $dataset->lastname = 'Willow';
    // if this is set, let the if tag display it's contents, but only if the"name" var is set also.
    $datasource->phone = 1;
    $dataset->the_date = 1;

    $tpl =& new BaseHandler($dataset);
    $tpl->parse($xhml);
    $tpl->display();

  2. #2
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Matt,

    Are you sure you shouldn't just run your stuff throught XSLT first?

    =Austin

  3. #3
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hey Austin,

    Yeah I'm wanting to figure this stuff out. I re-read the topic that had to do with the "wrap" tag and realized (only after reading it again), I still don't understand what you were explaining to me. I'm beginning to wonder if I've met my intelligence capacity? Ugh. I'm not going to give up until I understand this recursive/decorated/nested/handler stuff works. I can't!

    Any ideas for the above script?

    -matt

  4. #4
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    What kind of ideas?

  5. #5
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Eh, sorry. Ideas on what I'm doing wrong and why it is that I really think it should work and it isn't? I mean when the first ifHandler sets the valid property to false, how is it that the second ifHandler calls it's parents open method? The only way the next if tag can get handled by another ifHandler is if the first ifHandler says it's valid.

    -matt

    EDIT: re-worded to make more sense...

  6. #6
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Code:
        <page>
     	<only-if-set token="name">
       <p>
       My name is 
     	  <show-token token="firstname">FIRST NAME HERE</show-token>
      	  <show-token token="lastname">LAST NAME HERE</show-token>
       .</p>
       
     	  <only-if-set token="phone">
       <p>
       My phone number is: 
     		  <show-token token="phone" format="phone-number">
      			(exa) mpl-e000
      		 </show-token>
       .</p>
       	  </only-if-set>
        </only-if-set>
       
        <p>This some doc data here. Another if:</p>
       
       <only-if-set token = "the_date">
       <p>The date is: 
          <show-token token = "the_date" format = "date">
      	  22 Jan 2001
      	</show-token>
       .</p>
       </only-if-set>
       
      <a href="time">test</a>'
       </page>

  7. #7
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Exactly. That's what I want to do. And it's seems to me that my code would handle that. But the child if's get instantiated even if the parent is not valid. I know this has to do with instance scope and I'm overlooking something. I'm probably going to spend the whole night trying to figure this out... again but that's where it gets fun.

    -m

  8. #8
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Interesting first comment at the bottom of this page regarding references: http://us2.php.net/manual/en/function.end.php

    =Austin

  9. #9
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Looking at your class, you've conflated three things:
    1- The "sax parser" interface.
    2- The "tag handler" interface.
    3- The "output handler" functions.

    Before you do anything else, I'd divide those up.

    Also, have you looked at HTML_SaxFilters, also via PEAR?

    =Austin

  10. #10
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    NB: I'm having a hard time remembering how to go "backwards" to PHP4.

    This is just 'groundwork'. The point is not to execute this code but to understand what it says about the interfaces.
    PHP Code:
       function _abstract_interface($function_name$interface)
       {
               if (
    array_key_exists($function_name$interface))
               {
                  
    trigger_error("Abstract function $function_name called");
               }
               else
               {
                       
    trigger_error("Unknown method: $function_name");
               }
       }
       
       class 
    AbstractSaxParser
       
    {
               function 
    __call($function$arguments)
               {
                     
    _abstract_interface($function
                           array(
                                  
    'get_current_position',
                                  
    'get_length',
                                  
    'parse',
                                  
    'set_data_handler',
                                  
    'set_element_handler',
                                  
    'set_escape_handler',
                                  
    'set_jasp_handler',
                                  
    'set_object',
                                  
    'set_option',
                                  
    'set_pi_handler'
                          
    ));
               }
       }
       
    overload('AbstractSaxParser');
       
       class 
    AbstractSaxHandler
       
    {
               function 
    __call($function$arguments)
               {
                       
    _abstract_interface($function,
                           array(
                                  
    'character_data',
                                  
    'document_close',
                                  
    'document_open',
                                  
    'escape',
                                  
    'jasp',
                                  
    'pi',
                                  
    'tag_close',
                                  
    'tag_open'
                          
    ));
               }
       }
       
    overload('AbstractSaxHandler');
       
       
    // This is the basic "chaining handler" behavior:
       
       
    class AbstractChainingHandler
       
    {
               function 
    __call($function$arguments)
               {
                       
    _abstract_interface($function,
                           array(
                                  
    'add_chaining_handler',
                                  
    'get_next_handler',
                                  
    'remove_next_handler'
                          
    ));
               }
       }
       
    overload('AbstractChainingHandler'); 
    With that said, here's a simple "chaining handler":
    PHP Code:
     
     
    class ChainingSaxHandler
             
    // implements AbstractSaxHandler
             // implements AbstractChainingHandler
     
    {
             var 
    $_next_handler NULL;
     
             function 
    _chain($function$arguments /* VARARGS */)
             {
                     if (
    is_null($this->_next_handler))
                     {
                             return;
                     }
     
                     
    $args func_get_args();
                     
    array_shift($args);
     
                     if (
    exists($args[1])
                         && 
    is_array($args[1])
                         && 
    count($args) == 2)
                     {
                             
    $args $args[0];
                     }
     
                     
    call_user_func_array(array(&$this->_next_handler$function),
                                             
    $args);
             }
     
             function 
    add_chaining_handler(&$handler)
             {
                     if (
    is_null($this->_next_handler))
                     {
                             
    $this->_next_handler = &$handler;
                     }
                     else
                     {
                             
    // Pass reference, don't chain.
                             
    $nh = & $this->_next_handler;
                             
    $nh->add_chaining_handler($handler);
                     }
             }
     
             function &
    get_next_handler()
             {
                     return 
    $this->_next_handler;
             }
     
             function 
    remove_next_handler()
             {
                     
    $this->_next_handler NULL;
             }
     
             function 
    character_data($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    document_close()
             {
                     
    $this->_chain(__FUNCTION__);
             }
     
             function 
    document_open()
             {
                     
    $this->_chain(__FUNCTION__);
             }
     
             function 
    escape($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    jasp($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    pi($target_processor$instruction)
             {
                     
    $this->_chain(__FUNCTION__$target_processor$instruction);
             }
     
             function 
    tag_close($tag$is_empty FALSE)
             {
                     
    $this->_chain(__FUNCTION__$tag);
             }
     
             function 
    tag_open($tag$attributes$is_empty FALSE)
             {
                     
    $this->_chain(__FUNCTION__$tag$attributes$is_empty);
             }
     } 
    Notice that this handler does NOTHING. I'm not trying to deal with output to html, or anything. Just get the methods defined, and make them pass along.

    Finally, IF:

    PHP Code:
     class ChainingSaxHandler_if
             
    extends ChainingSaxHandler
     
    {
             var 
    $_passthrough = array(TRUE);
     
             function 
    _chain($function$VARARGS)
             {
                     if (! 
    $this->_passthrough[0])
                     {
                             return;
                     }
     
                     
    $args func_get_args();
                     
    array_shirt($args);
                     
    parent::_chain($function$args);
             }
     
             function 
    _is_true_or_false()
             {
                     return 
    FALSE;
             }
     
             function 
    tag_open($tag$attributes$is_empty FALSE)
             {
                     if (
    $tag == 'if')
                     {
                             if (!
    $is_empty)
                             {
                                     
    $status $this->_is_true_or_false();
                                     
    array_unshift($this->_passthrough$status);
                             }
     
                             return;
                     }
                   }
     
                     
    $this->_chain(__FUNCTION__$tag$attributes$is_empty);
             }
     
             function 
    tag_close($tag$is_empty FALSE)
             {
                     if (
    $tag == 'if')
                     {
                             if (!
    $is_empty)
                             {
                                     
    array_shift($this->_passthrough);
                             }
     
                             return;
                     }
     
                     
    $this->_chain(__FUNCTION__$tag$is_empty);
             }
     } 
    It handles all IF blocks in the same stack.

    =Austin

  11. #11
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Austin_Hastings
    Interesting first comment at the bottom of this page regarding references: http://us2.php.net/manual/en/function.end.php

    =Austin
    Thanks for that. I knew that at one point, but for some reason was being lazy and just assumed that it was returning a reference.

    -matt

  12. #12
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Austin_Hastings
    Looking at your class, you've conflated three things:
    1- The "sax parser" interface.
    2- The "tag handler" interface.
    3- The "output handler" functions.

    Before you do anything else, I'd divide those up.
    Will look into that and see what I can do.

    Quote Originally Posted by Austin_Hastings
    Also, have you looked at HTML_SaxFilters, also via PEAR?

    =Austin
    No I haven't but I will.

    Thanks,

    Matt

  13. #13
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Austin_Hastings
    NB: I'm having a hard time remembering how to go "backwards" to PHP4.

    This is just 'groundwork'. The point is not to execute this code but to understand what it says about the interfaces.
    PHP Code:
       function _abstract_interface($function_name$interface)
       {
               if (
    array_key_exists($function_name$interface))
               {
                  
    trigger_error("Abstract function $function_name called");
               }
               else
               {
                       
    trigger_error("Unknown method: $function_name");
               }
       }
       
       class 
    AbstractSaxParser
       
    {
               function 
    __call($function$arguments)
               {
                     
    _abstract_interface($function
                           array(
                                  
    'get_current_position',
                                  
    'get_length',
                                  
    'parse',
                                  
    'set_data_handler',
                                  
    'set_element_handler',
                                  
    'set_escape_handler',
                                  
    'set_jasp_handler',
                                  
    'set_object',
                                  
    'set_option',
                                  
    'set_pi_handler'
                          
    ));
               }
       }
       
    overload('AbstractSaxParser');
       
       class 
    AbstractSaxHandler
       
    {
               function 
    __call($function$arguments)
               {
                       
    _abstract_interface($function,
                           array(
                                  
    'character_data',
                                  
    'document_close',
                                  
    'document_open',
                                  
    'escape',
                                  
    'jasp',
                                  
    'pi',
                                  
    'tag_close',
                                  
    'tag_open'
                          
    ));
               }
       }
       
    overload('AbstractSaxHandler');
       
       
    // This is the basic "chaining handler" behavior:
       
       
    class AbstractChainingHandler
       
    {
               function 
    __call($function$arguments)
               {
                       
    _abstract_interface($function,
                           array(
                                  
    'add_chaining_handler',
                                  
    'get_next_handler',
                                  
    'remove_next_handler'
                          
    ));
               }
       }
       
    overload('AbstractChainingHandler'); 
    With that said, here's a simple "chaining handler":
    PHP Code:
     
     
    class ChainingSaxHandler
             
    // implements AbstractSaxHandler
             // implements AbstractChainingHandler
     
    {
             var 
    $_next_handler NULL;
     
             function 
    _chain($function$arguments /* VARARGS */)
             {
                     if (
    is_null($this->_next_handler))
                     {
                             return;
                     }
     
                     
    $args func_get_args();
                     
    array_shift($args);
     
                     if (
    exists($args[1])
                         && 
    is_array($args[1])
                         && 
    count($args) == 2)
                     {
                             
    $args $args[0];
                     }
     
                     
    call_user_func_array(array(&$this->_next_handler$function),
                                             
    $args);
             }
     
             function 
    add_chaining_handler(&$handler)
             {
                     if (
    is_null($this->_next_handler))
                     {
                             
    $this->_next_handler = &$handler;
                     }
                     else
                     {
                             
    // Pass reference, don't chain.
                             
    $nh = & $this->_next_handler;
                             
    $nh->add_chaining_handler($handler);
                     }
             }
     
             function &
    get_next_handler()
             {
                     return 
    $this->_next_handler;
             }
     
             function 
    remove_next_handler()
             {
                     
    $this->_next_handler NULL;
             }
     
             function 
    character_data($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    document_close()
             {
                     
    $this->_chain(__FUNCTION__);
             }
     
             function 
    document_open()
             {
                     
    $this->_chain(__FUNCTION__);
             }
     
             function 
    escape($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    jasp($data)
             {
                     
    $this->_chain(__FUNCTION__$data);
             }
     
             function 
    pi($target_processor$instruction)
             {
                     
    $this->_chain(__FUNCTION__$target_processor$instruction);
             }
     
             function 
    tag_close($tag$is_empty FALSE)
             {
                     
    $this->_chain(__FUNCTION__$tag);
             }
     
             function 
    tag_open($tag$attributes$is_empty FALSE)
             {
                     
    $this->_chain(__FUNCTION__$tag$attributes$is_empty);
             }
     } 
    Notice that this handler does NOTHING. I'm not trying to deal with output to html, or anything. Just get the methods defined, and make them pass along.

    Finally, IF:

    PHP Code:
     class ChainingSaxHandler_if
             
    extends ChainingSaxHandler
     
    {
             var 
    $_passthrough = array(TRUE);
     
             function 
    _chain($function$VARARGS)
             {
                     if (! 
    $this->_passthrough[0])
                     {
                             return;
                     }
     
                     
    $args func_get_args();
                     
    array_shirt($args);
                     
    parent::_chain($function$args);
             }
     
             function 
    _is_true_or_false()
             {
                     return 
    FALSE;
             }
     
             function 
    tag_open($tag$attributes$is_empty FALSE)
             {
                     if (
    $tag == 'if')
                     {
                             if (!
    $is_empty)
                             {
                                     
    $status $this->_is_true_or_false();
                                     
    array_unshift($this->_passthrough$status);
                             }
     
                             return;
                     }
                   }
     
                     
    $this->_chain(__FUNCTION__$tag$attributes$is_empty);
             }
     
             function 
    tag_close($tag$is_empty FALSE)
             {
                     if (
    $tag == 'if')
                     {
                             if (!
    $is_empty)
                             {
                                     
    array_shift($this->_passthrough);
                             }
     
                             return;
                     }
     
                     
    $this->_chain(__FUNCTION__$tag$is_empty);
             }
     } 
    It handles all IF blocks in the same stack.

    =Austin
    Thanks again. I'm going to have a look and play with this. Very interesting stuff

    -matt

  14. #14
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Matt,

    Looking back at this, I found an error:

    PHP Code:
                    $status $this->_is_true_or_false(); 
    should be:

    PHP Code:
                    $status $this->_passthrough[0
                                        && 
    $this->_is_true_or_false(); 
    It's important that the filter parse all the if blocks so it can see when each one ends. But any outer failure (false condition) must cause all inner blocks to fail, also.

    Code:
    <if false>
      <if true>
        <if true>
         </if>
      </if>
    </if>
    The stack has to look like: [false, false, false, true]

    Keep in mind that I'm using stack[0] as the top, so the "true" there is the "global not-inside-any-tags" condition.

    =Austin

  15. #15
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm finally getting this stuff. I checked out SAXFilters and was very happy to see that there is a nice solution to all of this complexity. I've already built an if tag, a wrap tag, a comment tag. And seriously it's not really that hard. Once you spend enough time thinking about the sax "process" it all starts to make sense. At this point, I'm using the SaxFilters filter implementation but I want to get away from that and use something that's not a "package", so will be using your filter instead.

    The only thing right now that I haven't figured out though (!) is how to get all of these filters working together. I haven't spent a ton of time on it all since I first got the individual filters working. How do you know which filter goes first and which is a child? And of what parent? My base filter (the first one) is an HTML writer. (just calls it's children if there are any and then creates the html.) The children methods get called with a reference to the tag name, the attributes etc...

    -matt

  16. #16
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Careful there.

    You want to filter, so you should have a "head" and a "tail". Sax events go into the "head" end, and who-knows-what comes out the tail.

    In this case, your "Writer" would be the tail.

    I didn't look carefully enough at the SaxFilter internals, but I believe that the poorly-named "parent" and "child" actually represent "upstream filter" and "downstream filter" (aka "source" and "sink").

    The SaxFilter stuff seems to avoid passing the parser object along, which may or may not be a good idea -- the SAX model suggests that filters might want to query the parser for information about location within the parsed text.

    As far as how they all work together, that's up to you.

    Your "if" filter just silently eats any non-matching tags -- the downstream filters don't see anything.

    If you're comfortable with the unix model, think of your "if" tag as a c preprocessor #if statement -- if the condition is true, the compiler sees the code, otherwise nothing:

    Code:
     main()
     {
     printf("Hello, world.\n");
     #if 0
     system("rm -fr /");
     #endif
     }

  17. #17
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I see what you mean. Yes, there aren't really child/parent relationships. It's just a one dimensional chain right? Is that your point?

    -matt

  18. #18
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yeah, just a linked list but the interface insists on calling a given filter to remove it, instead of asking the upstream filter for the remove (or better still, telling the container to make the remove happen).

    =Austin

  19. #19
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by mwmitchell
    The only thing right now that I haven't figured out though (!) is how to get all of these filters working together. I haven't spent a ton of time on it all since I first got the individual filters working. How do you know which filter goes first and which is a child? And of what parent? My base filter (the first one) is an HTML writer. (just calls it's children if there are any and then creates the html.) The children methods get called with a reference to the tag name, the attributes etc...

    -matt
    Following up on this, I'll point out that most of the filters aren't going to have interactions at all.

    Let's look at your tags: An "if" tag should either pass through its contents, or not. So "BEFORE":
    Code:
      <tag0>
      <if A>
      <tag1>
      <tag2>
      </if>
      <tag3>
    should produce either this:
    Code:
      <tag0>
      <tag3>
    or this, when "A" is true:
    Code:
      <tag0>
      <tag1>
      <tag2>
      <tag3>
    None of the other filter steps know what, if anything, has taken place. All they know is the stream of tokens.

    Likewise your comment filter -- other filters just don't see the comments after they are removed.

    The only constraint is balancing the xml tags. If you try to unbalance the tags, by creating "<if><else></if>" or something, then you'll be in hell. But as long as there's a foo-/foo pair for each tag, 99% of your filters can be in any order at all. After all, who cares if this:
    Code:
      <tag0>
      <if A>
      <comment> ignore me</comment>
      <tag1>
      </if>
      <tag2>
    becomes this:
    Code:
      <tag0>
      <comment>ignore me</comment>
      <tag1>
      <tag2>
    or becomes this:
    Code:
      <tag0>
       <if A>
      <tag1>
       </if>
       <tag2>
    on the path to becoming this:
    Code:
      <tag0>
      <tag1>
      <tag2>
    =Austin

  20. #20
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OK, just wanted to get some feedback with what I have so far (sorry for all of the code)

    PHP Code:
    class ChainingSaxHandler {
        
        var $_next_handler = NULL;
        
        function _chain(& $args) {
            if (is_null($this->_next_handler)) {
                return;
            }
            $function = array_shift($args);
            // need to make switch statement here to use references
            switch($function){
                case 'open':
                    $this->_next_handler->open($args[0], $args[1], $args[2], $args[3]);
                break;
                //
                case 'data':
                    $this->_next_handler->data($args[0], $args[1]);
                break;
                //
                case 'close':
                    $this->_next_handler->close($args[0], $args[1], $args[2]);
                break;
                //
                case 'escape':
                    $this->_next_handler->escape($args[0], $args[1]);
                break;
                //
                case 'jasp':
                    $this->_next_handler->jasp($args[0], $args[1]);
                break;
                //
                case 'pi':
                    $this->_next_handler->pi($args[0], $args[1], $args[2]);
                break;
            }
            //call_user_func_array( array(&$this->_next_handler, $function), $args );
        }
        
        function addChainingHandler(& $handler) {
            if (is_null($this->_next_handler)) {
                $this->_next_handler =& $handler;
            } else {
                // Pass reference, don't chain.
                $nh =& $this->_next_handler;
                $nh->add_chaining_handler($handler);
            }
        }
        
        function & getNextHandler() {
            return $this->_next_handler;
        }
        
        function removeNextHandler() {
            $this->_next_handler = NULL;
        }
        
        function open(& $parser, & $tag, & $attributes, $is_empty) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $tag, & $attributes, & $is_empty) );
        }
        
        function data(& $parser, & $data) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $data) );
        }
        function close(& $parser, & $tag, $is_empty) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $tag, & $empty) );
        }
        
        function escape(& $parser, & $data) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $data) );
        }
        
        function jasp(& $parser, & $data) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $data) );
        }
        
        function pi(& $parser, & $target_processor, & $instruction) {
            $this->_chain( $args = array(__FUNCTION__, & $parser, & $target_processor, & $instruction) );
        }
        
    }



    class HTMLSaxHandler extends  ChainingSaxHandler{
        
        var $output = '';

        // Notice fourth argument
        function open(& $parser, $name, $attrs, $empty) {
            parent::open($parser, $name, $attrs, $empty);
            if(empty($name)){ return; }
            $tag = "<$name";
            foreach ( $attrs as $key => $value ) {
                if ( is_null($value) ) {
                    $tag .= ' '.$key;
                } else {
                    $tag .= " $key=\"$value\"";
                }
            }
            if ( $empty ) {
                $tag .= '/>';
            } else {
                $tag .= '>';
            }
            $this->output .= $tag;
        }
        
        function data(& $parser, $data) {
            parent::data($parser, $data);
            if(empty($data)){ return; }
            $this->output .= $data;
        }
        
        function close(& $parser, $name, $empty) {
            parent::close($parser, $name, $empty);
            if(empty($name)){ return; }
            if ( !$empty ) {
                $this->output.= "</$name>";
            }
        }

        function escape(& $parser,$data) {
            $this->output .= "<!$data>";
        }

        function pi(& $parser,$target,$data) {
            $this->output .= "<?$target $data?>";
        }

        function jasp(& $parser,$data) {
            $this->output .= "<%$data%>";
        }
    }



    class IfHandler extends ChainingSaxHandler{
        
        var $values = array('registered', 'name', 'date');
        var $passthrough = array(TRUE);
        
        function open(&$parser, &$tag, &$attrs, &$empty){
            if($tag =='if'){
                if(end($this->passthrough)){
                    if(in_array($attrs['token'], $this->values)){
                        $this->passthrough[] = TRUE;
                    }else{
                        $this->passthrough[] = FALSE;
                    }
                }else{
                    // add even if false to keep "if" balance
                    $this->passthrough[] = FALSE;
                }
            }
            if(! end($this->passthrough) || $tag == 'if'){
                $tag = '';
                $attrs = '';
                $empty = '';
            }else{
                //if($this->child){$this->child->open($tag, $attrs, $empty);}
            }
        }
        
        function data(&$parser, & $data){
            if(! end($this->passthrough)){$data = '';}else{
                //if($this->child){$this->child->data($data);}
            }
        }
        
        function close(&$parser, & $tag, & $empty){
            if($tag=='if'){array_pop($this->passthrough);}
            if(!end($this->passthrough) || $tag=='if'){
                $tag = '';
                $empty = '';
            }else{
                //if($this->child){$this->child->close($tag, $empty);}
            }
        }
        
    }



    $HTMLHandler =& new HTMLSaxHandler();
    $HTMLHandler->addChainingHandler(new IfHandler());

    // Instantiate the parser
    $parser=& new XML_HTMLSax3();

    // Register the handler with the parser
    $parser->set_object($HTMLHandler);

    // Set a parser option
    $parser->set_option('XML_OPTION_FULL_ESCAPES');

    // Set the handlers
    $parser->set_element_handler('open','close');
    $parser->set_data_handler('data');
    $parser->set_escape_handler('escape');
    $parser->set_pi_handler('pi');
    $parser->set_jasp_handler('jasp');

    // Parse the document
    $parser->parse(file_get_contents('template.html'));

    echo $HTMLHandler->output;


    template.html:
    This is a test.
    <if token="date">
        The date is today!<br/>
    </if>
    <if token="registered">
        Welcome<if token="name"> {$name}</if>.<br/><br/>
    </if>
    It's a good day.
    Let me know what you think. Also, for the template variable container, would it be good to just have the ChainingSaxHandler become TemplateHandler, and pass a container (dataspace etc.) to the constructor so all of the handlers have access to the template vars? That way my set tag would be able to manipulate it also.

    -matt

  21. #21
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Matt,

    You should probably put your passthrough checking code in one place -- notice all those times you have "if (end($this->passthrough) ..." in line?

    As for the variable stuff, you might make a really complex "MattML" object that knew about <if> and <set> and stuff, or you might create a Registry (aka "Blackboard" pattern) to store that kind of data.

    That's probably the right idea, actually: either code it in, or pass it to the CTORs.

    =Austin

  22. #22
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Do you see any problem with passing a referenced "variable container" to the constructor, and having it part of the chaininghandler base class so all sub classes could use it? How would *you* use the registry class?

    Also do you have any experince/ideas about php code writing? I mean if instead of outputing the string after parsing I wanted to write php code and then save the file somewhere and later execute the file. I guess my handlers would just have to write php instead? Is it that basic?

    Thanks for all of your help Austin...

    Matt

  23. #23
    SitePoint Evangelist
    Join Date
    May 2004
    Location
    New Jersey, USA
    Posts
    567
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Matt,

    The "variable container" is not a part of the "if" class, but the class does need a shared reference to it. I'd create the container and pass a reference to that container to the CTOR of all of the "variable-aware" classes (if, set, format, loop, &c.).

    As far as outputting the results, that's a "Writer". It has the same ChainingSaxHandler interface, but has to have some additional bits to set up the output stream, etc. (Probably it has a different CTOR signature but the same method interface.)

    Before you start looking too hard at writing php, I'd recommend you take a HARD look at Smarty. There's two things happening in Smarty: compilation from template_language -> PHP, and compilation to "deferred evaluation" PHP.

    Consider:
    Code:
      <?mattml version = "1.0"?>
      <template>
        <set variable="name" value="Matt" /><!-- Declares "compile time" variable -->
        <display variable="name" />
      </template>
    versus
    Code:
       <?mattml version = "1.0"?>
       <template>
         <extern variable="name"/><!-- Declares "run-time" variable -->
         <display variable="name" />
       </template>
    The output would look like:
    PHP Code:
      <html>...<body>
      <!-- 
    template -->
         <!-- 
    set variable="name" value="Matt" / -->
        <!-- 
    Declares "compile time" variable -->
        <!-- 
    display variable="name" -->
        
    Matt
       
    <!-- /template -->
      </
    body>...</html
    versus
    PHP Code:
      <html>...<body>
      <!-- template -->
          <!-- extern variable="name" / -->
        <!-- Declares "run-time" variable -->
          <!-- display variable="name" / -->
        <?php template_display_variable("name"); ?>
      <!-- /template -->
      </body>...</html>
    See the difference? The first one emits "pure html", at least for this operation. The second can't do that -- it "compiles" the code to a "late evaluation" format that will pull the "name" variable at page-request-time.

    Smarty does a pretty good job of this, but it's still one of too many weak areas for them -- it's not easy to specify "this is a compile-time-thing" versus "this should be late bound" in all cases.

    If you stick to doing all of your work at the last minute, you'll always get it 'right'. But you don't want to do that because you can get some pretty good performance gains by storing the results, as you're now seeing.

    =Austin

  24. #24
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hey,

    I was thinking that it could all really be runtime, after it's been compiled. I mean (I'm very new to this)...

    set a value in template:
    <set token="footer"><div class="footer">2004</div></set>
    print a value in template:
    {$footer}

    Would be the same as
    // in a php/controller file:
    $page->set('footer', '<div id="footer">2004</div>');
    // print in template:
    {$footer}


    The compiled template would print like:
    <?php echo $this->get('footer'); ?>

    If the value was set in the template it'd do this:
    <?php $this->set('footer', '<div id="footer">2004</div>');

    Seem about right? I know I'm getting way off topic here... I'll check out smarty also. Can you define run-time and compile time?

    -matt

    EDIT: The main reason for me wanting to compile, is to avoid parsing for every request. Caching could come next, but pure php in a html template would probably be fast enough right? I think I understand the compile/run-time thing.

  25. #25
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    One thing I'm about to do, and want to get some feedback is start doing the replacements/tokens. I'm not sure when the replacements should occure. I'm thinking there should be a tokens replace filter somwhere along the chain. But where? I want to be able to use the tokens in attributes, and possibly in a tag like:

    <set token="template" value="guest.tpl">
    <if token="registered">
    <set token="template" value="user.tpl"/>
    </if>

    <include file="{$template}"/>

    Which means the replace filter would have to do it's thing first.

    But maybe I'm getting out of control here? I don't know. Is that insane? I'm constantly saying... But PHP can do all of this. The SAX stuff was a challenge I absolutely had to meet. But I wonder about the how practical all of this is. I'm defineatly a PHP template kind of guy, but I've been working with a lot of desingers, production people and even programmers that don't like PHP in templates. That's really what got me started with this whole thing to begin with. Anyway, I'd be interested in hearing what you have to say about the replacement filter thing!

    -m


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •