Mercurial > pub > Impl
changeset 148:e6447ad85cb4
DOM objects now have a schema and schemaSource properties
RegExp now can launder data
Improved post to DOM transformation (multiple values a now supported)
Added new axes to navigation queries: ancestor and descendant
minor changes and bug fixes
author | wizard |
---|---|
date | Mon, 16 Aug 2010 08:26:44 +0400 |
parents | c2aa10fbb396 |
children | b04e978d6d5a |
files | Lib/IMPL/DOM/Document.pm Lib/IMPL/DOM/Navigator/Builder.pm Lib/IMPL/DOM/Node.pm Lib/IMPL/DOM/Schema/Node.pm Lib/IMPL/DOM/Schema/Validator/RegExp.pm Lib/IMPL/DOM/Transform.pm Lib/IMPL/DOM/Transform/PostToDOM.pm Lib/IMPL/Object.pm Lib/IMPL/Object/Autofill.pm Lib/IMPL/Resources/Format.pm Lib/IMPL/Transform.pm Lib/IMPL/Web/Application/ControllerUnit.pm Lib/IMPL/Web/DOM/FileNode.pm Lib/IMPL/Web/QueryHandler/UrlController.pm Lib/IMPL/Web/TT/Form.pm Lib/IMPL/base.pm _test/Test/DOM/Node.pm |
diffstat | 17 files changed, 482 insertions(+), 34 deletions(-) [+] |
line wrap: on
line diff
--- a/Lib/IMPL/DOM/Document.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Document.pm Mon Aug 16 08:26:44 2010 +0400 @@ -13,6 +13,12 @@ sub Create { my ($this,$nodeName,$class,$refProps) = @_; + if ( ref $class eq 'HASH' ) { + $refProps = $class; + $class = undef; + } + + $class ||= typeof IMPL::DOM::Node; $refProps ||= {}; delete $refProps->{nodeName};
--- a/Lib/IMPL/DOM/Navigator/Builder.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Navigator/Builder.pm Mon Aug 16 08:26:44 2010 +0400 @@ -33,7 +33,11 @@ if (my $schemaNode = $this->{$_schemaNavi}->NavigateName($nodeName)) { my $class = $schemaNode->can('nativeType') ? $schemaNode->nativeType || 'IMPL::DOM::Node' : 'IMPL::DOM::Node'; + my $schemaSource = $this->{$_schemaNavi}->SourceSchemaNode; + my @errors = $this->inflateProperties($schemaNode,\%props); + $props{schema} = $schemaNode; + $props{schemaSource} = $schemaSource; my $node; if (! $this->{$Document}) { @@ -51,7 +55,7 @@ map { IMPL::DOM::Schema::ValidationError->new( Node => $node, - Source => $this->{$_schemaNavi}->SourceSchemaNode, + Source => $schemaSource, Schema => $schemaNode, Message => $schemaNode->messageInflateError, Error => $_ @@ -171,12 +175,12 @@ создания экземпляра и созданный узел доавляется в документ. При создании нового узла используется метод документа C<< IMPL::DOM::Document->Create >> -Свойства узла передаются при создании через параметр C<props>, но имя создаваемого +Свойства узла передаются при создании через параметр C<%props>, но имя создаваемого узла НЕ может быть переопределено свойством C<nodeName>, оно будет проигнорировано. =item C< Document > -Свойство, которое содержит документ по окончании процедурв построения. +Свойство, которое содержит документ по окончании процедуры построения. =back
--- a/Lib/IMPL/DOM/Node.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Node.pm Mon Aug 16 08:26:44 2010 +0400 @@ -17,7 +17,9 @@ public _direct property isComplex => { get => \&_getIsComplex } ; public _direct property nodeValue => prop_all; public _direct property childNodes => { get => \&_getChildNodes }; # prop_list - public _direct property parentNode => prop_get ; + public _direct property parentNode => prop_get | owner_set; + public _direct property schema => prop_get | owner_set; + public _direct property schemaSource => prop_get | owner_set; private _direct property _propertyMap => prop_all ; __PACKAGE__->class_data(property_bind => {}); @@ -27,7 +29,9 @@ parent => \&selectParent, siblings => \&selectSiblings, child => \&childNodes, - document => \&selectDocument + document => \&selectDocument, + ancestor => \&selectAncestors, + descendant => \&selectDescendant ); sub CTOR { @@ -290,6 +294,24 @@ } } +sub selectDescendant { + wantarray ? + map $_->selectAll(), $_[0]->childNodes : + [map $_->selectAll(), $_[0]->childNodes] +} + +sub selectAll { + map(selectAll($_),@{$_[0]->childNodes}) , $_[0] +} + +sub selectAncestors { + my $parent = $_[0]->parentNode; + + wantarray ? + ($parent ? ($parent->selectAncestors,$parent) : ()) : + [$parent ? ($parent->selectAncestors,$parent) : ()] +} + sub firstChild { @_ >=2 ? $_[0]->replaceNodeAt(0,$_[1]) : $_[0]->childNodes->[0]; } @@ -349,9 +371,11 @@ my $name = shift; if (my $method = $this->can($name)) { - return &$method($this,@_); + unshift @_,$this; + # use goto to preserve calling context + goto &$method; } - + # dynamic property if (@_) { # set return $this->{$_propertyMap}{$name} = shift; @@ -429,6 +453,20 @@ Ссылка на родительский элемент, если таковой имеется. +=item C<[get] schema> + +Ссылка на узел из C<IMPL::DOM::Schema>, представляющий схему данных текущего узла. Может быть C<undef>. + +=item C<[get] schema> + +Ссылка на узел из C<IMPL::DOM::Schema>, представляющий элемент схемы, объявляющий данный узел. Может быть C<undef>. + +Отличается от свойства C<schema> тем, что узел в случае ссылки на тип узла, данной свойство будет содержать +описание ссылки C<IMPL::DOM::Schema::Node>, а свойство C<schema> например будет ссылаться на +C<IMPL::DOM::Schema::ComplexType>. + +=back + =head2 METHODS =cut \ No newline at end of file
--- a/Lib/IMPL/DOM/Schema/Node.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Schema/Node.pm Mon Aug 16 08:26:44 2010 +0400 @@ -18,7 +18,20 @@ } our %CTOR = ( - 'IMPL::DOM::Node' => sub {my %args = @_; $args{nodeName} ||= 'Node'; %args} + 'IMPL::DOM::Node' => sub { + my %args = @_; + delete @args{qw( + minOccur + maxOccur + type + name + display + display_no + display_blame + )} ; + $args{nodeName} ||= 'Node'; + %args + } ); sub CTOR { @@ -28,9 +41,9 @@ $this->{$maxOccur} = defined $args{maxOccur} ? $args{maxOccur} : 1; $this->{$type} = $args{type}; $this->{$name} = $args{name} or die new IMPL::InvalidArgumentException('Argument is required','name'); - $this->{$display} = $args{display}; - $this->{$display_no} = $args{display_no}; - $this->{$display_blame} = $args{display_blame}; + $this->{$display} = $args{display} if $args{display}; + $this->{$display_no} = $args{display_no} if $args{display}; + $this->{$display_blame} = $args{display_blame} if $args{display}; } sub Validate { @@ -70,6 +83,48 @@ =head1 DESCRIPTION -Базовый класс для элементов схемы. +Базовый класс для элементов схемы. Также позволяет объявлять узлы определенного типа. + +=head1 MEMBERS + +=head2 PROPERTIES + +=over + +=item C<[get,set] minOccur> + +C<default: 1>. + +Минимальное количество повторений узла. + +=item C<[get,set] maxOccur> + +C<default: 1>. + +Максимальное количество повторений узла + +=item C<[get,set] type> + +C<default: undef> + +Имя типа из схемы. + +=item C<[get,set] name> + +Имя узла. + +=item C<[get,set] display> + +Имя узла для отображения. + +=item C<[get,set] display_no> + +Имя узла для отображения (родительный падеж). + +=item C<[get,set] display_blame> + +Имя узла для отображения (винительный падеж). + +=back =cut
--- a/Lib/IMPL/DOM/Schema/Validator/RegExp.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Schema/Validator/RegExp.pm Mon Aug 16 08:26:44 2010 +0400 @@ -14,6 +14,8 @@ BEGIN { public property message => prop_all; + public property launder => prop_all; + private property _rx => prop_all; } sub CTOR { @@ -25,13 +27,17 @@ sub Validate { my ($this,$node,$ctx) = @_; - my $rx = $this->nodeValue; + my $rx = $this->_rx() || $this->_rx( map qr{$_}, $this->nodeValue ); + return new IMPL::DOM::Schema::ValidationError( Node => $node, Source => $ctx && $ctx->{Source} || $this->parentNode, Schema => $this->parentNode, Message => $this->message - ) unless (not $node->isComplex) and $node->nodeValue =~ /$rx/; + ) unless (not $node->isComplex) and $node->nodeValue =~ /($rx)/; + + $node->nodeValue($1) if $this->launder; + return (); }
--- a/Lib/IMPL/DOM/Transform.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Transform.pm Mon Aug 16 08:26:44 2010 +0400 @@ -28,6 +28,6 @@ =head1 DESCRIPTION -Преобразование для DOM документа, использует имя документа узла для применения подходящего преобразования. +Преобразование для DOM документа, использует имя узла для применения подходящего преобразования. =cut
--- a/Lib/IMPL/DOM/Transform/PostToDOM.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/DOM/Transform/PostToDOM.pm Mon Aug 16 08:26:44 2010 +0400 @@ -114,11 +114,26 @@ =begin code -my $transform = new IMPL::DOM::Transform::PostToDOM( - 'My::DOM::Document', - IMPL::DOM::Schema->LoadSchema('Data/user.add.schema.xml'), - 'myForm' -); + my $schema = IMPL::DOM::Schema->LoadSchema('Data/user.add.schema.xml'); + + my $transform = IMPL::DOM::Transform::PostToDOM->new( + undef, # default class + $schema, + $schema->selectSingleNode('ComplexNode')->name + ); + + my $doc = $transform->Transform( + CGI->new({ + 'user/login' => 'bob', + 'user/fullName' => 'Bob Marley', + 'user/password' => 'secret', + 'user/password_retype' => 'secret', + 'user/birthday' => '1978-12-17', + 'user/email[1]' => 'bob@marley.com', + 'user/email[2]' => 'bob.marley@google.com', + process => 1 + }) + ); =end code
--- a/Lib/IMPL/Object.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Object.pm Mon Aug 16 08:26:44 2010 +0400 @@ -25,7 +25,7 @@ =pod -=head1 SYNOPSIS +=head1 SINOPSYS =begin code
--- a/Lib/IMPL/Object/Autofill.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Object/Autofill.pm Mon Aug 16 08:26:44 2010 +0400 @@ -62,7 +62,7 @@ } else { my $fld = $prop_info->Implementor->FieldName($prop_info); if ($prop_info->Mutators & prop_list) { - $text .= "\t\$this->{$fld} = ref \$fields->{$name} ? \$fields->{$name} : [\$fields->{$name}] if exists \$fields->{$name};\n"; + $text .= "\t\$this->{$fld} = IMPL::Object::List->new ( ref \$fields->{$name} ? \$fields->{$name} : [\$fields->{$name}] ) if exists \$fields->{$name};\n"; } else { $text .= "\t\$this->{$fld} = \$fields->{$name} if exists \$fields->{$name};\n"; }
--- a/Lib/IMPL/Resources/Format.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Resources/Format.pm Mon Aug 16 08:26:44 2010 +0400 @@ -9,6 +9,7 @@ sub FormatMessage { my ($string,$args,$resolver) = @_; + $args ||= {}; $resolver ||= \&_defaultResolver; $string =~ s/%(\w+(?:\.\w+)*)%/_getvalue($args,$1,"\[$1\]",$resolver)/ge;
--- a/Lib/IMPL/Transform.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Transform.pm Mon Aug 16 08:26:44 2010 +0400 @@ -73,7 +73,7 @@ my $obj = new AnyObject; my $t = new Transform ( - AnyClass => sub { + SomeClass => sub { my ($this,$object) = @_; return new NewClass({ Name => $object->name, Document => $this->Transform($object->Data) }) },
--- a/Lib/IMPL/Web/Application/ControllerUnit.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Web/Application/ControllerUnit.pm Mon Aug 16 08:26:44 2010 +0400 @@ -72,11 +72,9 @@ } elsif (ref $info eq 'HASH') { die new IMPL::Exception("A schema must be specified",$self,$method) unless $info->{schema}; - $self->class_data(CONTROLLER_METHODS)->{$method} = { - wrapper => 'FormWrapper', - schema => $info->{schema}, - form => $info->{form} - }; + $info->{wrapper} = 'FormWrapper'; + + $self->class_data(CONTROLLER_METHODS)->{$method} = $info; } else { die new IMPL::Exception("Unsupported method information",$self,$method); }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/IMPL/Web/DOM/FileNode.pm Mon Aug 16 08:26:44 2010 +0400 @@ -0,0 +1,193 @@ +package IMPL::Web::DOM::FileNode; +use IMPL::base qw(IMPL::DOM::Node); + +__PACKAGE__->PassThroughArgs; + +use IMPL::Class::Property; +use File::Temp qw(tempfile); + +BEGIN { + public property parameterName => { + get => sub { + my ($this) = @_; + $this->_parameterName() or + $this->_parameterName( + join '/', ( map { + $_->nodeProperty('instanceId') ? + $_->nodeName . '['.$_->nodeProperty('instanceId').']': + $_->nodeName + } $this->_selectParents, $this ) + ); + } + }; + private property _parameterName => prop_all; + public property fileName => { + get => sub { + my ($this) = @_; + return $this->document->query->param($this->parameterName); + } + }; + public property fileHandle => { + get => sub { + my ($this) = @_; + return $this->document->query->upload($this->parameterName); + } + }; +} + +sub invokeTempFile { + my ($this,$sub,$target) = @_; + + die new IMPL::InvalidArgumentException("A reference to a function should be specified") unless $sub && ref $sub eq 'CODE'; + + $target ||= $this; + + my $query = $this->document->nodeProperty('query') or die new IMPL::InvalidOperationException("Failed to get a CGI query from the document"); + my $hFile = $query->upload($this->parameterName) or $query->cgi_error ? die new IMPL::IOException("Failed to open the uploaded file",$query->cgi_error) : return; + + my ($hTemp,$tempFileName) = tempfile(); + binmode($hTemp); + + print $hTemp $_ while <$hFile>; + + $hTemp->flush(); + seek $hTemp, 0,0; + { + local $_ = $tempFileName; + &$sub($this,$tempFileName,$hTemp); + } +} + +sub _selectParents { + my ($node) = @_; + + my @result; + + unshift @result, $node while $node = $node->parentNode; + + return @result; +} + +1; + +__END__ + +=pod + +=head1 NAME + +C<IMPL::Web::DOM::FileNode> - узел, использующийся для представления параметра запроса в котором передан файл. + +=head1 SINOPSYS + +=begin code xml + +<!-- input.schema.xml --> +<schema> + <SimpleType type="file" nativeType="IMPL::Web::DOM::FileNode"/> + <ComplexNode name="user"> + <Node type="file" name="avatar"/> + </ComplexNode> +</schema> + +=end code xml + +=begin code + +# handle.pl +use IMPL::DOM::Transform::PostToDOM (); +use IMPL::DOM::Schema; +use CGI; +use File::Copy qw(copy); + +my $t = new IMPL::DOM::Transform::PostToDOM( + undef, + IMPL::DOM::Schema->LoadSchema('input.schema.xml'), + 'user' +); + +my $doc = $t->Transform(CGI->new()); + +if ($t->Errors->Count) { + # handle errors +} + +$doc->selectSingleNode('avatar')->invokeTempFile( + sub { + my($node,$fname,$fhandle) = @_; + + # do smth with file + copy($_,'avatar.jpg'); + + # same thing + # copy($fname,'avatar.jpg'); + } +); + +=end code + +=head1 DESCRIPTION + +Данный класс используется для представлении параметров C<CGI> запросов при преобзаовании +запроса в ДОМ документ преобразованием C<IMPL::DOM::Transform::PostToDOM>. + +Узлы данного типа расширяют стандатрный C<IMPL::DOM::Node> несколькими свойствами и +методами для доступа к файлу, переданному в виде параметра запроса. + +=head1 MEMBERS + +=head2 PROPERTIES + +=over + +=item C<[get] parameterName> + +Имя параметра C<CGI> запроса соответствующего данному узлу. + +=item C<[get] fileName> + +Имя файла из параметра запроса + +=item C<[get] fileHandle> + +Указатель на файл из параметра запроса + +=back + +=head2 METHODS + +=over + +=item C<invokeTempFile($callback,$target)> + +Сохраняет файл, переданный в запросе во временный, вызывает C<$callback> для обработки временного файла. + +=over + +=item C<$callback> + +Ссылка на функцию которая будет вызвана для обработки временного файла. C<callback($target,$fname,$fhandle)> + +=over + +=item C<$fname> + +Имя временного файла + +=item C<$fhandle> + +Указатель на временный файл + +=back + +Также пременная C<$_> содержит имя временного файла. + +=item C<$target> + +Значение этого параметра будет передано первым параметром функции C<$callback>. + +=back + +=back + +=cut \ No newline at end of file
--- a/Lib/IMPL/Web/QueryHandler/UrlController.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Web/QueryHandler/UrlController.pm Mon Aug 16 08:26:44 2010 +0400 @@ -5,6 +5,7 @@ use IMPL::Class::Property; use IMPL::Exception; use Carp qw(croak); +use Scalar::Util qw(tainted); BEGIN { public property namespace => prop_all; @@ -20,10 +21,18 @@ my @target = grep $_, split /\//, ($ENV{PATH_INFO} || '') or die new IMPL::Exception("No target specified"); my $method = pop @target; - $method =~ s/\.\w+$//; + if ( $method =~ /^(\w+)/ ) { + $method = $1; + } else { + die new IMPL::Exception("Invalid method name",$method); + } + + (/^(\w+)$/ or die new IMPL::Exception("Invalid module name part", $_)) and $_=$1 foreach @target; my $module = join '::',$namespace,@target; + die new IMPL::Exception("A module name is untrusted", $module) if tainted($module); + eval "require $module; 1;" unless eval{ $module->can('InvokeAction'); }; if (my $err = $@ ) { die new IMPL::Exception("Failed to load module",$module,$err);
--- a/Lib/IMPL/Web/TT/Form.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/Web/TT/Form.pm Mon Aug 16 08:26:44 2010 +0400 @@ -4,7 +4,8 @@ use base qw(IMPL::Web::TT::Control); use IMPL::Class::Property; -use IMPL::DOM::Navigator::SchemaNavigator; +use IMPL::DOM::Navigator::SchemaNavigator(); + __PACKAGE__->PassThroughArgs; BEGIN { @@ -39,6 +40,97 @@ $this->errors([]) unless $this->errors; } +sub fillContents { + my ($this) = @_; + + my $schema = $this->schema->selectSingleNode(sub { $_->nodeName eq 'ComplexNode' and $_->name eq $this->base }); + + $this->buildContainer( + $schema, + $schema, + $this->data->isComplex ? $this->data : undef, + $this + ); +} + +sub buildContainer { + my ($this,$schemaSource,$schema,$domNode,$container,$path) = @_; + + $path = [@{$path || []},{node => $domNode, schemaSource => $schemaSource}]; + + $container ||= $this->document->Create($schemaSource->name,'IMPL::Web::TT::Collection'); + + foreach my $schemaItem ( $schema->content->childNodes ) { + my $schemaItemSource = $schemaItem; + + $schemaItem = $this->schema->resolveType($schemaItem->type) + if typeof $schemaItem eq typeof IMPL::DOM::Schema::Node; + + my @nodesData = $domNode->selectNodes(sub { $_->schemaSource == $schemaItemSource } ) if $domNode; + + push @nodesData, undef unless @nodesData; + + if ($schemaItem->isa(typeof IMPL::DOM::Schema::ComplexNode) ) { + $this->appendChild( $this->buildContainer($schemaItemSource,$schemaItem,$_,undef,$path) ) foreach @nodesData; + } elsif ($schemaItem->isa(typeof IMPL::DOM::Schema::SimpleNode)) { + $this->appendChild( $this->buildControl($schemaItemSource,$schemaItem,$_,$path) ) foreach @nodesData; + } + } + + return $container; +} + +sub buildControl { + my ($this,$schemaSource,$schema,$node,$path) = @_; + + my @errors; + + if ($node) { + @errors = grep { ($_->Node || $_->Parent) == $node } @{$this->errors}; + } else { + @errors = grep $_->Schema == $schemaSource, @{$this->errors}; + } + + return $this->document->CreateControl( + $schemaSource->name, + $this->mapType($schemaSource), + { + schema => $schema, + sourceSchema => $schemaSource, + errors => \@errors, + data => $node, + nodeValue => $node && $node->nodeValue, # small hack set a non dom class property through + queryParameter => $this->makeParameterName([@$path,{ node => $node, schemaSource => $schemaSource}]) + } + ); +} + +sub mapType { + my ($this,$schema) = @_; + + $schema->nodeProperty('control') || + ( $schema->type && $this->schema->resolveType($schema->type)->nodeProperty('control') ) + or die new IMPL::Exception("Unable to get control class for the form element",$schema->path); +} + +sub makeParameterName { + my ($this,$path) = @_; + + join '/', map { + $_->{node} ? + ( + $_->{node}->nodeProperty('instanceId') ? + $_->{node}->nodeName . '['. ']' : + $_->{node}->nodeName + ) : + ( + $_->{schemaSource}->maxOccur eq 'unbounded' || $_->{schemaSource}->maxOccur > 1 ? + $_->{schemaSource}->name . '[0]' : + $_->{schemaSource}->name + ) + } @$path; +} + sub makeControlArgs{ my ($this,$path) = @_; @@ -107,9 +199,7 @@ } } - 1; - __END__ =pod
--- a/Lib/IMPL/base.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/Lib/IMPL/base.pm Mon Aug 16 08:26:44 2010 +0400 @@ -14,7 +14,7 @@ undef $!; undef $@; $loaded{$baseClass} = 1; - eval "require $baseClass;"; + eval "require $baseClass; 1;"; die $@ if $@ and not $!; }
--- a/_test/Test/DOM/Node.pm Mon Aug 09 08:45:36 2010 +0400 +++ b/_test/Test/DOM/Node.pm Mon Aug 16 08:26:44 2010 +0400 @@ -45,6 +45,15 @@ failed "document property returned incorrect value" unless $child->document == $this->Root; }; +test DocumentCreateNode => sub { + my ($this) = @_; + + my $child = $this->Root->firstChild->appendNode($this->Root->Create(Info => { uuid => '77f9-9a-6d58' } )) or failed "Failed to append a child node"; + + failed "document property is undef" unless $child->document; + failed "document property returned incorrect value" unless $child->document == $this->Root; +}; + test MoveNode => sub { my ($this) = @_; @@ -94,6 +103,30 @@ unless @result == 2; }; +test SelectNodesPath => sub { + my ($this) = @_; + + my @result = $this->Root->selectNodes('Child','Info'); + + failed "Failed to select a node by path 'Child/Info'" unless @result; +}; + +test SelectByAxisDescendant => sub { + my ($this) = @_; + + my @result = $this->Root->selectNodes( { descendant => ['GrandChild','Info']} ); + + failed "Failed to select a node by path '//(GrandChild|Info)/'" unless @result == 2; +}; + +test SelectByAxisAncestor => sub { + my ($this) = @_; + + my @result = $this->Root->selectSingleNode( { descendant => 'Info'} )->selectNodes( { ancestor => undef } ) ; + + failed "Failed to select a node by path '//Info/ancestor:*'" unless @result == 2; +}; + test CheckNodesValues => sub { my ($this) = @_; @@ -118,7 +151,7 @@ my ($this) = @_; failed "property isComplex returned false for the root node" unless $this->Root->isComplex; - failed "property isComplex returned true for a simple node", $this->Root->firstChild->nodeName if $this->Root->firstChild->isComplex; + failed "property isComplex returned true for a simple node", $this->Root->selectSingleNode('Item')->childNodes->Count if $this->Root->selectSingleNode('Item')->isComplex; }; test setObjectProperty => sub {