Anonymous avatar Anonymous committed 60e23e3

Added support for findnodes in scalar context (returning a NodeList object)
Added support for find() a-la XML::XPath
Added support for findvalue() a-la XML::XPath
Added support for non-node returning XPath's (Boolean, Literal, Number)

Comments (0)

Files changed (11)

 use strict;
 use vars qw($VERSION @ISA @EXPORT);
 use Carp;
+use XML::LibXML::NodeList;
 
-$VERSION = "0.99";
+$VERSION = "1.00";
 require Exporter;
 require DynaLoader;
 
     return $rv;
 }
 
+sub XML::LibXML::Node::findnodes {
+    my ($node, $xpath) = @_;
+    my @nodes = $node->_findnodes($xpath);
+    if (wantarray) {
+        return @nodes;
+    }
+    else {
+        return XML::LibXML::NodeList->new(@nodes);
+    }
+}
+
+sub XML::LibXML::Node::findvalue {
+    my ($node, $xpath) = @_;
+    return $node->findnodes($xpath)->to_literal->value;
+}
+
+sub XML::LibXML::Node::find {
+    my ($node, $xpath) = @_;
+    my ($type, @params) = $node->_find($xpath);
+    return $type->new(@params);
+}
+
 1;
 __END__
 
 
 
 void
-findnodes( node, xpath )
+_findnodes( node, xpath )
         ProxyObject* node
         char * xpath 
     PREINIT:
         xmlNodeSetPtr nodelist = NULL;
         SV * element = NULL ;
         int len = 0 ;
-        int wantarray = GIMME_V;
     PPCODE:
         nodelist = domXPathSelect( node->object, xpath );
         if ( nodelist && nodelist->nodeNr > 0 ) {
             const char * cls = "XML::LibXML::Node";
             xmlNodePtr tnode;
             ProxyObject * proxy;
-
+            
             len = nodelist->nodeNr;
-            if( wantarray != G_SCALAR ) {         
-                for( i ; i < len; i++){
-                /* we have to create a new instance of an objectptr. and then 
+            for( i ; i < len; i++){
+               /* we have to create a new instance of an objectptr. and then 
                  * place the current node into the new object. afterwards we can 
                  * push the object to the array!
                  */
-                    element = 0;
-                    tnode = nodelist->nodeTab[i];
-                    element = sv_newmortal();
-                    cls = domNodeTypeName( tnode );
+                element = NULL;
+                tnode = nodelist->nodeTab[i];
+                element = sv_newmortal();
+                cls = domNodeTypeName( tnode );
 
-                    proxy = make_proxy_node(tnode);
-                    if ( node->extra != NULL ) {
-                        proxy->extra = node->extra;
-                        SvREFCNT_inc(node->extra);
-                    }
+                proxy = make_proxy_node(tnode);
+                if ( node->extra != NULL ) {
+                    proxy->extra = node->extra;
+                    SvREFCNT_inc(node->extra);
+                }
         
-                    element = sv_setref_pv( element, (char *)cls, (void*)proxy );
-                    cls = domNodeTypeName( tnode );
-                    XPUSHs( element );
-                }
+                element = sv_setref_pv( element, (char *)cls, (void*)proxy );
+                cls = domNodeTypeName( tnode );
+                XPUSHs( element );
             }
-            else {
-                XPUSHs( newSViv(len) );
-            }
+            
             xmlXPathFreeNodeSet( nodelist );
         }
 
 void
