diff Lib/IMPL/DOM/Schema/Validator/Compare.pm @ 104:196bf443b5e1

DOM::Schema RC0 inflators support, validation and some other things, Minor and major fixes almost for everything. A 'Source' property of the ValidationErrors generated from a NodeSet or a NodeList is subject to change in the future.
author wizard
date Tue, 11 May 2010 02:42:59 +0400
parents
children c6fb6964de4c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/IMPL/DOM/Schema/Validator/Compare.pm	Tue May 11 02:42:59 2010 +0400
@@ -0,0 +1,237 @@
+package IMPL::DOM::Schema::Validator::Compare;
+use strict;
+
+use base qw(IMPL::DOM::Schema::Validator);
+
+use IMPL::Resources::Format qw(FormatMessage);
+use IMPL::Class::Property;
+
+BEGIN {
+	public property targetProperty => prop_all;
+	public property op => prop_all;
+	public property nodePath => prop_get | owner_set;
+	public property optional => prop_all;
+	private property _pathTranslated => prop_all;
+	private property _targetNode => prop_all;
+	public property message => prop_all;
+}
+
+our %CTOR = (
+	'IMPL::DOM::Schema::Validator' => sub {
+		my %args = @_;
+		$args{nodeName} ||= 'Compare';
+		%args;
+	}
+);
+
+our %Ops = (
+	'='  => \&_equals,
+	'eq' => \&_equalsString,
+	'!=' => \&_notEquals,
+	'ne' => \&_notEqualsString,
+	'=~' => \&_matchRx,
+	'!~' => \&_notMatchRx,
+	'<'  => \&_less,
+	'>'  => \&_greater,
+	'lt' => \&_lessString,
+	'gt' => \&_greaterString
+);
+
+my $rxOps = map qr/$_/, join( '|', keys %Ops );
+
+sub CTOR {
+	my ($this,%args) = @_;
+	
+	$this->targetProperty($args{targetProperty} || 'nodeValue');
+	$this->op( $Ops{ $args{op} || '=' } ) or die new IMPL::InvalidArgumentException("Invalid parameter value",'op',$args{op},$this->path);
+	$this->nodePath($args{nodePath}) or die new IMPL::InvalidArgumentException("The argument is required", 'nodePath', $this->path);
+	$this->message($args{message} || 'The value of %Node.path% %Source.op% %Value% (%Source.nodePath%)' );
+	$this->optional($args{optional}) if $args{optional};
+}
+
+sub TranslatePath {
+	my ($this,$path) = @_;
+	
+	$path ||= '';
+	
+	my @selectQuery;
+	
+	my $i = 0;
+	
+	foreach my $chunk (split /\//,$path) {
+		$chunk = 'document:*' if $i == 0 and not length $chunk;
+		next if not length $chunk;
+		
+		my $query;		
+		my ($axis,$filter) = ( $chunk =~ /^(?:(\w+):)?(.*)$/);
+		
+		if ($filter =~ /^\w+|\*$/ ) {
+			$query = $filter eq '*' ? undef : $filter;
+		} elsif ( $filter =~ /^(\w+|\*)\s*((?:\[\s*\w+\s*(?:=|!=|=~|!~|eq|ne|lt|gt|)\s*["'](?:[\\'"]|\\[\\"'])*["']\])+)$/) {
+			my ($nodeName,$filterArgs) = ($1,$2);
+			
+			my @parsedFilters = map {
+				my ($prop,$op,$value) = ($_ =~ /\s*(\w+)\s*(=|!=|=~|!~)\s*(["'](?:[\\'"]|\\[\\"'])*["'])/);
+				$value =~ s/\\[\\'"]/$1/g;
+				{
+					prop => $prop,
+					op => $Ops{$op},
+					value => $value
+				}
+			} grep ( $_, split ( /[\]\[]+/,$filterArgs ) );
+			
+			$query = sub {
+				my ($node) = shift;
+				
+				$node->nodeName eq $nodeName or return 0 if $nodeName ne '*';
+				$_->{op}->(
+						_resovleProperty($node,$_->{prop}),
+						FormatMessage($_->{value},{
+							Schema => $this->parentNode,
+							Node => $this->_targetNode
+						},\&_resovleProperty)
+					) or return 0 foreach @parsedFilters;
+				
+			};
+		} else {
+			die new IMPL::Exception("Invalid query syntax",$path,$chunk);
+		}
+		
+		push @selectQuery, $axis ? { $axis => $query } : $query;
+		
+		$i++;
+	}
+	
+	return \@selectQuery;
+}
+
+sub Validate {
+	my ($this,$node,$ctx) = @_;
+	
+	my @result;
+	
+	$this->_targetNode($node);
+	
+	my $query = $this->_pathTranslated() || $this->_pathTranslated($this->TranslatePath($this->nodePath));
+	
+	my ($foreignNode) = $node->selectPath($query);
+	
+	my $Source = $ctx && $ctx->{Source} || $this->parentNode;
+	
+	if ($foreignNode) {
+		my $value = $this->nodeValue;
+		
+		if ($value) {
+			$value = FormatMessage($value, { Schema => $this->parentNode, Node => $this->_targetNode, ForeignNode => $foreignNode },\&_resovleProperty);
+		} else {
+			$value = $foreignNode->nodeValue;
+		}
+		
+		push @result, new IMPL::DOM::Schema::ValidationError(
+			Node => $node,
+			ForeignNode => $foreignNode,
+			Value => $value,
+			Source => $Source,
+			Schema => $this->parentNode,
+			Message => $this->message
+		) unless $this->op->(_resovleProperty($node,$this->targetProperty),$value);
+	} elsif (not $this->optional) {
+		push @result,  new IMPL::DOM::Schema::ValidationError(
+			Node => $node,
+			Value => '',
+			Source => $Source,
+			Schema => $this->parentNode,
+			Message => $this->message
+		);
+	}
+	
+	$this->_targetNode(undef);
+	
+	return @result;
+}
+
+sub _resovleProperty {
+	my ($node,$prop) = @_;
+	
+	return $node->can($prop) ? $node->$prop() : $node->nodeProperty($prop);
+}
+
+sub _matchRx {
+	$_[0] =~ $_[1];
+}
+
+sub _notMatchRx {
+	$_[0] !~ $_[1];
+}
+
+sub _equals {
+	$_[0] == $_[1];
+}
+
+sub _notEquals {
+	$_[0] != $_[0];
+}
+
+sub _equalsString {
+	$_[0] eq $_[1];
+}
+
+sub _notEqualsString {
+	$_[0] ne $_[1];
+}
+
+sub _less {
+	$_[0] < $_[1];
+}
+
+sub _greater {
+	$_[0] > $_[1];
+}
+
+sub _lessString {
+	$_[0] lt $_[1];
+}
+
+sub _greaterString {
+	$_[0] gt $_[1];
+}
+
+sub _lessEq {
+	$_[0] <= $_[1];
+}
+
+sub _greaterEq {
+	$_[0] >= $_[1];
+}
+
+1;
+
+__END__
+
+=pod
+
+=head1 NAME
+
+C<IMPL::DOM::Schema::Validator::Compare> - ограничение на содержимое текущего узла,
+сравнивая его со значением другого узла.
+
+=head1 SYNOPSIS
+
+Пример типа описания поля с проверочным полем
+
+=begin code xml
+
+<schema>
+	<SimpleType type="retype_field">
+		<Property name="linkedNode" message="Для узла %Node.nodeName% необходимо задать свойство %Source.name%"/>
+		<Compare op="eq" nodePath="sibling:*[nodeName eq '%Node.linkedNode%']"/>
+	</SimpleType>
+</schema>
+
+=begin code xml
+
+=head1 DESCRIPTION
+
+Позволяет сравнивать значение текущего узла со значением другого узла. 
+
+=cut
\ No newline at end of file