# $Id: Function.pm,v 1.26 2002/12/26 17:24:50 matt Exp $ package XML::XPath::Function; use XML::XPath::Number; use XML::XPath::Literal; use XML::XPath::Boolean; use XML::XPath::NodeSet; use XML::XPath::Node::Attribute; use strict; sub new { my $class = shift; my ($pp, $name, $params) = @_; bless { pp => $pp, name => $name, params => $params }, $class; } sub as_string { my $self = shift; my $string = $self->{name} . "("; my $second; foreach (@{$self->{params}}) { $string .= "," if $second++; $string .= $_->as_string; } $string .= ")"; return $string; } sub as_xml { my $self = shift; my $string = "{name}\""; my $params = ""; foreach (@{$self->{params}}) { $params .= "" . $_->as_string . "\n"; } if ($params) { $string .= ">\n$params\n"; } else { $string .= " />\n"; } return $string; } sub evaluate { my $self = shift; my $node = shift; if ($node->isa('XML::XPath::NodeSet')) { $node = $node->get_node(1); } my @params; foreach my $param (@{$self->{params}}) { my $results = $param->evaluate($node); push @params, $results; } $self->_execute($self->{name}, $node, @params); } sub _execute { my $self = shift; my ($name, $node, @params) = @_; $name =~ s/-/_/g; no strict 'refs'; $self->$name($node, @params); } # All functions should return one of: # XML::XPath::Number # XML::XPath::Literal (string) # XML::XPath::NodeSet # XML::XPath::Boolean ### NODESET FUNCTIONS ### sub last { my $self = shift; my ($node, @params) = @_; die "last: function doesn't take parameters\n" if (@params); return XML::XPath::Number->new($self->{pp}->get_context_size); } sub position { my $self = shift; my ($node, @params) = @_; if (@params) { die "position: function doesn't take parameters [ ", @params, " ]\n"; } # return pos relative to axis direction return XML::XPath::Number->new($self->{pp}->get_context_pos); } sub count { my $self = shift; my ($node, @params) = @_; die "count: Parameter must be a NodeSet\n" unless $params[0]->isa('XML::XPath::NodeSet'); return XML::XPath::Number->new($params[0]->size); } sub id { my $self = shift; my ($node, @params) = @_; die "id: Function takes 1 parameter\n" unless @params == 1; my $results = XML::XPath::NodeSet->new(); if ($params[0]->isa('XML::XPath::NodeSet')) { # result is the union of applying id() to the # string value of each node in the nodeset. foreach my $node ($params[0]->get_nodelist) { my $string = $node->string_value; $results->append($self->id($node, XML::XPath::Literal->new($string))); } } else { # The actual id() function... my $string = $self->string($node, $params[0]); $_ = $string->value; # get perl scalar my @ids = split; # splits $_ foreach my $id (@ids) { if (my $found = $node->getElementById($id)) { $results->push($found); } } } return $results; } sub local_name { my $self = shift; my ($node, @params) = @_; if (@params > 1) { die "name() function takes one or no parameters\n"; } elsif (@params) { my $nodeset = shift(@params); $node = $nodeset->get_node(1); } return XML::XPath::Literal->new($node->getLocalName); } sub namespace_uri { my $self = shift; my ($node, @params) = @_; die "namespace-uri: Function not supported\n"; } sub name { my $self = shift; my ($node, @params) = @_; if (@params > 1) { die "name() function takes one or no parameters\n"; } elsif (@params) { my $nodeset = shift(@params); $node = $nodeset->get_node(1); } return XML::XPath::Literal->new($node->getName); } ### STRING FUNCTIONS ### sub string { my $self = shift; my ($node, @params) = @_; die "string: Too many parameters\n" if @params > 1; if (@params) { return XML::XPath::Literal->new($params[0]->string_value); } # TODO - this MUST be wrong! - not sure now. -matt return XML::XPath::Literal->new($node->string_value); # default to nodeset with just $node in. } sub concat { my $self = shift; my ($node, @params) = @_; die "concat: Too few parameters\n" if @params < 2; my $string = join('', map {$_->string_value} @params); return XML::XPath::Literal->new($string); } sub starts_with { my $self = shift; my ($node, @params) = @_; die "starts-with: incorrect number of params\n" unless @params == 2; my ($string1, $string2) = ($params[0]->string_value, $params[1]->string_value); if (substr($string1, 0, length($string2)) eq $string2) { return XML::XPath::Boolean->True; } return XML::XPath::Boolean->False; } sub contains { my $self = shift; my ($node, @params) = @_; die "starts-with: incorrect number of params\n" unless @params == 2; my $value = $params[1]->string_value; if ($params[0]->string_value =~ /(.*?)\Q$value\E(.*)/) { # $1 and $2 stored for substring funcs below # TODO: Fix this nasty implementation! return XML::XPath::Boolean->True; } return XML::XPath::Boolean->False; } sub substring_before { my $self = shift; my ($node, @params) = @_; die "starts-with: incorrect number of params\n" unless @params == 2; if ($self->contains($node, @params)->value) { return XML::XPath::Literal->new($1); # hope that works! } else { return XML::XPath::Literal->new(''); } } sub substring_after { my $self = shift; my ($node, @params) = @_; die "starts-with: incorrect number of params\n" unless @params == 2; if ($self->contains($node, @params)->value) { return XML::XPath::Literal->new($2); } else { return XML::XPath::Literal->new(''); } } sub substring { my $self = shift; my ($node, @params) = @_; die "substring: Wrong number of parameters\n" if (@params < 2 || @params > 3); my ($str, $offset, $len); $str = $params[0]->string_value; $offset = $params[1]->value; $offset--; # uses 1 based offsets if (@params == 3) { $len = $params[2]->value; } return XML::XPath::Literal->new(substr($str, $offset, $len)); } sub string_length { my $self = shift; my ($node, @params) = @_; die "string-length: Wrong number of params\n" if @params > 1; if (@params) { return XML::XPath::Number->new(length($params[0]->string_value)); } else { return XML::XPath::Number->new( length($node->string_value) ); } } sub normalize_space { my $self = shift; my ($node, @params) = @_; die "normalize-space: Wrong number of params\n" if @params > 1; my $str; if (@params) { $str = $params[0]->string_value; } else { $str = $node->string_value; } $str =~ s/^\s*//; $str =~ s/\s*$//; $str =~ s/\s+/ /g; return XML::XPath::Literal->new($str); } sub translate { my $self = shift; my ($node, @params) = @_; die "translate: Wrong number of params\n" if @params != 3; local $_ = $params[0]->string_value; my $find = $params[1]->string_value; my $repl = $params[2]->string_value; eval "tr/\\Q$find\\E/\\Q$repl\\E/d, 1" or die $@; return XML::XPath::Literal->new($_); } ### BOOLEAN FUNCTIONS ### sub boolean { my $self = shift; my ($node, @params) = @_; die "boolean: Incorrect number of parameters\n" if @params != 1; return $params[0]->to_boolean; } sub not { my $self = shift; my ($node, @params) = @_; $params[0] = $params[0]->to_boolean unless $params[0]->isa('XML::XPath::Boolean'); $params[0]->value ? XML::XPath::Boolean->False : XML::XPath::Boolean->True; } sub true { my $self = shift; my ($node, @params) = @_; die "true: function takes no parameters\n" if @params > 0; XML::XPath::Boolean->True; } sub false { my $self = shift; my ($node, @params) = @_; die "true: function takes no parameters\n" if @params > 0; XML::XPath::Boolean->False; } sub lang { my $self = shift; my ($node, @params) = @_; die "lang: function takes 1 parameter\n" if @params != 1; my $lang = $node->findvalue('(ancestor-or-self::*[@xml:lang]/@xml:lang)[last()]'); my $lclang = lc($params[0]->string_value); # warn("Looking for lang($lclang) in $lang\n"); if (substr(lc($lang), 0, length($lclang)) eq $lclang) { return XML::XPath::Boolean->True; } else { return XML::XPath::Boolean->False; } } ### NUMBER FUNCTIONS ### sub number { my $self = shift; my ($node, @params) = @_; die "number: Too many parameters\n" if @params > 1; if (@params) { if ($params[0]->isa('XML::XPath::Node')) { return XML::XPath::Number->new( $params[0]->string_value ); } return $params[0]->to_number; } return XML::XPath::Number->new( $node->string_value ); } sub sum { my $self = shift; my ($node, @params) = @_; die "sum: Parameter must be a NodeSet\n" unless $params[0]->isa('XML::XPath::NodeSet'); my $sum = 0; foreach my $node ($params[0]->get_nodelist) { $sum += $self->number($node)->value; } return XML::XPath::Number->new($sum); } sub floor { my $self = shift; my ($node, @params) = @_; require POSIX; my $num = $self->number($node, @params); return XML::XPath::Number->new( POSIX::floor($num->value)); } sub ceiling { my $self = shift; my ($node, @params) = @_; require POSIX; my $num = $self->number($node, @params); return XML::XPath::Number->new( POSIX::ceil($num->value)); } sub round { my $self = shift; my ($node, @params) = @_; my $num = $self->number($node, @params); require POSIX; return XML::XPath::Number->new( POSIX::floor($num->value + 0.5)); # Yes, I know the spec says don't do this... } 1;