+_find ( node, xpath )
+        ProxyObject* node
+        char * xpath
+    PREINIT:
+        xmlXPathObjectPtr found = NULL;
+        xmlNodeSetPtr nodelist = NULL;
+        SV * element = NULL ;
+        int len = 0 ;
+    PPCODE:
+        found = domXPathFind( node->object, xpath );
+        switch (found->type) {
+            case XPATH_NODESET:
+                /* return as a NodeList */
+                /* access ->nodesetval */
+                XPUSHs(newSVpv("XML::LibXML::NodeList", 0));
+                nodelist = found->nodesetval;
+                if ( nodelist && nodelist->nodeNr > 0 ) {
+                    int i = 0 ;
+                    const char * cls = "XML::LibXML::Node";
+                    xmlNodePtr tnode;
+                    ProxyObject * proxy;
+                    SV * element;
+                    
+                    len = nodelist->nodeNr;
+                    for( i ; i < len; i++){
+                       /* we have to create a new instance of an objectptr. and then 
+                         * place the current node into the new object. afterwards we can 
+                         * push the object to the array!
+                         */
+                        element = NULL;
+                        tnode = nodelist->nodeTab[i];
+                        element = sv_newmortal();
+                        cls = domNodeTypeName( tnode );
+        
+                        proxy = make_proxy_node(tnode);
+                        if ( node->extra != NULL ) {
+                            proxy->extra = node->extra;
+                            SvREFCNT_inc(node->extra);
+                        }
+                
+                        element = sv_setref_pv( element, (char *)cls, (void*)proxy );
+                        cls = domNodeTypeName( tnode );
+                        XPUSHs( element );
+                    }
+                }
+                break;
+            case XPATH_BOOLEAN:
+                /* return as a Boolean */
+                /* access ->boolval */
+                XPUSHs(newSVpv("XML::LibXML::Boolean", 0));
+                XPUSHs(newSViv(found->boolval));
+                break;
+            case XPATH_NUMBER:
+                /* return as a Number */
+                /* access ->floatval */
+                XPUSHs(newSVpv("XML::LibXML::Number", 0));
+                XPUSHs(newSVnv(found->floatval));
+                break;
+            case XPATH_STRING:
+                /* access ->stringval */
+                /* return as a Literal */
+                XPUSHs(newSVpv("XML::LibXML::Literal", 0));
+                XPUSHs(newSVpv(found->stringval, 0));
+                break;
+            default:
+                croak("Uknown XPath return type");
+        }
+        xmlXPathFreeObject(found);
+
+void
 getChildnodes( node )
         ProxyObject* node
     ALIAS:
         if( wantarray == G_SCALAR ) {
             XPUSHs( newSViv( len ) );
         }
+
+char *
+string_value ( node )
+        xmlNodePtr node
+    ALIAS:
+        to_literal = 1
+    CODE:
+        RETVAL = xmlXPathCastNodeToString(node);
+    OUTPUT:
+        RETVAL
+
+double
+to_number ( node )
+        xmlNodePtr node
+    CODE:
+        RETVAL = xmlXPathCastNodeToNumber(node);
+    OUTPUT:
+        RETVAL
+
         
 MODULE = XML::LibXML         PACKAGE = XML::LibXML::Element
 
 lib/XML/LibXML/Text.pod
 lib/XML/LibXML/DocumentFragment.pod
 lib/XML/LibXML/Namespace.pod
+lib/XML/LibXML/NodeList.pm
+lib/XML/LibXML/Literal.pm
+lib/XML/LibXML/Boolean.pm
+lib/XML/LibXML/Number.pm

lib/XML/LibXML/Boolean.pm

