From 0717408e4bf314cf2d3abcb4ac3290f2eed8bef3 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Tue, 19 Jun 2007 23:29:44 +0000 Subject: [PATCH 001/149] Hello re-po ;) --- phpQuery.php | 1004 +++++++++++++++++++++++++++++++++++ test-cases/run.php | 3 + test-cases/selectors.php | 80 +++ test-cases/test.html | 44 ++ test-cases/testTemplate.php | 48 ++ test-cases/test_1.php | 69 +++ test-cases/test_2.php | 37 ++ 7 files changed, 1285 insertions(+) create mode 100644 phpQuery.php create mode 100644 test-cases/run.php create mode 100644 test-cases/selectors.php create mode 100644 test-cases/test.html create mode 100644 test-cases/testTemplate.php create mode 100644 test-cases/test_1.php create mode 100644 test-cases/test_2.php diff --git a/phpQuery.php b/phpQuery.php new file mode 100644 index 0000000..776f73a --- /dev/null +++ b/phpQuery.php @@ -0,0 +1,1004 @@ + + * @link http://wiadomosc.info/plainTemplate + * @license MIT http://www.opensource.org/licenses/mit-license.php + * @version 0.5 alpha + * + * @todo xpath support + * @todo missing jquery functions (css) + * @todo docs (copied from jquery) + * @todo more test cases + * @todo cache (mainly class and regex attrs) + * @todo charset + */ + +class phpQuery implements Iterator { + private static $documents = array(); + private static $lastDocument = null; + public static $debug = false; + private $DOM = null; + private $XPath = null; + private $stack = array(); + private $history = array(); + private $root = array(); + private $_stack = array(); + /** + * Interator helpers + */ + private $valid = false; + private $current = null; + /** + * Other helpers + */ + private $regexpChars = array('^','*','$'); + + public static function load( $path ) { + self::$documents[ $path ]['document'] = new DOMDocument(); + $DOM =& self::$documents[ $path ]; + if (! $DOM['document']->loadHTMLFile( $path ) ) { + unset( self::$documents[ $path ] ); + return false; + } + $DOM['document']->preserveWhiteSpace = true; + $DOM['xpath'] = new DOMXPath( + $DOM['document'] + ); + self::$lastDocument = $path; + return true; + } + public static function unload( $path = null ) { + if ( $path ) + unset( self::$documents[ $path ] ); + else + unset( self::$documents ); + } + public function __construct( $path = null ) { + if ( $path ) + self::load($path); + else + $path = self::$lastDocument; + $this->DOM = self::$documents[ $path ]['document']; + $this->XPath = self::$documents[ $path ]['xpath']; + $this->root = $this->DOM->documentElement; + $this->stackToRoot(); + } + private function debug($in) { + if (! self::$debug ) + return; + print('
');
+		print_r($in);
+		print('
'); + } + private function stackToRoot() { + $this->stack = array( $this->DOM->documentElement ); + } + private function isRegexp($pattern) { + return in_array( + $pattern[ strlen($pattern)-1 ], + $this->regexpChars + ); + } + public static function isHTMLfile( $filename ) { + return substr( $filename, -5 ) == '.html' + || substr( $filename, -4 ) == '.htm'; + } + private function parseSelectors( $selectors ) { + $return = array(); + foreach( split(',', $selectors) as $parse ) { + // clean spaces + $parse = trim( + preg_replace('@\s+@', ' ', + str_replace('>', ' > ', $parse) + ) + ); + $elements = array(); + // TODO: split by / and // + foreach( split(' ', $parse) as $s ) { + if ( $elements && $elements[ count($elements)-1 ] != '>' && $s != '>' ) + $elements[] = ' '; + $elements = array_merge( + $elements, + $this->parseSimpleSelector( $s ) + ); + } + if ( $elements[0] != '>' ) + array_unshift($elements, ' '); + $return[] = $elements; + $this->debug($elements); + } + return $return; + } + // tag.class1.class2[@attr]:checkbox + private function parseSimpleSelector( $s ) { + $selector = array(); + $match = preg_split( + '@(\\.|#|\\[|:)@', + $s, null, + PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY + ); + // tag present + if ( count( $match ) % 2 == 1 ) + array_unshift($match, ''); + for( $i = 0; $i < count( $match )-1; $i = $i+2 ) { + // join classes, args and pseudo-selectors + $append = ( + $selector + && ( + $match[ $i ][0] == '.' + || + $match[ $i ][0] == '[' + || + $match[ $i ][0] == ':' + ) && + $selector[ count($selector)-1 ][0] == $match[ $i ][0] + ); + if ( $append ) + $selector[ count($selector)-1 ] .= $match[ $i ].$match[ $i+1 ]; + else + $selector[] = $match[ $i ].$match[ $i+1 ]; + } + return $selector; + } + private function matchClasses( $class, $node ) { + $class = strpos($class, '.', 1) + // multi-class + ? explode('.', substr($class, 1)) + // single-class + : substr($class, 1); + $classCount = is_array( $class ) + ? count( $class ) + : null; + if ( is_array( $class )) { + $nodeClasses = explode(' ', $node->getAttribute('class') ); + $nodeClassesCount = count( $nodeClasses ); + if ( $classCount > $nodeClassesCount ) + return false; + $diff = count( + array_diff( + $class, + $nodeClasses + ) + ); + if ( $diff == count($nodeClasses) - count($class) ) + return true; + } else if ( in_array($class, explode(' ', $node->getAttribute('class') )) ) + return true; + } + public function find( $selectors, $stack = null ) { + // backup last stack /for end()/ + $this->history[] = $this->stack; + if ( $stack && get_class($stack) == get_class($this) ) + $this->stack = $stack; + $spaceBefore = false; + $XQuery = ''; + foreach( $this->parseSelectors( $selectors ) as $selector ) { + foreach( $selector as $s ) { + if ( preg_match('@^\w+$@', $s) || $s == '*' ) { + // tag + $XQuery .= $s; + } else if ( $s[0] == '#' ) { + // id + if ( $spaceBefore ) + $XQuery .= '*'; + $XQuery .= "[@id='".substr($s, 1)."']"; + } else if ( $s[0] == '[' ) { + // attribute(s) + if ( $spaceBefore ) + $XQuery .= '*'; + $attrs = explode('][', substr($s, 1, -2)); + $execute = false; + foreach( $attrs as $attr ) { + if ( $attr[0] == '@' && strpos( $attr, '=' ) ) { + list( $attr, $value ) = explode('=', $attr); + if ( $this->isRegexp($attr) ) { + $attr = substr($attr, 0, -1); + $execute = true; + } + } + $XQuery .= "[{$attr}]"; + } + if ( $execute ) { + $this->runQuery($XQuery, $s, 'is'); + $XQuery = ''; + if (! $this->length() ) + return $this; + } + } else if ( $s[0] == '.' ) { + // class(es) + if ( $spaceBefore ) + $XQuery .= '*'; + $this->runQuery($XQuery, $s, 'matchClasses'); + $XQuery = ''; + if (! $this->length() ) + return $this; + } else if ( $s[0] == ':' ) { + // pseudo classes + // TODO optimization for :first :last + $this->runQuery($XQuery); + $XQuery = ''; + if (! $this->length() ) + return $this; + $this->filterPseudoClasses( $s ); + if (! $this->length() ) + return $this; + } else if ( $s == '>' ) { + // direct descendant + $XQuery .= '/'; + } else { + $XQuery .= '//'; + } + if ( $s == ' ' ) + $spaceBefore = true; + else + $spaceBefore = false; + } + // run query if any + if ( $XQuery ) { + $this->runQuery($XQuery); + $XQuery = ''; + if (! $this->length() ) + return $this; + } + } + // preserve chain + return $this; + } + private function runQuery( $XQuery, $selector = null, $compare = null ) { + if ( $compare && ! method_exists($this, $compare) ) + return false; + $stack = array(); + foreach( $this->stack as $k => $stackNode ) { + $remove = false; + if (! $stackNode->parentNode ) { + $this->root->appendChild($stackNode); + $remove = true; + } + $xpath = $this->getNodeXpath($stackNode); + $query = $xpath.$XQuery; + $this->debug("XPATH: {$query}\n"); + $nodes = $this->XPath->query($query); + foreach( $nodes as $node ) { + $matched = false; + if ( $compare ) { + $this->debug("Found: ".$this->whois( $node )); + if ( call_user_method($compare, $this, $selector, $node) ) + $matched = true; + } else { + $matched = true; + } + if ( $matched ) { + $this->debug("Matched: ".$this->whois( $node )); + $stack[] = $node; + } + } + if ( $remove ) + $stackNode = $this->root->removeChild( $this->root->lastChild ); + } + $this->stack = $stack; + } + + public function filterPseudoClasses( $classes ) { + foreach( explode(':', substr($classes, 1)) as $class ) { + // TODO clean args parsing + $haveArgs = strpos($class, '('); + if ( $haveArgs !== false ) { + $args = substr($class, $haveArgs+1, -1); + $class = substr($class, 0, $haveArgs); + } + switch( $class ) { + case 'even': + case 'odd': + $stack = array(); + foreach( $this->stack as $i => $node ) { + if ( $class == 'even' && $i % 2 == 0 ) + $stack[] = $node; + else if ( $class == 'odd' && $i % 2 ) + $stack[] = $node; + } + $this->stack = $stack; + break; + case 'eq': + $this->stack = isset( $this->stack[ intval($args) ] ) + ? array( $this->stack[ $args ] ) + : array(); + break; + case 'gt': + $this->stack = array_slice($this->stack, $args+1); + break; + case 'lt': + $this->stack = array_slice($this->stack, 0, $args+1); + break; + case 'first': + if ( isset( $this->stack[0] ) ) + $this->stack = array( $this->stack[0] ); + break; + case 'last': + if ( $this->stack ) + $this->stack = array( $this->stack[ count($this->stack)-1 ] ); + break; + case 'parent': + $stack = array(); + foreach( $this->stack as $node ) { + if ( $node->childNodes->length ) + $stack = $node; + } + $this->stack = $stack; + break; + case 'contains': + $this->contains( trim($args, "\"'"), false ); + break; + } + } + } + public function is( $selector, $_node = null ) { + $oldStack = $this->stack; + if ( $_node ) + $this->stack = array($_node); + $this->filter($selector, true); + $match = (bool)$this->length(); + $this->stack = $oldStack; + return $match; + } + + public function filter( $selector, $_skipHistory = false ) { + if (! $_skipHistory ) + $this->history[] = $this->stack; + $selector = $this->parseSimpleSelector( $selector ); + $stack = array(); + foreach( $this->stack as $k => $node ) { + foreach( $selector as $s ) { + switch( $s[0] ) { + case '#': + if ( $node->getAttribute('id') != $val ) + $stack[] = $node; + break; + case '.': + if ( $this->matchClasses( $s, $node ) ) + $stack[] = $node; + break; + case '[': + foreach( explode('][', substr($s, 1, -1)) as $attr ) { + if ( $attr[0] != '@' ) + continue; + if ( strpos($attr, '=') ) { + list( $attr, $val ) = explode('=', $attr); + if ( $this->isRegexp($attr)) { + switch( $attr[ strlen($attr)-1 ] ) { + case '^': + $pattern = '^'.preg_quote($val, '@'); + break; + case '*': + $pattern = '.*'.preg_quote($val, '@').'.*'; + break; + case '$': + $pattern = preg_quote($val, '@').'$'; + break; + } + $attr = substr($attr, 1, -1); + if ( preg_match("@{$pattern}@", $node->getAttribute($attr))) + $stack[] = $node; + } else if ( $node->getAttribute( substr($attr, 1) ) != $val ) + $stack[] = $node; + } else if ( $node->hasAttribute( substr($attr, 1) ) != $val ) + $stack[] = $node; + } + break; + case ':': + // at the end of function + break; + default: + // tag + if (! $node->tagName == $s ) { + $match = false; + break; + } + } + } + } + $this->stack = $stack; + // pseudoclasses + if ( $selector[ count($selector)-1 ][0] == ':' ) + $this->filterPseudoClasses( $selector[ count($selector)-1 ] ); + return $this; + } + + public function css() { + // TODO + } + + public function contains( $text, $history = true ) { + $this->history[] = $this->stack; + $stack = array(); + foreach( $this->stack as $node ) { + if ( strpos( $node->textContent, $text ) === false ) + continue; + $stack[] = $node; + } + $this->stack = $stack; + return $this; + } + + public function gt($num) { + $this->history[] = $this->stack; + $this->stack = array_slice( $this->stack, $num+1 ); + return $this; + } + + public function lt($num) { + $this->history[] = $this->stack; + $this->stack = array_slice( $this->stack, 0, $num+1 ); + return $this; + } + + public function eq($num) { + $this->history[] = $this->stack; + $this->stack = array( $this->stack[$num] ); + return $this; + } + + public function size() { + return $this->length(); + } + + public function length() { + return count( $this->stack ); + } + + public function end() { + $this->stack = array_pop( $this->history ); + return $this; + } + + public function each($callabck) { + $this->history[] = $this->stack; + foreach( $this->history[ count( $this->history-1 ) ] as $node ) { + $this->stack = array($node); + if ( is_array( $func ) ) { + ${$callabck[0]}->{$callabck[1]}( $this ); + } else { + $callabck[1]( $this ); + } + } + return $this->end(); + } + + public function _copy() { + $newStack = array(); + //pr(array('copy... ', $this->whois())); + //$this->dumpHistory('copy'); + $this->history[] = $this->stack; + foreach( $this->stack as $node ) { + $newStack[] = $node->cloneNode(true); + } + $this->stack = $newStack; + return $this; + } + + public function remove() { + foreach( $this->stack as $node ) { + $this->debug("Removing '{$node->tagName}'"); + $node->parentNode->removeChild( $node ); + } + return $this; + } + + private function isHTML( $html ) { + return $html[0] == '<'; + } + + public function html($html = null) { + if ( $html ) { + if ( $this->isHTML( $html ) ) { + $toInserts = array(); + $DOM = new DOMDocument(); + @$DOM->loadHTML( $html ); + foreach($DOM->documentElement->firstChild->childNodes as $node) { + $toInserts[] = $this->DOM->importNode( $node, true ); + } + } else { + $toInserts = array($this->DOM->createTextNode( $html )); + } + foreach( $toInserts as $toInsert ){ + $i = 0; + foreach( $this->stack as $node ) { + foreach( $node->childNodes as $child ) + $node->removeChild($child); + if ( $i ) + $toInsert = clone $toInsert; + $node->appendChild( $toInsert ); + $i++; + } + } + return $this; + } else { + $DOM = new DOMDocument(); + foreach( $this->stack as $node ) { + $DOM->appendChild( + $DOM->importNode( $node, true ) + ); + } + $DOM->formatOutput = true; + return $DOM->saveHTML(); + } + } + public function php($code){ + return $this->html("{$code}"); + } + private function dumpHistory($when) { + foreach( $this->history as $nodes ) { + $history[] = array(); + foreach( $nodes as $node ) { + $history[ count($history)-1 ][] = $this->whois( $node ); + } + } + //pr(array("{$when}/history", $history)); + } + public function children( $selector = null ) { + $tack = array(); + foreach( $this->stack as $node ) { + foreach( $node->childNodes as $newNode ) { + if ( $selector && ! $this->is($selector, $newNode) ) + continue; + $stack[] = $newNode; + } + } + $this->history[] = $this->stack; + $this->stack = $tack; + return $this; + } + public function ancestors( $selector ) { + return $this->children( $selector ); + } + + public function append( $content ) { + return $this->insert($content, __FUNCTION__); + } + public function appendPHP( $content ) { + return $this->insert("{$content}", 'append'); + } + public function appendTo( $seletor ) { + return $this->insert($seletor, __FUNCTION__); + } + + public function prepend( $content ) { + return $this->insert($content, __FUNCTION__); + } + public function prependPHP( $content ) { + return $this->insert("{$content}", 'prepend'); + } + public function prependTo( $seletor ) { + return $this->insert($seletor, __FUNCTION__); + } + + public function before( $content ) { + return $this->insert($content, __FUNCTION__); + } + public function beforePHP( $content ) { + return $this->insert("{$content}", 'before'); + } + public function insertBefore( $seletor ) { + return $this->insert($seletor, __FUNCTION__); + } + + public function after( $content ) { + return $this->insert($content, __FUNCTION__); + } + public function afterPHP( $content ) { + return $this->insert("{$content}", 'after'); + } + public function insertAfter( $seletor ) { + return $this->insert($seletor, __FUNCTION__); + } + private function insert( $target, $type ) { + $to = false; + switch( $type ) { + case 'appendTo': + case 'prependTo': + case 'insertBefore': + case 'insertAfter': + $to = true; + } + switch(gettype( $target )) { + case 'string': + if ( $to ) { + //$this->dumpHistory('appendTo'); + $oldStack = $this->stack; + $historyCount = count( $this->history ); + $this->stack = array( $this->root ); + $this->find($target); + $insertTo = $this->stack; + $this->stack = $oldStack; + $insertFrom = $this->stack; + if ( count( $this->history ) > $historyCount ) + $this->history = array_slice( $this->history, 0, $historyCount ); + //$this->dumpHistory('appendTo-END'); + } else { + $insertTo = $this->stack; + if ( $this->isHTML( $target ) ) { + $DOM = new DOMDocument(); + @$DOM->loadHTML($target); + foreach($DOM->documentElement->firstChild->childNodes as $node) { + $insertFrom[] = $this->DOM->importNode( $node, true ); + } + } else { + $insertFrom = array( + $this->DOM->createTextNode( $html ) + ); + } + } + break; + case 'object': + if ( get_class( $target ) == get_class( $this )) { + if ( $to ) { + $insertTo = $target->stack; + foreach( $this->stack as $node ) + $insertFrom[] = $target->DOM->importNode($node); + } else { + $insertTo = $this->stack; + foreach( $target->stack as $node ) + $insertFrom[] = $this->DOM->importNode($node); + } + } + break; + } + foreach( $insertFrom as $fromNode ) { + foreach( $insertTo as $toNode ) { + switch( $type ) { + case 'appendTo': + case 'append': + $toNode->insertBefore( + $fromNode, + $toNode->lastChild->nextSibling + ); + break; + case 'prependTo': + case 'prepend': + $toNode->insertBefore( + $fromNode, + $toNode->firstChild + ); + break; + case 'insertBefore': + case 'before': + $toNode->parentNode->insertBefore( + $fromNode, + $toNode + ); + break; + case 'insertAfter': + case 'after': + $toNode->parentNode->insertBefore( + $fromNode, + $toNode->nextSibling + ); + break; + } + } + } + return $this; + } + + public function text() { + $return = ''; + foreach( $this->stack as $node ) { + $return .= $node->textContent; + } + return $return; + } + + public function _next( $selector = null ) { + $this->sibling( $selector, 'previousSibling' ); + return $this; + } + + public function _prev( $selector = null ) { + $this->sibling( $selector, 'previousSibling' ); + return $this; + } + + private function sibling( $selector, $direction ) { + $stack = array(); + foreach( $this->stack as $node ) { + $test = $node; + while( $test->{$direction} ) { + $test = $test->nextSibling; + if ( $selector ) { + if ( $this->is( $selector, $test ) ) { + $stack[] = $test; + continue; + } + } else { + $stack[] = $test; + continue; + } + } + } + $this->history[] = $this->stack; + $this->stack = $stack; + } + + public function siblings( $selector = null ) { + $stack = array(); + foreach( $this->stack as $node ) { + if ( $selector ) { + if ( $this->is( $selector, $test ) ) + $stack[] = $node; + } else + $stack[] = $node; + } + $this->history[] = $this->stack; + $this->stack = $stack; + return $this; + } + + public function not( $selector = null ) { + $stack = array(); + foreach( $this->stack as $node ) { + if (! $this->is( $selector, node ) ) + $stack[] = $node; + } + $this->history[] = $this->stack; + $this->stack = $stack; + return $this; + } + + public function add( $selector = null ) { + $stack = array(); + $this->history[] = $this->stack; + $this->find($selector); + $this->merge( + $this->history[ count($this->history)-2 ] + ); + return $this; + + } + + private function merge() { + foreach( get_func_args() as $nodes ) { + foreach( $nodes as $newNode ) { + foreach( $this->stack as $node ) { + if (! $node->isSameNode( $newNode )) + $this->stack[] = $newNode; + } + } + } + } + + public function parent( $selector = null ) { + $stack = array(); + foreach( $this->stack as $node ) { + if ( $this->is( $selector, $node->parentNode ) ) + $stack[] = $node->parentNode; + } + $this->history[] = $this->stack; + $this->stack = $stack; + return $this; + + } + + public function parents( $selector = null ) { + $stack = array(); + foreach( $this->stack as $node ) { + $test = $node; + while( $test->parentNode ) { + $test = $test->parentNode; + if ( $selector ) { + if ( $this->is( $selector, $test ) ) { + $stack[] = $test; + continue; + } + } else { + $stack[] = $test; + continue; + } + } + } + $this->history[] = $this->stack; + $this->stack = $stack; + return $this; + } + + public function attr( $attr ) { + foreach( $this->stack as $node ) + return $node->getAttribute($attr); + } + + public function val( $selector = null ) { + + } + + public function removeAttr( $attr ) { + foreach( $this->stack as $node ) + $node->removeAttribute($attr); + } + + public function addClass( $className ) { + foreach( $this->stack as $node ) { + if (! $this->is( $node, '.'.$className)) + $node->setAttribute( + 'class', + $node->getAttribute('class').' '.$className + ); + } + } + + public function removeClass( $className ) { + foreach( $this->stack as $node ) { + $classes = explode( ' ', $node->getAttribute('class')); + if ( in_array($className, $classes) ) { + $classes = array_diff($classes, array($className)); + if ( $classes ) + $node->setAttribute('class', $classes); + else + $node->removeAttribute('class'); + } + } + } + + public function toggleClass( $className ) { + foreach( $this->stack as $node ) { + if ( $this->is( $node, '.'.$className )) + $this->removeClass($className); + else + $this->addClass($className); + } + } + + /** + * Removes all child nodes from the set of matched elements. + * + * Example: + * $("p").empty() + * + * HTML: + *

Hello, Person and person

+ * + * Result: + * [

] + * + * @return phpQuery + */ + public function _empty() { + + } + + + // INTERATOR INTERFACE + function rewind(){ + $this->_stack = $this->stack; + $this->valid = isset( $this->stack[0] ) + ? 1 + : 0; + $this->current = 0; + } + + function current(){ + return $this; + } + + function key(){ + return $this->current; + } + + function next(){ + $this->current++; + $this->valid = isset( $this->_stack[ $this->current ] ) + ? true + : false; + if ( $this->valid ) + $this->stack = array( + $this->_stack[ $this->current++ ] + ); + } + + // ADDONS + function valid(){ + return $this->valid; + } + + function getNodeXpath( $oneNode = null ) { + $return = array(); + $loop = $oneNode + ? array($oneNode) + : $this->stack; + foreach( $loop as $node ) { + $xpath = array(); + while( get_class($node) != 'DOMDocument' ) { + $i = 1; + $node2 = $node; + while( $node2->previousSibling && get_class($node2->previousSibling) == 'DOMElement' ) { + $node2 = $node2->previousSibling; + if ( $node2->tagName == $node->tagName ) + $i++; + } + $xpath[] = "{$node->tagName}[{$i}]"; + $node = $node->parentNode; + } + $xpath = join('/', array_reverse($xpath)); + $return[] = '/'.$xpath; + } + return $oneNode + ? $return[0] + : $return; + } + + /** + * @todo sprawdzanie klasy + * @todo obsluga jednego elementu + */ + function whois($oneNode = null) { + $return = array(); + $loop = $oneNode + ? array( $oneNode ) + : $this->stack; + foreach( $loop as $node ) { + $return[] = ( + $node->tagName + .($node->getAttribute('id') + ? '#'.$node->getAttribute('id'):'') + .($node->getAttribute('class') + ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') + ); + } + return $oneNode + ? $return[0] + : $return; + } + + // HELPERS + + public function dumpStack() { + foreach( $this->stack as $node ) { + $this->debug($node->tagName); + } + } + + public function dumpSource( $node = null ) { + $return = array(); + $loop = $node + ? array( $node ) + : $this->stack; + foreach( $loop as $node ) { + $DOM = new DOMDocument(); + $DOM->appendChild( + $DOM->importNode( $node, true ) + ); + $return[] = $DOM->saveHTML(); + } + return $return; + } +} + +function _() { + $input = func_get_args(); + // load template file + if ( phpQuery::isHTMLfile( $input[0] ) ) { + $loaded = phpQuery::load( $input[0] ); + return new phpQuery(); + } else { + $last = count($input)-1; + $PQ = new phpQuery( + // document path + isset( $input[$last] ) && phpQuery::isHTMLfile( $input[$last] ) + ? $input[$last] + : null + ); + if ( $input[0][0] == '<' ) + // load HTML + return $PQ->importHTML( $input[0] ); + else // do query + return $PQ->find( + $input[0], + isset( $input[1] ) + && is_object( $input[1] ) + && get_class( $input[1] ) == 'phpQuery' + ? $input[1] + : null + ); + } +} +?> diff --git a/test-cases/run.php b/test-cases/run.php new file mode 100644 index 0000000..300c690 --- /dev/null +++ b/test-cases/run.php @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/test-cases/selectors.php b/test-cases/selectors.php new file mode 100644 index 0000000..3ade592 --- /dev/null +++ b/test-cases/selectors.php @@ -0,0 +1,80 @@ + $test ) { + $tests[ $k ][2] = _( $test[0] )->whois(); +} +foreach( $tests as $test ) { + if ( $test[1] == $test[2] ) + print "Test '{$test[0]}' passed :)"; + else { + print "Test '{$test[0]}' FAILED !!!"; + print_r($test[2]); + } + print "

"; +} +?> \ No newline at end of file diff --git a/test-cases/test.html b/test-cases/test.html new file mode 100644 index 0000000..6cae4cd --- /dev/null +++ b/test-cases/test.html @@ -0,0 +1,44 @@ + + + + + + + plainTemplate test + + + + +
+ div.articles text node + +

paragraph after UL

+
+ + diff --git a/test-cases/testTemplate.php b/test-cases/testTemplate.php new file mode 100644 index 0000000..b94bca1 --- /dev/null +++ b/test-cases/testTemplate.php @@ -0,0 +1,48 @@ + 95 ) + print "Test '{$testName}' passed :)"; +else + print "Test '{$testName}' FAILED !!!"; +print "\n"; +?> \ No newline at end of file diff --git a/test-cases/test_1.php b/test-cases/test_1.php new file mode 100644 index 0000000..33bb432 --- /dev/null +++ b/test-cases/test_1.php @@ -0,0 +1,69 @@ + + div.articles text node + +

paragraph after UL

+ +EOF; +$rows = array( + array( + 'title' => 'News 1 title', + 'body' => 'News 1 body', + ), + array( + 'title' => 'News 2 title', + 'body' => 'News 2 body', + ), + array( + 'title' => 'News 3', + 'body' => 'News 3 body', + ), +); +_('test.html'); +$articles = _('.articles ul'); +$row = clone $articles->find('li'); +$row->remove()->eq(0); +foreach( $rows as $r ) { + $row->_copy(); + foreach( $r as $field => $value ) { + $row->find(".{$field}") + ->html( $value ) + ->end(); + } + $row->appendTo('.articles ul') + // DOESNT WORK +// $row->appendTo($articles) + ->end(); +} +$result = _('.articles')->html(); +// DOESNT WORK +// print $articles->html(); +$similarity = 0.0; +similar_text($testResult, $result, $similarity); +if ( $similarity > 95 ) + print "Test '{$testName}' passed :)"; +else + print "Test '{$testName}' FAILED !!!"; +print "\n"; +?> \ No newline at end of file diff --git a/test-cases/test_2.php b/test-cases/test_2.php new file mode 100644 index 0000000..5b22a6b --- /dev/null +++ b/test-cases/test_2.php @@ -0,0 +1,37 @@ + + div.articles text node + +

paragraph after UL

+ +EOF; +$result = _('test.html') + ->find('p') + ->filter('.body:gt(1)'); +if ( $result->whois() == array('p.body') ) + print "Test '{$testName}' passed :)"; +else + print "Test '{$testName}' FAILED !!!"; +print "\n"; +?> \ No newline at end of file From 7dc4c7f364fcd0e03a667ea273751edb27c2dcf2 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 24 Jun 2007 10:25:55 +0000 Subject: [PATCH 002/149] - restore missing optimization for class selectors - html() now deletes content - children() fixed - phpMeta() added - fixes for in is() and html(), including doctype - fixed isHTML() and whitespaces - __toString() returns html() - getNodeXpath() returned only [1] index - fixed & cleaned attr stuff in find() and filter() --- phpQuery.php | 286 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 193 insertions(+), 93 deletions(-) diff --git a/phpQuery.php b/phpQuery.php index 776f73a..e845867 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -3,13 +3,14 @@ * jQuery port to PHP. * phpQuery is chainable DOM selector & manipulator. * - * @author Tobiasz Cudnik + * @author Tobiasz Cudnik * @link http://wiadomosc.info/plainTemplate - * @license MIT http://www.opensource.org/licenses/mit-license.php - * @version 0.5 alpha + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @version 0.6 beta * - * @todo xpath support - * @todo missing jquery functions (css) + * @todo rewrite selector explode (support attr values with spaces, dots and xpath queries) + * @todo comma separated queries + * @todo missing jquery functions (css, wrap, val) * @todo docs (copied from jquery) * @todo more test cases * @todo cache (mainly class and regex attrs) @@ -17,9 +18,9 @@ */ class phpQuery implements Iterator { + public static $debug = false; private static $documents = array(); private static $lastDocument = null; - public static $debug = false; private $DOM = null; private $XPath = null; private $stack = array(); @@ -44,6 +45,8 @@ public static function load( $path ) { return false; } $DOM['document']->preserveWhiteSpace = true; + $DOM['document']->formatOutput = true; + $DOM['nodes'] = array(); $DOM['xpath'] = new DOMXPath( $DOM['document'] ); @@ -63,6 +66,7 @@ public function __construct( $path = null ) { $path = self::$lastDocument; $this->DOM = self::$documents[ $path ]['document']; $this->XPath = self::$documents[ $path ]['xpath']; + $this->nodes = self::$documents[ $path ]['nodes']; $this->root = $this->DOM->documentElement; $this->stackToRoot(); } @@ -71,6 +75,8 @@ private function debug($in) { return; print('
');
 		print_r($in);
+	//	if ( is_array($in))
+	//		print_r(array_slice(debug_backtrace(), 3));
 		print('
'); } private function stackToRoot() { @@ -83,8 +89,11 @@ private function isRegexp($pattern) { ); } public static function isHTMLfile( $filename ) { - return substr( $filename, -5 ) == '.html' - || substr( $filename, -4 ) == '.htm'; + return is_string($filename) && ( + substr( $filename, -5 ) == '.html' + || + substr( $filename, -4 ) == '.htm' + ); } private function parseSelectors( $selectors ) { $return = array(); @@ -96,7 +105,7 @@ private function parseSelectors( $selectors ) { ) ); $elements = array(); - // TODO: split by / and // + // TODO: realy parsing of selector foreach( split(' ', $parse) as $s ) { if ( $elements && $elements[ count($elements)-1 ] != '>' && $s != '>' ) $elements[] = ' '; @@ -105,10 +114,10 @@ private function parseSelectors( $selectors ) { $this->parseSimpleSelector( $s ) ); } - if ( $elements[0] != '>' ) + if ( isset($elements[0]) && $elements[0] != '>' ) array_unshift($elements, ' '); $return[] = $elements; - $this->debug($elements); + $this->debug(array('SELECTOR',$parse,$elements)); } return $return; } @@ -186,20 +195,33 @@ public function find( $selectors, $stack = null ) { $XQuery .= '*'; $XQuery .= "[@id='".substr($s, 1)."']"; } else if ( $s[0] == '[' ) { - // attribute(s) + // attributes and nests if ( $spaceBefore ) $XQuery .= '*'; - $attrs = explode('][', substr($s, 1, -2)); + // strip side brackets + $attrs = explode('][', trim($s, '[]')); $execute = false; foreach( $attrs as $attr ) { - if ( $attr[0] == '@' && strpos( $attr, '=' ) ) { - list( $attr, $value ) = explode('=', $attr); - if ( $this->isRegexp($attr) ) { - $attr = substr($attr, 0, -1); - $execute = true; + if ( $attr[0] == '@' ) { + // attr with specifed value + if ( strpos( $attr, '=' ) ) { + list( $attr, $value ) = explode('=', $attr); + $value = trim($value, "'\"'"); + if ( $this->isRegexp($attr) ) { + // cut regexp character + $attr = substr($attr, 0, -1); + $execute = true; + $XQuery .= "[{$attr}]"; + } else { + $XQuery .= "[{$attr}='{$value}']"; + } + // attr without specified value + } else { + $XQuery .= "[{$attr}]"; } + // TODO nested xpath + } else { } - $XQuery .= "[{$attr}]"; } if ( $execute ) { $this->runQuery($XQuery, $s, 'is'); @@ -211,6 +233,7 @@ public function find( $selectors, $stack = null ) { // class(es) if ( $spaceBefore ) $XQuery .= '*'; + $XQuery .= '[@class]'; $this->runQuery($XQuery, $s, 'matchClasses'); $XQuery = ''; if (! $this->length() ) @@ -251,27 +274,41 @@ private function runQuery( $XQuery, $selector = null, $compare = null ) { if ( $compare && ! method_exists($this, $compare) ) return false; $stack = array(); + if (! $this->stack ) + $this->debug('Stack empty, skipping...'); foreach( $this->stack as $k => $stackNode ) { $remove = false; - if (! $stackNode->parentNode ) { + if (! $stackNode->parentNode && ! $this->isRoot($stackNode) ) { $this->root->appendChild($stackNode); $remove = true; } $xpath = $this->getNodeXpath($stackNode); $query = $xpath.$XQuery; $this->debug("XPATH: {$query}\n"); + // run query, get elements $nodes = $this->XPath->query($query); +// // TEST: keep document nodes in one place +// foreach( $nodes as $k => $node ) { +// foreach( $this->nodes as $fetchedNode ) { +// if ( $node->isSameNode( $fetchedNode ) ) +// $nodes[$k] = $fetchedNode; +// else { +// $this->nodes[] = $node; +// } +// } +// } foreach( $nodes as $node ) { $matched = false; if ( $compare ) { - $this->debug("Found: ".$this->whois( $node )); + self::$debug ? $this->debug("Found: ".$this->whois( $node )) : null; + $this->debug("Comparing with {$compare}()"); if ( call_user_method($compare, $this, $selector, $node) ) $matched = true; } else { $matched = true; } if ( $matched ) { - $this->debug("Matched: ".$this->whois( $node )); + self::$debug ? $this->debug("Matched: ".$this->whois( $node )) : null; $stack[] = $node; } } @@ -361,30 +398,38 @@ public function filter( $selector, $_skipHistory = false ) { $stack[] = $node; break; case '[': - foreach( explode('][', substr($s, 1, -1)) as $attr ) { - if ( $attr[0] != '@' ) - continue; - if ( strpos($attr, '=') ) { - list( $attr, $val ) = explode('=', $attr); - if ( $this->isRegexp($attr)) { - switch( $attr[ strlen($attr)-1 ] ) { - case '^': - $pattern = '^'.preg_quote($val, '@'); - break; - case '*': - $pattern = '.*'.preg_quote($val, '@').'.*'; - break; - case '$': - $pattern = preg_quote($val, '@').'$'; - break; - } - $attr = substr($attr, 1, -1); - if ( preg_match("@{$pattern}@", $node->getAttribute($attr))) + foreach( explode( '][', trim($s, '[]') ) as $attr ) { + // attrs + if ( $attr[0] == '@' ) { + // cut-da-monkey ;) + $attr = substr($attr, 1); + if ( strpos($attr, '=') ) { + list( $attr, $val ) = explode('=', $attr); + if ( $this->isRegexp($attr)) { + // switch last character + switch( substr($attr, -1) ) { + case '^': + $pattern = '^'.preg_quote($val, '@'); + break; + case '*': + $pattern = '.*'.preg_quote($val, '@').'.*'; + break; + case '$': + $pattern = preg_quote($val, '@').'$'; + break; + } + // cut last character + $attr = substr($attr, 0, -1); + if ( preg_match("@{$pattern}@", $node->getAttribute($attr))) + $stack[] = $node; + } else if ( $node->getAttribute($attr) == $val ) $stack[] = $node; - } else if ( $node->getAttribute( substr($attr, 1) ) != $val ) + } else if ( $node->hasAttribute($attr) ) $stack[] = $node; - } else if ( $node->hasAttribute( substr($attr, 1) ) != $val ) - $stack[] = $node; + // nested xpath + } else { + // TODO + } } break; case ':': @@ -392,10 +437,11 @@ public function filter( $selector, $_skipHistory = false ) { break; default: // tag - if (! $node->tagName == $s ) { - $match = false; - break; - } + if ( isset($node->tagName) ) { + if ( $node->tagName == $s ) + $stack[] = $node; + } else if ( $s == 'html' && $this->isRoot($node) ) + $stack[] = $node; } } } @@ -405,11 +451,32 @@ public function filter( $selector, $_skipHistory = false ) { $this->filterPseudoClasses( $selector[ count($selector)-1 ] ); return $this; } + + private function isRoot( $node ) { + return get_class($node) == 'DOMDocument'; + } public function css() { // TODO } + private function importHTML($html) { + $this->history[] = $this->stack; + $this->stack = array(); + $DOM = new DOMDocument(); + @$DOM->loadHTML( $html ); + foreach($DOM->documentElement->firstChild->childNodes as $node) + $this->stack[] = $this->DOM->importNode( $node, true ); + } + + public function wrap($before, $after) { + foreach( $this->stack as $node ) { + _($before.$after) + ->insertAfter($node); + + } + } + public function contains( $text, $history = true ) { $this->history[] = $this->stack; $stack = array(); @@ -435,8 +502,11 @@ public function lt($num) { } public function eq($num) { + $oldStack = $this->stack; $this->history[] = $this->stack; - $this->stack = array( $this->stack[$num] ); + $this->stack = array(); + if ( isset($oldStack[$num]) ) + $this->stack[] = $oldStack[$num]; return $this; } @@ -466,7 +536,7 @@ public function each($callabck) { return $this->end(); } - public function _copy() { + public function _clone() { $newStack = array(); //pr(array('copy... ', $this->whois())); //$this->dumpHistory('copy'); @@ -487,7 +557,7 @@ public function remove() { } private function isHTML( $html ) { - return $html[0] == '<'; + return substr(trim($html), 0, 1) == '<'; } public function html($html = null) { @@ -496,25 +566,24 @@ public function html($html = null) { $toInserts = array(); $DOM = new DOMDocument(); @$DOM->loadHTML( $html ); - foreach($DOM->documentElement->firstChild->childNodes as $node) { + foreach($DOM->documentElement->firstChild->childNodes as $node) $toInserts[] = $this->DOM->importNode( $node, true ); - } + // $toInserts = array_reverse( $toInserts ); } else { $toInserts = array($this->DOM->createTextNode( $html )); } - foreach( $toInserts as $toInsert ){ - $i = 0; - foreach( $this->stack as $node ) { - foreach( $node->childNodes as $child ) - $node->removeChild($child); - if ( $i ) - $toInsert = clone $toInsert; - $node->appendChild( $toInsert ); - $i++; - } - } + $this->_empty(); + // i dont like brackets ! python rules ! ;) + foreach( $toInserts as $toInsert ) + foreach( $this->stack as $k => $node ) + $node->appendChild( $k + ? $toInsert->cloneNode() + : $toInsert + ); return $this; } else { + if ( $this->length() == 1 && $this->isRoot( $this->stack[0] ) ) + return $this->DOM->saveHTML(); $DOM = new DOMDocument(); foreach( $this->stack as $node ) { $DOM->appendChild( @@ -525,8 +594,25 @@ public function html($html = null) { return $DOM->saveHTML(); } } - public function php($code){ - return $this->html("{$code}"); + public function __toString() { + return $this->html(); + } + public function php($code) { + return $this->html("".trim($code).""); + } + public function phpPrint($var) { + return $this->php("print {$var};"); + } + /** + * Meta PHP insert - finds element(s), inserts code and rolls back stack. + * + * @param string Selector + * @param string Valid PHP Code + */ + public function phpMeta($selector, $code) { + return $this->find($selector) + ->php($code) + ->end(); } private function dumpHistory($when) { foreach( $this->history as $nodes ) { @@ -540,7 +626,7 @@ private function dumpHistory($when) { public function children( $selector = null ) { $tack = array(); foreach( $this->stack as $node ) { - foreach( $node->childNodes as $newNode ) { + foreach( $node->getElementsByTagName('*') as $newNode ) { if ( $selector && ! $this->is($selector, $newNode) ) continue; $stack[] = $newNode; @@ -624,9 +710,10 @@ private function insert( $target, $type ) { foreach($DOM->documentElement->firstChild->childNodes as $node) { $insertFrom[] = $this->DOM->importNode( $node, true ); } + $insertFrom = array_reverse($insertFrom); } else { $insertFrom = array( - $this->DOM->createTextNode( $html ) + $this->DOM->createTextNode( $target ) ); } } @@ -802,9 +889,13 @@ public function parents( $selector = null ) { return $this; } - public function attr( $attr ) { - foreach( $this->stack as $node ) - return $node->getAttribute($attr); + public function attr( $attr, $value = null ) { + foreach( $this->stack as $node ) { + if ( $value ) + return $node->setAttribute($attr, $value); + else + return $node->getAttribute($attr); + } } public function val( $selector = null ) { @@ -852,7 +943,7 @@ public function toggleClass( $className ) { * Removes all child nodes from the set of matched elements. * * Example: - * $("p").empty() + * _("p")._empty() * * HTML: *

Hello, Person and person

@@ -863,28 +954,32 @@ public function toggleClass( $className ) { * @return phpQuery */ public function _empty() { - + foreach( $this->stack as $node ) { + // many thx to 'dave at dgx dot cz' :) + $node->nodeValue = ''; + } + return $this; } // INTERATOR INTERFACE - function rewind(){ + public function rewind(){ $this->_stack = $this->stack; $this->valid = isset( $this->stack[0] ) - ? 1 - : 0; + ? 1 + : 0; $this->current = 0; } - function current(){ + public function current(){ return $this; } - function key(){ + public function key(){ return $this->current; } - function next(){ + public function next(){ $this->current++; $this->valid = isset( $this->_stack[ $this->current ] ) ? true @@ -894,25 +989,30 @@ function next(){ $this->_stack[ $this->current++ ] ); } - - // ADDONS - function valid(){ + public function valid(){ return $this->valid; } - function getNodeXpath( $oneNode = null ) { + // ADDONS + + private function getNodeXpath( $oneNode = null ) { $return = array(); $loop = $oneNode ? array($oneNode) : $this->stack; foreach( $loop as $node ) { + if ( $this->isRoot($node) ) { + $return[] = ''; + continue; + } $xpath = array(); while( get_class($node) != 'DOMDocument' ) { $i = 1; - $node2 = $node; - while( $node2->previousSibling && get_class($node2->previousSibling) == 'DOMElement' ) { - $node2 = $node2->previousSibling; - if ( $node2->tagName == $node->tagName ) + $sibling = $node; + while( $sibling->previousSibling ) { + $sibling = $sibling->previousSibling; + $isElement = get_class($sibling) == 'DOMElement'; + if ( $isElement && $sibling->tagName == $node->tagName ) $i++; } $xpath[] = "{$node->tagName}[{$i}]"; @@ -926,11 +1026,7 @@ function getNodeXpath( $oneNode = null ) { : $return; } - /** - * @todo sprawdzanie klasy - * @todo obsluga jednego elementu - */ - function whois($oneNode = null) { + public function whois($oneNode = null) { $return = array(); $loop = $oneNode ? array( $oneNode ) @@ -974,11 +1070,15 @@ public function dumpSource( $node = null ) { } function _() { + if (! func_num_args() ) + return new phpQuery(); $input = func_get_args(); // load template file - if ( phpQuery::isHTMLfile( $input[0] ) ) { + if ( phpQuery::isHTMLfile( $input[0] ) ) { $loaded = phpQuery::load( $input[0] ); return new phpQuery(); + } else if ( is_object($input[0]) && get_class($input[0]) == 'DOMElement' ) { + // TODO suppot dom nodes } else { $last = count($input)-1; $PQ = new phpQuery( From 006d4e264505cf1b3cc135328d2ce64f81fb7a53 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Mon, 25 Jun 2007 22:04:19 +0000 Subject: [PATCH 003/149] - fixed setting values with attr() - fixed matching various numer of classes per element - shortcut function _() is optional and doesn't crash script if Gettext eqivalent is present - fixed interation with several results --- phpQuery.php | 100 ++++++++++++++++++++------------------- test-cases/selectors.php | 7 +++ 2 files changed, 59 insertions(+), 48 deletions(-) diff --git a/phpQuery.php b/phpQuery.php index e845867..fcba44e 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -10,6 +10,8 @@ * * @todo rewrite selector explode (support attr values with spaces, dots and xpath queries) * @todo comma separated queries + * @todo each() should pass DOM node to callback, actually it's stack-history dangerous + * @todo _() should accept DOM nodes * @todo missing jquery functions (css, wrap, val) * @todo docs (copied from jquery) * @todo more test cases @@ -17,8 +19,8 @@ * @todo charset */ -class phpQuery implements Iterator { - public static $debug = false; +class phpQueryClass implements Iterator { + public static $debug = true; private static $documents = array(); private static $lastDocument = null; private $DOM = null; @@ -26,7 +28,7 @@ class phpQuery implements Iterator { private $stack = array(); private $history = array(); private $root = array(); - private $_stack = array(); + private $interator_stack = array(); /** * Interator helpers */ @@ -46,7 +48,6 @@ public static function load( $path ) { } $DOM['document']->preserveWhiteSpace = true; $DOM['document']->formatOutput = true; - $DOM['nodes'] = array(); $DOM['xpath'] = new DOMXPath( $DOM['document'] ); @@ -66,7 +67,6 @@ public function __construct( $path = null ) { $path = self::$lastDocument; $this->DOM = self::$documents[ $path ]['document']; $this->XPath = self::$documents[ $path ]['xpath']; - $this->nodes = self::$documents[ $path ]['nodes']; $this->root = $this->DOM->documentElement; $this->stackToRoot(); } @@ -153,29 +153,30 @@ private function parseSimpleSelector( $s ) { return $selector; } private function matchClasses( $class, $node ) { - $class = strpos($class, '.', 1) - // multi-class - ? explode('.', substr($class, 1)) - // single-class - : substr($class, 1); - $classCount = is_array( $class ) - ? count( $class ) - : null; - if ( is_array( $class )) { + // multi-class + if ( strpos($class, '.', 1) ) { + $classes = explode('.', substr($class, 1)); + $classesCount = count( $classes ); $nodeClasses = explode(' ', $node->getAttribute('class') ); $nodeClassesCount = count( $nodeClasses ); - if ( $classCount > $nodeClassesCount ) + if ( $classesCount > $nodeClassesCount ) return false; $diff = count( array_diff( - $class, + $classes, $nodeClasses ) ); - if ( $diff == count($nodeClasses) - count($class) ) + if (! $diff ) return true; - } else if ( in_array($class, explode(' ', $node->getAttribute('class') )) ) - return true; + // single-class + } else { + // strip leading dot + $class = substr($class, 1); + $nodeClasses = explode(' ', $node->getAttribute('class') ); + if ( in_array($class, $nodeClasses) ) + return true; + } } public function find( $selectors, $stack = null ) { // backup last stack /for end()/ @@ -287,28 +288,21 @@ private function runQuery( $XQuery, $selector = null, $compare = null ) { $this->debug("XPATH: {$query}\n"); // run query, get elements $nodes = $this->XPath->query($query); -// // TEST: keep document nodes in one place -// foreach( $nodes as $k => $node ) { -// foreach( $this->nodes as $fetchedNode ) { -// if ( $node->isSameNode( $fetchedNode ) ) -// $nodes[$k] = $fetchedNode; -// else { -// $this->nodes[] = $node; -// } -// } -// } foreach( $nodes as $node ) { $matched = false; if ( $compare ) { - self::$debug ? $this->debug("Found: ".$this->whois( $node )) : null; - $this->debug("Comparing with {$compare}()"); + self::$debug ? + $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") + : null; if ( call_user_method($compare, $this, $selector, $node) ) $matched = true; } else { $matched = true; } if ( $matched ) { - self::$debug ? $this->debug("Matched: ".$this->whois( $node )) : null; + self::$debug + ? $this->debug("Matched: ".$this->whois( $node )) + : null; $stack[] = $node; } } @@ -525,12 +519,12 @@ public function end() { public function each($callabck) { $this->history[] = $this->stack; - foreach( $this->history[ count( $this->history-1 ) ] as $node ) { + foreach( $this->history[ count( $this->history )-1 ] as $node ) { $this->stack = array($node); - if ( is_array( $func ) ) { + if ( is_array( $callabck ) ) { ${$callabck[0]}->{$callabck[1]}( $this ); } else { - $callabck[1]( $this ); + $callabck( $this ); } } return $this->end(); @@ -892,10 +886,11 @@ public function parents( $selector = null ) { public function attr( $attr, $value = null ) { foreach( $this->stack as $node ) { if ( $value ) - return $node->setAttribute($attr, $value); + $node->setAttribute($attr, $value); else return $node->getAttribute($attr); } + return $this; } public function val( $selector = null ) { @@ -951,7 +946,7 @@ public function toggleClass( $className ) { * Result: * [

] * - * @return phpQuery + * @return */ public function _empty() { foreach( $this->stack as $node ) { @@ -964,10 +959,12 @@ public function _empty() { // INTERATOR INTERFACE public function rewind(){ - $this->_stack = $this->stack; + $this->history[] = $this->stack; + $this->interator_stack = $this->stack; $this->valid = isset( $this->stack[0] ) ? 1 : 0; + $this->stack = array($this->interator_stack[0]); $this->current = 0; } @@ -981,12 +978,12 @@ public function key(){ public function next(){ $this->current++; - $this->valid = isset( $this->_stack[ $this->current ] ) + $this->valid = isset( $this->interator_stack[ $this->current ] ) ? true : false; if ( $this->valid ) $this->stack = array( - $this->_stack[ $this->current++ ] + $this->interator_stack[ $this->current++ ] ); } public function valid(){ @@ -1069,21 +1066,21 @@ public function dumpSource( $node = null ) { } } -function _() { +function phpQuery() { if (! func_num_args() ) - return new phpQuery(); + return new phpQueryClass(); $input = func_get_args(); // load template file - if ( phpQuery::isHTMLfile( $input[0] ) ) { - $loaded = phpQuery::load( $input[0] ); - return new phpQuery(); + if ( phpQueryClass::isHTMLfile( $input[0] ) ) { + $loaded = phpQueryClass::load( $input[0] ); + return new phpQueryClass(); } else if ( is_object($input[0]) && get_class($input[0]) == 'DOMElement' ) { // TODO suppot dom nodes } else { $last = count($input)-1; - $PQ = new phpQuery( + $PQ = new phpQueryClass( // document path - isset( $input[$last] ) && phpQuery::isHTMLfile( $input[$last] ) + isset( $input[$last] ) && phpQueryClass::isHTMLfile( $input[$last] ) ? $input[$last] : null ); @@ -1095,10 +1092,17 @@ function _() { $input[0], isset( $input[1] ) && is_object( $input[1] ) - && get_class( $input[1] ) == 'phpQuery' + && get_class( $input[1] ) == 'phpQueryClass' ? $input[1] : null ); } } + +if (! function_exists('_')) { + function _() { + $args = func_get_args(); + return call_user_func_array('phpQuery', $args); + } +} ?> diff --git a/test-cases/selectors.php b/test-cases/selectors.php index 3ade592..a58233c 100644 --- a/test-cases/selectors.php +++ b/test-cases/selectors.php @@ -62,6 +62,13 @@ 'meta' ) ), + array( + "li#testID, div.articles", + array( + 'div.articles', + 'li#testID' + ) + ), ); _('test.html'); From 038d4447d80badff4e949772f82553e88f1e38fe Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 22 Jul 2007 19:39:42 +0000 Subject: [PATCH 004/149] - multi-query (with comma) support - more unload features - fixed several issues with stack uniquenes - fixed typo in children() - fixed inserting full document into full document (skipping html/body in first one) - findRoot() added - nested xpath support - added :not() pseudoclass - rewritten selector parser - interation fixed once again... - rewritten main multi-function, moved into class - fixed typo in end() - reversable methods now returns new instance, like jQuery do - fixed object callbacks, support for callback in static classes - htmlWithTag() added - fixed html() - now returns content WITHOUT container - insert function accepts HTML code targets eg ->appendTo('
') - safer appendTo() - fixed element order issues in insert() --- phpQuery.php | 1171 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 785 insertions(+), 386 deletions(-) diff --git a/phpQuery.php b/phpQuery.php index fcba44e..675dabe 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -4,155 +4,367 @@ * phpQuery is chainable DOM selector & manipulator. * * @author Tobiasz Cudnik - * @link http://wiadomosc.info/plainTemplate + * @link http://meta20.net/phpQuery * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @version 0.6 beta + * @version 0.7 beta * - * @todo rewrite selector explode (support attr values with spaces, dots and xpath queries) - * @todo comma separated queries - * @todo each() should pass DOM node to callback, actually it's stack-history dangerous - * @todo _() should accept DOM nodes + * @todo missing selectors (div + div, div ~ div) + * @todo missing pseudo classes (:even, :odd +all form specific) * @todo missing jquery functions (css, wrap, val) * @todo docs (copied from jquery) * @todo more test cases * @todo cache (mainly class and regex attrs) - * @todo charset + * @todo check if there is any problem with charset */ class phpQueryClass implements Iterator { - public static $debug = true; - private static $documents = array(); - private static $lastDocument = null; - private $DOM = null; - private $XPath = null; - private $stack = array(); - private $history = array(); - private $root = array(); - private $interator_stack = array(); + public static $debug = false; + protected static $documents = array(); + public static $lastDocID = null; + public $docID = null; + protected $DOM = null; + protected $XPath = null; + protected $stack = array(); + protected $history = array(); + protected $root = array(); + protected $interator_stack = array(); /** - * Interator helpers + * Iterator helpers */ - private $valid = false; - private $current = null; + protected $valid = false; + protected $current = null; /** * Other helpers */ - private $regexpChars = array('^','*','$'); + protected $regexpChars = array('^','*','$'); + protected $tmpNodes = array(); - public static function load( $path ) { - self::$documents[ $path ]['document'] = new DOMDocument(); - $DOM =& self::$documents[ $path ]; - if (! $DOM['document']->loadHTMLFile( $path ) ) { - unset( self::$documents[ $path ] ); - return false; + /** + * Multi-purpose function. + * Use phpQuery() or _() as shortcut. + * + * 1. Create new DOM: + * _('file.htm') + * _('
', true) + * 2. Import HTML: + * _('
') + * 3. Run query: + * _('div.myClass') + * _('div.myClass', 'myFile.htm') + * _('div.myClass', _('div.anotherClass') ) + * + * @return phpQueryClass|false phpQueryClass object or false in case of error. + */ + public static function phpQuery() { + $input = func_get_args(); + /** + * Create new DOM: + * _('file.htm') + * _('
', true) + */ + if ( ($isHTMLfile = self::isHTMLfile($input[0])) || ( isset($input[1]) && self::isHTML($input[0]) && $input[1] )) { + // set document ID + $ID = $isHTMLfile + ? $input[0] + : md5(microtime()); + // check if already loaded + if ( $isHTMLfile && isset( self::$documents[ $ID ] ) ) + return new phpQueryClass($ID); + // create document + self::$documents[ $ID ]['document'] = new DOMDocument(); + $DOM =& self::$documents[ $ID ]; + // load + $isLoaded = $isHTMLfile + ? $DOM['document']->loadHTMLFile($ID) + : $DOM['document']->loadHTML($input[0]); + if (! $isLoaded ) { + throw new Exception("Can't load '{$ID}'"); + return false; + } + $DOM['document']->preserveWhiteSpace = true; + $DOM['document']->formatOutput = true; + $DOM['xpath'] = new DOMXPath( + $DOM['document'] + ); + // remember last document + self::$lastDocID = $ID; + // we ready to create object + return new phpQueryClass($ID); + } else if ( is_object($input[0]) && get_class($input[0]) == 'DOMElement' ) { + throw new Exception('DOM nodes not supported'); + /** + * Import HTML: + * _('
') + */ + } else if ( self::isHTML($input[0]) ) { + $phpQuery = new phpQueryClass(self::$lastDocID); + return $phpQuery->importHTML( $input[0] ); + /** + * Run query: + * _('div.myClass') + * _('div.myClass', 'myFile.htm') + * _('div.myClass', _('div.anotherClass') ) + */ + } else { + $last = count($input)-1; + $ID = isset( $input[$last] ) && self::isHTMLfile( $input[$last] ) + ? $input[$last] + : self::$lastDocID; + $phpQuery = new phpQueryClass($ID); + return $phpQuery->find( + $input[0], + isset( $input[1] ) + && is_object( $input[1] ) + && is_a( $input[1], 'phpQueryClass') + ? $input[1] + : null + ); } - $DOM['document']->preserveWhiteSpace = true; - $DOM['document']->formatOutput = true; - $DOM['xpath'] = new DOMXPath( - $DOM['document'] - ); - self::$lastDocument = $path; - return true; } - public static function unload( $path = null ) { + public function getDocID() { + return $this->docID; + } + public function unload() { + unset( self::$documents[ $this->docID ] ); + } + public static function unloadDocuments( $path = null ) { if ( $path ) unset( self::$documents[ $path ] ); else unset( self::$documents ); } - public function __construct( $path = null ) { - if ( $path ) - self::load($path); - else - $path = self::$lastDocument; - $this->DOM = self::$documents[ $path ]['document']; - $this->XPath = self::$documents[ $path ]['xpath']; + public function __construct($docPath) { + if (! isset(self::$documents[ $docPath ] ) ) { + throw new Exception("Doc path '{$docPath}' isn't loaded."); + return; + } + $this->docID = $docPath; + $this->DOM = self::$documents[ $docPath ]['document']; + $this->XPath = self::$documents[ $docPath ]['xpath']; $this->root = $this->DOM->documentElement; - $this->stackToRoot(); + $this->findRoot(); } - private function debug($in) { + protected function debug($in) { if (! self::$debug ) return; print('
');
 		print_r($in);
-	//	if ( is_array($in))
-	//		print_r(array_slice(debug_backtrace(), 3));
-		print('
'); - } - private function stackToRoot() { + // file debug +// file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND); + // quite handy debug trace +// if ( is_array($in)) +// print_r(array_slice(debug_backtrace(), 3)); + print("\n"); + } + public function findRoot() { $this->stack = array( $this->DOM->documentElement ); + return $this; } - private function isRegexp($pattern) { + protected function isRegexp($pattern) { return in_array( $pattern[ strlen($pattern)-1 ], $this->regexpChars ); } - public static function isHTMLfile( $filename ) { + protected static function isHTMLfile( $filename ) { return is_string($filename) && ( substr( $filename, -5 ) == '.html' || substr( $filename, -4 ) == '.htm' ); } - private function parseSelectors( $selectors ) { - $return = array(); - foreach( split(',', $selectors) as $parse ) { - // clean spaces - $parse = trim( - preg_replace('@\s+@', ' ', - str_replace('>', ' > ', $parse) - ) - ); - $elements = array(); - // TODO: realy parsing of selector - foreach( split(' ', $parse) as $s ) { - if ( $elements && $elements[ count($elements)-1 ] != '>' && $s != '>' ) - $elements[] = ' '; - $elements = array_merge( - $elements, - $this->parseSimpleSelector( $s ) - ); - } - if ( isset($elements[0]) && $elements[0] != '>' ) - array_unshift($elements, ' '); - $return[] = $elements; - $this->debug(array('SELECTOR',$parse,$elements)); - } - return $return; - } + /** + * Determines if $char is really a char. + * + * @param string $char + * @return bool + * @todo rewrite me to charcode ! ;) + */ + protected function isChar($char) { + return preg_match('/\w/', $char); + } +// protected function parseSelector( $selectors ) { +// $return = array(); +// foreach( split(',', $selectors) as $parse ) { +// // clean spaces +// $parse = trim( +// preg_replace('@\s+@', ' ', +// str_replace('>', ' > ', $parse) +// ) +// ); +// $elements = array(); +// // TODO: realy parsing of selector +// foreach( split(' ', $parse) as $s ) { +// if ( $elements && $elements[ count($elements)-1 ] != '>' && $s != '>' ) +// $elements[] = ' '; +// $elements = array_merge( +// $elements, +// $this->parseSimpleSelector( $s ) +// ); +// } +// if ( isset($elements[0]) && $elements[0] != '>' ) +// array_unshift($elements, ' '); +// $return[] = $elements; +// $this->debug(array('SELECTOR',$parse,$elements)); +// } +// return $return; +// } // tag.class1.class2[@attr]:checkbox - private function parseSimpleSelector( $s ) { - $selector = array(); - $match = preg_split( - '@(\\.|#|\\[|:)@', - $s, null, - PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY +// protected function parseSimpleSelector( $s ) {- +// $selector = array(); +// $match = preg_split( +// '@(\\.|#|\\[|:)@', +// $s, null, +// PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY +// ); +// // tag present +// if ( count( $match ) % 2 == 1 ) +// array_unshift($match, ''); +// for( $i = 0; $i < count( $match )-1; $i = $i+2 ) { +// // join classes, args and pseudo-selectors +// $append = ( +// $selector +// && ( +// $match[ $i ][0] == '.' +// || +// $match[ $i ][0] == '[' +// || +// $match[ $i ][0] == ':' +// ) && +// $selector[ count($selector)-1 ][0] == $match[ $i ][0] +// ); +// if ( $append ) +// $selector[ count($selector)-1 ] .= $match[ $i ].$match[ $i+1 ]; +// else +// $selector[] = $match[ $i ].$match[ $i+1 ]; +// } +// return $selector; +// } + protected function parseSelector( $query ) { + // clean spaces + // TODO include this inside parsing + $query = trim( + preg_replace('@\s+@', ' ', + preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) + ) ); - // tag present - if ( count( $match ) % 2 == 1 ) - array_unshift($match, ''); - for( $i = 0; $i < count( $match )-1; $i = $i+2 ) { - // join classes, args and pseudo-selectors - $append = ( - $selector - && ( - $match[ $i ][0] == '.' - || - $match[ $i ][0] == '[' - || - $match[ $i ][0] == ':' - ) && - $selector[ count($selector)-1 ][0] == $match[ $i ][0] - ); - if ( $append ) - $selector[ count($selector)-1 ] .= $match[ $i ].$match[ $i+1 ]; - else - $selector[] = $match[ $i ].$match[ $i+1 ]; + $queries = array(array()); + $return =& $queries[0]; + $specialChars = array('>','+','~',' '); + $specialCharsMapping = array('/' => '>'); + $strlen = strlen($query); + $classChars = array('.', '-'); + $pseudoChars = array('-'); + // it works, but i dont like it... + $i = 0; + while( $i < $strlen) { + $c = $query[$i]; + $tmp = ''; + // TAG + if ( $this->isChar($c) || $c == '*' ) { + while( isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '*')) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // IDs + } else if ( $c == '#' ) { + $i++; + while( isset($query[$i]) && $this->isChar($query[$i])) { + $tmp .= $query[$i]; + $i++; + } + $return[] = '#'.$tmp; + // SPECIAL CHARS + // todo znaki specjalne sie wykluczaja miedzy tagami (trimowac) + // np 'tag + tag2' da w wyniku [tag, ,+, ,tag2] a to zle ;] + } else if (in_array($c, $specialChars)) { + $return[] = $c; + $i++; + // MAPPED SPECIAL MULTICHARS + } else if ( $c.$query[$i+1] == '//' ) { + $return[] = ' '; + $i = $i+2; + // MAPPED SPECIAL CHARS + } else if ( isset($specialCharsMapping[$c]) ) { + $return[] = $specialCharsMapping[$c]; + $i++; + // COMMA + } else if ( $c == ',' ) { + $queries[] = array(); + $return =& $queries[ count($queries)-1 ]; + $i++; + while( isset($query[$i]) && $query[$i] == ' ') + $i++; + // CLASSES + } else if ($c == '.') { + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) { + $tmp .= $query[$i]; + $i++; + } + $return[] = $tmp; + // ATTRS & NESTED XPATH + } else if ($c == '[') { + $stack = 1; + $tmp .= $c; + while( isset($query[++$i]) ) { + $tmp .= $query[$i]; + if ( $query[$i] == '[' ) { + $stack++; + } else if ( $query[$i] == ']' ) { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + // PSEUDO CLASSES + } else if ($c == ':') { + $stack = 1; + $tmp .= $query[$i++]; + while( isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) { + $tmp .= $query[$i]; + $i++; + } + // with arguments ? + if ( isset($query[$i]) && $query[$i] == '(' ) { + $tmp .= $query[$i]; + $stack = 1; + while( isset($query[++$i]) ) { + $tmp .= $query[$i]; + if ( $query[$i] == '(' ) { + $stack++; + } else if ( $query[$i] == ')' ) { + $stack--; + if (! $stack ) + break; + } + } + $return[] = $tmp; + $i++; + } else { + $return[] = $tmp; + } + } else { + $i++; + } } - return $selector; + foreach($queries as $k =>$q ) { + if ( isset($q[0]) && $q[0] != '>' ) + array_unshift($queries[$k], ' '); + } + return $queries; + } + protected function newInstance() { + $new = new phpQueryClass($this->docID); + $new->history = $this->history; + $new->stack = $this->stack; + $this->stack = array_pop($this->history); + return $new; } - private function matchClasses( $class, $node ) { + + protected function matchClasses( $class, $node ) { // multi-class if ( strpos($class, '.', 1) ) { $classes = explode('.', substr($class, 1)); @@ -178,77 +390,137 @@ private function matchClasses( $class, $node ) { return true; } } - public function find( $selectors, $stack = null ) { + protected function runQuery( $XQuery, $selector = null, $compare = null ) { + if ( $compare && ! method_exists($this, $compare) ) + return false; + $stack = array(); + if (! $this->stack ) + $this->debug('Stack empty, skipping...'); + foreach( $this->stack as $k => $stackNode ) { + $remove = false; + // to work on detached nodes we need temporary place them somewhere + // thats because context xpath queries sucks ;] + if (! $stackNode->parentNode && ! $this->isRoot($stackNode) ) { + $this->root->appendChild($stackNode); + $remove = true; + } + $xpath = $this->getNodeXpath($stackNode); + $query = $xpath.$XQuery; + $this->debug("XPATH: {$query}"); + // run query, get elements + $nodes = $this->XPath->query($query); + $this->debug("QUERY FETCHED"); + if (! $nodes->length ) + $this->debug('Nothing found'); + foreach( $nodes as $node ) { + $matched = false; + if ( $compare ) { + self::$debug ? + $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") + : null; + if ( call_user_method($compare, $this, $selector, $node) ) + $matched = true; + } else { + $matched = true; + } + if ( $matched ) { + self::$debug + ? $this->debug("Matched: ".$this->whois( $node )) + : null; + $stack[] = $node; + } + } + if ( $remove ) + $stackNode = $this->root->removeChild( $this->root->lastChild ); + } + $this->stack = $stack; + } + public function find( $selectors, $context = null ) { // backup last stack /for end()/ $this->history[] = $this->stack; - if ( $stack && get_class($stack) == get_class($this) ) - $this->stack = $stack; + // allow to define context + if ( $context && is_a($context, get_class($this)) ) + $this->stack = $context->stack; $spaceBefore = false; + $queries = $this->parseSelector( $selectors ); + $this->debug(array('FIND',$selectors,$queries)); $XQuery = ''; - foreach( $this->parseSelectors( $selectors ) as $selector ) { + // remember stack state because of multi-queries + $oldStack = $this->stack; + // here will be kept found elements + $stack = array(); + foreach( $queries as $selector ) { + $this->stack = $oldStack; foreach( $selector as $s ) { + // TAG if ( preg_match('@^\w+$@', $s) || $s == '*' ) { - // tag $XQuery .= $s; } else if ( $s[0] == '#' ) { // id if ( $spaceBefore ) $XQuery .= '*'; $XQuery .= "[@id='".substr($s, 1)."']"; - } else if ( $s[0] == '[' ) { - // attributes and nests + // ATTRIBUTES + } else if ( isset($s[1]) && $s[0].$s[1] == '[@' ) { if ( $spaceBefore ) $XQuery .= '*'; // strip side brackets - $attrs = explode('][', trim($s, '[]')); + $attr = trim($s, ']['); $execute = false; - foreach( $attrs as $attr ) { - if ( $attr[0] == '@' ) { - // attr with specifed value - if ( strpos( $attr, '=' ) ) { - list( $attr, $value ) = explode('=', $attr); - $value = trim($value, "'\"'"); - if ( $this->isRegexp($attr) ) { - // cut regexp character - $attr = substr($attr, 0, -1); - $execute = true; - $XQuery .= "[{$attr}]"; - } else { - $XQuery .= "[{$attr}='{$value}']"; - } - // attr without specified value - } else { - $XQuery .= "[{$attr}]"; - } - // TODO nested xpath + // attr with specifed value + if ( strpos( $s, '=' ) ) { + list( $attr, $value ) = explode('=', $attr); + $value = trim($value, "'\"'"); + if ( $this->isRegexp($attr) ) { + // cut regexp character + $attr = substr($attr, 0, -1); + $execute = true; + $XQuery .= "[{$attr}]"; } else { + $XQuery .= "[{$attr}='{$value}']"; } + // attr without specified value + } else { + $XQuery .= "[{$attr}]"; } if ( $execute ) { $this->runQuery($XQuery, $s, 'is'); $XQuery = ''; if (! $this->length() ) - return $this; + break; } + // NESTED XPATH + } else if ( $s[0] == '[' ) { + if ( $XQuery && $XQuery != '//' ) { + $this->runQuery($XQuery); + $XQuery = ''; + if (! $this->length() ) + break; + } + // strip side brackets + $x = substr($s, 1, -1); + $this->stack = $this->find($x)->stack; + // CLASSES } else if ( $s[0] == '.' ) { - // class(es) if ( $spaceBefore ) $XQuery .= '*'; $XQuery .= '[@class]'; $this->runQuery($XQuery, $s, 'matchClasses'); $XQuery = ''; if (! $this->length() ) - return $this; + break; + // PSEUDO CLASSES } else if ( $s[0] == ':' ) { - // pseudo classes // TODO optimization for :first :last - $this->runQuery($XQuery); - $XQuery = ''; + if ( $XQuery ) { + $this->runQuery($XQuery); + $XQuery = ''; + } if (! $this->length() ) - return $this; + break; $this->filterPseudoClasses( $s ); if (! $this->length() ) - return $this; + break; } else if ( $s == '>' ) { // direct descendant $XQuery .= '/'; @@ -261,108 +533,85 @@ public function find( $selectors, $stack = null ) { $spaceBefore = false; } // run query if any - if ( $XQuery ) { + if ( $XQuery && $XQuery != '//' ) { $this->runQuery($XQuery); $XQuery = ''; if (! $this->length() ) - return $this; - } - } - // preserve chain - return $this; - } - private function runQuery( $XQuery, $selector = null, $compare = null ) { - if ( $compare && ! method_exists($this, $compare) ) - return false; - $stack = array(); - if (! $this->stack ) - $this->debug('Stack empty, skipping...'); - foreach( $this->stack as $k => $stackNode ) { - $remove = false; - if (! $stackNode->parentNode && ! $this->isRoot($stackNode) ) { - $this->root->appendChild($stackNode); - $remove = true; + break; } - $xpath = $this->getNodeXpath($stackNode); - $query = $xpath.$XQuery; - $this->debug("XPATH: {$query}\n"); - // run query, get elements - $nodes = $this->XPath->query($query); - foreach( $nodes as $node ) { - $matched = false; - if ( $compare ) { - self::$debug ? - $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") - : null; - if ( call_user_method($compare, $this, $selector, $node) ) - $matched = true; - } else { - $matched = true; - } - if ( $matched ) { - self::$debug - ? $this->debug("Matched: ".$this->whois( $node )) - : null; + foreach( $this->stack as $node ) + if (! $this->stackContains($node, $stack) ) $stack[] = $node; - } - } - if ( $remove ) - $stackNode = $this->root->removeChild( $this->root->lastChild ); } $this->stack = $stack; + return $this->newInstance(); } - public function filterPseudoClasses( $classes ) { - foreach( explode(':', substr($classes, 1)) as $class ) { - // TODO clean args parsing - $haveArgs = strpos($class, '('); - if ( $haveArgs !== false ) { - $args = substr($class, $haveArgs+1, -1); - $class = substr($class, 0, $haveArgs); - } - switch( $class ) { - case 'even': - case 'odd': - $stack = array(); - foreach( $this->stack as $i => $node ) { - if ( $class == 'even' && $i % 2 == 0 ) - $stack[] = $node; - else if ( $class == 'odd' && $i % 2 ) - $stack[] = $node; - } - $this->stack = $stack; - break; - case 'eq': - $this->stack = isset( $this->stack[ intval($args) ] ) - ? array( $this->stack[ $args ] ) - : array(); - break; - case 'gt': - $this->stack = array_slice($this->stack, $args+1); - break; - case 'lt': - $this->stack = array_slice($this->stack, 0, $args+1); - break; - case 'first': - if ( isset( $this->stack[0] ) ) - $this->stack = array( $this->stack[0] ); - break; - case 'last': - if ( $this->stack ) - $this->stack = array( $this->stack[ count($this->stack)-1 ] ); - break; - case 'parent': - $stack = array(); - foreach( $this->stack as $node ) { - if ( $node->childNodes->length ) - $stack = $node; - } - $this->stack = $stack; - break; - case 'contains': - $this->contains( trim($args, "\"'"), false ); - break; - } + /** + * @todo create API for classes with pseudoselectors + */ + protected function filterPseudoClasses( $class ) { + // TODO clean args parsing ? + $class = trim($class, ':'); + $haveArgs = strpos($class, '('); + if ( $haveArgs !== false ) { + $args = substr($class, $haveArgs+1, -1); + $class = substr($class, 0, $haveArgs); + } + switch( $class ) { + case 'even': + case 'odd': + $stack = array(); + foreach( $this->stack as $i => $node ) { + if ( $class == 'even' && $i % 2 == 0 ) + $stack[] = $node; + else if ( $class == 'odd' && $i % 2 ) + $stack[] = $node; + } + $this->stack = $stack; + break; + case 'eq': + $k = intval($args); + $this->stack = isset( $this->stack[$k] ) + ? array( $this->stack[$k] ) + : array(); + break; + case 'gt': + $this->stack = array_slice($this->stack, $args+1); + break; + case 'lt': + $this->stack = array_slice($this->stack, 0, $args+1); + break; + case 'first': + if ( isset( $this->stack[0] ) ) + $this->stack = array( $this->stack[0] ); + break; + case 'last': + if ( $this->stack ) + $this->stack = array( $this->stack[ count($this->stack)-1 ] ); + break; + case 'parent': + $stack = array(); + foreach( $this->stack as $node ) { + if ( $node->childNodes->length ) + $stack = $node; + } + $this->stack = $stack; + break; + case 'contains': + $this->contains( trim($args, "\"'"), false ); + break; + case 'not': + $query = trim($args, "\"'"); + $stack = $this->stack; + $newStack = array(); + foreach( $stack as $node ) { + $this->stack = array($node); + if (! $this->is($query) ) + $newStack[] = $node; + } + $this->stack = $newStack; + break; } } public function is( $selector, $_node = null ) { @@ -375,86 +624,102 @@ public function is( $selector, $_node = null ) { return $match; } - public function filter( $selector, $_skipHistory = false ) { + public function filter( $selectors, $_skipHistory = false ) { if (! $_skipHistory ) $this->history[] = $this->stack; - $selector = $this->parseSimpleSelector( $selector ); + $selectors = $this->parseSelector( $selectors ); $stack = array(); - foreach( $this->stack as $k => $node ) { - foreach( $selector as $s ) { - switch( $s[0] ) { - case '#': - if ( $node->getAttribute('id') != $val ) - $stack[] = $node; - break; - case '.': - if ( $this->matchClasses( $s, $node ) ) - $stack[] = $node; - break; - case '[': - foreach( explode( '][', trim($s, '[]') ) as $attr ) { - // attrs - if ( $attr[0] == '@' ) { - // cut-da-monkey ;) - $attr = substr($attr, 1); - if ( strpos($attr, '=') ) { - list( $attr, $val ) = explode('=', $attr); - if ( $this->isRegexp($attr)) { - // switch last character - switch( substr($attr, -1) ) { - case '^': - $pattern = '^'.preg_quote($val, '@'); - break; - case '*': - $pattern = '.*'.preg_quote($val, '@').'.*'; - break; - case '$': - $pattern = preg_quote($val, '@').'$'; - break; - } - // cut last character - $attr = substr($attr, 0, -1); - if ( preg_match("@{$pattern}@", $node->getAttribute($attr))) - $stack[] = $node; - } else if ( $node->getAttribute($attr) == $val ) - $stack[] = $node; - } else if ( $node->hasAttribute($attr) ) - $stack[] = $node; - // nested xpath - } else { - // TODO - } + foreach ( $selectors as $selector ) { + // PER NODE selector chunks + foreach( $this->stack as $node ) { + $break = false; + foreach( $selector as $s ) { + // ID + if ( $s[0] == '#' ) { + if ( $node->getAttribute('id') != substr($s, 1) ) + $break = true; + // CLASSES + } else if ( $s[0] == '.' ) { + if (! $this->matchClasses( $s, $node ) ) + $break = true; + // ATTRS + } else if ( isset($s[1]) && $s[0].$s[1] == '[@' ) { + // strip side brackets and @ + $attr = substr($s, 2, -1); + if ( strpos($attr, '=') ) { + list( $attr, $val ) = explode('=', $attr); + if ( $this->isRegexp($attr)) { + // switch last character + switch( substr($attr, -1) ) { + case '^': + $pattern = '^'.preg_quote($val, '@'); + break; + case '*': + $pattern = '.*'.preg_quote($val, '@').'.*'; + break; + case '$': + $pattern = preg_quote($val, '@').'$'; + break; + } + // cut last character + $attr = substr($attr, 0, -1); + if (! preg_match("@{$pattern}@", $node->getAttribute($attr))) + $break = true; + } else if ( $node->getAttribute($attr) != $val ) + $break = true; + } else if (! $node->hasAttribute($attr) ) + $break = true; + // PSEUDO CLASSES + } else if ( $s[0] == ':' ) { + // skip + // NESTED XPATH + } else if ( $s[0] == '[' ) { + // strip side brackets + $x = substr($s, 1, -1); + $oldStack = $this->stack; + $this->stack = array($node); + $pass = $this->find( $x )->size(); + $this->stack = $oldStack; + if (! $pass ) + $break = true; + // TAG + } else if ( trim($s) ) { + if ( $s != '*' ) { + if ( isset($node->tagName) ) { + if ( $node->tagName != $s ) + $break = true; + } else if ( $s == 'html' && ! $this->isRoot($node) ) + $break = true; } + } + if ( $break ) break; - case ':': - // at the end of function - break; - default: - // tag - if ( isset($node->tagName) ) { - if ( $node->tagName == $s ) - $stack[] = $node; - } else if ( $s == 'html' && $this->isRoot($node) ) - $stack[] = $node; } + // if element passed all chunks of selector - add it to new stack + if (! $break ) + $stack[] = $node; } + $this->stack = $stack; + // PER ALL NODES selector chunks + foreach($selector as $s) + // PSEUDO CLASSES + if ( $s[0] == ':' ) + $this->filterPseudoClasses($s); } - $this->stack = $stack; - // pseudoclasses - if ( $selector[ count($selector)-1 ][0] == ':' ) - $this->filterPseudoClasses( $selector[ count($selector)-1 ] ); - return $this; + return $_skipHistory + ? $this + : $this->newInstance(); } - private function isRoot( $node ) { - return get_class($node) == 'DOMDocument'; + protected function isRoot( $node ) { + return is_a($node, 'DOMDocument') || $node->tagName == 'html'; } public function css() { // TODO } - private function importHTML($html) { + protected function importHTML($html) { $this->history[] = $this->stack; $this->stack = array(); $DOM = new DOMDocument(); @@ -486,13 +751,13 @@ public function contains( $text, $history = true ) { public function gt($num) { $this->history[] = $this->stack; $this->stack = array_slice( $this->stack, $num+1 ); - return $this; + return $this->newInstance(); } public function lt($num) { $this->history[] = $this->stack; $this->stack = array_slice( $this->stack, 0, $num+1 ); - return $this; + return $this->newInstance(); } public function eq($num) { @@ -501,7 +766,7 @@ public function eq($num) { $this->stack = array(); if ( isset($oldStack[$num]) ) $this->stack[] = $oldStack[$num]; - return $this; + return $this->newInstance(); } public function size() { @@ -522,12 +787,15 @@ public function each($callabck) { foreach( $this->history[ count( $this->history )-1 ] as $node ) { $this->stack = array($node); if ( is_array( $callabck ) ) { - ${$callabck[0]}->{$callabck[1]}( $this ); + if ( is_object( $callabck[0] ) ) + $callabck[0]->{$callabck[1]}( $this->newInstance() ); + else + eval("{$callabck[0]}::{$callabck[1]}( \$this->newInstance() );"); } else { - $callabck( $this ); + $callabck( $this->newInstance() ); } } - return $this->end(); + return $this; } public function _clone() { @@ -550,27 +818,27 @@ public function remove() { return $this; } - private function isHTML( $html ) { + protected function isHTML( $html ) { return substr(trim($html), 0, 1) == '<'; } public function html($html = null) { - if ( $html ) { + if (! is_null($html) ) { + $this->debug("Inserting data with 'html'"); if ( $this->isHTML( $html ) ) { $toInserts = array(); $DOM = new DOMDocument(); @$DOM->loadHTML( $html ); foreach($DOM->documentElement->firstChild->childNodes as $node) $toInserts[] = $this->DOM->importNode( $node, true ); - // $toInserts = array_reverse( $toInserts ); } else { $toInserts = array($this->DOM->createTextNode( $html )); } $this->_empty(); // i dont like brackets ! python rules ! ;) foreach( $toInserts as $toInsert ) - foreach( $this->stack as $k => $node ) - $node->appendChild( $k + foreach( $this->stack as $alreadyAdded => $node ) + $node->appendChild( $alreadyAdded ? $toInsert->cloneNode() : $toInsert ); @@ -580,14 +848,28 @@ public function html($html = null) { return $this->DOM->saveHTML(); $DOM = new DOMDocument(); foreach( $this->stack as $node ) { - $DOM->appendChild( - $DOM->importNode( $node, true ) - ); + foreach( $node->childNodes as $child ) { + $DOM->appendChild( + $DOM->importNode( $child, true ) + ); + } } $DOM->formatOutput = true; return $DOM->saveHTML(); } } + public function htmlWithTag() { + if ( $this->length() == 1 && $this->isRoot( $this->stack[0] ) ) + return $this->DOM->saveHTML(); + $DOM = new DOMDocument(); + foreach( $this->stack as $node ) { + $DOM->appendChild( + $DOM->importNode( $node, true ) + ); + } + $DOM->formatOutput = true; + return $DOM->saveHTML(); + } public function __toString() { return $this->html(); } @@ -604,11 +886,11 @@ public function phpPrint($var) { * @param string Valid PHP Code */ public function phpMeta($selector, $code) { - return $this->find($selector) - ->php($code) - ->end(); + $this->find($selector) + ->php($code); + return $this; } - private function dumpHistory($when) { + protected function dumpHistory($when) { foreach( $this->history as $nodes ) { $history[] = array(); foreach( $nodes as $node ) { @@ -627,10 +909,10 @@ public function children( $selector = null ) { } } $this->history[] = $this->stack; - $this->stack = $tack; - return $this; + $this->stack = $stack; + return $this->newInstance(); } - public function ancestors( $selector ) { + public function ancestors( $selector = null ) { return $this->children( $selector ); } @@ -673,7 +955,8 @@ public function afterPHP( $content ) { public function insertAfter( $seletor ) { return $this->insert($seletor, __FUNCTION__); } - private function insert( $target, $type ) { + protected function insert( $target, $type ) { + $this->debug("Inserting data with '{$type}'"); $to = false; switch( $type ) { case 'appendTo': @@ -685,26 +968,39 @@ private function insert( $target, $type ) { switch(gettype( $target )) { case 'string': if ( $to ) { - //$this->dumpHistory('appendTo'); - $oldStack = $this->stack; - $historyCount = count( $this->history ); - $this->stack = array( $this->root ); - $this->find($target); - $insertTo = $this->stack; - $this->stack = $oldStack; $insertFrom = $this->stack; - if ( count( $this->history ) > $historyCount ) - $this->history = array_slice( $this->history, 0, $historyCount ); - //$this->dumpHistory('appendTo-END'); + // insert into created element + if ( $this->isHTML( $target ) ) { + $DOM = new DOMDocument(); + @$DOM->loadHTML($target); + $i = count($this->tmpNodes); + $this->tmpNodes[] = array(); + foreach($DOM->documentElement->firstChild->childNodes as $node) { + $this->tmpNodes[$i][] = $this->DOM->importNode( $node, true ); + } + // XXX needed ?! + // $this->tmpNodes[$i] = array_reverse($this->tmpNodes[$i]); + $insertTo =& $this->tmpNodes[$i]; + // insert into selected element + } else { + $thisStack = $this->stack; + $this->findRoot(); + $insertTo = $this->find($target)->stack; + $this->stack = $thisStack; + } } else { $insertTo = $this->stack; + // insert created element if ( $this->isHTML( $target ) ) { $DOM = new DOMDocument(); @$DOM->loadHTML($target); + $insertFrom = array(); foreach($DOM->documentElement->firstChild->childNodes as $node) { - $insertFrom[] = $this->DOM->importNode( $node, true ); + $insertFrom[] = $this->DOM->importNode($node, true); } - $insertFrom = array_reverse($insertFrom); + // XXX needed ?! + // $insertFrom = array_reverse($insertFrom); + // insert selected element } else { $insertFrom = array( $this->DOM->createTextNode( $target ) @@ -713,34 +1009,56 @@ private function insert( $target, $type ) { } break; case 'object': - if ( get_class( $target ) == get_class( $this )) { + $insertFrom = $insertTo = array(); + if ( is_a($target, get_class($this)) ){ if ( $to ) { $insertTo = $target->stack; - foreach( $this->stack as $node ) - $insertFrom[] = $target->DOM->importNode($node); + if ( $this->size() == 1 && $this->isRoot($this->stack[0]) ) + $loop = $this->find('body>*')->stack; + else + $loop = $this->stack; + foreach( $loop as $node ) + $insertFrom[] = $target->DOM->importNode($node, true); } else { $insertTo = $this->stack; - foreach( $target->stack as $node ) - $insertFrom[] = $this->DOM->importNode($node); + if ( $target->size() == 1 && $this->isRoot($target->stack[0]) ) + $loop = $target->find('body>*')->stack; + else + $loop = $target->stack; + foreach( $loop as $node ) { + $insertFrom[] = $this->DOM->importNode($node, true); + } } } break; } - foreach( $insertFrom as $fromNode ) { - foreach( $insertTo as $toNode ) { + foreach( $insertTo as $toNode ) { + // we need static relative elements in some cases + switch( $type ) { + case 'prependTo': + case 'prepend': + $firstChild = $toNode->firstChild; + break; + case 'insertAfter': + case 'after': + $nextSibling = $toNode->nextSibling; + break; + } + foreach( $insertFrom as $fromNode ) { switch( $type ) { case 'appendTo': case 'append': - $toNode->insertBefore( - $fromNode, - $toNode->lastChild->nextSibling - ); +// $toNode->insertBefore( +// $fromNode, +// $toNode->lastChild->nextSibling +// ); + $toNode->appendChild($fromNode); break; case 'prependTo': case 'prepend': $toNode->insertBefore( $fromNode, - $toNode->firstChild + $firstChild ); break; case 'insertBefore': @@ -751,10 +1069,10 @@ private function insert( $target, $type ) { ); break; case 'insertAfter': - case 'after': + case 'after': $toNode->parentNode->insertBefore( $fromNode, - $toNode->nextSibling + $nextSibling ); break; } @@ -762,6 +1080,20 @@ private function insert( $target, $type ) { } return $this; } + + public function replace($html = null) { + // TODO + } + + + public function slice() { + // TODO python slices + } + + public function reverse() { + $this->history[] = $this->stack; + $this->stack = array_reverse($this->stack); + } public function text() { $return = ''; @@ -773,15 +1105,38 @@ public function text() { public function _next( $selector = null ) { $this->sibling( $selector, 'previousSibling' ); - return $this; + return $this->newInstance(); } public function _prev( $selector = null ) { $this->sibling( $selector, 'previousSibling' ); - return $this; + return $this->newInstance(); } - private function sibling( $selector, $direction ) { + /** + * @return phpQueryClass + * @todo + */ + public function prevSiblings( $selector = null ) { + } + + /** + * @return phpQueryClass + * @todo + */ + public function nextSiblings( $selector = null ) { + } + + /** + * Number of prev siblings + * @return int + * @todo + */ + public function index() { + return $this->prevSiblings()->size(); + } + + protected function sibling( $selector, $direction ) { $stack = array(); foreach( $this->stack as $node ) { $test = $node; @@ -806,73 +1161,79 @@ public function siblings( $selector = null ) { $stack = array(); foreach( $this->stack as $node ) { if ( $selector ) { - if ( $this->is( $selector, $test ) ) + if ( $this->is( $selector ) && ! $this->stackContains($node, $stack) ) $stack[] = $node; - } else + } else if (! $this->stackContains($node, $stack) ) $stack[] = $node; } $this->history[] = $this->stack; $this->stack = $stack; - return $this; + return $this->newInstance(); } public function not( $selector = null ) { $stack = array(); foreach( $this->stack as $node ) { - if (! $this->is( $selector, node ) ) + if (! $this->is( $selector, $node ) ) $stack[] = $node; } $this->history[] = $this->stack; $this->stack = $stack; - return $this; + return $this->newInstance(); } public function add( $selector = null ) { $stack = array(); $this->history[] = $this->stack; - $this->find($selector); - $this->merge( - $this->history[ count($this->history)-2 ] - ); - return $this; - + $found = $this->find($selector); + $this->merge($found->stack); + return $this->newInstance(); } - private function merge() { - foreach( get_func_args() as $nodes ) { - foreach( $nodes as $newNode ) { - foreach( $this->stack as $node ) { - if (! $node->isSameNode( $newNode )) - $this->stack[] = $newNode; - } - } + protected function merge() { + foreach( get_func_args() as $nodes ) + foreach( $nodes as $newNode ) + if (! $this->stackContains($newNode) ) + $this->stack[] = $newNode; + } + + protected function stackContains($nodeToCheck, $stackToCheck = null) { + $loop = ! is_null($stackToCheck) + ? $stackToCheck + : $this->stack; + foreach( $loop as $node ) { + if ( $node->isSameNode( $nodeToCheck ) ) + return true; } + return false; } public function parent( $selector = null ) { $stack = array(); - foreach( $this->stack as $node ) { - if ( $this->is( $selector, $node->parentNode ) ) + foreach( $this->stack as $node ) + if ( $node->parentNode && ! $this->stackContains($node->parentNode, $stack) ) $stack[] = $node->parentNode; - } $this->history[] = $this->stack; $this->stack = $stack; - return $this; + $this->filter($selector, true); + return $this->newInstance(); } public function parents( $selector = null ) { $stack = array(); + if (! $this->stack ) + $this->debug('parents() - stack empty'); foreach( $this->stack as $node ) { $test = $node; while( $test->parentNode ) { $test = $test->parentNode; if ( $selector ) { - if ( $this->is( $selector, $test ) ) { + if ( $this->is( $selector, $test ) && ! $this->stackContains($test, $stack) ) { $stack[] = $test; continue; } - } else { + } else if (! $this->stackContains($test, $stack) ) { $stack[] = $test; continue; } @@ -880,7 +1241,7 @@ public function parents( $selector = null ) { } $this->history[] = $this->stack; $this->stack = $stack; - return $this; + return $this->newInstance(); } public function attr( $attr, $value = null ) { @@ -893,8 +1254,12 @@ public function attr( $attr, $value = null ) { return $this; } - public function val( $selector = null ) { - + /** + * Enter description here... + * + * @todo val() + */ + public function val() { } public function removeAttr( $attr ) { @@ -912,6 +1277,25 @@ public function addClass( $className ) { } } + /** + * Returns if className (optionally match with regex in // delimiters) is set for element. + * + * @param string $className + * Optional. Can be regexp in // delimiters. + * @return string + * Matched class. + * @todo hasClass + */ + public function hasClass( $className ) { + foreach( $this->stack as $node ) { + if (! $this->is( $node, '.'.$className)) + $node->setAttribute( + 'class', + $node->getAttribute('class').' '.$className + ); + } + } + public function removeClass( $className ) { foreach( $this->stack as $node ) { $classes = explode( ' ', $node->getAttribute('class')); @@ -957,14 +1341,17 @@ public function _empty() { } - // INTERATOR INTERFACE + // ITERATOR INTERFACE public function rewind(){ + $this->debug('interating foreach'); $this->history[] = $this->stack; $this->interator_stack = $this->stack; $this->valid = isset( $this->stack[0] ) ? 1 : 0; - $this->stack = array($this->interator_stack[0]); + $this->stack = $this->valid + ? array($this->stack[0]) + : array(); $this->current = 0; } @@ -983,27 +1370,25 @@ public function next(){ : false; if ( $this->valid ) $this->stack = array( - $this->interator_stack[ $this->current++ ] + $this->interator_stack[ $this->current ] ); } public function valid(){ return $this->valid; } - // ADDONS - - private function getNodeXpath( $oneNode = null ) { + protected function getNodeXpath( $oneNode = null ) { $return = array(); $loop = $oneNode ? array($oneNode) : $this->stack; foreach( $loop as $node ) { - if ( $this->isRoot($node) ) { + if ( is_a($node, 'DOMDocument') ) { $return[] = ''; continue; } $xpath = array(); - while( get_class($node) != 'DOMDocument' ) { + while(! is_a($node, 'DOMDocument') ) { $i = 1; $sibling = $node; while( $sibling->previousSibling ) { @@ -1044,9 +1429,11 @@ public function whois($oneNode = null) { // HELPERS - public function dumpStack() { + public function dumpStack() { + $i = 1; foreach( $this->stack as $node ) { - $this->debug($node->tagName); + $this->debug("Node {$i} ".$this->whois($node)); + $i++; } } @@ -1066,7 +1453,19 @@ public function dumpSource( $node = null ) { } } +/** + * Shortcut to new phpQuery($arg1, $arg2, ...) + * + * @return phpQuery + * @todo move logic to contructor + */ function phpQuery() { + $args = func_get_args(); + return call_user_func_array( + array('phpQueryClass', 'phpQuery'), + $args + ); + // old code if (! func_num_args() ) return new phpQueryClass(); $input = func_get_args(); @@ -1075,7 +1474,6 @@ function phpQuery() { $loaded = phpQueryClass::load( $input[0] ); return new phpQueryClass(); } else if ( is_object($input[0]) && get_class($input[0]) == 'DOMElement' ) { - // TODO suppot dom nodes } else { $last = count($input)-1; $PQ = new phpQueryClass( @@ -1099,6 +1497,7 @@ function phpQuery() { } } +// handy phpQuery shortcut if (! function_exists('_')) { function _() { $args = func_get_args(); From 8a62c04e1c262fcc188549f2b52fcded804b1fac Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 22 Jul 2007 19:40:58 +0000 Subject: [PATCH 005/149] updated test cases, all are successfully passed :) --- test-cases/run.php | 3 ++ test-cases/test.html | 10 +++++ test-cases/testTemplate.php | 48 ------------------------ test-cases/test_1.php | 21 +++++------ test-cases/test_2.php | 31 ++------------- test-cases/{selectors.php => test_3.php} | 32 ++++++++++++---- test-cases/xpath.php | 25 ++++++++++++ 7 files changed, 77 insertions(+), 93 deletions(-) delete mode 100644 test-cases/testTemplate.php rename test-cases/{selectors.php => test_3.php} (67%) create mode 100644 test-cases/xpath.php diff --git a/test-cases/run.php b/test-cases/run.php index 300c690..6fe1e5f 100644 --- a/test-cases/run.php +++ b/test-cases/run.php @@ -1,3 +1,6 @@ \ No newline at end of file diff --git a/test-cases/test.html b/test-cases/test.html index 6cae4cd..3cc38e2 100644 --- a/test-cases/test.html +++ b/test-cases/test.html @@ -7,6 +7,7 @@ plainTemplate test +
@@ -40,5 +41,14 @@

paragraph after UL

+
    +
  • +
      +
    • +
    +
  • +
  • +
  • +
diff --git a/test-cases/testTemplate.php b/test-cases/testTemplate.php deleted file mode 100644 index b94bca1..0000000 --- a/test-cases/testTemplate.php +++ /dev/null @@ -1,48 +0,0 @@ - 95 ) - print "Test '{$testName}' passed :)"; -else - print "Test '{$testName}' FAILED !!!"; -print "\n"; -?> \ No newline at end of file diff --git a/test-cases/test_1.php b/test-cases/test_1.php index 33bb432..23c5bcb 100644 --- a/test-cases/test_1.php +++ b/test-cases/test_1.php @@ -1,6 +1,7 @@ div.articles text node @@ -41,24 +42,22 @@ ), ); _('test.html'); -$articles = _('.articles ul'); -$row = clone $articles->find('li'); -$row->remove()->eq(0); +$articles = phpQuery('.articles ul'); +$rowSrc = $articles + ->find('li') + ->remove() + ->eq(0); foreach( $rows as $r ) { - $row->_copy(); + $row = $rowSrc->_clone(); foreach( $r as $field => $value ) { $row->find(".{$field}") ->html( $value ) ->end(); } - $row->appendTo('.articles ul') - // DOESNT WORK -// $row->appendTo($articles) + $row->appendTo($articles) ->end(); } -$result = _('.articles')->html(); -// DOESNT WORK -// print $articles->html(); +$result = phpQuery('.articles')->htmlWithTag(); $similarity = 0.0; similar_text($testResult, $result, $similarity); if ( $similarity > 95 ) diff --git a/test-cases/test_2.php b/test-cases/test_2.php index 5b22a6b..b7d4d64 100644 --- a/test-cases/test_2.php +++ b/test-cases/test_2.php @@ -1,35 +1,12 @@ - div.articles text node -
    - -
  • -

    This is paragraph of first LI

    -

    News 1 title

    -

    News 1 body

    -
  • - -
  • -

    This is paragraph of first LI

    -

    News 2 title

    -

    News 2 body

    -
  • -
  • -

    This is paragraph of first LI

    -

    News 3

    -

    News 3 body

    -
  • -
-

paragraph after UL

-
-EOF; -$result = _('test.html') +$testResult = array('p.body'); +$result = phpQuery('test.html') ->find('p') ->filter('.body:gt(1)'); -if ( $result->whois() == array('p.body') ) +if ( $result->whois() == $testResult ) print "Test '{$testName}' passed :)"; else print "Test '{$testName}' FAILED !!!"; diff --git a/test-cases/selectors.php b/test-cases/test_3.php similarity index 67% rename from test-cases/selectors.php rename to test-cases/test_3.php index a58233c..fa60f73 100644 --- a/test-cases/selectors.php +++ b/test-cases/test_3.php @@ -1,6 +1,6 @@ $test ) { - $tests[ $k ][2] = _( $test[0] )->whois(); + $tests[ $k ][2] = phpQuery( $test[0] )->whois(); } foreach( $tests as $test ) { if ( $test[1] == $test[2] ) diff --git a/test-cases/xpath.php b/test-cases/xpath.php new file mode 100644 index 0000000..adc1b57 --- /dev/null +++ b/test-cases/xpath.php @@ -0,0 +1,25 @@ +loadHTMLFile('test.html'); +$X = new DOMXPath($DOM); +print $Query; +whois($X->query($Query)); +function whois($nodeList) { + $return = array(); + foreach( $nodeList as $node ) { + $return[] = ( + $node->tagName + .($node->getAttribute('id') + ? '#'.$node->getAttribute('id'):'') + .($node->getAttribute('class') + ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') + ); + } + print "
";
+	print_r($return);
+	print "
"; +} +?> \ No newline at end of file From 5cd88cd46efa17ef5ee7a89e155d20e62d312a84 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Mon, 30 Jul 2007 15:39:42 +0000 Subject: [PATCH 006/149] - replace() fixed - wrap() added - attr(), removeAttr() acccepts wildcard - find() with multi query fixed - parents() fixed - phpQuery::defaultDoctype added and set to xhtml 1.0 transitional - xhtml compatibility --- phpQuery.php | 306 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 197 insertions(+), 109 deletions(-) diff --git a/phpQuery.php b/phpQuery.php index 675dabe..a1124bb 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -6,9 +6,10 @@ * @author Tobiasz Cudnik * @link http://meta20.net/phpQuery * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @version 0.7 beta + * @version 0.7.1 beta * * @todo missing selectors (div + div, div ~ div) + * @todo fix phpQueryClass::isHTML() stuff (dom nodes, text nodes...) * @todo missing pseudo classes (:even, :odd +all form specific) * @todo missing jquery functions (css, wrap, val) * @todo docs (copied from jquery) @@ -21,6 +22,7 @@ class phpQueryClass implements Iterator { public static $debug = false; protected static $documents = array(); public static $lastDocID = null; + public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'; public $docID = null; protected $DOM = null; protected $XPath = null; @@ -45,9 +47,10 @@ class phpQueryClass implements Iterator { * * 1. Create new DOM: * _('file.htm') - * _('
', true) + * _('
', true) // accepts text nodes at beginning * 2. Import HTML: - * _('
') + * _('
') // DOESNT accept text nodes at beginning ! + * _('
', docID) * 3. Run query: * _('div.myClass') * _('div.myClass', 'myFile.htm') @@ -62,7 +65,7 @@ public static function phpQuery() { * _('file.htm') * _('
', true) */ - if ( ($isHTMLfile = self::isHTMLfile($input[0])) || ( isset($input[1]) && self::isHTML($input[0]) && $input[1] )) { + if ( ($isHTMLfile = self::isHTMLfile($input[0])) || ( isset($input[1]) && $input[1] === true/* && self::isHTML($input[0])*/ )) { // set document ID $ID = $isHTMLfile ? $input[0] @@ -75,14 +78,12 @@ public static function phpQuery() { $DOM =& self::$documents[ $ID ]; // load $isLoaded = $isHTMLfile - ? $DOM['document']->loadHTMLFile($ID) - : $DOM['document']->loadHTML($input[0]); + ? @$DOM['document']->loadHTMLFile($ID) + : @$DOM['document']->loadHTML($input[0]); if (! $isLoaded ) { throw new Exception("Can't load '{$ID}'"); return false; } - $DOM['document']->preserveWhiteSpace = true; - $DOM['document']->formatOutput = true; $DOM['xpath'] = new DOMXPath( $DOM['document'] ); @@ -97,8 +98,12 @@ public static function phpQuery() { * _('
') */ } else if ( self::isHTML($input[0]) ) { - $phpQuery = new phpQueryClass(self::$lastDocID); - return $phpQuery->importHTML( $input[0] ); + $docID = isset($input[1]) && $input[1] + ? $input[1] + : self::$lastDocID; + $phpQuery = new phpQueryClass($docID); + $phpQuery->importHTML($input[0]); + return $phpQuery; /** * Run query: * _('div.myClass') @@ -178,68 +183,11 @@ protected static function isHTMLfile( $filename ) { * * @param string $char * @return bool - * @todo rewrite me to charcode ! ;) + * @todo rewrite me to charcode range ! ;) */ protected function isChar($char) { return preg_match('/\w/', $char); } -// protected function parseSelector( $selectors ) { -// $return = array(); -// foreach( split(',', $selectors) as $parse ) { -// // clean spaces -// $parse = trim( -// preg_replace('@\s+@', ' ', -// str_replace('>', ' > ', $parse) -// ) -// ); -// $elements = array(); -// // TODO: realy parsing of selector -// foreach( split(' ', $parse) as $s ) { -// if ( $elements && $elements[ count($elements)-1 ] != '>' && $s != '>' ) -// $elements[] = ' '; -// $elements = array_merge( -// $elements, -// $this->parseSimpleSelector( $s ) -// ); -// } -// if ( isset($elements[0]) && $elements[0] != '>' ) -// array_unshift($elements, ' '); -// $return[] = $elements; -// $this->debug(array('SELECTOR',$parse,$elements)); -// } -// return $return; -// } - // tag.class1.class2[@attr]:checkbox -// protected function parseSimpleSelector( $s ) {- -// $selector = array(); -// $match = preg_split( -// '@(\\.|#|\\[|:)@', -// $s, null, -// PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY -// ); -// // tag present -// if ( count( $match ) % 2 == 1 ) -// array_unshift($match, ''); -// for( $i = 0; $i < count( $match )-1; $i = $i+2 ) { -// // join classes, args and pseudo-selectors -// $append = ( -// $selector -// && ( -// $match[ $i ][0] == '.' -// || -// $match[ $i ][0] == '[' -// || -// $match[ $i ][0] == ':' -// ) && -// $selector[ count($selector)-1 ][0] == $match[ $i ][0] -// ); -// if ( $append ) -// $selector[ count($selector)-1 ] .= $match[ $i ].$match[ $i+1 ]; -// else -// $selector[] = $match[ $i ].$match[ $i+1 ]; -// } -// return $selector; -// } protected function parseSelector( $query ) { // clean spaces // TODO include this inside parsing @@ -447,7 +395,7 @@ public function find( $selectors, $context = null ) { $XQuery = ''; // remember stack state because of multi-queries $oldStack = $this->stack; - // here will be kept found elements + // here we will be keeping found elements $stack = array(); foreach( $queries as $selector ) { $this->stack = $oldStack; @@ -536,8 +484,8 @@ public function find( $selectors, $context = null ) { if ( $XQuery && $XQuery != '//' ) { $this->runQuery($XQuery); $XQuery = ''; - if (! $this->length() ) - break; +// if (! $this->length() ) +// break; } foreach( $this->stack as $node ) if (! $this->stackContains($node, $stack) ) @@ -594,7 +542,7 @@ protected function filterPseudoClasses( $class ) { $stack = array(); foreach( $this->stack as $node ) { if ( $node->childNodes->length ) - $stack = $node; + $stack[] = $node; } $this->stack = $stack; break; @@ -615,6 +563,9 @@ protected function filterPseudoClasses( $class ) { } } public function is( $selector, $_node = null ) { + $this->debug(array("Is:", $selector)); + if (! $selector) + return false; $oldStack = $this->stack; if ( $_node ) $this->stack = array($_node); @@ -627,9 +578,17 @@ public function is( $selector, $_node = null ) { public function filter( $selectors, $_skipHistory = false ) { if (! $_skipHistory ) $this->history[] = $this->stack; + $notSimpleSelector = array(' ', '>', '~', '+', '/'); $selectors = $this->parseSelector( $selectors ); + if (! $_skipHistory ) + $this->debug(array("Filtering:", $selectors)); $stack = array(); foreach ( $selectors as $selector ) { + if (! $selector ) + break; + // avoid first space or / + if (in_array( $selector[0], $notSimpleSelector ) ) + $selector = array_slice($selector, 1); // PER NODE selector chunks foreach( $this->stack as $node ) { $break = false; @@ -691,6 +650,10 @@ public function filter( $selectors, $_skipHistory = false ) { } else if ( $s == 'html' && ! $this->isRoot($node) ) $break = true; } + // AVOID NON-SIMPLE SELECTORS + } else if ( in_array($s, $notSimpleSelector)) { + $break = true; + $this->debug(array('Skipping non simple selector', $selector)); } if ( $break ) break; @@ -728,12 +691,24 @@ protected function importHTML($html) { $this->stack[] = $this->DOM->importNode( $node, true ); } - public function wrap($before, $after) { - foreach( $this->stack as $node ) { - _($before.$after) + /** + * Wraps elements with $before and $after. + * In case ! $after, $before should be a tag name. + */ + public function wrap($before, $after = null) { + if (! $after ) { + $after = ""; + $before = "<{$before}>"; + } + // safer... + $each = clone $this; + foreach( $each as $node ) { + $wrap = self::phpQuery($before."
".$after) ->insertAfter($node); - + $wrap->find('#__wrap') + ->replace($node); } + return $this; } public function contains( $text, $history = true ) { @@ -781,7 +756,19 @@ public function end() { $this->stack = array_pop( $this->history ); return $this; } - + + /** + * Test + * + * @param unknown_type $selector + * @return unknown + */ + public function select($selector) { + return $this->is($selector) + ? $this->filter($selector) + : $this->find($selector); + } + public function each($callabck) { $this->history[] = $this->stack; foreach( $this->history[ count( $this->history )-1 ] as $node ) { @@ -810,16 +797,45 @@ public function _clone() { return $this; } + /** + * Replaces current element with $content and changes stack to new element(s) (except text nodes). + * Can be reverted with end(). + * + * @param string|phpQueryClass $with + * @return phpQueryClass + */ + public function replace($content) { + $stack = array(); + // safer... + $each = clone $this; + foreach( $each as $node ) { + $prev = $node->before($content)->_prev(); + if ( $this->isHTML($content) ) + $stack[] = $prev->stack; + } + $stack = $this->before($content); + $this->remove(); + $this->history[] = $this->stack; + $this->stack = $stack; + return $this->newInstance(); + } + public function replacePHP($code) { + return $thuis->replace("{$code}"); + } + public function remove() { foreach( $this->stack as $node ) { + if (! $node->parentNode ) + continue; $this->debug("Removing '{$node->tagName}'"); $node->parentNode->removeChild( $node ); } return $this; } - protected function isHTML( $html ) { + protected function isHTML($html) { return substr(trim($html), 0, 1) == '<'; +// || is_object($html) && is_a($html, 'DOMElement'); } public function html($html = null) { @@ -845,7 +861,7 @@ public function html($html = null) { return $this; } else { if ( $this->length() == 1 && $this->isRoot( $this->stack[0] ) ) - return $this->DOM->saveHTML(); + return $this->save(); $DOM = new DOMDocument(); foreach( $this->stack as $node ) { foreach( $node->childNodes as $child ) { @@ -854,24 +870,40 @@ public function html($html = null) { ); } } - $DOM->formatOutput = true; - return $DOM->saveHTML(); + return $this->save($DOM); } } public function htmlWithTag() { if ( $this->length() == 1 && $this->isRoot( $this->stack[0] ) ) - return $this->DOM->saveHTML(); + return $this->save(); $DOM = new DOMDocument(); foreach( $this->stack as $node ) { $DOM->appendChild( $DOM->importNode( $node, true ) ); } - $DOM->formatOutput = true; - return $DOM->saveHTML(); + return $this->save($DOM); + } + protected function save($DOM = null){ + if (! $DOM) + $DOM = $this->DOM; + $DOM->formatOutput = false; + $DOM->preserveWhiteSpace = true; + $doctype = isset($DOM->doctype) && is_object($DOM->doctype) + ? $DOM->doctype->publicId + : self::$defaultDoctype; + return stripos($doctype, 'xhtml') !== false + ? str_replace(array(' ',' '), '', + preg_replace('!]*)/>!', '', + implode("\n", + array_slice( + explode("\n", $DOM->saveXML()), + 1 + )))) + : $DOM->saveHTML(); } public function __toString() { - return $this->html(); + return $this->htmlWithTag(); } public function php($code) { return $this->html("".trim($code).""); @@ -912,6 +944,22 @@ public function children( $selector = null ) { $this->stack = $stack; return $this->newInstance(); } + public function unwrapContent() { + foreach( $this->stack as $node ) { + if (! $node->parentNode ) + continue; + $childNodes = array(); + // any modification in DOM tree breaks childNodes iteration, so cache them first + foreach( $node->childNodes as $chNode ) + $childNodes[] = $chNode; + foreach( $childNodes as $chNode ) +// $node->parentNode->appendChild($chNode); + $node->parentNode->insertBefore($chNode, $node); + $node->parentNode->removeChild($node); + } + return $this->newInstance(); + } + public function ancestors( $selector = null ) { return $this->children( $selector ); } @@ -1080,11 +1128,7 @@ protected function insert( $target, $type ) { } return $this; } - - public function replace($html = null) { - // TODO - } - + public function slice() { // TODO python slices @@ -1140,7 +1184,7 @@ protected function sibling( $selector, $direction ) { $stack = array(); foreach( $this->stack as $node ) { $test = $node; - while( $test->{$direction} ) { + while( isset($test->{$direction}) && $test->{$direction} ) { $test = $test->nextSibling; if ( $selector ) { if ( $this->is( $selector, $test ) ) { @@ -1215,9 +1259,9 @@ public function parent( $selector = null ) { $stack[] = $node->parentNode; $this->history[] = $this->stack; $this->stack = $stack; - $this->filter($selector, true); + if ( $selector ) + $this->filter($selector, true); return $this->newInstance(); - } public function parents( $selector = null ) { @@ -1228,6 +1272,8 @@ public function parents( $selector = null ) { $test = $node; while( $test->parentNode ) { $test = $test->parentNode; + if ( is_a($test, 'DOMDocument') ) + break; if ( $selector ) { if ( $this->is( $selector, $test ) && ! $this->stackContains($test, $stack) ) { $stack[] = $test; @@ -1244,16 +1290,63 @@ public function parents( $selector = null ) { return $this->newInstance(); } - public function attr( $attr, $value = null ) { + /** + * Attribute method. + * Accepts * for all attributes (for setting and getting) + * + * @param unknown_type $attr + * @param unknown_type $value + * @return string|array + */ + public function attr( $attr = null, $value = null ) { foreach( $this->stack as $node ) { - if ( $value ) - $node->setAttribute($attr, $value); - else + if (! is_null( $value )) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach( $loop as $a ) { + if ( $value ) + $node->setAttribute($a, $value); + else + $node->removeAttribute($a); + } + } else if ( $attr == '*' ) { + $return = array(); + foreach( $node->attributes as $n => $v) + $return[$n] = $v->value; + return $return; + } else return $node->getAttribute($attr); } return $this; } + protected function getNodeAttrs($node) { + $return = array(); + foreach( $node->attributes as $n => $o) + $return[] = $n; + return $return; + } + + + public function attrPHP( $attr, $value ) { + foreach( $this->stack as $node ) { + $node->setAttribute($attr, ""); + } + return $this; + } + + public function removeAttr( $attr ) { + foreach( $this->stack as $node ) { + $loop = $attr == '*' + ? $this->getNodeAttrs($node) + : array($attr); + foreach( $loop as $a ) + $node->removeAttribute($a); + } + + } + /** * Enter description here... * @@ -1262,11 +1355,6 @@ public function attr( $attr, $value = null ) { public function val() { } - public function removeAttr( $attr ) { - foreach( $this->stack as $node ) - $node->removeAttribute($attr); - } - public function addClass( $className ) { foreach( $this->stack as $node ) { if (! $this->is( $node, '.'.$className)) @@ -1414,13 +1502,13 @@ public function whois($oneNode = null) { ? array( $oneNode ) : $this->stack; foreach( $loop as $node ) { - $return[] = ( - $node->tagName - .($node->getAttribute('id') - ? '#'.$node->getAttribute('id'):'') - .($node->getAttribute('class') - ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') - ); + $return[] = isset($node->tagName) + ? $node->tagName + .($node->getAttribute('id') + ? '#'.$node->getAttribute('id'):'') + .($node->getAttribute('class') + ? '.'.join('.', split(' ', $node->getAttribute('class'))):'') + : get_class($node); } return $oneNode ? $return[0] From 46365388e1ed861fee028c7313ed5909c659af59 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Mon, 6 Aug 2007 07:31:05 +0000 Subject: [PATCH 007/149] - fixed cdata issues in xhtml --- phpQuery.php | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/phpQuery.php b/phpQuery.php index a1124bb..7cae535 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -893,15 +893,26 @@ protected function save($DOM = null){ ? $DOM->doctype->publicId : self::$defaultDoctype; return stripos($doctype, 'xhtml') !== false - ? str_replace(array(' ',' '), '', - preg_replace('!]*)/>!', '', - implode("\n", - array_slice( - explode("\n", $DOM->saveXML()), - 1 - )))) + ? $this->saveXHTML( $DOM->saveXML() ) : $DOM->saveHTML(); } + protected function saveXHTML($content){ + return + // TODO find out what and why it is. maybe it has some relations with extra new lines ? + str_replace(array(' ',' '), '', + // strip non-commented cdata + str_replace(']]]]>', ']]>', + preg_replace('@(]*>\s*)(\s*)@', '\1', + // textarea can't be short tagged + preg_replace('!]*)/>!', '', + // cut first line xml declaration + implode("\n", + array_slice( + explode("\n", $content), + 1 + ))))))); + } public function __toString() { return $this->htmlWithTag(); } From 3193a679cfd861307086a9d4013f0738c75417c0 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 12 Aug 2007 18:58:16 +0000 Subject: [PATCH 008/149] TODO updates --- phpQuery.php | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/phpQuery.php b/phpQuery.php index 7cae535..545801e 100644 --- a/phpQuery.php +++ b/phpQuery.php @@ -9,6 +9,10 @@ * @version 0.7.1 beta * * @todo missing selectors (div + div, div ~ div) + * @todo plugin interface thru magic __call() + * @todo metadata plugin + * @todo phpQueryPie JS layer + * @todo HTTP sources * @todo fix phpQueryClass::isHTML() stuff (dom nodes, text nodes...) * @todo missing pseudo classes (:even, :odd +all form specific) * @todo missing jquery functions (css, wrap, val) @@ -1140,6 +1144,27 @@ protected function insert( $target, $type ) { return $this; } + /** + * Returns JSON representation of stacked HTML elements and it's children. + * Compatible with @link http://programming.arantius.com/dollar-e + * + * @return string + */ + public function toJSON() { + $json = ''; + foreach( $this->stack as $node ) { + + } + return $json; + } + protected function _toJSON($node) { + $json = ''; + switch( $node->type ) { + case 3: + break; + } + return $json; + } public function slice() { // TODO python slices From d8ed95e1f21222c456739d31fd831c0c60ad5bc1 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 19 Aug 2007 00:16:09 +0000 Subject: [PATCH 009/149] - introduced phpjQueryProxy - reorganized repository structure - added simple phpDocs for methods with @return directive to support editor's autocompetition - htmlWithTag() refactored to htmlWithElement() - fill() added - phpMeta() refactored to fillPhp() - TODOs moved to google's code issue tracker - fixed chainability in couple of methods (mostly class names related) --- phpQuery.php => phpQuery/phpQuery.php | 391 ++++++++++--- phpjQueryProxy/JSON.php | 806 ++++++++++++++++++++++++++ phpjQueryProxy/phpjQueryProxy.js | 79 +++ phpjQueryProxy/phpjQueryProxy.php | 44 ++ 4 files changed, 1241 insertions(+), 79 deletions(-) rename phpQuery.php => phpQuery/phpQuery.php (84%) create mode 100644 phpjQueryProxy/JSON.php create mode 100644 phpjQueryProxy/phpjQueryProxy.js create mode 100644 phpjQueryProxy/phpjQueryProxy.php diff --git a/phpQuery.php b/phpQuery/phpQuery.php similarity index 84% rename from phpQuery.php rename to phpQuery/phpQuery.php index 545801e..c3cfaf5 100644 --- a/phpQuery.php +++ b/phpQuery/phpQuery.php @@ -5,21 +5,9 @@ * * @author Tobiasz Cudnik * @link http://meta20.net/phpQuery + * @link http://code.google.com/p/phpquery/ * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @version 0.7.1 beta - * - * @todo missing selectors (div + div, div ~ div) - * @todo plugin interface thru magic __call() - * @todo metadata plugin - * @todo phpQueryPie JS layer - * @todo HTTP sources - * @todo fix phpQueryClass::isHTML() stuff (dom nodes, text nodes...) - * @todo missing pseudo classes (:even, :odd +all form specific) - * @todo missing jquery functions (css, wrap, val) - * @todo docs (copied from jquery) - * @todo more test cases - * @todo cache (mainly class and regex attrs) - * @todo check if there is any problem with charset + * @version 0.7.2 beta */ class phpQueryClass implements Iterator { @@ -51,9 +39,10 @@ class phpQueryClass implements Iterator { * * 1. Create new DOM: * _('file.htm') - * _('
', true) // accepts text nodes at beginning - * 2. Import HTML: - * _('
') // DOESNT accept text nodes at beginning ! + * _('
', true) // accepts text nodes at beginning of input string + * _('http://wikipedia.org', false) + * 2. Import HTML into existing DOM: + * _('
') // DOESNT accept text nodes at beginning of input string ! * _('
', docID) * 3. Run query: * _('div.myClass') @@ -69,9 +58,9 @@ public static function phpQuery() { * _('file.htm') * _('
', true) */ - if ( ($isHTMLfile = self::isHTMLfile($input[0])) || ( isset($input[1]) && $input[1] === true/* && self::isHTML($input[0])*/ )) { + if ( ($isHTMLfile = self::isHTMLfile($input[0])) || ( isset($input[1]) && gettype($input[1]) === 'boolean'/* && self::isHTML($input[0])*/ )) { // set document ID - $ID = $isHTMLfile + $ID = $input[1] !== true ? $input[0] : md5(microtime()); // check if already loaded @@ -81,7 +70,7 @@ public static function phpQuery() { self::$documents[ $ID ]['document'] = new DOMDocument(); $DOM =& self::$documents[ $ID ]; // load - $isLoaded = $isHTMLfile + $isLoaded = $input[1] !== true ? @$DOM['document']->loadHTMLFile($ID) : @$DOM['document']->loadHTML($input[0]); if (! $isLoaded ) { @@ -130,9 +119,19 @@ public static function phpQuery() { ); } } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function getDocID() { return $this->docID; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function unload() { unset( self::$documents[ $this->docID ] ); } @@ -142,6 +141,11 @@ public static function unloadDocuments( $path = null ) { else unset( self::$documents ); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function __construct($docPath) { if (! isset(self::$documents[ $docPath ] ) ) { throw new Exception("Doc path '{$docPath}' isn't loaded."); @@ -165,6 +169,11 @@ protected function debug($in) { // print_r(array_slice(debug_backtrace(), 3)); print("\n"); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function findRoot() { $this->stack = array( $this->DOM->documentElement ); return $this; @@ -387,6 +396,11 @@ protected function runQuery( $XQuery, $selector = null, $compare = null ) { } $this->stack = $stack; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function find( $selectors, $context = null ) { // backup last stack /for end()/ $this->history[] = $this->stack; @@ -566,6 +580,11 @@ protected function filterPseudoClasses( $class ) { break; } } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function is( $selector, $_node = null ) { $this->debug(array("Is:", $selector)); if (! $selector) @@ -579,6 +598,11 @@ public function is( $selector, $_node = null ) { return $match; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function filter( $selectors, $_skipHistory = false ) { if (! $_skipHistory ) $this->history[] = $this->stack; @@ -682,6 +706,11 @@ protected function isRoot( $node ) { return is_a($node, 'DOMDocument') || $node->tagName == 'html'; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function css() { // TODO } @@ -694,10 +723,11 @@ protected function importHTML($html) { foreach($DOM->documentElement->firstChild->childNodes as $node) $this->stack[] = $this->DOM->importNode( $node, true ); } - /** * Wraps elements with $before and $after. - * In case ! $after, $before should be a tag name. + * In case when there's no $after, $before should be a tag name (without <>). + * + * @return phpQueryClass */ public function wrap($before, $after = null) { if (! $after ) { @@ -715,6 +745,11 @@ public function wrap($before, $after = null) { return $this; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function contains( $text, $history = true ) { $this->history[] = $this->stack; $stack = array(); @@ -727,18 +762,33 @@ public function contains( $text, $history = true ) { return $this; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function gt($num) { $this->history[] = $this->stack; $this->stack = array_slice( $this->stack, $num+1 ); return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function lt($num) { $this->history[] = $this->stack; $this->stack = array_slice( $this->stack, 0, $num+1 ); return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function eq($num) { $oldStack = $this->stack; $this->history[] = $this->stack; @@ -748,24 +798,37 @@ public function eq($num) { return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function size() { return $this->length(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function length() { return count( $this->stack ); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function end() { $this->stack = array_pop( $this->history ); return $this; } - /** - * Test + * Enter description here... * - * @param unknown_type $selector - * @return unknown + * @return phpQueryClass */ public function select($selector) { return $this->is($selector) @@ -773,6 +836,11 @@ public function select($selector) { : $this->find($selector); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function each($callabck) { $this->history[] = $this->stack; foreach( $this->history[ count( $this->history )-1 ] as $node ) { @@ -789,6 +857,11 @@ public function each($callabck) { return $this; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function _clone() { $newStack = array(); //pr(array('copy... ', $this->whois())); @@ -823,10 +896,20 @@ public function replace($content) { $this->stack = $stack; return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function replacePHP($code) { - return $thuis->replace("{$code}"); + return $this->replace("{$code}"); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function remove() { foreach( $this->stack as $node ) { if (! $node->parentNode ) @@ -877,7 +960,12 @@ public function html($html = null) { return $this->save($DOM); } } - public function htmlWithTag() { + /** + * Enter description here... + * + * @todo change to htmlWithElement + */ + public function htmlWithElement() { if ( $this->length() == 1 && $this->isRoot( $this->stack[0] ) ) return $this->save(); $DOM = new DOMDocument(); @@ -918,21 +1006,46 @@ protected function saveXHTML($content){ ))))))); } public function __toString() { - return $this->htmlWithTag(); + return $this->htmlWithElement(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function php($code) { return $this->html("".trim($code).""); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function phpPrint($var) { return $this->php("print {$var};"); } /** - * Meta PHP insert - finds element(s), inserts code and rolls back stack. + * Fills elements selected by $selector with $content using html(), and roll back selection. + * + * @param string Selector + * @param string Content + * + * @return phpQueryClass + */ + public function fill($selector, $content) { + $this->find($selector) + ->html($content); + return $this; + } + /** + * Fills elements selected by $selector with $code using php(), and roll back selection. * * @param string Selector * @param string Valid PHP Code + * + * @return phpQueryClass */ - public function phpMeta($selector, $code) { + public function fillPhp($selector, $code) { $this->find($selector) ->php($code); return $this; @@ -946,6 +1059,11 @@ protected function dumpHistory($when) { } //pr(array("{$when}/history", $history)); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function children( $selector = null ) { $tack = array(); foreach( $this->stack as $node ) { @@ -959,6 +1077,11 @@ public function children( $selector = null ) { $this->stack = $stack; return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function unwrapContent() { foreach( $this->stack as $node ) { if (! $node->parentNode ) @@ -975,46 +1098,111 @@ public function unwrapContent() { return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function ancestors( $selector = null ) { return $this->children( $selector ); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function append( $content ) { return $this->insert($content, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function appendPHP( $content ) { return $this->insert("{$content}", 'append'); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function appendTo( $seletor ) { return $this->insert($seletor, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function prepend( $content ) { return $this->insert($content, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function prependPHP( $content ) { return $this->insert("{$content}", 'prepend'); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function prependTo( $seletor ) { return $this->insert($seletor, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function before( $content ) { return $this->insert($content, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function beforePHP( $content ) { return $this->insert("{$content}", 'before'); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function insertBefore( $seletor ) { return $this->insert($seletor, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function after( $content ) { return $this->insert($content, __FUNCTION__); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function afterPHP( $content ) { return $this->insert("{$content}", 'after'); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function insertAfter( $seletor ) { return $this->insert($seletor, __FUNCTION__); } @@ -1149,6 +1337,8 @@ protected function insert( $target, $type ) { * Compatible with @link http://programming.arantius.com/dollar-e * * @return string + * + * @todo */ public function toJSON() { $json = ''; @@ -1166,10 +1356,20 @@ protected function _toJSON($node) { return $json; } + /** + * Python slices. + * + * @return phpQueryClass + * @todo + */ public function slice() { - // TODO python slices } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function reverse() { $this->history[] = $this->stack; $this->stack = array_reverse($this->stack); @@ -1183,11 +1383,21 @@ public function text() { return $return; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function _next( $selector = null ) { $this->sibling( $selector, 'previousSibling' ); return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function _prev( $selector = null ) { $this->sibling( $selector, 'previousSibling' ); return $this->newInstance(); @@ -1237,6 +1447,11 @@ protected function sibling( $selector, $direction ) { $this->stack = $stack; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function siblings( $selector = null ) { $stack = array(); foreach( $this->stack as $node ) { @@ -1251,6 +1466,11 @@ public function siblings( $selector = null ) { return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function not( $selector = null ) { $stack = array(); foreach( $this->stack as $node ) { @@ -1262,6 +1482,11 @@ public function not( $selector = null ) { return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function add( $selector = null ) { $stack = array(); $this->history[] = $this->stack; @@ -1288,6 +1513,11 @@ protected function stackContains($nodeToCheck, $stackToCheck = null) { return false; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function parent( $selector = null ) { $stack = array(); foreach( $this->stack as $node ) @@ -1300,6 +1530,11 @@ public function parent( $selector = null ) { return $this->newInstance(); } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function parents( $selector = null ) { $stack = array(); if (! $this->stack ) @@ -1365,6 +1600,11 @@ protected function getNodeAttrs($node) { } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function attrPHP( $attr, $value ) { foreach( $this->stack as $node ) { $node->setAttribute($attr, ""); @@ -1372,6 +1612,11 @@ public function attrPHP( $attr, $value ) { return $this; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function removeAttr( $attr ) { foreach( $this->stack as $node ) { $loop = $attr == '*' @@ -1380,7 +1625,7 @@ public function removeAttr( $attr ) { foreach( $loop as $a ) $node->removeAttribute($a); } - + return $this; } /** @@ -1391,6 +1636,11 @@ public function removeAttr( $attr ) { public function val() { } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function addClass( $className ) { foreach( $this->stack as $node ) { if (! $this->is( $node, '.'.$className)) @@ -1399,40 +1649,47 @@ public function addClass( $className ) { $node->getAttribute('class').' '.$className ); } + return $this; } /** - * Returns if className (optionally match with regex in // delimiters) is set for element. + * Enter description here... * * @param string $className - * Optional. Can be regexp in // delimiters. - * @return string - * Matched class. - * @todo hasClass + * @return bool */ public function hasClass( $className ) { foreach( $this->stack as $node ) { - if (! $this->is( $node, '.'.$className)) - $node->setAttribute( - 'class', - $node->getAttribute('class').' '.$className - ); + if ( $this->is( $node, '.'.$className)) + return true; } + return false; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function removeClass( $className ) { foreach( $this->stack as $node ) { $classes = explode( ' ', $node->getAttribute('class')); if ( in_array($className, $classes) ) { $classes = array_diff($classes, array($className)); if ( $classes ) - $node->setAttribute('class', $classes); + $node->setAttribute('class', implode(' ', $classes)); else $node->removeAttribute('class'); } } + return $this; } + /** + * Enter description here... + * + * @return phpQueryClass + */ public function toggleClass( $className ) { foreach( $this->stack as $node ) { if ( $this->is( $node, '.'.$className )) @@ -1440,6 +1697,7 @@ public function toggleClass( $className ) { else $this->addClass($className); } + return $this; } /** @@ -1454,7 +1712,7 @@ public function toggleClass( $className ) { * Result: * [

] * - * @return + * @return phpQueryClass */ public function _empty() { foreach( $this->stack as $node ) { @@ -1578,10 +1836,9 @@ public function dumpSource( $node = null ) { } /** - * Shortcut to new phpQuery($arg1, $arg2, ...) + * Shortcut to new phpQueryClass($arg1, $arg2, ...) * - * @return phpQuery - * @todo move logic to contructor + * @return phpQueryClass */ function phpQuery() { $args = func_get_args(); @@ -1589,40 +1846,16 @@ function phpQuery() { array('phpQueryClass', 'phpQuery'), $args ); - // old code - if (! func_num_args() ) - return new phpQueryClass(); - $input = func_get_args(); - // load template file - if ( phpQueryClass::isHTMLfile( $input[0] ) ) { - $loaded = phpQueryClass::load( $input[0] ); - return new phpQueryClass(); - } else if ( is_object($input[0]) && get_class($input[0]) == 'DOMElement' ) { - } else { - $last = count($input)-1; - $PQ = new phpQueryClass( - // document path - isset( $input[$last] ) && phpQueryClass::isHTMLfile( $input[$last] ) - ? $input[$last] - : null - ); - if ( $input[0][0] == '<' ) - // load HTML - return $PQ->importHTML( $input[0] ); - else // do query - return $PQ->find( - $input[0], - isset( $input[1] ) - && is_object( $input[1] ) - && get_class( $input[1] ) == 'phpQueryClass' - ? $input[1] - : null - ); - } } -// handy phpQuery shortcut if (! function_exists('_')) { + /** + * Handy phpQuery shortcut. + * Optional, because conflicts with gettext extension. + * @link http://php.net/_ + * + * @return phpQueryClass + */ function _() { $args = func_get_args(); return call_user_func_array('phpQuery', $args); diff --git a/phpjQueryProxy/JSON.php b/phpjQueryProxy/JSON.php new file mode 100644 index 0000000..0cddbdd --- /dev/null +++ b/phpjQueryProxy/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/phpjQueryProxy/phpjQueryProxy.js b/phpjQueryProxy/phpjQueryProxy.js new file mode 100644 index 0000000..bda4012 --- /dev/null +++ b/phpjQueryProxy/phpjQueryProxy.js @@ -0,0 +1,79 @@ +jQuery.extend({ + phpQueryCfg: { + // URL to phpjQueryProxy.php + url: 'http://localhost/projekty/phpQuery/phpjQueryProxy/phpjQueryProxy.php', + // asynchronous communication + async: false + }, + phpQuery: function(url){ + // this is cache object + var objectCache = {}; + // dump all jQuery methods, but only once + // $.each doesnt work ? + for( var i in jQuery.fn) { + // closure to preserve loop iterator in scope + (function(){ + var name = i; + // create dummy method + objectCache[name] = function(){ + // create method data object + var data = { + method: name, + arguments: [] + }; + // collect arguments + $.each(arguments, function(k, v){ + data.arguments.push(v); + }); + // push data into stack + this.stack.push(data); + // preserve chain + return this; + } + })(); + } + /** + * Fetches results from phpQuery. + * + * @param {Function} callback Optional. Turns on async request. + * First parameter for callback is usually an JSON array of mathed elements. Use $(result) to append it to DOM. + * It can also be a boolean value or string, depending on last method called. + */ + objectCache.get = function(callback){ +// console.log(this.stack.toSource()); + if ( typeof callback == 'function' ) + $.post( + jQuery.phpQueryCfg.url, + {data: this.stack.toSource()}, + callback + ); + else { + var result; + $.ajax({ + type: 'POST', + data: {data: this.stack.toSource()}, + async: false, + url: jQuery.phpQueryCfg.url, + success: function(response){ + result = response; + } + }) + return result; + } + } + // replace method with new one, which only uses cache + jQuery.phpQuery = function(url){ + // clone cache object + var myCache = jQuery.extend({}, objectCache); + myCache.stack = [url]; + return myCache; + } + return jQuery.phpQuery(url); + } +}); +$.phpQuery('http://meta20.net') + .find('h3') + .eq(3) + .get(function(result){ + console.log( $(result) ); + }); \ No newline at end of file diff --git a/phpjQueryProxy/phpjQueryProxy.php b/phpjQueryProxy/phpjQueryProxy.php new file mode 100644 index 0000000..4c9fc93 --- /dev/null +++ b/phpjQueryProxy/phpjQueryProxy.php @@ -0,0 +1,44 @@ +decode($data); + $count = count($data); + foreach($data as $k => $r) { + // load document (required for firs t $data element) + if (! $k || is_string($r) ) { + if ( is_string($r) ) + $_ = phpQuery($r, false); + if (! is_a($_, 'phpQueryClass')) { + throw new Exception("URL needed to download content"); + break; + } + // check if method exists + } else if (! method_exists(get_class($_), $r['method'])) { + throw new Exception("Method {$r['method']} not implemented in phpQuery"); + // execute method + } else { + $_ = call_user_func_array( + array($_, $r['method']), + $r['arguments'] + ); + } + } + // output results + if ( is_a($_, 'phpQueryClass') ) { + $results = array(); + foreach($_ as $__) { + $results[] = (string)$__; + } + print $json->encode($results); + } else { + print $_; + } + } +} +?> \ No newline at end of file From ad41796e5d35901cf03f1826c185f683c5915b60 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Thu, 24 Jan 2008 23:17:35 +0000 Subject: [PATCH 010/149] --- jQueryPhpProxy/JSON.php | 806 ++++++++++++++++++++++++++++++ jQueryPhpProxy/phpjQueryProxy.js | 79 +++ jQueryPhpProxy/phpjQueryProxy.php | 44 ++ 3 files changed, 929 insertions(+) create mode 100644 jQueryPhpProxy/JSON.php create mode 100644 jQueryPhpProxy/phpjQueryProxy.js create mode 100644 jQueryPhpProxy/phpjQueryProxy.php diff --git a/jQueryPhpProxy/JSON.php b/jQueryPhpProxy/JSON.php new file mode 100644 index 0000000..0cddbdd --- /dev/null +++ b/jQueryPhpProxy/JSON.php @@ -0,0 +1,806 @@ + + * @author Matt Knapp + * @author Brett Stimmerman + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, 'encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} + +?> diff --git a/jQueryPhpProxy/phpjQueryProxy.js b/jQueryPhpProxy/phpjQueryProxy.js new file mode 100644 index 0000000..b364069 --- /dev/null +++ b/jQueryPhpProxy/phpjQueryProxy.js @@ -0,0 +1,79 @@ +jQuery.extend({ + phpQueryCfg: { + // URL to phpjQueryProxy.php + url: 'http://localhost/projekty/phpQuery/phpjQueryProxy/phpjQueryProxy.php', + // asynchronous communication + async: false + }, + phpQuery: function(url){ + // this is cache object + var objectCache = {}; + // dump all jQuery methods, but only once + // $.each doesn't work ? + for( var i in jQuery.fn) { + // closure to preserve loop iterator in scope + (function(){ + var name = i; + // create dummy method + objectCache[name] = function(){ + // create method data object + var data = { + method: name, + arguments: [] + }; + // collect arguments + $.each(arguments, function(k, v){ + data.arguments.push(v); + }); + // push data into stack + this.stack.push(data); + // preserve chain + return this; + } + })(); + } + /** + * Fetches results from phpQuery. + * + * @param {Function} callback Optional. Turns on async request. + * First parameter for callback is usually an JSON array of mathed elements. Use $(result) to append it to DOM. + * It can also be a boolean value or string, depending on last method called. + */ + objectCache.get = function(callback){ +// console.log(this.stack.toSource()); + if ( typeof callback == 'function' ) + $.post( + jQuery.phpQueryCfg.url, + {data: this.stack.toSource()}, + callback + ); + else { + var result; + $.ajax({ + type: 'POST', + data: {data: this.stack.toSource()}, + async: false, + url: jQuery.phpQueryCfg.url, + success: function(response){ + result = response; + } + }) + return result; + } + } + // replace method with new one, which only uses cache + jQuery.phpQuery = function(url){ + // clone cache object + var myCache = jQuery.extend({}, objectCache); + myCache.stack = [url]; + return myCache; + } + return jQuery.phpQuery(url); + } +}); +$.phpQuery('http://meta20.net') + .find('h3') + .eq(3) + .get(function(result){ + console.log( $(result) ); + }); \ No newline at end of file diff --git a/jQueryPhpProxy/phpjQueryProxy.php b/jQueryPhpProxy/phpjQueryProxy.php new file mode 100644 index 0000000..4c9fc93 --- /dev/null +++ b/jQueryPhpProxy/phpjQueryProxy.php @@ -0,0 +1,44 @@ +decode($data); + $count = count($data); + foreach($data as $k => $r) { + // load document (required for firs t $data element) + if (! $k || is_string($r) ) { + if ( is_string($r) ) + $_ = phpQuery($r, false); + if (! is_a($_, 'phpQueryClass')) { + throw new Exception("URL needed to download content"); + break; + } + // check if method exists + } else if (! method_exists(get_class($_), $r['method'])) { + throw new Exception("Method {$r['method']} not implemented in phpQuery"); + // execute method + } else { + $_ = call_user_func_array( + array($_, $r['method']), + $r['arguments'] + ); + } + } + // output results + if ( is_a($_, 'phpQueryClass') ) { + $results = array(); + foreach($_ as $__) { + $results[] = (string)$__; + } + print $json->encode($results); + } else { + print $_; + } + } +} +?> \ No newline at end of file From c0c854ab34c6c20b5328620ca22b3eb8a1019ff7 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Wed, 25 Jun 2008 22:32:30 +0000 Subject: [PATCH 011/149] --- jQueryPhpProxy/jQueryPhpProxy.js | 90 +++++++++++++++++++++++++++++++ jQueryPhpProxy/jQueryPhpProxy.php | 44 +++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 jQueryPhpProxy/jQueryPhpProxy.js create mode 100644 jQueryPhpProxy/jQueryPhpProxy.php diff --git a/jQueryPhpProxy/jQueryPhpProxy.js b/jQueryPhpProxy/jQueryPhpProxy.js new file mode 100644 index 0000000..e70c81b --- /dev/null +++ b/jQueryPhpProxy/jQueryPhpProxy.js @@ -0,0 +1,90 @@ +var phpQueryConfig = { + // URL to phpjQueryProxy.php + url: 'http://localhost/projekty/phpQuery/phpjQueryProxy/phpjQueryProxy.php', + // asynchronous communication + async: false +}; + +jQuery.extend({ + phpQueryCfg: phpQueryConfig || { + url: null, + // asynchronous communication + async: false + }, + get():function(){}, + post():function(){}, + ajax():function(){}, + phpQuery: function(url){ + // this is cache object + var objectCache = {}; + // dump all jQuery methods, but only once + // $.each doesn't work ? + for( var i in jQuery.fn) { + // closure to preserve loop iterator in scope + (function(){ + var name = i; + // create dummy method + objectCache[name] = function(){ + // create method data object + var data = { + method: name, + arguments: [] + }; + // collect arguments + $.each(arguments, function(k, v){ + data.arguments.push(v); + }); + // push data into stack + this.stack.push(data); + // preserve chain + return this; + } + })(); + } + /** + * Fetches results from phpQuery. + * + * @param {Function} callback Optional. Turns on async request. + * First parameter for callback is usually an JSON array of mathed elements. Use $(result) to append it to DOM. + * It can also be a boolean value or string, depending on last method called. + */ + objectCache.download = function(callback){ +// console.log(this.stack.toSource()); + callback = + callback || + function(){ + return $result; + }; + $.ajax({ + type: 'POST', + data: {data: this.stack.toSource()}, + async: false, + // jQuery.phpQuery.config ??? + url: jQuery.phpQueryCfg.url, + success: function(response){ + var $result = jQuery(); + $.each(result, function(v) { + $result.add(v); + }) + callback.call($result); + } + }) + } + // replace orginal method with generated method using cache (lazy-load) + jQuery.phpQuery = function(url){ + // clone cache object + var myCache = jQuery.extend({}, objectCache); + myCache.stack = [url]; + return myCache; + } + // returen result from new method (only done for first call) + return jQuery.phpQuery(url); + } +}); +jQuery.phpQuery.config = phpQueryConfig; +$.phpQuery('http://meta20.net') + .find('h3') + .eq(3) + .download(function($result){ + console.log( $(result) ); + }); \ No newline at end of file diff --git a/jQueryPhpProxy/jQueryPhpProxy.php b/jQueryPhpProxy/jQueryPhpProxy.php new file mode 100644 index 0000000..9399c1a --- /dev/null +++ b/jQueryPhpProxy/jQueryPhpProxy.php @@ -0,0 +1,44 @@ +decode($data); + $count = count($data); + foreach($data as $k => $r) { + // load document (required for firs t $data element) + if (! $k || is_string($r) ) { + if ( is_string($r) ) + $_ = phpQuery($r, false); + if (! is_a($_, 'phpQueryClass')) { + throw new Exception("URL needed to download content"); + break; + } + // check if method exists + } else if (! method_exists(get_class($_), $r['method'])) { + throw new Exception("Method '{$r['method']}' not implemented in phpQuery"); + // execute method + } else { + $_ = call_user_func_array( + array($_, $r['method']), + $r['arguments'] + ); + } + } + // output results + if ( $_ instanceof self ) { + $results = array(); + foreach($_ as $__) { + $results[] = (string)$__; + } + print $json->encode($results); + } else { + print $_; + } + } +} +?> \ No newline at end of file From edf2352629af68b23acf6c53b17913447b476c50 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Thu, 26 Jun 2008 08:23:02 +0000 Subject: [PATCH 012/149] jQueryPhpProxy --- jQueryPhpProxy/demo/demo.htm | 276 +++ jQueryPhpProxy/demo/jquery.js | 2991 +++++++++++++++++++++++++++++++++ 2 files changed, 3267 insertions(+) create mode 100644 jQueryPhpProxy/demo/demo.htm create mode 100755 jQueryPhpProxy/demo/jquery.js diff --git a/jQueryPhpProxy/demo/demo.htm b/jQueryPhpProxy/demo/demo.htm new file mode 100644 index 0000000..a387e2d --- /dev/null +++ b/jQueryPhpProxy/demo/demo.htm @@ -0,0 +1,276 @@ + + + + + + + Blueprint CSS Framework Test Suite + + + + + + + + + + +
+

Tests for common HTML elements

+
+ + +
PARAGRAPHS & BOXES
+ +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ + +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+ +

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+
+ +
+ +
+

Aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

+
+ +
+

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+
+ +
+
+ + +
LISTS
+ +
+
    + +
  • Unordered list test
  • +
  • Another list element. Lorem ipsum dolor sit amet, consectetur adipisicing elit.
  • +
  • Yet another element in the list
  • +
  • Some long text. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Lorem ipsum dolor sit amet, consectetur adipisicing elit.
  • +
+
    +
  1. Ordered list test
  2. + +
  3. Another list element
  4. +
  5. Yet another element in the list
  6. +
+
+ + +
+
    +
  1. Ordered list
  2. + +
      +
    • Nested Unordered list
    • +
        +
      1. Nested Ordered list
      2. +
      +
    +
  3. Ordered List item
  4. + +
      +
    1. Nested Ordered list
    2. +
        +
      • Nested Unordered list
      • +
      +
    +
+
+ + +
+
+
definition list dt
+
definition list dd
+
definition list dt
+
definition list dd
+
definition list dt
+ +
definition list dd
+
+
+
+ +
HEADINGS
+ +
+

H1: Lorem ipsum dolor sit amet

+

H2: Lorem ipsum dolor sit amet, consectetur elit

+ +

H3: Lorem ipsum dolor sit amet, consectetur adipisicing elit

+

H4: Lorem ipsum dolor sit amet, consectetur adipisicing elit adipis

+
H5: Lorem ipsum dolor sit amet, consectetur adipisicing elit adipisicing elit adipisicing elit
+
H6: Lorem ipsum dolor sit amet, consectetur adipisicing elit adipisicing elit adipisicing elit
+
+ +
+

Heading 1


+ +

Heading 2


+

Heading 3


+

Heading 4


+
Heading 5

+
Heading 6
+
+ + +
+

Heading 1

+

Heading 2

+

Heading 3

+

Heading 4

+
Heading 5
+ +
Heading 6
+
+
+ + +
MISC ELEMENTS
+ +
+

+ <strong>
+ <del> deleted
+ + <dfn> dfn
+ <em> emphasis
+

+

+ <a> anchor
+ + <a> a + href
+

+

+ <abbr> abbr - extended text when mouseover.
+ <acronym> acronym - extended text when mouseover.
+ +

+

+

+ <address>
+ Donald Duck
+ Box 555
+ Disneyland +
+ +

+
+ + +
+ + + + + + + + + + + + + + + +
A standard test table with a caption, tr, td elements
Table Header OneTable Header Two
TD OneTD Two
TD colspan 2
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A test table with a thead, tfoot, and tbody elements
Table Header OneTable Header Two
tfoot footer
TD OneTD Two
TD OneTD Two
TD OneTD Two
TD OneTD Two
+
+ + +
+ +
<pre>
+pre  space1
+pre  space1
+pre    space2
+pre    space2
+pre	tab
+pre	tab
+ +<code> +Not indented + indent1 + indent1 + indent2 + indent3 + + <tt> + This tt text should be monospaced + and + wrap as if + one line of text + even though the code has newlines, spaces, and tabs. + It should be the same size as <p> text. + + +
+
+ + +

+

+ +
+ + + diff --git a/jQueryPhpProxy/demo/jquery.js b/jQueryPhpProxy/demo/jquery.js new file mode 100755 index 0000000..c7ced10 --- /dev/null +++ b/jQueryPhpProxy/demo/jquery.js @@ -0,0 +1,2991 @@ +(function(){ +/* + * jQuery 1.2.1 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-09-16 23:42:06 -0400 (Sun, 16 Sep 2007) $ + * $Rev: 3353 $ + */ + +// Map over jQuery in case of overwrite +if ( typeof jQuery != "undefined" ) + var _jQuery = jQuery; + +var jQuery = window.jQuery = function(selector, context) { + // If the context is a namespace object, return a new object + return this instanceof jQuery ? + this.init(selector, context) : + new jQuery(selector, context); +}; + +// Map over the $ in case of overwrite +if ( typeof $ != "undefined" ) + var _$ = $; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +jQuery.fn = jQuery.prototype = { + init: function(selector, context) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle HTML strings + if ( typeof selector == "string" ) { + var m = quickExpr.exec(selector); + if ( m && (m[1] || !context) ) { + // HANDLE: $(html) -> $(array) + if ( m[1] ) + selector = jQuery.clean( [ m[1] ], context ); + + // HANDLE: $("#id") + else { + var tmp = document.getElementById( m[3] ); + if ( tmp ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( tmp.id != m[3] ) + return jQuery().find( selector ); + else { + this[0] = tmp; + this.length = 1; + return this; + } + else + selector = []; + } + + // HANDLE: $(expr) + } else + return new jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction(selector) ) + return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray( + // HANDLE: $(array) + selector.constructor == Array && selector || + + // HANDLE: $(arraylike) + // Watch for when an array-like object is passed as the selector + (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || + + // HANDLE: $(*) + [ selector ] ); + }, + + jquery: "1.2.1", + + size: function() { + return this.length; + }, + + length: 0, + + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[num]; + }, + + pushStack: function( a ) { + var ret = jQuery(a); + ret.prevObject = this; + return ret; + }, + + setArray: function( a ) { + this.length = 0; + Array.prototype.push.apply( this, a ); + return this; + }, + + each: function( fn, args ) { + return jQuery.each( this, fn, args ); + }, + + index: function( obj ) { + var pos = -1; + this.each(function(i){ + if ( this == obj ) pos = i; + }); + return pos; + }, + + attr: function( key, value, type ) { + var obj = key; + + // Look for the case where we're accessing a style value + if ( key.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined; + else { + obj = {}; + obj[ key ] = value; + } + + // Check to see if we're setting style values + return this.each(function(index){ + // Set all the styles + for ( var prop in obj ) + jQuery.attr( + type ? this.style : this, + prop, jQuery.prop(this, obj[prop], type, index, prop) + ); + }); + }, + + css: function( key, value ) { + return this.attr( key, value, "curCSS" ); + }, + + text: function(e) { + if ( typeof e != "object" && e != null ) + return this.empty().append( document.createTextNode( e ) ); + + var t = ""; + jQuery.each( e || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + t += this.nodeType != 1 ? + this.nodeValue : jQuery.fn.text([ this ]); + }); + }); + return t; + }, + + wrapAll: function(html) { + if ( this[0] ) + // The elements to wrap the target around + jQuery(html, this[0].ownerDocument) + .clone() + .insertBefore(this[0]) + .map(function(){ + var elem = this; + while ( elem.firstChild ) + elem = elem.firstChild; + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function(html) { + return this.each(function(){ + jQuery(this).contents().wrapAll(html); + }); + }, + + wrap: function(html) { + return this.each(function(){ + jQuery(this).wrapAll(html); + }); + }, + + append: function() { + return this.domManip(arguments, true, 1, function(a){ + this.appendChild( a ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, -1, function(a){ + this.insertBefore( a, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, 1, function(a){ + this.parentNode.insertBefore( a, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, -1, function(a){ + this.parentNode.insertBefore( a, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery([]); + }, + + find: function(t) { + var data = jQuery.map(this, function(a){ return jQuery.find(t,a); }); + return this.pushStack( /[^+>] [^+>]/.test( t ) || t.indexOf("..") > -1 ? + jQuery.unique( data ) : data ); + }, + + clone: function(events) { + // Do the clone + var ret = this.map(function(){ + return this.outerHTML ? jQuery(this.outerHTML)[0] : this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if (events === true) + this.find("*").andSelf().each(function(i) { + var events = jQuery.data(this, "events"); + for ( var type in events ) + for ( var handler in events[type] ) + jQuery.event.add(clone[i], type, events[type][handler], events[type][handler].data); + }); + + // Return the cloned set + return ret; + }, + + filter: function(t) { + return this.pushStack( + jQuery.isFunction( t ) && + jQuery.grep(this, function(el, index){ + return t.apply(el, [index]); + }) || + + jQuery.multiFilter(t,this) ); + }, + + not: function(t) { + return this.pushStack( + t.constructor == String && + jQuery.multiFilter(t, this, true) || + + jQuery.grep(this, function(a) { + return ( t.constructor == Array || t.jquery ) + ? jQuery.inArray( a, t ) < 0 + : a != t; + }) + ); + }, + + add: function(t) { + return this.pushStack( jQuery.merge( + this.get(), + t.constructor == String ? + jQuery(t).get() : + t.length != undefined && (!t.nodeName || jQuery.nodeName(t, "form")) ? + t : [t] ) + ); + }, + + is: function(expr) { + return expr ? jQuery.multiFilter(expr,this).length > 0 : false; + }, + + hasClass: function(expr) { + return this.is("." + expr); + }, + + val: function( val ) { + if ( val == undefined ) { + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName(elem, "select") ) { + var index = elem.selectedIndex, + a = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[i]; + if ( option.selected ) { + // Get the specifc value for the option + var val = jQuery.browser.msie && !option.attributes["value"].specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return val; + + // Multi-Selects return an array + a.push(val); + } + } + + return a; + + // Everything else, we just grab the value + } else + return this[0].value.replace(/\r/g, ""); + } + } else + return this.each(function(){ + if ( val.constructor == Array && /radio|checkbox/.test(this.type) ) + this.checked = (jQuery.inArray(this.value, val) >= 0 || + jQuery.inArray(this.name, val) >= 0); + else if ( jQuery.nodeName(this, "select") ) { + var tmp = val.constructor == Array ? val : [val]; + + jQuery("option", this).each(function(){ + this.selected = (jQuery.inArray(this.value, tmp) >= 0 || + jQuery.inArray(this.text, tmp) >= 0); + }); + + if ( !tmp.length ) + this.selectedIndex = -1; + } else + this.value = val; + }); + }, + + html: function( val ) { + return val == undefined ? + ( this.length ? this[0].innerHTML : null ) : + this.empty().append( val ); + }, + + replaceWith: function( val ) { + return this.after( val ).remove(); + }, + + eq: function(i){ + return this.slice(i, i+1); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function(fn) { + return this.pushStack(jQuery.map( this, function(elem,i){ + return fn.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function(args, table, dir, fn) { + var clone = this.length > 1, a; + + return this.each(function(){ + if ( !a ) { + a = jQuery.clean(args, this.ownerDocument); + if ( dir < 0 ) + a.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName(this, "table") && jQuery.nodeName(a[0], "tr") ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild(document.createElement("tbody")); + + jQuery.each( a, function(){ + var elem = clone ? this.cloneNode(true) : this; + if ( !evalScript(0, elem) ) + fn.call( obj, elem ); + }); + }); + } +}; + +function evalScript(i, elem){ + var script = jQuery.nodeName(elem, "script"); + + if ( script ) { + if ( elem.src ) + jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild(elem); + + } else if ( elem.nodeType == 1 ) + jQuery("script", elem).each(evalScript); + + return script; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + } + + // extend jQuery itself if only one argument is passed + if ( al == 1 ) { + target = this; + a = 0; + } + + var prop; + + for ( ; a < al; a++ ) + // Only deal with non-null/undefined values + if ( (prop = arguments[a]) != null ) + // Extend the base object + for ( var i in prop ) { + // Prevent never-ending loop + if ( target == prop[i] ) + continue; + + // Recurse if we're merging object values + if ( deep && typeof prop[i] == 'object' && target[i] ) + jQuery.extend( target[i], prop[i] ); + + // Don't bring in undefined values + else if ( prop[i] != undefined ) + target[i] = prop[i]; + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + (new Date()).getTime(), uuid = 0, win = {}; + +jQuery.extend({ + noConflict: function(deep) { + window.$ = _$; + if ( deep ) + window.jQuery = _jQuery; + return jQuery; + }, + + // This may seem like some crazy code, but trust me when I say that this + // is the only cross-browser way to do this. --John + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a XML document + isXMLDoc: function(elem) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + // Evaluates Async. in Safari 2 :-( + globalEval: function( data ) { + data = jQuery.trim( data ); + if ( data ) { + if ( window.execScript ) + window.execScript( data ); + else if ( jQuery.browser.safari ) + // safari doesn't provide a synchronous global eval + window.setTimeout( data, 0 ); + else + eval.call( window, data ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? win : elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data != undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? jQuery.cache[ id ][ name ] : id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? win : elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + for ( name in jQuery.cache[ id ] ) break; + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( obj, fn, args ) { + if ( args ) { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.apply( obj[i], args ); + else + for ( var i = 0, ol = obj.length; i < ol; i++ ) + if ( fn.apply( obj[i], args ) === false ) break; + + // A special, fast, case for the most common use of each + } else { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.call( obj[i], i, obj[i] ); + else + for ( var i = 0, ol = obj.length, val = obj[0]; + i < ol && fn.call(val,i,val) !== false; val = obj[++i] ){} + } + + return obj; + }, + + prop: function(elem, value, type, index, prop){ + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, [index] ); + + // exclude the following css properties to add px + var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test(prop) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, c ){ + jQuery.each( (c || "").split(/\s+/), function(i, cur){ + if ( !jQuery.className.has( elem.className, cur ) ) + elem.className += ( elem.className ? " " : "" ) + cur; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, c ){ + elem.className = c != undefined ? + jQuery.grep( elem.className.split(/\s+/), function(cur){ + return !jQuery.className.has( c, cur ); + }).join(" ") : ""; + }, + + // internal only, use is(".class") + has: function( t, c ) { + return jQuery.inArray( c, (t.className || t).toString().split(/\s+/) ) > -1; + } + }, + + swap: function(e,o,f) { + for ( var i in o ) { + e.style["old"+i] = e.style[i]; + e.style[i] = o[i]; + } + f.apply( e, [] ); + for ( var i in o ) + e.style[i] = e.style["old"+i]; + }, + + css: function(e,p) { + if ( p == "height" || p == "width" ) { + var old = {}, oHeight, oWidth, d = ["Top","Bottom","Right","Left"]; + + jQuery.each( d, function(){ + old["padding" + this] = 0; + old["border" + this + "Width"] = 0; + }); + + jQuery.swap( e, old, function() { + if ( jQuery(e).is(':visible') ) { + oHeight = e.offsetHeight; + oWidth = e.offsetWidth; + } else { + e = jQuery(e.cloneNode(true)) + .find(":radio").removeAttr("checked").end() + .css({ + visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0" + }).appendTo(e.parentNode)[0]; + + var parPos = jQuery.css(e.parentNode,"position") || "static"; + if ( parPos == "static" ) + e.parentNode.style.position = "relative"; + + oHeight = e.clientHeight; + oWidth = e.clientWidth; + + if ( parPos == "static" ) + e.parentNode.style.position = "static"; + + e.parentNode.removeChild(e); + } + }); + + return p == "height" ? oHeight : oWidth; + } + + return jQuery.curCSS( e, p ); + }, + + curCSS: function(elem, prop, force) { + var ret, stack = [], swap = []; + + // A helper method for determining if an element's values are broken + function color(a){ + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle(a,null); + return !ret || ret.getPropertyValue("color") == ""; + } + + if (prop == "opacity" && jQuery.browser.msie) { + ret = jQuery.attr(elem.style, "opacity"); + return ret == "" ? "1" : ret; + } + + if (prop.match(/float/i)) + prop = styleFloat; + + if (!force && elem.style[prop]) + ret = elem.style[prop]; + + else if (document.defaultView && document.defaultView.getComputedStyle) { + + if (prop.match(/float/i)) + prop = "float"; + + prop = prop.replace(/([A-Z])/g,"-$1").toLowerCase(); + var cur = document.defaultView.getComputedStyle(elem, null); + + if ( cur && !color(elem) ) + ret = cur.getPropertyValue(prop); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( a = 0; a < stack.length; a++ ) + if ( color(stack[a]) ) { + swap[a] = stack[a].style.display; + stack[a].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = prop == "display" && swap[stack.length-1] != null ? + "none" : + document.defaultView.getComputedStyle(elem,null).getPropertyValue(prop) || ""; + + // Finally, revert the display styles back + for ( a = 0; a < swap.length; a++ ) + if ( swap[a] != null ) + stack[a].style.display = swap[a]; + } + + if ( prop == "opacity" && ret == "" ) + ret = "1"; + + } else if (elem.currentStyle) { + var newProp = prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();}); + ret = elem.currentStyle[prop] || elem.currentStyle[newProp]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test(ret) && /^\d/.test(ret) ) { + var style = elem.style.left; + var runtimeStyle = elem.runtimeStyle.left; + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = ret || 0; + ret = elem.style.pixelLeft + "px"; + elem.style.left = style; + elem.runtimeStyle.left = runtimeStyle; + } + } + + return ret; + }, + + clean: function(a, doc) { + var r = []; + doc = doc || document; + + jQuery.each( a, function(i,arg){ + if ( !arg ) return; + + if ( arg.constructor == Number ) + arg = arg.toString(); + + // Convert html string into DOM nodes + if ( typeof arg == "string" ) { + // Fix "XHTML"-style tags in all browsers + arg = arg.replace(/(<(\w+)[^>]*?)\/>/g, function(m, all, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i)? m : all+">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var s = jQuery.trim(arg).toLowerCase(), div = doc.createElement("div"), tb = []; + + var wrap = + // option or optgroup + !s.indexOf("", ""] || + + !s.indexOf("", ""] || + + s.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [1, "", "
"] || + + !s.indexOf("", ""] || + + // matched above + (!s.indexOf("", ""] || + + !s.indexOf("", ""] || + + // IE can't serialize and + + + + +
jQuery Server Plugin demo...
+
    +
  • test1
  • +
  • test2
  • +
  • test3
  • +
+ + diff --git a/jQueryServer/demo/jquery.js b/jQueryServer/demo/jquery.js new file mode 100755 index 0000000..c7ced10 --- /dev/null +++ b/jQueryServer/demo/jquery.js @@ -0,0 +1,2991 @@ +(function(){ +/* + * jQuery 1.2.1 - New Wave Javascript + * + * Copyright (c) 2007 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2007-09-16 23:42:06 -0400 (Sun, 16 Sep 2007) $ + * $Rev: 3353 $ + */ + +// Map over jQuery in case of overwrite +if ( typeof jQuery != "undefined" ) + var _jQuery = jQuery; + +var jQuery = window.jQuery = function(selector, context) { + // If the context is a namespace object, return a new object + return this instanceof jQuery ? + this.init(selector, context) : + new jQuery(selector, context); +}; + +// Map over the $ in case of overwrite +if ( typeof $ != "undefined" ) + var _$ = $; + +// Map the jQuery namespace to the '$' one +window.$ = jQuery; + +var quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/; + +jQuery.fn = jQuery.prototype = { + init: function(selector, context) { + // Make sure that a selection was provided + selector = selector || document; + + // Handle HTML strings + if ( typeof selector == "string" ) { + var m = quickExpr.exec(selector); + if ( m && (m[1] || !context) ) { + // HANDLE: $(html) -> $(array) + if ( m[1] ) + selector = jQuery.clean( [ m[1] ], context ); + + // HANDLE: $("#id") + else { + var tmp = document.getElementById( m[3] ); + if ( tmp ) + // Handle the case where IE and Opera return items + // by name instead of ID + if ( tmp.id != m[3] ) + return jQuery().find( selector ); + else { + this[0] = tmp; + this.length = 1; + return this; + } + else + selector = []; + } + + // HANDLE: $(expr) + } else + return new jQuery( context ).find( selector ); + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction(selector) ) + return new jQuery(document)[ jQuery.fn.ready ? "ready" : "load" ]( selector ); + + return this.setArray( + // HANDLE: $(array) + selector.constructor == Array && selector || + + // HANDLE: $(arraylike) + // Watch for when an array-like object is passed as the selector + (selector.jquery || selector.length && selector != window && !selector.nodeType && selector[0] != undefined && selector[0].nodeType) && jQuery.makeArray( selector ) || + + // HANDLE: $(*) + [ selector ] ); + }, + + jquery: "1.2.1", + + size: function() { + return this.length; + }, + + length: 0, + + get: function( num ) { + return num == undefined ? + + // Return a 'clean' array + jQuery.makeArray( this ) : + + // Return just the object + this[num]; + }, + + pushStack: function( a ) { + var ret = jQuery(a); + ret.prevObject = this; + return ret; + }, + + setArray: function( a ) { + this.length = 0; + Array.prototype.push.apply( this, a ); + return this; + }, + + each: function( fn, args ) { + return jQuery.each( this, fn, args ); + }, + + index: function( obj ) { + var pos = -1; + this.each(function(i){ + if ( this == obj ) pos = i; + }); + return pos; + }, + + attr: function( key, value, type ) { + var obj = key; + + // Look for the case where we're accessing a style value + if ( key.constructor == String ) + if ( value == undefined ) + return this.length && jQuery[ type || "attr" ]( this[0], key ) || undefined; + else { + obj = {}; + obj[ key ] = value; + } + + // Check to see if we're setting style values + return this.each(function(index){ + // Set all the styles + for ( var prop in obj ) + jQuery.attr( + type ? this.style : this, + prop, jQuery.prop(this, obj[prop], type, index, prop) + ); + }); + }, + + css: function( key, value ) { + return this.attr( key, value, "curCSS" ); + }, + + text: function(e) { + if ( typeof e != "object" && e != null ) + return this.empty().append( document.createTextNode( e ) ); + + var t = ""; + jQuery.each( e || this, function(){ + jQuery.each( this.childNodes, function(){ + if ( this.nodeType != 8 ) + t += this.nodeType != 1 ? + this.nodeValue : jQuery.fn.text([ this ]); + }); + }); + return t; + }, + + wrapAll: function(html) { + if ( this[0] ) + // The elements to wrap the target around + jQuery(html, this[0].ownerDocument) + .clone() + .insertBefore(this[0]) + .map(function(){ + var elem = this; + while ( elem.firstChild ) + elem = elem.firstChild; + return elem; + }) + .append(this); + + return this; + }, + + wrapInner: function(html) { + return this.each(function(){ + jQuery(this).contents().wrapAll(html); + }); + }, + + wrap: function(html) { + return this.each(function(){ + jQuery(this).wrapAll(html); + }); + }, + + append: function() { + return this.domManip(arguments, true, 1, function(a){ + this.appendChild( a ); + }); + }, + + prepend: function() { + return this.domManip(arguments, true, -1, function(a){ + this.insertBefore( a, this.firstChild ); + }); + }, + + before: function() { + return this.domManip(arguments, false, 1, function(a){ + this.parentNode.insertBefore( a, this ); + }); + }, + + after: function() { + return this.domManip(arguments, false, -1, function(a){ + this.parentNode.insertBefore( a, this.nextSibling ); + }); + }, + + end: function() { + return this.prevObject || jQuery([]); + }, + + find: function(t) { + var data = jQuery.map(this, function(a){ return jQuery.find(t,a); }); + return this.pushStack( /[^+>] [^+>]/.test( t ) || t.indexOf("..") > -1 ? + jQuery.unique( data ) : data ); + }, + + clone: function(events) { + // Do the clone + var ret = this.map(function(){ + return this.outerHTML ? jQuery(this.outerHTML)[0] : this.cloneNode(true); + }); + + // Need to set the expando to null on the cloned set if it exists + // removeData doesn't work here, IE removes it from the original as well + // this is primarily for IE but the data expando shouldn't be copied over in any browser + var clone = ret.find("*").andSelf().each(function(){ + if ( this[ expando ] != undefined ) + this[ expando ] = null; + }); + + // Copy the events from the original to the clone + if (events === true) + this.find("*").andSelf().each(function(i) { + var events = jQuery.data(this, "events"); + for ( var type in events ) + for ( var handler in events[type] ) + jQuery.event.add(clone[i], type, events[type][handler], events[type][handler].data); + }); + + // Return the cloned set + return ret; + }, + + filter: function(t) { + return this.pushStack( + jQuery.isFunction( t ) && + jQuery.grep(this, function(el, index){ + return t.apply(el, [index]); + }) || + + jQuery.multiFilter(t,this) ); + }, + + not: function(t) { + return this.pushStack( + t.constructor == String && + jQuery.multiFilter(t, this, true) || + + jQuery.grep(this, function(a) { + return ( t.constructor == Array || t.jquery ) + ? jQuery.inArray( a, t ) < 0 + : a != t; + }) + ); + }, + + add: function(t) { + return this.pushStack( jQuery.merge( + this.get(), + t.constructor == String ? + jQuery(t).get() : + t.length != undefined && (!t.nodeName || jQuery.nodeName(t, "form")) ? + t : [t] ) + ); + }, + + is: function(expr) { + return expr ? jQuery.multiFilter(expr,this).length > 0 : false; + }, + + hasClass: function(expr) { + return this.is("." + expr); + }, + + val: function( val ) { + if ( val == undefined ) { + if ( this.length ) { + var elem = this[0]; + + // We need to handle select boxes special + if ( jQuery.nodeName(elem, "select") ) { + var index = elem.selectedIndex, + a = [], + options = elem.options, + one = elem.type == "select-one"; + + // Nothing was selected + if ( index < 0 ) + return null; + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[i]; + if ( option.selected ) { + // Get the specifc value for the option + var val = jQuery.browser.msie && !option.attributes["value"].specified ? option.text : option.value; + + // We don't need an array for one selects + if ( one ) + return val; + + // Multi-Selects return an array + a.push(val); + } + } + + return a; + + // Everything else, we just grab the value + } else + return this[0].value.replace(/\r/g, ""); + } + } else + return this.each(function(){ + if ( val.constructor == Array && /radio|checkbox/.test(this.type) ) + this.checked = (jQuery.inArray(this.value, val) >= 0 || + jQuery.inArray(this.name, val) >= 0); + else if ( jQuery.nodeName(this, "select") ) { + var tmp = val.constructor == Array ? val : [val]; + + jQuery("option", this).each(function(){ + this.selected = (jQuery.inArray(this.value, tmp) >= 0 || + jQuery.inArray(this.text, tmp) >= 0); + }); + + if ( !tmp.length ) + this.selectedIndex = -1; + } else + this.value = val; + }); + }, + + html: function( val ) { + return val == undefined ? + ( this.length ? this[0].innerHTML : null ) : + this.empty().append( val ); + }, + + replaceWith: function( val ) { + return this.after( val ).remove(); + }, + + eq: function(i){ + return this.slice(i, i+1); + }, + + slice: function() { + return this.pushStack( Array.prototype.slice.apply( this, arguments ) ); + }, + + map: function(fn) { + return this.pushStack(jQuery.map( this, function(elem,i){ + return fn.call( elem, i, elem ); + })); + }, + + andSelf: function() { + return this.add( this.prevObject ); + }, + + domManip: function(args, table, dir, fn) { + var clone = this.length > 1, a; + + return this.each(function(){ + if ( !a ) { + a = jQuery.clean(args, this.ownerDocument); + if ( dir < 0 ) + a.reverse(); + } + + var obj = this; + + if ( table && jQuery.nodeName(this, "table") && jQuery.nodeName(a[0], "tr") ) + obj = this.getElementsByTagName("tbody")[0] || this.appendChild(document.createElement("tbody")); + + jQuery.each( a, function(){ + var elem = clone ? this.cloneNode(true) : this; + if ( !evalScript(0, elem) ) + fn.call( obj, elem ); + }); + }); + } +}; + +function evalScript(i, elem){ + var script = jQuery.nodeName(elem, "script"); + + if ( script ) { + if ( elem.src ) + jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); + else + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + + if ( elem.parentNode ) + elem.parentNode.removeChild(elem); + + } else if ( elem.nodeType == 1 ) + jQuery("script", elem).each(evalScript); + + return script; +} + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, a = 1, al = arguments.length, deep = false; + + // Handle a deep copy situation + if ( target.constructor == Boolean ) { + deep = target; + target = arguments[1] || {}; + } + + // extend jQuery itself if only one argument is passed + if ( al == 1 ) { + target = this; + a = 0; + } + + var prop; + + for ( ; a < al; a++ ) + // Only deal with non-null/undefined values + if ( (prop = arguments[a]) != null ) + // Extend the base object + for ( var i in prop ) { + // Prevent never-ending loop + if ( target == prop[i] ) + continue; + + // Recurse if we're merging object values + if ( deep && typeof prop[i] == 'object' && target[i] ) + jQuery.extend( target[i], prop[i] ); + + // Don't bring in undefined values + else if ( prop[i] != undefined ) + target[i] = prop[i]; + } + + // Return the modified object + return target; +}; + +var expando = "jQuery" + (new Date()).getTime(), uuid = 0, win = {}; + +jQuery.extend({ + noConflict: function(deep) { + window.$ = _$; + if ( deep ) + window.jQuery = _jQuery; + return jQuery; + }, + + // This may seem like some crazy code, but trust me when I say that this + // is the only cross-browser way to do this. --John + isFunction: function( fn ) { + return !!fn && typeof fn != "string" && !fn.nodeName && + fn.constructor != Array && /function/i.test( fn + "" ); + }, + + // check if an element is in a XML document + isXMLDoc: function(elem) { + return elem.documentElement && !elem.body || + elem.tagName && elem.ownerDocument && !elem.ownerDocument.body; + }, + + // Evalulates a script in a global context + // Evaluates Async. in Safari 2 :-( + globalEval: function( data ) { + data = jQuery.trim( data ); + if ( data ) { + if ( window.execScript ) + window.execScript( data ); + else if ( jQuery.browser.safari ) + // safari doesn't provide a synchronous global eval + window.setTimeout( data, 0 ); + else + eval.call( window, data ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase(); + }, + + cache: {}, + + data: function( elem, name, data ) { + elem = elem == window ? win : elem; + + var id = elem[ expando ]; + + // Compute a unique ID for the element + if ( !id ) + id = elem[ expando ] = ++uuid; + + // Only generate the data cache if we're + // trying to access or manipulate it + if ( name && !jQuery.cache[ id ] ) + jQuery.cache[ id ] = {}; + + // Prevent overriding the named cache with undefined values + if ( data != undefined ) + jQuery.cache[ id ][ name ] = data; + + // Return the named cache data, or the ID for the element + return name ? jQuery.cache[ id ][ name ] : id; + }, + + removeData: function( elem, name ) { + elem = elem == window ? win : elem; + + var id = elem[ expando ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( jQuery.cache[ id ] ) { + // Remove the section of cache data + delete jQuery.cache[ id ][ name ]; + + // If we've removed all the data, remove the element's cache + name = ""; + for ( name in jQuery.cache[ id ] ) break; + if ( !name ) + jQuery.removeData( elem ); + } + + // Otherwise, we want to remove all of the element's data + } else { + // Clean up the element expando + try { + delete elem[ expando ]; + } catch(e){ + // IE has trouble directly removing the expando + // but it's ok with using removeAttribute + if ( elem.removeAttribute ) + elem.removeAttribute( expando ); + } + + // Completely remove the data cache + delete jQuery.cache[ id ]; + } + }, + + // args is for internal usage only + each: function( obj, fn, args ) { + if ( args ) { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.apply( obj[i], args ); + else + for ( var i = 0, ol = obj.length; i < ol; i++ ) + if ( fn.apply( obj[i], args ) === false ) break; + + // A special, fast, case for the most common use of each + } else { + if ( obj.length == undefined ) + for ( var i in obj ) + fn.call( obj[i], i, obj[i] ); + else + for ( var i = 0, ol = obj.length, val = obj[0]; + i < ol && fn.call(val,i,val) !== false; val = obj[++i] ){} + } + + return obj; + }, + + prop: function(elem, value, type, index, prop){ + // Handle executable functions + if ( jQuery.isFunction( value ) ) + value = value.call( elem, [index] ); + + // exclude the following css properties to add px + var exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i; + + // Handle passing in a number to a CSS property + return value && value.constructor == Number && type == "curCSS" && !exclude.test(prop) ? + value + "px" : + value; + }, + + className: { + // internal only, use addClass("class") + add: function( elem, c ){ + jQuery.each( (c || "").split(/\s+/), function(i, cur){ + if ( !jQuery.className.has( elem.className, cur ) ) + elem.className += ( elem.className ? " " : "" ) + cur; + }); + }, + + // internal only, use removeClass("class") + remove: function( elem, c ){ + elem.className = c != undefined ? + jQuery.grep( elem.className.split(/\s+/), function(cur){ + return !jQuery.className.has( c, cur ); + }).join(" ") : ""; + }, + + // internal only, use is(".class") + has: function( t, c ) { + return jQuery.inArray( c, (t.className || t).toString().split(/\s+/) ) > -1; + } + }, + + swap: function(e,o,f) { + for ( var i in o ) { + e.style["old"+i] = e.style[i]; + e.style[i] = o[i]; + } + f.apply( e, [] ); + for ( var i in o ) + e.style[i] = e.style["old"+i]; + }, + + css: function(e,p) { + if ( p == "height" || p == "width" ) { + var old = {}, oHeight, oWidth, d = ["Top","Bottom","Right","Left"]; + + jQuery.each( d, function(){ + old["padding" + this] = 0; + old["border" + this + "Width"] = 0; + }); + + jQuery.swap( e, old, function() { + if ( jQuery(e).is(':visible') ) { + oHeight = e.offsetHeight; + oWidth = e.offsetWidth; + } else { + e = jQuery(e.cloneNode(true)) + .find(":radio").removeAttr("checked").end() + .css({ + visibility: "hidden", position: "absolute", display: "block", right: "0", left: "0" + }).appendTo(e.parentNode)[0]; + + var parPos = jQuery.css(e.parentNode,"position") || "static"; + if ( parPos == "static" ) + e.parentNode.style.position = "relative"; + + oHeight = e.clientHeight; + oWidth = e.clientWidth; + + if ( parPos == "static" ) + e.parentNode.style.position = "static"; + + e.parentNode.removeChild(e); + } + }); + + return p == "height" ? oHeight : oWidth; + } + + return jQuery.curCSS( e, p ); + }, + + curCSS: function(elem, prop, force) { + var ret, stack = [], swap = []; + + // A helper method for determining if an element's values are broken + function color(a){ + if ( !jQuery.browser.safari ) + return false; + + var ret = document.defaultView.getComputedStyle(a,null); + return !ret || ret.getPropertyValue("color") == ""; + } + + if (prop == "opacity" && jQuery.browser.msie) { + ret = jQuery.attr(elem.style, "opacity"); + return ret == "" ? "1" : ret; + } + + if (prop.match(/float/i)) + prop = styleFloat; + + if (!force && elem.style[prop]) + ret = elem.style[prop]; + + else if (document.defaultView && document.defaultView.getComputedStyle) { + + if (prop.match(/float/i)) + prop = "float"; + + prop = prop.replace(/([A-Z])/g,"-$1").toLowerCase(); + var cur = document.defaultView.getComputedStyle(elem, null); + + if ( cur && !color(elem) ) + ret = cur.getPropertyValue(prop); + + // If the element isn't reporting its values properly in Safari + // then some display: none elements are involved + else { + // Locate all of the parent display: none elements + for ( var a = elem; a && color(a); a = a.parentNode ) + stack.unshift(a); + + // Go through and make them visible, but in reverse + // (It would be better if we knew the exact display type that they had) + for ( a = 0; a < stack.length; a++ ) + if ( color(stack[a]) ) { + swap[a] = stack[a].style.display; + stack[a].style.display = "block"; + } + + // Since we flip the display style, we have to handle that + // one special, otherwise get the value + ret = prop == "display" && swap[stack.length-1] != null ? + "none" : + document.defaultView.getComputedStyle(elem,null).getPropertyValue(prop) || ""; + + // Finally, revert the display styles back + for ( a = 0; a < swap.length; a++ ) + if ( swap[a] != null ) + stack[a].style.display = swap[a]; + } + + if ( prop == "opacity" && ret == "" ) + ret = "1"; + + } else if (elem.currentStyle) { + var newProp = prop.replace(/\-(\w)/g,function(m,c){return c.toUpperCase();}); + ret = elem.currentStyle[prop] || elem.currentStyle[newProp]; + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + if ( !/^\d+(px)?$/i.test(ret) && /^\d/.test(ret) ) { + var style = elem.style.left; + var runtimeStyle = elem.runtimeStyle.left; + elem.runtimeStyle.left = elem.currentStyle.left; + elem.style.left = ret || 0; + ret = elem.style.pixelLeft + "px"; + elem.style.left = style; + elem.runtimeStyle.left = runtimeStyle; + } + } + + return ret; + }, + + clean: function(a, doc) { + var r = []; + doc = doc || document; + + jQuery.each( a, function(i,arg){ + if ( !arg ) return; + + if ( arg.constructor == Number ) + arg = arg.toString(); + + // Convert html string into DOM nodes + if ( typeof arg == "string" ) { + // Fix "XHTML"-style tags in all browsers + arg = arg.replace(/(<(\w+)[^>]*?)\/>/g, function(m, all, tag){ + return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area)$/i)? m : all+">"; + }); + + // Trim whitespace, otherwise indexOf won't work as expected + var s = jQuery.trim(arg).toLowerCase(), div = doc.createElement("div"), tb = []; + + var wrap = + // option or optgroup + !s.indexOf("", ""] || + + !s.indexOf("", ""] || + + s.match(/^<(thead|tbody|tfoot|colg|cap)/) && + [1, "", "
"] || + + !s.indexOf("", ""] || + + // matched above + (!s.indexOf("", ""] || + + !s.indexOf("", ""] || + + // IE can't serialize and diff --git a/jQueryServer/jQueryServer.js b/jQueryServer/jQueryServer.js index c55405e..576d4cf 100755 --- a/jQueryServer/jQueryServer.js +++ b/jQueryServer/jQueryServer.js @@ -1,3 +1,18 @@ +/** + * jQuery Server Plugin + * + * Server-side AJAX requests supporting jQuery manipulations + * before sending content to the browser. + * + * Example: + * $.server({url: ${URL}) + * .find('.my-class') + * .client(${CALLBACK}); + * + * @version 0.5 + * @author Tobiasz Cudnik tobiasz.cudnik/gmail.com + * @link http://code.google.com/p/phpquery/ + */ jQuery.extend({ serverConfig: function() { if (typeof jQueryServerConfig != 'undefined') diff --git a/jQueryServer/jQueryServer.php b/jQueryServer/jQueryServer.php index 89417f4..7eefce2 100755 --- a/jQueryServer/jQueryServer.php +++ b/jQueryServer/jQueryServer.php @@ -8,6 +8,7 @@ * * Backend class using phpQuery. * + * @version 0.5 * @author Tobiasz Cudnik tobiasz.cudnik/gmail.com * @link http://code.google.com/p/phpquery/ * @todo local files support (safe...) diff --git a/phpQuery/phpQuery.php b/phpQuery/phpQuery.php index 5cb5524..aa71e3b 100644 --- a/phpQuery/phpQuery.php +++ b/phpQuery/phpQuery.php @@ -1793,6 +1793,10 @@ public function filter( $selectors, $_skipHistory = false ) { * @todo Support $selector */ public function load($url, $data = null, $callback = null) { + if ($data && ! is_array($data)) { + $callback = $data; + $data = null; + } if (strpos($url, ' ') !== false) { $matches = null; preg_match('@^(.+?) (.*)$@', $url, $matches); From f42e8d2f2a10e82895fe2244597f1165fb93b94e Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Fri, 19 Sep 2008 09:41:20 +0000 Subject: [PATCH 038/149] * little fix in .trigger() * fixed serializeArray and unchecked checkboxes task 15: WebBrowser gmail login (Incomplete) task 38: Memory issues (Incomplete) http://code.google.com/p/phpquery/issues/detail?id=38 --- phpQuery/phpQuery.php | 56 ++++++++++++++++++++++----------- phpQuery/plugins/WebBrowser.php | 36 ++++++++++++++++++--- test-cases/test_selectors.php | 15 ++++++++- test-cases/test_webbrowser.php | 45 ++++++++++++++------------ 4 files changed, 109 insertions(+), 43 deletions(-) diff --git a/phpQuery/phpQuery.php b/phpQuery/phpQuery.php index aa71e3b..b37ce0f 100644 --- a/phpQuery/phpQuery.php +++ b/phpQuery/phpQuery.php @@ -10,6 +10,7 @@ * @link http://jquery.com * @license http://www.opensource.org/licenses/mit-license.php MIT License * @version 0.9.4 beta2 + * @package phpQuery */ // class names for instanceof @@ -18,7 +19,13 @@ define('DOMNODELIST', 'DOMNodeList'); define('DOMNODE', 'DOMNode'); define('PHPQUERYOBJECT', 'phpQueryObject'); - + +/** + * Static namespace for phpQuery functions. + * + * @author Tobiasz Cudnik + * @package phpQuery + */ abstract class phpQuery { public static $debug = false; public static $documents = array(); @@ -509,7 +516,9 @@ public static function ajax($options = array(), $xhr = null) { // reuse existing XHR object, but clean it up $client = $xhr; $client->setParameterPost(null); - $client->setParameterGet(null); + $client->setParameterGet(null); + $client->setAuth(false); + $client->setHeaders("If-Modified-Since", null); } else { // create new XHR object require_once('Zend/Http/Client.php'); @@ -532,14 +541,12 @@ public static function ajax($options = array(), $xhr = null) { $client->setUri($options['url']); $client->setMethod($options['type']); $client->setHeaders(array( - 'contentType' => $options['contentType'], +// 'content-type' => $options['contentType'], 'user-agent' => 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9a8) Gecko/2007100619 GranParadiso/3.0a8', 'accept-charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', )); if ($options['username']) $client->setAuth($options['username'], $options['password']); - else - $client->setAuth(false); if (isset($options['ifModified']) && $options['ifModified']) $client->setHeaders("If-Modified-Since", self::$lastModified @@ -561,7 +568,8 @@ public static function ajax($options = array(), $xhr = null) { } if (strtolower($options['type']) == 'get') { $client->setParameterGet($options['data']); - } else if (strtolower($options['type']) == 'post') { + } else if (strtolower($options['type']) == 'post') { + $client->setEncType($options['contentType']); $client->setParameterPost($options['data']); } if (self::$active == 0 && $options['global']) @@ -573,8 +581,9 @@ public static function ajax($options = array(), $xhr = null) { // ajaxSend event if ($options['global']) phpQueryEvent::trigger($documentID, 'ajaxSend', array($client, $options)); - self::debug("{$options['type']}: {$options['url']}\n"); - self::debug("Data:
".var_export($options['data'], true)."
\n"); + self::debug("{$options['type']}: {$options['url']}\n"); + self::debug("Options:
".var_export($options, true)."
\n"); + self::debug("Cookies:
".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)."
\n"); // request $response = $client->request(); if ($response->isSuccessful()) { @@ -803,7 +812,7 @@ public static function trim($str) { /** * Plugins static namespace class. * - * @author Tobiasz Cudnik + * @author Tobiasz Cudnik * @package phpQuery */ class phpQueryPlugins { @@ -819,8 +828,15 @@ public function __call($method, $args) { } else throw new Exception("Method '{$method}' doesnt exist"); } -} -class phpQueryObject implements Iterator, Countable, ArrayAccess { +} +/** + * Main class of phpQuery library. + * + * @author Tobiasz Cudnik + * @package phpQuery + */ +class phpQueryObject + implements Iterator, Countable, ArrayAccess { public $domId = null; /** * Alias for $document @@ -913,6 +929,7 @@ public function __construct($domId) { } public function __get($attr) { switch($attr) { + // FIXME doesnt work at all ? case 'length': return $this->size(); break; @@ -1073,7 +1090,7 @@ public function serializeArray($submit = null) { continue; if (!$input->is('[name]')) continue; - if ($input->is('[type=radio]') && !$input->is('[checked]')) + if ($input->is('[type=checkbox]') && !$input->is('[checked]')) continue; // jquery diff if ($submit && $input->is('[type=submit]')) { @@ -3415,7 +3432,7 @@ class phpQueryEvent { * @TODO global events * @TODO support more than event in $type (space-separated) */ - public function trigger($document, $type, $data = array(), $node = null) { + public static function trigger($document, $type, $data = array(), $node = null) { // trigger: function(type, data, elem, donative, extra) { $documentID = phpQuery::getDocumentID($document); $namespace = null; @@ -3437,7 +3454,7 @@ public function trigger($document, $type, $data = array(), $node = null) { 'timeStamp' => time(), )); while($node) { - phpQuery::debug("Triggering event '{$type}' on node ".$this->whois($node)); + phpQuery::debug("Triggering event '{$type}' on node ".phpQueryObject::whois($node)."\n"); $event->currentTarget = $node; $eventNode = self::getNode($documentID, $node); if (isset($eventNode->eventHandlers)) { @@ -3481,7 +3498,7 @@ public function trigger($document, $type, $data = array(), $node = null) { * @TODO support '!' (exclusive) events * @TODO support more than event in $type (space-separated) */ - public function add($document, $node, $type, $data, $callback = null) { + public static function add($document, $node, $type, $data, $callback = null) { $documentID = phpQuery::getDocumentID($document); // if (is_null($callback) && is_callable($data)) { // $callback = $data; @@ -3542,9 +3559,10 @@ protected static function issetGlobal($documentID, $type) { * DOMEvent class. * * Based on - * @link http://developer.mozilla.org/En/DOM:event - * @author Tobiasz Cudnik - * @todo implement array_access + * @link http://developer.mozilla.org/En/DOM:event + * @author Tobiasz Cudnik + * @package phpQuery + * @todo implement ArrayAccess ? */ class DOMEvent { /** @@ -3641,6 +3659,8 @@ public function stopPropagation() { * * @see phpQuery::pq() * @return phpQueryObject|queryTemplatesFetch|queryTemplatesParse|queryTemplatesPickup + * @author Tobiasz Cudnik + * @package phpQuery */ function pq($arg1, $context = null) { $args = func_get_args(); diff --git a/phpQuery/plugins/WebBrowser.php b/phpQuery/plugins/WebBrowser.php index c50a7ce..86b9c4d 100644 --- a/phpQuery/plugins/WebBrowser.php +++ b/phpQuery/plugins/WebBrowser.php @@ -54,19 +54,25 @@ public static function browserGet($url, $callback) { $xhr = phpQuery::ajax(array( 'type' => 'GET', 'url' => $url, + 'dataType' => 'html', )); if ($xhr->getLastResponse()->isSuccessful()) { call_user_func_array($callback, array( - self::browserReceive($xhr)->WebBrowser($callback) + self::browserReceive($xhr)//->WebBrowser($callback) )); + return true; } else return false; } public static function browserPost($url, $data, $callback) { } + /** + * @param Zend_Http_Client $xhr + */ public static function browserReceive($xhr) { // TODO handle meta redirects $body = $xhr->getLastResponse()->getBody(); + // XXX error ??? if (strpos($body, '') !== false) { $body = '' @@ -76,7 +82,29 @@ public static function browserReceive($xhr) { $pq = phpQuery::newDocument($body); $pq->document->xhr = $xhr; $pq->document->location = $xhr->getUri(); - return $pq; + $refresh = $pq->find('meta[http-equiv=refresh]')->add('meta[http-equiv=Refresh]'); + if ($refresh->size()) { +// print htmlspecialchars(var_export($xhr->getCookieJar()->getAllCookies(), true)); +// print htmlspecialchars(var_export($xhr->getLastResponse()->getHeader('Set-Cookie'), true)); + phpQuery::debug("Meta redirect... '{$refresh->attr('content')}'\n"); + // there is a refresh, so get the new url + $content = $refresh->attr('content'); + $urlRefresh = substr($content, strpos($content, '=')+1); + $urlRefresh = trim($urlRefresh, '\'"'); + // make ajax call, passing last $xhr object to preserve important stuff + $xhr = phpQuery::ajax(array( + 'type' => 'GET', + 'url' => $urlRefresh, + 'dataType' => 'html', + ), $xhr); + if ($xhr->getLastResponse()->isSuccessful()) { + // if all is ok, repeat this method... + return call_user_func_array( + array('phpQueryPlugin_WebBrowser', 'browserReceive'), array($xhr) + ); + } + } else + return $pq; } public static function hadleClick($e) { $node = phpQuery::pq($e->target); @@ -115,8 +143,8 @@ public static function handleSubmit($e) { foreach($node->serializeArray($defaultSubmit) as $r) $data[ $r['name'] ] = $r['value']; $options = array( - 'type' => $node->attr('type') - ? $node->attr('type') + 'type' => $node->attr('method') + ? $node->attr('method') : 'GET', 'url' => resolve_url($e->data[0], $node->attr('action')), 'data' => $data, diff --git a/test-cases/test_selectors.php b/test-cases/test_selectors.php index 731b49c..943c229 100644 --- a/test-cases/test_selectors.php +++ b/test-cases/test_selectors.php @@ -121,5 +121,18 @@ print_r($test[2]); } print "

"; -} +} + +// +$testName = 'Complicated selector 1'; +phpQuery::newDocumentFile('test.html'); +pq('') + ->appendTo('body'); +$result = pq('select[name="test[]"]:has(option[value=3])'); +if ( $result->size() == 1 ) + print "Test '{$testName}' PASSED :)"; +else + print "Test '{$testName}' FAILED !!! "; +$result->dump(); +print "\n"; ?> \ No newline at end of file diff --git a/test-cases/test_webbrowser.php b/test-cases/test_webbrowser.php index c978c34..99b624d 100755 --- a/test-cases/test_webbrowser.php +++ b/test-cases/test_webbrowser.php @@ -1,33 +1,36 @@ WebBrowserBind('WebBrowser'); phpQuery::$plugins->browserGet('http://google.com/', 'success1'); -function success1($browser) { -// $html = mb_convert_encoding($html, 'HTML-ENTITIES', "UTF-8"); -// die($html); -// phpQuery::newDocument($html) -// ->WebBrowser('BrowserHandler', 'http://google.com/') -// ->find('input')->dump()->end() -// ->dump(); - // google results - $browser - ->WebBrowser('BrowserHandler') +/** + * + * @param $pq phpQueryObject + * @return unknown_type + */ +function success1($pq) { + print 'success1 callback'; +// print 'SETCOOKIE'.$pq->document->xhr->getLastResponse()->getHeader('Set-Cookie'); + $pq + ->WebBrowser('success2') + /* google results */ ->find('input[name=q]') ->val('phpQuery') ->parents('form') ->submit() - // gmail login + /* gmail login */ + // it doesnt work and i dont know why... :( // ->find('#Email') -// ->val('tobiasz.cudnik@gmail.com')->end() +// ->val('XXX@gmail.com')->end() // ->find('#Passwd') // ->val('XXX') // ->parents('form') @@ -41,9 +44,11 @@ function success1($browser) { * @param $html phpQueryObject * @return unknown_type */ -function BrowserHandler($html) { - print 'WebBrowser callback'; - die($html); +function success2($pq) { +// die(var_dump($pq->document->xhr->getCookieJar()->getMatchingCookies('http://mail.google.com/mail/?ui=html&zy=l'))); + print 'success2 callback'; + print $pq + ->find('script')->remove()->end(); } // if ( $result->whois() == $testResult ) From 5a892027c84a0ce7a7baf91f9ed2e3ff4a3b50e5 Mon Sep 17 00:00:00 2001 From: "tobiasz.cudnik" Date: Sun, 21 Sep 2008 09:28:08 +0000 Subject: [PATCH 039/149] =?UTF-8?q?=E2=88=98=20missing=20CSS3=20selectors?= =?UTF-8?q?=20=E2=80=A3=20:header=20=E2=80=A3=20:last-child=20=E2=80=A3=20?= =?UTF-8?q?:first-child=20=E2=80=A3=20:nth-child(index/even/odd/equation)?= =?UTF-8?q?=20=E2=80=A3=20:input=20=E2=80=A3=20:text=20=E2=80=A3=20:passwo?= =?UTF-8?q?rd=20=E2=80=A3=20:radio=20=E2=80=A3=20:checkbox=20=E2=80=A3=20:?= =?UTF-8?q?submit=20=E2=80=A3=20:image=20=E2=80=A3=20:reset=20=E2=80=A3=20?= =?UTF-8?q?:button=20=E2=80=A3=20:file=20=E2=80=A3=20:hidden=20=E2=80=A3?= =?UTF-8?q?=20:empty=20=E2=88=98=20new=20callback=20handling=20=E2=80=A3?= =?UTF-8?q?=20support=20paramStructure=20to=20controll=20passed=20params?= =?UTF-8?q?=20=E2=80=A3=20applied=20to=20::each(),=20::map(),=20.each(),?= =?UTF-8?q?=20.map()=20=E2=88=98=20fixed=20siblings(),=20which=20doesnt=20?= =?UTF-8?q?return=20ANY=20of=20previous=20siblings=20=E2=88=98=20WebBrowse?= =?UTF-8?q?r=20=E2=80=A3=20click=20on=20submit=20now=20submits=20form?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task 6: Missing pseudo classes http://code.google.com/p/phpquery/issues/detail?id=6 --- phpQuery/phpQuery.php | 359 +++++++++++++++++++++++++++----- phpQuery/plugins/WebBrowser.php | 38 ++-- test-cases/test.html | 22 +- test-cases/test_selectors.php | 166 ++++++++++++++- test-cases/xpath.php | 2 +- 5 files changed, 510 insertions(+), 77 deletions(-) diff --git a/phpQuery/phpQuery.php b/phpQuery/phpQuery.php index b37ce0f..66bae77 100644 --- a/phpQuery/phpQuery.php +++ b/phpQuery/phpQuery.php @@ -18,7 +18,7 @@ define('DOMELEMENT', 'DOMElement'); define('DOMNODELIST', 'DOMNodeList'); define('DOMNODE', 'DOMNode'); -define('PHPQUERYOBJECT', 'phpQueryObject'); +//define('PHPQUERYOBJECT', 'phpQueryObject'); /** * Static namespace for phpQuery functions. @@ -489,7 +489,7 @@ public static function isXhtml($dom) { } public function debug($text) { if (self::$debug) - print $text; + print var_dump($text); } /** * Make an AJAX request. @@ -749,32 +749,83 @@ public static function inArray($value, $array) { * @return unknown_type * @link http://docs.jquery.com/Utilities/jQuery.each */ - public static function each($object, $callback) { + public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null) { + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } if (is_object($object) && ! ($object instanceof Iterator)) { foreach(get_object_vars($object) as $name => $value) - call_user_func_array($callback, array($name, $value)); + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); } else { foreach($object as $name => $value) - call_user_func_array($callback, array($name, $value)); + phpQuery::callbackRun($callback, array($name, $value), $paramStructure); } } /** * * @link http://docs.jquery.com/Utilities/jQuery.map */ - public static function map($array, $callback) { + public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null) { $result = array(); + $paramStructure = null; + if (func_num_args() > 2) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 2); + } foreach($array as $v) { - $vv = call_user_func_array($callback, array($v)); + $vv = phpQuery::callbackRun($callback, array($v), $paramStructure); +// $callbackArgs = $args; +// foreach($args as $i => $arg) { +// $callbackArgs[$i] = $arg instanceof CallbackParam +// ? $v +// : $arg; +// } +// $vv = call_user_func_array($callback, $callbackArgs); if (is_array($vv)) { foreach($vv as $vvv) $result[] = $vvv; } else if ($vv !== null) { - $result[$k] = $vv; + $result[] = $vv; } } return $result; } + public static function callbackRun($callback, $params, $paramStructure = null) { + if (! $paramStructure) + return call_user_func_array($callback, $params); + $p = 0; + foreach($paramStructure as $i => $v) { + $paramStructure[$i] = $v instanceof CallbackParam + ? $params[$p++] + : $v; + } + return call_user_func_array($callback, $paramStructure); + } + /** + * Merge 2 phpQuery objects. + * @param array $one + * @param array $two + * @protected + * @todo node lists, phpQueryObject + */ + public static function merge($one, $two) { + $elements = $one->elements; + foreach($two->elements as $node) { + $exists = false; + foreach($elements as $node2) { + if ($node2->isSameNode($node)) + $exists = true; + } + if (! $exists) + $elements[] = $node; + } + return $elements; +// $one = $one->newInstance(); +// $one->elements = $elements; +// return $one; + } /** * * @param $array @@ -875,7 +926,7 @@ class phpQueryObject * * @var int */ - public $length = 0; +// public $length = 0; protected $elementsBackup = array(); protected $previous = null; protected $root = array(); @@ -1396,31 +1447,37 @@ protected function runQuery( $XQuery, $selector = null, $compare = null ) { $remove = true; } $xpath = $this->getNodeXpath($stackNode); - $query = $xpath.$XQuery; + // FIXME deam... + $query = $XQuery == '//' && $xpath == '/html[1]' + ? '//*' + : $xpath.$XQuery; $this->debug("XPATH: {$query}"); // run query, get elements $nodes = $this->XPath->query($query); $this->debug("QUERY FETCHED"); if (! $nodes->length ) $this->debug('Nothing found'); + $debug = array(); foreach( $nodes as $node ) { $matched = false; if ( $compare ) { phpQuery::$debug ? $this->debug("Found: ".$this->whois( $node ).", comparing with {$compare}()") : null; - if ( call_user_method($compare, $this, $selector, $node) ) + if (call_user_func_array(array($this, $compare), array($selector, $node))) $matched = true; } else { $matched = true; } if ( $matched ) { - phpQuery::$debug - ? $this->debug("Matched: ".$this->whois( $node )) - : null; + if (phpQuery::$debug) + $debug[] = $this->whois( $node ); $stack[] = $node; } } + if (phpQuery::$debug) { + $this->debug("Matched ".count($debug).": ".implode(', ', $debug)); + } if ( $remove ) $stackNode = $this->root->removeChild( $this->root->lastChild ); } @@ -1498,6 +1555,8 @@ public function find( $selectors, $context = null, $noHistory = false ) { } // CLASSES } else if ( $s[0] == '.' ) { + // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]"); + // thx wizDom ;) if ( $spaceBefore ) $XQuery .= '*'; $XQuery .= '[@class]'; @@ -1517,6 +1576,7 @@ public function find( $selectors, $context = null, $noHistory = false ) { break; // + Adjacent sibling selectors } else if ( $s[0] == '+' ) { + // TODO /following-sibling:: $this->runQuery($XQuery); $XQuery = ''; $subSelector = substr($s, 1); @@ -1541,7 +1601,7 @@ public function find( $selectors, $context = null, $noHistory = false ) { } if (! $this->length() ) break; - $this->filterPseudoClasses( $s ); + $this->pseudoClasses($s); if (! $this->length() ) break; // DIRECT DESCENDANDS @@ -1573,7 +1633,7 @@ public function find( $selectors, $context = null, $noHistory = false ) { /** * @todo create API for classes with pseudoselectors */ - protected function filterPseudoClasses( $class ) { + protected function pseudoClasses( $class ) { // TODO clean args parsing ? $class = ltrim($class, ':'); $haveArgs = strpos($class, '('); @@ -1586,7 +1646,7 @@ protected function filterPseudoClasses( $class ) { case 'odd': $stack = array(); foreach( $this->elements as $i => $node ) { - if ( $class == 'even' && $i % 2 == 0 ) + if ($class == 'even' && ($i%2) == 0) $stack[] = $node; else if ( $class == 'odd' && $i % 2 ) $stack[] = $node; @@ -1613,14 +1673,14 @@ protected function filterPseudoClasses( $class ) { if ( $this->elements ) $this->elements = array( $this->elements[ count($this->elements)-1 ] ); break; - case 'parent': + /*case 'parent': $stack = array(); foreach( $this->elements as $node ) { if ( $node->childNodes->length ) $stack[] = $node; } $this->elements = $stack; - break; + break;*/ case 'contains': $text = trim($args, "\"'"); $stack = array(); @@ -1642,7 +1702,8 @@ protected function filterPseudoClasses( $class ) { } $this->elements = $newStack; break; - case 'slice': + case 'slice': + // jQuery difference ? $args = exlode(',', str_replace(', ', ',', trim($args, "\"'")) ); @@ -1663,26 +1724,205 @@ protected function filterPseudoClasses( $class ) { } $this->elements = $stack; break; + case 'submit': + case 'reset': + $this->elements = phpQuery::merge( + $this->map(array($this, 'is'), + "input[type=$class]", new CallbackParam() + ), + $this->map(array($this, 'is'), + "button[type=$class]", new CallbackParam() + ) + ); + break; +// $stack = array(); +// foreach($this->elements as $node) +// if ($node->is('input[type=submit]') || $node->is('button[type=submit]')) +// $stack[] = $el; +// $this->elements = $stack; + case 'input': + $this->elements = $this->map( + array($this, 'is'), + 'input', new CallbackParam() + )->elements; + break; + case 'password': + case 'checkbox': + case 'hidden': + case 'image': + case 'file': + $this->elements = $this->map( + array($this, 'is'), + "input[type=$class]", new CallbackParam() + )->elements; + break; + case 'parent': + $this->elements = $this->map( + create_function('$node', ' + return $node->childNodes->length ? $node : null;') + )->elements; + break; + case 'empty': + $this->elements = $this->map( + create_function('$node', ' + return $node->childNodes->length ? null : $node;') + )->elements; + break; + case 'disabled': + case 'selected': + case 'checked': + $this->elements = $this->map( + array($this, 'is'), + "[$class]", new CallbackParam() + )->elements; + break; + case 'enabled': + $this->elements = $this->map( + create_function('$node', ' + return pq($node)->not(":disabled") ? $node : null;') + )->elements; + break; + case 'header': + $this->elements = $this->map( + create_function('$node', + '$isHeader = $node->tagName && in_array($node->tagName, array( + "h1", "h2", "h3", "h4", "h5", "h6", "h7" + )); + return $isHeader + ? $node + : null;') + )->elements; +// $this->elements = $this->map( +// create_function('$node', '$node = pq($node); +// return $node->is("h1") +// || $node->is("h2") +// || $node->is("h3") +// || $node->is("h4") +// || $node->is("h5") +// || $node->is("h6") +// || $node->is("h7") +// ? $node +// : null;') +// )->elements; + break; + case 'only-child': + $this->elements = $this->map( + create_function('$node', + 'return pq($node)->siblings()->size() == 0 ? $node : null;') + )->elements; + break; + case 'first-child': + $this->elements = $this->map( + create_function('$node', 'return pq($node)->prevAll()->size() == 0 ? $node : null;') + )->elements; + break; + case 'last-child': + $this->elements = $this->map( + create_function('$node', 'return pq($node)->nextAll()->size() == 0 ? $node : null;') + )->elements; + break; + case 'nth-child': + $param = trim($args, "\"'"); + if (! $param) + break; + // nth-child(n+b) to nth-child(1n+b) + if ($param{0} == 'n') + $param = '1'.$param; + // :nth-child(index/even/odd/equation) + if ($param == 'even' || $param == 'odd') + $mapped = $this->map( + create_function('$node, $param', + '$index = pq($node)->prevAll()->size()+1; + if ($param == "even" && ($index%2) == 0) + return $node; + else if ($param == "odd" && $index%2 == 1) + return $node; + else + return null;'), + new CallbackParam(), $param + ); + else if (strlen($param) > 1 && $param{1} == 'n') + // an+b + $mapped = $this->map( + create_function('$node, $param', + '$prevs = pq($node)->prevAll()->size(); + $index = 1+$prevs; + $b = strlen($param) > 3 + ? $param{3} + : 0; + $a = $param{0}; + if ($b && $param{2} == "-") + $b = -$b; + if ($a > 0) { + return ($index-$b)%$a == 0 + ? $node + : null; + phpQuery::debug($a."*".floor($index/$a)."+$b-1 == ".($a*floor($index/$a)+$b-1)." ?= $prevs"); + return $a*floor($index/$a)+$b-1 == $prevs + ? $node + : null; + } else if ($a == 0) + return $index == $b + ? $node + : null; + else + // negative value + return $index <= $b + ? $node + : null; +// if (! $b) +// return $index%$a == 0 +// ? $node +// : null; +// else +// return ($index-$b)%$a == 0 +// ? $node +// : null; + '), + new CallbackParam(), $param + ); + else + // index + $mapped = $this->map( + create_function('$node, $index', + '$prevs = pq($node)->prevAll()->size(); + if ($prevs && $prevs == $index-1) + return $node; + else if (! $prevs && $index == 1) + return $node; + else + return null;'), + new CallbackParam(), $param + ); + $this->elements = $mapped->elements; + break; default: $this->debug("Unknown pseudoclass '{$class}', skipping..."); } } + protected function __pseudoClassParam($paramsString) { + // TODO; + } /** * Enter description here... * * @return phpQueryObject|queryTemplatesFetch|queryTemplatesParse|queryTemplatesPickup - */ - public function is($selector, $_node = null) { - $this->debug(array("Is:", $selector)); + */ public function is($selector, $nodes = null) { + phpQuery::debug(array("Is:", $selector)); if (! $selector) return false; $oldStack = $this->elements; - if ( $_node ) - $this->elements = array($_node); + $returnArray = false; + if ($nodes && is_array($nodes)) { + $this->elements = $nodes; + } else if ($nodes) + $this->elements = array($nodes); $this->filter($selector, true); - $match = (bool)$this->length(); - $this->elements = $oldStack; - return $match; + $stack = $this->elements; + $this->elements = $oldStack; + if ($nodes) + return $stack ? $stack : null; + return (bool)count($stack); } /** @@ -1796,7 +2036,7 @@ public function filter( $selectors, $_skipHistory = false ) { foreach($selector as $s) // PSEUDO CLASSES if ( $s[0] == ':' ) - $this->filterPseudoClasses($s); + $this->pseudoClasses($s); return $_skipHistory ? $this : $this->newInstance(); @@ -2670,8 +2910,11 @@ protected function insert($target, $type) { */ public function index($subject) { $index = -1; + $subject = $subject instanceof phpQueryObject + ? $subject->elements[0] + : $subject; foreach($this->newInstance() as $k => $node) { - if ($node->isSameNode($subject->elements[0])) + if ($node->isSameNode($subject)) $index = $k; } return $index; @@ -2710,6 +2953,7 @@ public function slice($start, $end = null) { public function reverse() { $this->elementsBackup = $this->elements; $this->elements = array_reverse($this->elements); + return $this->newInstance(); } public function text() { @@ -2833,7 +3077,7 @@ protected function getElementSiblings($direction, $selector = null, $limitToOne public function siblings($selector = null) { $stack = array(); $siblings = array_merge( - $this->getElementSiblings('prevSibling', $selector), + $this->getElementSiblings('previousSibling', $selector), $this->getElementSiblings('nextSibling', $selector) ); foreach($siblings as $node) { @@ -3230,20 +3474,20 @@ public function _empty() { /** * Enter description here... * - * @param array|string $callback + * @param array|string $callback Expects $node as first param, $index as second * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope) * @param array $arg1 Will ba passed as third and futher args to callback. * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on... * @return phpQueryObject|queryTemplatesFetch|queryTemplatesParse|queryTemplatesPickup */ - public function each($callback, $scope = null, $arg1 = null, $arg2 = null) { - $args = func_get_args(); - array_shift($args); - foreach($this->newInstance() as $node) { - $nodeArgs = $args; - array_unshift($nodeArgs, $node); - call_user_func_array($callback, $nodeArgs); + public function each($callback, $param1 = null, $param2 = null, $param3 = null) { + $paramStructure = null; + if (func_num_args() > 1) { + $paramStructure = func_get_args(); + $paramStructure = array_slice($paramStructure, 1); } + foreach($this->elements as $v) + phpQuery::callbackRun($callback, array($v), $paramStructure); return $this; } @@ -3253,7 +3497,7 @@ public function each($callback, $scope = null, $arg1 = null, $arg2 = null) { * @return phpQueryObject|queryTemplatesFetch|queryTemplatesParse|queryTemplatesPickup * @todo add $scope and $args as in each() ??? */ - public function map($callback) { + public function map($callback, $param1 = null, $param2 = null, $param3 = null) { // $stack = array(); //// foreach($this->newInstance() as $node) { // foreach($this->newInstance() as $node) { @@ -3261,8 +3505,11 @@ public function map($callback) { // if ($result) // $stack[] = $result; // } + $params = func_get_args(); + array_unshift($params, $this->elements); return $this->newInstance( - phpQuery::map($this->elements, $callback) + call_user_func_array(array('phpQuery', 'map'), $params) +// phpQuery::map($this->elements, $callback) ); } @@ -3399,10 +3646,15 @@ public function whois($oneNode = null) { * @return phpQueryObject|queryTemplatesFetch|queryTemplatesParse|queryTemplatesPickup */ public function dump() { - print __FILE__.':'.__LINE__; + print __FILE__.':'.__LINE__."\n"; var_dump($this->htmlOuter()); return $this; } + public function dumpWhois() { + print __FILE__.':'.__LINE__."\n"; + var_dump($this->whois()); + return $this; + } /** * Dump htmlOuter and stop script execution. Usefull for debugging. * @@ -3448,11 +3700,16 @@ public static function trigger($document, $type, $data = array(), $node = null) ->trigger($type, $data); } } else { - $event = new DOMEvent(array( - 'type' => $type, - 'target' => $node, - 'timeStamp' => time(), - )); + if (isset($data[0]) && $data[0] instanceof DOMEvent) { + $event = $data[0]; + $data = array_slice($data, 1); + } else { + $event = new DOMEvent(array( + 'type' => $type, + 'target' => $node, + 'timeStamp' => time(), + )); + } while($node) { phpQuery::debug("Triggering event '{$type}' on node ".phpQueryObject::whois($node)."\n"); $event->currentTarget = $node; @@ -3554,6 +3811,12 @@ protected static function issetGlobal($documentID, $type) { ? in_array($type, phpQuery::$documents[$documentID]['eventGlobals']) : false; } +} +class CallbackParam { + public $index = null; + public function __construct($index = null) { + $this->index = $index; + } } /** * DOMEvent class. @@ -3677,4 +3940,4 @@ function pq($arg1, $context = null) { ); // why ? no __call nor __get for statics in php... phpQuery::$plugins = new phpQueryPlugins(); -?> \ No newline at end of file +?> diff --git a/phpQuery/plugins/WebBrowser.php b/phpQuery/plugins/WebBrowser.php index 86b9c4d..9f40102 100644 --- a/phpQuery/plugins/WebBrowser.php +++ b/phpQuery/plugins/WebBrowser.php @@ -108,19 +108,21 @@ public static function browserReceive($xhr) { } public static function hadleClick($e) { $node = phpQuery::pq($e->target); - if (!$node->is('a') || !$node->is('[href]')) - return; - // TODO document.location - $xhr = isset($node->document->xhr) - ? $node->document->xhr - : null; - $xhr = phpQuery::ajax(array( - 'url' => resolve_url($e->data[0], $node->attr('href')), - ), $xhr); - if ($xhr->getLastResponse()->isSuccessfull()) - call_user_func_array($e->data[1], array( - self::browserReceive($xhr, $e->data[1]) - )); + $type = null; + if ($node->is('a[href]')) { + // TODO document.location + $xhr = isset($node->document->xhr) + ? $node->document->xhr + : null; + $xhr = phpQuery::ajax(array( + 'url' => resolve_url($e->data[0], $node->attr('href')), + ), $xhr); + if ($xhr->getLastResponse()->isSuccessfull()) + call_user_func_array($e->data[1], array( + self::browserReceive($xhr, $e->data[1]) + )); + } else if ($node->is(':submit') && $node->parents('form')->size()) + $node->parents('form')->trigger('submit', array($e)); } /** * Enter description here... @@ -136,11 +138,15 @@ public static function handleSubmit($e) { $xhr = isset($node->document->xhr) ? $node->document->xhr : null; - $defaultSubmit = pq($e->target)->is('[type=submit]') + $submit = pq($e->target)->is(':submit') ? $e->target - : $node->find('input[type=submit]:first')->get(0); + // will this work ? +// : $node->find(':submit:first')->get(0); + : $node->find('*:submit:first')->get(0); $data = array(); - foreach($node->serializeArray($defaultSubmit) as $r) + foreach($node->serializeArray($submit) as $r) + // XXXt.c maybe $node->not(':submit')->add($sumit) would be better ? +// foreach($node->serializeArray($submit) as $r) $data[ $r['name'] ] = $r['value']; $options = array( 'type' => $node->attr('method') diff --git a/test-cases/test.html b/test-cases/test.html index b3c3aaf..75ea10e 100644 --- a/test-cases/test.html +++ b/test-cases/test.html @@ -1,19 +1,19 @@ - - - - plainTemplate test - - + + + + plainTemplate test + + - - + +
div.articles text node