# HG changeset patch # User cin # Date 1386078936 -14400 # Node ID 608e74bc309f115de7856bfe366daebd86179590 # Parent 935629bf80df4d186c7d3fcbfe25a4196b260370 form metadata, mostly done diff -r 935629bf80df -r 608e74bc309f Lib/IMPL/Web/View/Metadata/BaseMeta.pm --- a/Lib/IMPL/Web/View/Metadata/BaseMeta.pm Mon Dec 02 17:44:38 2013 +0400 +++ b/Lib/IMPL/Web/View/Metadata/BaseMeta.pm Tue Dec 03 17:55:36 2013 +0400 @@ -6,7 +6,8 @@ use IMPL::declare { require => { Exception => 'IMPL::Exception', - ArgException => '-IMPL::InvalidArgumentException' + ArgException => '-IMPL::InvalidArgumentException', + NotImplException => '-IMPL::NotImplementedException' }, base => [ 'IMPL::Object' => undef @@ -14,8 +15,6 @@ props => [ model => PROP_RO, modelType => PROP_RO, - holdingType => PROP_RO, - provider => PROP_RO, name => PROP_RO, label => PROP_RO, container => PROP_RO, @@ -27,27 +26,22 @@ }; sub CTOR { - my ($this,$provider,$model,$type,$args) = @_; - - $type ||= typeof($model); + my ($this,$model,$type,$args) = @_; - die ArgException->new(provider => 'A provider must be specified'); - - $this->provider($provider); $this->model($model); $this->modelType($type); - $this->childMap({}); + $this->_childMap({}); #mixin other args if ($args) { - $this->$_($args->{$_}) foreach grep $args->{$_}, qw(name label container template holdingType); + $this->$_($args->{$_}) foreach grep $args->{$_}, qw(name label container template); } } sub GetProperty { my ($this,$name) = @_; - $this->_PopulateProperties() + $this->GetProperties() unless $this->_childNames; return $this->_childMap->{$name}; @@ -59,38 +53,38 @@ if ($this->_childNames) { return [ map $this->_childMap->{$_}, @{$this->_childNames} ]; } else { - return $this->_PopulateProperties; + my @childNames; + my %childMap; + my @result; + + foreach my $child (@{$this->PopulateProperties($this)}) { + $childMap{$child->name} = $child; + push @childNames, $child->name; + push @result, $child; + } + + $this->_childMap(\%childMap); + $this->_childNames(\@childNames); + return \@result; } } -sub _PopulateProperties { +sub PopulateProperties { my ($this) = @_; - my @childNames; - my %childMap; - my @result; - - foreach my $child (@{$this->provider->PopulateProperties($this)}) { - $childMap{$child->name} = $child; - push @childNames, $child->name; - push @result, $child; - } - - $this->_childMap(\%childMap); - $this->_childNames(\@childNames); - return \@result; + die NotImplException->new(); } sub GetItems { my ($this) = @_; - return $this->provider->GetItems($this); + die NotImplException->new(); } sub GetItem { my ($this,$index) = @_; - return $this->provider->GetItem($this,$index); + die NotImplException->new(); } 1; diff -r 935629bf80df -r 608e74bc309f Lib/IMPL/Web/View/Metadata/FormMeta.pm --- a/Lib/IMPL/Web/View/Metadata/FormMeta.pm Mon Dec 02 17:44:38 2013 +0400 +++ b/Lib/IMPL/Web/View/Metadata/FormMeta.pm Tue Dec 03 17:55:36 2013 +0400 @@ -5,7 +5,9 @@ use IMPL::declare { require => { Exception => 'IMPL::Exception', - ArgException => '-IMPL::InvalidArgumentException' + ArgException => '-IMPL::InvalidArgumentException', + OpException => '-IMPL::InvalidOperationException', + SchemaNavigator => 'IMPL::DOM::Navigator::SchemaNavigator' }, base => [ 'IMPL::Web::View::Metadata::BaseMeta' => '@_' @@ -14,43 +16,152 @@ nodes => PROP_RO, decl => PROP_RO, schema => PROP_RO, - errors => PROP_RO + errors => PROP_RO, + group => PROP_RO ] }; +use constant { + Meta => __PACKAGE__ +}; + sub CTOR { - my ($this,$provider,$model,$type,$args) = @_; + my ($this,$model,$type,$args) = @_; if ($args) { - $this->$_($args->{$_}) foreach grep $args->{$_}, qw(decl schema nodes errors); + $this->$_($args->{$_}) foreach grep $args->{$_}, qw(decl schema nodes errors group); } $this->$_() || die ArgException->new($_ => "The $_ is required") - foreach qw(decl schema); + foreach qw(schema); } sub isMultiple { - shift->decl->isMultiple; + my ($this) = @_; + $this->decl && $this->decl->isMultiple; } sub isOptional { - shift->decl->isOptional; + my ($this) = @_; + not($this->decl) || $this->decl->isOptional; } sub GetOwnErrors { my ($this) = @_; - my $node = undef; - - $node = not($this->isMultiple) && $this->nodes ? $this->nodes->[0] : undef; + my $nodes = $this->nodes; return [ - grep { - ($node && $_->node && $_->node == $node) || (not($node) && $_->schema == $this->decl ) - } @{$this->errors || []} + grep _IsOwnError($nodes,$this->decl,$_), @{$this->errors || []} ]; } +sub _IsOwnError { + my ($nodes,$source,$err) = @_; + + return 1 if ($err->node && grep($err->node == $_, @$nodes)) || (not(@$nodes) && $err->schema == $source ); + + return 0; +} + +sub _IsErrorRelates { + my ($nodes,$source,$err) = @_; + + # this is an own error + return 1 if _IsOwnError($nodes,$source,$err); + + # this error relates to the child control + + return 0 unless @$nodes; + + for (my $n = $err->parent; $n ; $n = $n->parentNode) { + return 1 if grep($n == $_, @$nodes); + } + + return 0; +} + +sub PopulateProperties { + my ($this) = @_; + + my @props; + + # return empty list of properties in case of multiple values + return \@props if $this->isMultiple; + + my $navi = SchemaNavigator->new($this->schema); + + foreach my $decl (@{$this->schema->content->childNodes}) { + + my $schema = $navi->NavigateName($decl->name); + $navi->SchemaBack(); + + my @nodes = $this->model && $this->model->selectNodes( sub { $_->schemaSource == $decl } ); + + my %args = ( + name => $decl->name, + decl => $decl, + schema => $schema, + nodes => \@nodes, + errors => [grep _IsErrorRelates(\@nodes,$decl,$_), @{$this->errors || []}] + ); + + my ($model,$type); + + if ($decl->isMultiple) { + $model = \@nodes; + $type = 'ARRAY'; + $args{holdingType} = $decl->type; + } else { + $model = shift @nodes; + $type = $decl->type; + } + + push @props, Meta->new($model,$type,\%args); + } + + return \@props; +} + +sub GetItems { + my ($this) = @_; + + die OpException->new("The operation must be performed on the container") + unless $this->isMultiple; + + my $i = 0; + + return [ + map $this->_GetItemMeta($_,$i++), @{$this->model || []} + ]; +} + +sub GetItem { + my ($this,$index) = @_; + + die OpException->new("The operation must be performed on the container") + unless $this->isMultiple; + + my $node = $this->model->[$index]; + + return $this->GetItemMeta($node,$index); +} + +sub _GetItemMeta { + my ($this,$node,$index) = @_; + + return Meta->new( + $node, + $this->decl->type, + { + name => $index, + schema => $this->schema, + errors => [grep _IsOwnError([$node],$this->decl,$_), @{$this->errors ||[]} ], + group => $this + } + ); +} + 1; __END__ diff -r 935629bf80df -r 608e74bc309f Lib/IMPL/Web/View/Metadata/FormProvider.pm --- a/Lib/IMPL/Web/View/Metadata/FormProvider.pm Mon Dec 02 17:44:38 2013 +0400 +++ b/Lib/IMPL/Web/View/Metadata/FormProvider.pm Tue Dec 03 17:55:36 2013 +0400 @@ -30,6 +30,7 @@ foreach my $decl (@{$meta->schema->content->childNodes}) { my $schema = $navi->NavigateName($decl->name); + $navi->SchemaBack(); my @nodes = $meta->model && $meta->model->selectNodes( sub { $_->schemaSource == $decl } ); @@ -52,34 +53,21 @@ $type = $decl->type; } - push @props, Meta->new( - $this, - \@nodes, - $decl->type, - { - name => $decl->name, - schema => $schema, - } - ) + push @props, Meta->new($this,$model,$type,\%args); } } +sub GetItems { + my ($this,$meta) = @_; + + if ($meta->isMultiple) +} + +sub GetItem { + my ($this,$meta,$index) = @_; +} + -sub _IsErrorRelates { - my ($nodes,$source,$err) = @_; - - # this is an own error - return 1 if ($err->node && grep($err->node == $_, @$nodes)) || (not(@$nodes) && $err->schema == $source ); - - # this error relates to the child control - - return 0 unless @$nodes; - - for (my $n = $err->parent; $n ; $n = $n->parentNode) { - return 1 if grep($n == $_, @$nodes); - } - - return 0; -} + 1; \ No newline at end of file diff -r 935629bf80df -r 608e74bc309f _test/Resources/person.schema.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/_test/Resources/person.schema.xml Tue Dec 03 17:55:36 2013 +0400 @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \d+ + + \ No newline at end of file diff -r 935629bf80df -r 608e74bc309f _test/Resources/person_info.xml --- a/_test/Resources/person_info.xml Mon Dec 02 17:44:38 2013 +0400 +++ b/_test/Resources/person_info.xml Tue Dec 03 17:55:36 2013 +0400 @@ -6,5 +6,9 @@ hellroad 1 +
+ waypass + 2 +
diff -r 935629bf80df -r 608e74bc309f _test/Test/Web/View.pm --- a/_test/Test/Web/View.pm Mon Dec 02 17:44:38 2013 +0400 +++ b/_test/Test/Web/View.pm Tue Dec 03 17:55:36 2013 +0400 @@ -4,21 +4,26 @@ use warnings; use utf8; -use parent qw(IMPL::Test::Unit); -__PACKAGE__->PassThroughArgs; - use File::Slurp; use Scalar::Util qw(weaken); use Data::Dumper; use IMPL::lang; use IMPL::Test qw(assert assertarray test GetCallerSourceLine); -use IMPL::Web::View::TTLoader(); -use constant { - TTLoader => 'IMPL::Web::View::TTLoader', - MProfiler => 'IMPL::Profiler::Memory' +use IMPL::declare { + require => { + FormMeta => 'IMPL::Web::View::Metadata::FormMeta', + Schema => 'IMPL::DOM::Schema', + Builder => 'IMPL::DOM::Navigator::Builder', + Document => 'IMPL::DOM::Document', + XMLReader => 'IMPL::DOM::XMLReader' + }, + base => [ + 'IMPL::Test::Unit' => '@_' + ] }; + sub AssertMemoryLeak { my $code = shift; my $dump = shift; @@ -33,199 +38,40 @@ } sub templatesDir { - $_[0]->GetResourceDir('Resources','TTView'); -} - -sub CreateLoader { - my ($this) = @_; - - my $loader = TTLoader->new( - { - INCLUDE_PATH => [ - $this->templatesDir - ], - INTERPOLATE => 1, - POST_CHOMP => 1, - ENCODING => 'utf-8' - }, - ext => '.tt', - initializer => 'global.tt', - globals => { - site => { - name => 'Test Site' - }, - date => { - now => sub { localtime(time); } - }, - dynamic => sub { 'this is a dynamic value' }, - view => { - } - }, - layoutBase => 'Layout' - ); + shift->GetResourceDir('Resources','TTView'); } -test TTLoaderTests => sub { - my ($this) = @_; - - my $loader = $this->CreateLoader(); - - # test the loader to be able to find a desired resource - assert( defined($loader->template('simple') ) ); - - # loader should be initialized on demand - assert( not $loader->isInitialized ); - - # loader should be able to load a document - my $doc = $loader->document('simple'); - assert(defined $doc); - - assert( $loader->isInitialized ); - assert( $loader->context->stash->get('user') eq 'test_user'); - - # document should inherit loader's context - assert( $doc->context->stash->get('user') eq 'test_user'); - - # document should not have 'this' template variable - - assert( $doc->context != $loader->context); # document should have an own context -}; +test TestFormMetadata => sub { + my ($this) = @_; + + my $schema = Schema->LoadSchema($this->GetResourceFile('Resources','person.schema.xml')); + my $builder = Builder->new(Document, $schema); + + my $reader = XMLReader->new( Navigator => $builder ); + + $reader->ParseFile("Resources/person_info.xml"); + + my $doc = $builder->document; + my @errors = $builder->buildErrors; + + push @errors, $schema->Validate($doc); + + my $meta = FormMeta->new( + $doc, + $doc->schemaSource->type, + { + decl => $doc->schemaSource, + schema => $doc->schema, + errors => \@errors + } + ); -test TTDocumentTests => sub { - my ($this) = @_; - my $loader = $this->CreateLoader(); - - my $doc = $loader->document('simple'); - - assert(defined $doc); - - $doc->title('test document'); - $doc->name('document'); - - assert($doc->name eq 'document'); - assert($doc->title eq 'test document'); - - assert(not $doc->can('notexists')); # autoloaded property should be ignored - assert(not defined $doc->notexists); # nonexisting property - assert($doc->template->version == 10); # static metadata - assert($doc->context->stash->get('user') eq 'test_user' ); # runtime context should be derived from documentContext - - my $text = $doc->Render(); - my $expected = read_file($this->GetResourceFile('Resources','TTView.Output','simple.txt'), binmode => ':utf8'); - - assert($text eq $expected, "Bad Render() output","Got: $text", "Expected: $expected"); - + my $props = $meta->GetProperties; + + my $prop = $meta->GetProperty('address'); + + join ',', map $_->GetProperty('line')->name, @{$prop->GetItems}; }; -test TestDocumentLayout => sub { - my ($this) = @_; - - my $loader = $this->CreateLoader(); - - my $doc = $loader->document( - 'complex', - { - data => [qw(one two three)], - site => { - name => 'Test Site' - } - } - ); - - assert($doc->layout eq 'default'); - - my $text = $doc->Render(); - my $expected = read_file($this->GetResourceFile('Resources', 'TTView.Output', 'complex.default.txt'), binmode => ':utf8' ); - my ($text_raw,$expected_raw) = ($text, $expected); - $text_raw =~ s/\s+//g; - $expected_raw =~ s/\s+//g; - assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got: $text", "Expected: $expected"); -}; - -test TestControlInheritance => sub { - my ($this) = @_; - - my $loader = $this->CreateLoader(); - my $doc = $loader->document('derived'); - - my $text = $doc->Render(); - my $expected = read_file($this->GetResourceFile('Resources', 'TTView.Output', 'derived.txt'), binmode => ':utf8' ); - - my ($text_raw,$expected_raw) = ($text, $expected); - $text_raw =~ s/\s+//g; - $expected_raw =~ s/\s+//g; - - assert($text_raw eq $expected_raw, '$doc->Render(): Bad output', "Got: $text", "Expected: $expected"); -}; - -test TestDocumentsIsolation => sub { - my $this = shift; - - my $loader = $this->CreateLoader(); - - my $doc = $loader->document('simple'); - - assert(ref $loader->context->stash->get([ 'dojo', 0, 'require', 0]) eq 'ARRAY'); - assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]); - assert($loader->context->stash != $doc->context->stash); - - assert(defined $doc); - - # only root stash variables can be localized, to avoid modifying dojo we - # need to replace it completely - $doc->context->process(\q{ - [% SET dojo = { require => [] } %] - [% dojo.require.push('dijit/form/TextBox') %] - [% SET user = 'dummy guy' %] - }); - - assert($doc->context->stash->get('user') eq 'dummy guy'); - assert($loader->context->stash->get('user') eq 'test_user'); - assertarray($doc->context->stash->get([ 'dojo', 0, 'require', 0]),['dijit/form/TextBox']); - assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]); - - my $text = $doc->Render(); - - my $doc2 = $loader->document('complex'); - - assertarray($doc2->context->stash->get([ 'dojo', 0, 'require', 0]),[]); - - # This document has a layout ehich will replace 'dojo' global variable. - # The layout contains INIT block which runs first in the context of the - # document, then RenderContent is called and then the layout is applied - $doc2->Render(); - - assertarray($loader->context->stash->get([ 'dojo', 0, 'require', 0]),[]); - - # TODO: to be able to rendered multiple times, Render shouldn't affect the context of the document - #assertarray($doc2->context->stash->get([ 'dojo', 0, 'require', 0]),[]); -}; - -test TestMemoryLeaks => sub { - my ($this) = @_; - - my $loader = $this->CreateLoader(); - $loader->document('simple'); # force loader initialization - - AssertMemoryLeak(sub { - my $doc = $loader->document('simple'); - },'dump'); - - AssertMemoryLeak(sub { - my $doc = $loader->document('simple'); - $doc->Render( { self => $doc } ); - },'dump'); - - $loader->template('Layout/default'); - $loader->template('My/Org/Panel'); - $loader->template('My/Org/TextPreview'); - - $loader->template('complex'); - AssertMemoryLeak(sub { - my $doc = $loader->document('complex'); - $doc->Render(); - },'dump'); - -}; 1; \ No newline at end of file diff -r 935629bf80df -r 608e74bc309f _test/Test/Web/ViewSelector.pm --- a/_test/Test/Web/ViewSelector.pm Mon Dec 02 17:44:38 2013 +0400 +++ b/_test/Test/Web/ViewSelector.pm Tue Dec 03 17:55:36 2013 +0400 @@ -5,7 +5,7 @@ use IMPL::Test qw(test assert assertarray); use IMPL::declare { require => { - TTView => 'IMPL::Web::Handler::TTView' + TTView => 'IMPL::Web::Handler::View' }, base => [ 'IMPL::Test::Unit' => '@_'