+# $Id$
+
+package XML::LibXML::Boolean;
+use XML::LibXML::Number;
+use XML::LibXML::Literal;
+use strict;
+
+use overload
+        '""' => \&value,
+        '<=>' => \&cmp;
+
+sub new {
+    my $class = shift;
+    my ($param) = @_;
+    my $val = $param ? 1 : 0;
+    bless \$val, $class;
+}
+
+sub True {
+    my $class = shift;
+    my $val = 1;
+    bless \$val, $class;
+}
+
+sub False {
+    my $class = shift;
+    my $val = 0;
+    bless \$val, $class;
+}
+
+sub value {
+    my $self = shift;
+    $$self;
+}
+
+sub cmp {
+    my $self = shift;
+    my ($other, $swap) = @_;
+    if ($swap) {
+        return $other <=> $$self;
+    }
+    return $$self <=> $other;
+}
+
+sub to_number { XML::LibXML::Number->new($_[0]->value); }
+sub to_boolean { $_[0]; }
+sub to_literal { XML::LibXML::Literal->new($_[0]->value ? "true" : "false"); }
+
+sub string_value { return $_[0]->to_literal->value; }
+
+1;
+__END__
+
+=head1 NAME
+
+XML::LibXML::Boolean - Boolean true/false values
+
+=head1 DESCRIPTION
+
+XML::LibXML::Boolean objects implement simple boolean true/false objects.
+
+=head1 API
+
+=head2 XML::LibXML::Boolean->True
+
+Creates a new Boolean object with a true value.
+
+=head2 XML::LibXML::Boolean->False
+
+Creates a new Boolean object with a false value.
+
+=head2 value()
+
+Returns true or false.
+
+=head2 to_literal()
+
+Returns the string "true" or "false".
+
+=cut

lib/XML/LibXML/Literal.pm

+# $Id$
+
+package XML::LibXML::Literal;
+use XML::LibXML::Boolean;
+use XML::LibXML::Number;
+use strict;
+
+use overload 
+		'""' => \&value,
+		'cmp' => \&cmp;
+
+sub new {
+	my $class = shift;
+	my ($string) = @_;
+	
+#	$string =~ s/&quot;/"/g;
+#	$string =~ s/&apos;/'/g;
+	
+	bless \$string, $class;
+}
+
+sub as_string {
+	my $self = shift;
+	my $string = $$self;
+	$string =~ s/'/&apos;/g;
+	return "'$string'";
+}
+
+sub as_xml {
+    my $self = shift;
+    my $string = $$self;
+    return "<Literal>$string</Literal>\n";
+}
+
+sub value {
+	my $self = shift;
+	$$self;
+}
+
+sub cmp {
+	my $self = shift;
+	my ($cmp, $swap) = @_;
+	if ($swap) {
+		return $cmp cmp $$self;
+	}
+	return $$self cmp $cmp;
+}
+
+sub evaluate {
+	my $self = shift;
+	$self;
+}
+
+sub to_boolean {
+	my $self = shift;
+	return (length($$self) > 0) ? XML::LibXML::Boolean->True : XML::LibXML::Boolean->False;
+}
+
+sub to_number { return XML::LibXML::Number->new($_[0]->value); }
+sub to_literal { return $_[0]; }
+
+sub string_value { return $_[0]->value; }
+
+1;
+__END__
+
+=head1 NAME
+
+XML::LibXML::Literal - Simple string values.
+
+=head1 DESCRIPTION
+
+In XPath terms a Literal is what we know as a string.
+
+=head1 API
+
+=head2 new($string)
+
+Create a new Literal object with the value in $string. Note that &quot; and
+&apos; will be converted to " and ' respectively. That is not part of the XPath
+specification, but I consider it useful. Note though that you have to go
+to extraordinary lengths in an XML template file (be it XSLT or whatever) to
+make use of this:
+
+	<xsl:value-of select="&quot;I'm feeling &amp;quot;sad&amp;quot;&quot;"/>
+
+Which produces a Literal of:
+
+	I'm feeling "sad"
+
+=head2 value()
+
+Also overloaded as stringification, simply returns the literal string value.
+
+=head2 cmp($literal)
+
+Returns the equivalent of perl's cmp operator against the given $literal.
+
+=cut

lib/XML/LibXML/NodeList.pm

