1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
|
package XML::Validator::Schema::ElementNode;
use strict;
use warnings;
=head1 NAME
XML::Validator::Schema::ElementNode - an element node in a schema object
=head1 DESCRIPTION
This is an internal module used by XML::Validator::Schema to represent
element nodes derived from XML Schema documents.
=cut
use base qw(XML::Validator::Schema::Node);
use XML::Validator::Schema::Util qw(_attr _err);
# create a node based on the contents of an <element> found in the
# schema document
sub parse {
my ($pkg, $data) = @_;
my $self = $pkg->new();
my $name = _attr($data, 'name');
_err('Found element without a name.')
unless $name;
$self->name($name);
my $type_name = _attr($data, 'type');
if ($type_name) {
$self->{unresolved_type} = 1;
$self->{type_name} = $type_name;
}
my $min = _attr($data, 'minOccurs');
$min = 1 unless defined $min;
_err("Invalid value for minOccurs '$min' found in <$name>.")
unless $min =~ /^\d+$/;
$self->{min} = $min;
my $max = _attr($data, 'maxOccurs');
$max = 1 unless defined $max;
_err("Invalid value for maxOccurs '$max' found in <$name>.")
unless $max =~ /^\d+$/ or $max eq 'unbounded';
$self->{max} = $max;
return $self;
}
# override add_daughter to check parent-specific requirements
sub add_daughter {
my ($self, $d) = @_;
# check that min/mix are 0 or 1 for 'all' contents
if ($self->{is_all} and $d->isa('XML::Validator::Schema::ElementNode')) {
_err("Element '$d->{name}' must have minOccurs of 0 or 1 because it is within an <all>.")
unless ($d->{min} eq '0' or $d->{min} eq '1');
_err("Element '$d->{name}' must have maxOccurs of 0 or 1 because it is within an <all>.")
unless ($d->{max} eq '0' or $d->{max} eq '1');
}
return $self->SUPER::add_daughter($d);
}
# check contents of an element against declared type
sub check_contents {
my ($self, $contents) = @_;
# do type check if a type is declared
if ($self->{type}) {
# Union isn't really a simple type. In a sense it isn't a type
# at all, if it is, it sure as hell isn't simple. It's just
# a rather laissez-faire view of what the type might be.
# Hence I've not handled union in SimpleType::check. As it's
# not handled directly in SimpleType, I've bastardized the usage
# of $self->{type} to just contain a string effectively indicating
# that it is an exception
my ( $ok, $msg);
if ($self->{type} eq 'union' ) {
# it only has to match one of the member types:
if ( not defined($self->{members}) ){
die "Internal error: I aint got no members\n";
} else {
if (@{$self->{members}} == 0 ) {
_err("Element '$self->{name}' is a union with no members.");
}
}
my $types = '';
$ok = 0;
foreach my $m ( @{$self->{members}} ) {
if ( not my $x = ref($m) ) {
die ("Internal error, that isn't a reference\n");
}
( $ok, $msg ) = $m->{type}->check($contents);
last if $ok;
$types .= ' '.$m->{type}->{base}->{name};
}
if ( not $ok ) {
# Just giving the error for the last one checked isn't
# really that helpful. We need to make it explicit that
# NONE of the tests succeeded.
$msg = "content does not match any of the union base types".
" [ $types ]";
}
} else {
($ok, $msg) = $self->{type}->check($contents);
}
_err("Illegal value '$contents' in element <$self->{name}>, $msg")
unless $ok;
}
# mixed content isn't supported, so all complex elements must be
# element only or have nothing but whitespace between the elements
elsif ($self->{is_complex} and $contents =~ /\S/) {
_err("Illegal character data found in element <$self->{name}>.");
}
}
# check if a given name is a legal child, and return it if it is
sub check_daughter {
my ($self, $name) = @_;
my ($daughter) = grep { $_->{name} eq $name } ($self->daughters);
# doesn't even exist?
_err("Found unexpected <$name> inside <$self->{name}>. This is not a valid child element.")
unless $daughter;
# push on
push @{$self->{memory} ||= []}, $name;
# check model
$self->{model}->check_model($self->{name}, $self->{memory})
if $self->{model};
# does this daughter have a valid type? if not, attempt to elaborate
if ($daughter->{unresolved_type}) {
$self->root->complete_type($daughter);
($daughter) = grep { $_->{name} eq $name } ($self->daughters);
}
# is this daughter a dangling ref? if so, complete it
if ($daughter->{unresolved_ref}) {
$self->root->complete_ref($daughter);
($daughter) = grep { $_->{name} eq $name } ($self->daughters);
}
return $daughter;
}
# check that attributes are kosher
sub check_attributes {
my ($self, $data) = @_;
# get lists required and allowed attributes
my (@required, %allowed);
foreach my $attr (@{$self->{attr} || []}) {
$allowed{$attr->{name}} = $attr;
push(@required, $attr->{name}) if $attr->{required};
}
# check attributes
my %saw;
foreach my $jcname (keys %$data) {
my $attr = $data->{$jcname};
# attributes in the http://www.w3.org/2001/XMLSchema-instance
# namespace are processing instructions, not part of the
# document to be validated
next if $attr->{NamespaceURI}
eq 'http://www.w3.org/2001/XMLSchema-instance';
# attributes in http://www.w3.org/2000/xmlns/ are namespace
# declarations and don't concern us
next if $attr->{NamespaceURI} eq 'http://www.w3.org/2000/xmlns/';
my $name = $attr->{LocalName};
my $obj = $allowed{$name};
_err("Illegal attribute '$name' found in <$self->{name}>.")
unless $obj;
$saw{$name} = 1;
# does this obj have an incomplete type? complete it
if ($obj->{unresolved_type}) {
$self->root->complete_attr_type($obj);
}
# check value, if attribute is typed
if ($obj->{type}) {
my ($ok, $msg) = $obj->{type}->check($attr->{Value});
_err("Illegal value '$attr->{Value}' for attribute '$name' in <$self->{name}>, $msg")
unless $ok;
}
}
# make sure all required attributes are present
foreach my $name (@required) {
_err("Missing required attribute '$name' in <$self->{name}>.")
unless $saw{$name};
}
}
# finish
sub compile {
my $self = shift;
if ($self->daughters and
($self->daughters)[0]->isa('XML::Validator::Schema::ModelNode')) {
($self->daughters)[0]->compile;
}
}
# forget about the past
sub clear_memory {
@{$_[0]->{memory}} = () if $_[0]->{memory};
}
1;
|