+# $Id$
+
+package XML::LibXML::NodeList;
+use strict;
+use XML::LibXML::Boolean;
+use XML::LibXML::Literal;
+use XML::LibXML::Number;
+
+use overload 
+		'""' => \&to_literal,
+                'bool' => \&to_boolean,
+        ;
+
+sub new {
+	my $class = shift;
+	bless [@_], $class;
+}
+
+sub pop {
+	my $self = CORE::shift;
+	CORE::pop @$self;
+}
+
+sub push {
+	my $self = CORE::shift;
+	CORE::push @$self, @_;
+}
+
+sub append {
+	my $self = CORE::shift;
+	my ($nodelist) = @_;
+	CORE::push @$self, $nodelist->get_nodelist;
+}
+
+sub shift {
+	my $self = CORE::shift;
+	CORE::shift @$self;
+}
+
+sub unshift {
+	my $self = CORE::shift;
+	CORE::unshift @$self, @_;
+}
+
+sub prepend {
+	my $self = CORE::shift;
+	my ($nodelist) = @_;
+	CORE::unshift @$self, $nodelist->get_nodelist;
+}
+
+sub size {
+	my $self = CORE::shift;
+	scalar @$self;
+}
+
+sub get_node { # uses array index starting at 1, not 0
+	my $self = CORE::shift;
+	my ($pos) = @_;
+	$self->[$pos - 1];
+}
+
+*item = \&get_node;
+
+sub get_nodelist {
+	my $self = CORE::shift;
+	@$self;
+}
+
+sub to_boolean {
+	my $self = CORE::shift;
+	return (@$self > 0) ? XML::LibXML::Boolean->True : XML::LibXML::Boolean->False;
+}
+
+# string-value of a nodelist is the string-value of the first node
+sub string_value {
+	my $self = CORE::shift;
+	return '' unless @$self;
+	return $self->[0]->string_value;
+}
+
+sub to_literal {
+	my $self = CORE::shift;
+	return XML::LibXML::Literal->new(
+			join('', map { $_->string_value } @$self)
+			);
+}
+
+sub to_number {
+	my $self = CORE::shift;
+	return XML::LibXML::Number->new(
+			$self->to_literal
+			);
+}
+
+1;
+__END__
+
+=head1 NAME
+
+XML::LibXML::NodeList - a list of XML document nodes
+
+=head1 DESCRIPTION
+
+An XML::LibXML::NodeList object contains an ordered list of nodes, as
+detailed by the W3C DOM documentation of Node Lists.
+
+=head1 SYNOPSIS
+
+  my $results = $dom->findnodes('//somepath');
+  foreach my $context ($results->get_nodelist) {
+    my $newresults = $context->findnodes('./other/element');
+    ...
+  }
+
+=head1 API
+
+=head2 new()
+
+You will almost never have to create a new NodeSet object, as it is all
+done for you by XPath.
+
+=head2 get_nodelist()
+
+Returns a list of nodes, the contents of the node list, as a perl list.
+
+=head2 string_value()
+
+Returns the string-value of the first node in the list.
+See the XPath specification for what "string-value" means.
+
+=head2 to_literal()
+
+Returns the concatenation of all the string-values of all
+the nodes in the list.
+
+=head2 get_node($pos)
+
+Returns the node at $pos. The node position in XPath is based at 1, not 0.
+
+=head2 size()
+
+Returns the number of nodes in the NodeSet.
+
+=head2 pop()
+
+Equivalent to perl's pop function.
+
+=head2 push(@nodes)
+
+Equivalent to perl's push function.
+
+=head2 append($nodelist)
+
+Given a nodelist, appends the list of nodes in $nodelist to the end of the
+current list.
+
+=head2 shift()
+
+Equivalent to perl's shift function.
+
+=head2 unshift(@nodes)
+
+Equivalent to perl's unshift function.
+
+=head2 prepend($nodeset)
+
+Given a nodelist, prepends the list of nodes in $nodelist to the front of
+the current list.
+
+=cut

lib/XML/LibXML/Number.pm

+# $Id$
+
+package XML::LibXML::Number;
+use XML::LibXML::Boolean;
+use XML::LibXML::Literal;
+use strict;
+
+use overload
+        '""' => \&value,
+        '0+' => \&value,
+        '<=>' => \&cmp;
+
+sub new {
+    my $class = shift;
+    my $number = shift;
+    if ($number !~ /^\s*(\d+(\.\d*)?|\.\d+)\s*$/) {
+        $number = undef;
+    }
+    else {
+        $number =~ s/^\s*(.*)\s*$/$1/;
+    }
+    bless \$number, $class;
+}
+
+sub as_string {
+    my $self = shift;
+    defined $$self ? $$self : 'NaN';
+}
+
+sub as_xml {
+    my $self = shift;
+    return "<Number>" . (defined($$self) ? $$self : 'NaN') . "</Number>\n";
+}
+
+sub value {
+    my $self = shift;
+    $$self;
+}
+
+sub cmp {
+    my $self = shift;
+    my ($other, $swap) = @_;
+    if ($swap) {
+        return $other <=> $$self;
+    }
+    return $$self <=> $other;
+}
+
+sub evaluate {
+    my $self = shift;
+    $self;
+}
+
+sub to_boolean {
+    my $self = shift;
+    return $$self ? XML::LibXML::Boolean->True : XML::LibXML::Boolean->False;
+}
+
+sub to_literal { XML::LibXML::Literal->new($_[0]->as_string); }
+sub to_number { $_[0]; }
+
+sub string_value { return $_[0]->value }
+
+1;
+__END__
+
+=head1 NAME
+
+XML::LibXML::Number - Simple numeric values.
+
+=head1 DESCRIPTION
+
+This class holds simple numeric values. It doesn't support -0, +/- Infinity,
+or NaN, as the XPath spec says it should, but I'm not hurting anyone I don't think.
+
+=head1 API
+
+=head2 new($num)
+
+Creates a new XML::LibXML::Number object, with the value in $num. Does some
+rudimentary numeric checking on $num to ensure it actually is a number.
+
+=head2 value()
+
+Also as overloaded stringification. Returns the numeric value held.
+
+=cut
   
     # first very simple path starting at root
     my @list   = $elem->findnodes( "species" );
-    ok( scalar(@list) == 3 );
+    ok( scalar(@list), 3 );
 
     # a simple query starting somewhere ...
     my $node = $list[0];
     my @slist = $node->findnodes( "humps" );
-    ok( scalar(@slist) == 1 );
+    ok( scalar(@slist), 1 );
 
     # find a single node
     @list   = $elem->findnodes( "species[\@name='Llama']" );
-    ok( scalar( @list ) == 1 );
+    ok( scalar( @list ), 1 );
   
     # find with not conditions
     @list   = $elem->findnodes( "species[\@name!='Llama']/disposition" );
-    ok( scalar(@list) == 2 );
+    ok( scalar(@list), 2 );
 
 
     @list   = $elem->findnodes( 'species/@name' );
 
     my $x = XML::LibXML::Text->new( 1234 );
     if( defined $x ) {
-        ok( $x->getData() eq "1234" );
+        ok( $x->getData(), "1234" );
     }
     
     my $telem = $dom->createElement('test');
     $telem->appendWellBalancedChunk('<b>c</b>');
 
     $telem->iterator( sub { $itervar.=$_[0]->getName(); } );
-    ok( $itervar eq 'testbtext' );
+    ok( $itervar, 'testbtext' );
 
 }
+use Test;
+BEGIN { plan tests => 11 }
+use XML::LibXML;
+use IO::Handle;
+ok(1);
+
+my $dom = XML::LibXML->new->parse_fh(*DATA);
+ok($dom);
+
+my @nodelist = $dom->findnodes('//BBB');
+ok(scalar(@nodelist), 5);
+
+my $nodelist = $dom->findnodes('//BBB');
+ok($nodelist->size, 5);
+
+ok($nodelist->string_value, "OK"); # first node in set
+
+ok($nodelist->to_literal, "OKNOT OK");
+
+ok($dom->findvalue("//BBB"), "OKNOT OK");
+
+ok(ref($dom->find("1 and 2")), "XML::LibXML::Boolean");
+
+ok(ref($dom->find("'Hello World'")), "XML::LibXML::Literal");
+
+ok(ref($dom->find("32 + 13")), "XML::LibXML::Number");
+
+ok(ref($dom->find("//CCC")), "XML::LibXML::NodeList");
+
+__DATA__
+<AAA>
+<BBB>OK</BBB>
+<CCC/>
+<BBB/>
+<DDD><BBB/></DDD>
+<CCC><DDD><BBB/><BBB>NOT OK</BBB></DDD></CCC>
+</AAA>
  * libxml2.
  **/
 
+xmlXPathObjectPtr
+domXPathFind( xmlNodePtr refNode, xmlChar * path ) {
+    xmlNodeSetPtr rv ;
+    xmlXPathObjectPtr res = NULL;
+  
+    rv = xmlXPathNodeSetCreate( 0 );
+  
+    if ( refNode != NULL && refNode->doc != NULL && path != NULL ) {
+        /* we can only do a path in a valid document! 
+         */
+        xmlXPathContextPtr ctxt;
+        xmlXPathCompExprPtr comp;
+    
+        /* prepare the xpath context */
+        ctxt = xmlXPathNewContext( refNode->doc );
+        ctxt->node = refNode;
+    
+        comp = xmlXPathCompile( path );
+        if (comp != NULL) {
+            res = xmlXPathCompiledEval(comp, ctxt);
+            xmlXPathFreeCompExpr(comp);
+        }
+        
+        xmlXPathFreeContext(ctxt);
+    }
+    return res;
+}
+
 xmlNodeSetPtr
 domXPathSelect( xmlNodePtr refNode, xmlChar * path ) {
-  xmlNodeSetPtr rv ;
+    xmlNodeSetPtr rv ;
+    xmlXPathObjectPtr res;
   
-  rv = xmlXPathNodeSetCreate( 0 );
-  
-  if ( refNode != 0 && refNode->doc != 0 && path != 0 ) {
-    /* we can only do a path in a valid document! 
-     */
-    xmlXPathObjectPtr res;
-    xmlXPathContextPtr ctxt;
-    xmlXPathCompExprPtr comp;
+    rv = xmlXPathNodeSetCreate( 0 );
+    
+    res = domXPathFind( refNode, path );
+    
+    if (res != NULL) {
+            /* here we have to transfer the result from the internal
+               structure to the return value */
+        	/* get the result from the query */
+        	/* we have to unbind the nodelist, so free object can 
+        	   not kill it */
+        rv = res->nodesetval;  
+        res->nodesetval = 0 ;
+    
+    }
 
-    /* prepare the xpath context */
-    ctxt = xmlXPathNewContext( refNode->doc );
-    ctxt->node = refNode;
+    xmlXPathFreeObject(res);
 
-    comp = xmlXPathCompile( path );
-    if (comp != NULL) {
-      res = xmlXPathCompiledEval(comp, ctxt);
-      xmlXPathFreeCompExpr(comp);
-
-      /* here we have to transfer the result from the internal
-         structure to the return value */
-      if ( res != NULL ) {
-	/* get the result from the query */
-	/* we have to unbind the nodelist, so free object can 
-	   not kill it */
-	rv = res->nodesetval;  
-	res->nodesetval = 0 ;
-
-      }
-    }
-    else {
-      res = NULL;
-    }
-    
-    xmlXPathFreeObject(res);
-    xmlXPathFreeContext(ctxt);
-  }
-
-  return rv;
+    return rv;
 }
 xmlNodeSetPtr
 domXPathSelect( xmlNodePtr refNode, xmlChar * xpathstring );
 
+xmlXPathObjectPtr
+domXPathFind( xmlNodePtr refNode, xmlChar * xpathstring );
 
 #endif
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.