/[suikacvs]/markup/html/whatpm/Whatpm/ContentChecker/Atom.pm
Suika

Contents of /markup/html/whatpm/Whatpm/ContentChecker/Atom.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.4 - (hide annotations) (download)
Mon Sep 24 04:23:45 2007 UTC (17 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.3: +56 -8 lines
++ whatpm/t/ChangeLog	24 Sep 2007 04:23:24 -0000
2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* ContentChecker.t: New test data files are added.

	* content-model-atom-1.dat, content-model-atom-2.dat: New test data.

++ whatpm/Whatpm/ChangeLog	24 Sep 2007 04:21:59 -0000
2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* ContentChecker.pm: Set level values for later uses.

++ whatpm/Whatpm/ContentChecker/ChangeLog	24 Sep 2007 04:22:38 -0000
2007-09-24  Wakaba  <wakaba@suika.fam.cx>

	* Atom.pm (hreflang): Checker is implemented.
	(AtomDateConstruct): Checking for content is implemented.

1 wakaba 1.1 package Whatpm::ContentChecker;
2     use strict;
3     require Whatpm::ContentChecker;
4    
5 wakaba 1.2 require Whatpm::URIChecker;
6    
7 wakaba 1.1 my $ATOM_NS = q<http://www.w3.org/2005/Atom>;
8 wakaba 1.3 my $LINK_REL = q<http://www.iana.org/assignments/relation/>;
9 wakaba 1.1
10     ## MUST be well-formed XML (RFC 4287 references XML 1.0 REC 20040204)
11    
12     ## NOTE: Commants and PIs are not explicitly allowed.
13    
14     our $AttrChecker;
15    
16     ## Any element MAY have xml:base, xml:lang
17     my $GetAtomAttrsChecker = sub {
18     my $element_specific_checker = shift;
19     return sub {
20     my ($self, $todo) = @_;
21     for my $attr (@{$todo->{node}->attributes}) {
22     my $attr_ns = $attr->namespace_uri;
23     $attr_ns = '' unless defined $attr_ns;
24     my $attr_ln = $attr->manakai_local_name;
25     my $checker;
26     if ($attr_ns eq '') {
27     $checker = $element_specific_checker->{$attr_ln};
28     } else {
29     $checker = $AttrChecker->{$attr_ns}->{$attr_ln}
30     || $AttrChecker->{$attr_ns}->{''};
31     }
32     if ($checker) {
33     $checker->($self, $attr, $todo);
34     } else {
35     $self->{onerror}->(node => $attr, level => 'unsupported',
36     type => 'attribute');
37     ## ISSUE: No comformance createria for unknown attributes in the spec
38     }
39     }
40     };
41     }; # $GetAtomAttrsChecker
42    
43 wakaba 1.4 my $AtomLanguageTagAttrChecker = sub {
44     ## NOTE: See also $HTMLLanguageTagAttrChecker in HTML.pm.
45    
46     my ($self, $attr) = @_;
47     my $value = $attr->value;
48     require Whatpm::LangTag;
49     Whatpm::LangTag->check_rfc3066_language_tag ($value, sub {
50     my %opt = @_;
51     my $type = 'LangTag:'.$opt{type};
52     $type .= ':' . $opt{subtag} if defined $opt{subtag};
53     $self->{onerror}->(node => $attr, type => $type, value => $opt{value},
54     level => $opt{level});
55     });
56     ## ISSUE: RFC 4646 (3066bis)?
57     }; # $AtomLanguageTagAttrChecker
58    
59 wakaba 1.1 my $AtomTextConstruct = {
60     attrs_checker => $GetAtomAttrsChecker->({
61     type => sub { 1 }, # checked in |checker|
62     }),
63     checker => sub {
64     my ($self, $todo) = @_;
65    
66     my $attr = $todo->{node}->get_attribute_node_ns (undef, 'type');
67     my $value = 'text';
68     if ($attr) {
69     $value = $attr->value;
70     if ($value eq 'text' or $value eq 'html' or $value eq 'xhtml') {
71     # MUST
72     } else {
73     $self->{onerror}->(node => $attr, type => 'keyword:invalid');
74     }
75     # IMT MUST NOT be used
76     }
77    
78     if ($value eq 'text') {
79     my @nodes = (@{$todo->{node}->child_nodes});
80     my $new_todos = [];
81    
82     while (@nodes) {
83     my $node = shift @nodes;
84     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
85    
86     my $nt = $node->node_type;
87     if ($nt == 1) {
88     # MUST NOT
89     $self->{onerror}->(node => $node, type => 'element not allowed');
90     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
91     unshift @nodes, @$sib;
92     push @$new_todos, @$ch;
93     } elsif ($nt == 5) {
94     unshift @nodes, @{$node->child_nodes};
95     }
96     }
97    
98     return ($new_todos);
99     } elsif ($value eq 'html') {
100     my @nodes = (@{$todo->{node}->child_nodes});
101     my $new_todos = [];
102    
103     while (@nodes) {
104     my $node = shift @nodes;
105     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
106    
107     my $nt = $node->node_type;
108     if ($nt == 1) {
109     # MUST NOT
110     $self->{onerror}->(node => $node, type => 'element not allowed');
111     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
112     unshift @nodes, @$sib;
113     push @$new_todos, @$ch;
114     } elsif ($nt == 5) {
115     unshift @nodes, @{$node->child_nodes};
116     }
117     }
118    
119     ## TODO: SHOULD be suitable for handling as HTML [HTML4]
120     # markup MUST be escaped
121     ## TODO: HTML SHOULD be valid as if within <div>
122    
123     return ($new_todos);
124     } elsif ($value eq 'xhtml') {
125     my @nodes = (@{$todo->{node}->child_nodes});
126     my $new_todos = [];
127    
128     my $has_div;
129     while (@nodes) {
130     my $node = shift @nodes;
131     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
132    
133     my $nt = $node->node_type;
134     if ($nt == 1) {
135     # MUST
136     my $nsuri = $node->namespace_uri;
137     if (defined $nsuri and
138     $nsuri eq q<http://www.w3.org/1999/xhtml> and
139     $node->manakai_local_name eq 'div' and
140     not $has_div) {
141     ## TODO: SHOULD be suitable for handling as HTML [XHTML10]
142     $has_div = 1;
143     } else {
144     $self->{onerror}->(node => $node, type => 'element not allowed');
145     }
146     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
147     unshift @nodes, @$sib;
148     push @$new_todos, @$ch;
149     } elsif ($nt == 3 or $nt == 4) {
150     ## TODO: Are white spaces allowed?
151     $self->{onerror}->(node => $node, type => 'character not allowed');
152     } elsif ($nt == 5) {
153     unshift @nodes, @{$node->child_nodes};
154     }
155     }
156    
157     unless ($has_div) {
158     $self->{onerror}->(node => $todo->{node},
159     type => 'element missing:div');
160     }
161    
162     return ($new_todos);
163     }
164    
165     },
166     }; # $AtomTextConstruct
167    
168     my $AtomPersonConstruct = {
169     attrs_checker => $GetAtomAttrsChecker->({}),
170     checker => sub {
171     my ($self, $todo) = @_;
172    
173     my @nodes = (@{$todo->{node}->child_nodes});
174     my $new_todos = [];
175    
176     my $has_name;
177     my $has_uri;
178     my $has_email;
179     while (@nodes) {
180     my $node = shift @nodes;
181     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
182    
183     my $nt = $node->node_type;
184     if ($nt == 1) {
185     # MUST
186     my $nsuri = $node->namespace_uri;
187     $nsuri = '' unless defined $nsuri;
188     my $not_allowed;
189     if ($nsuri eq $ATOM_NS) {
190     my $ln = $node->manakai_local_name;
191     if ($ln eq 'name') {
192     unless ($has_name) {
193     $has_name = 1;
194     } else {
195     $not_allowed = 1;
196     }
197     } elsif ($ln eq 'uri') {
198     unless ($has_uri) {
199     $has_uri = 1;
200     } else {
201     $not_allowed = 1; # MUST NOT
202     }
203     } elsif ($ln eq 'email') {
204     unless ($has_email) {
205     $has_email = 1;
206     } else {
207     $not_allowed = 1; # MUST NOT
208     }
209     } else {
210     $not_allowed = 1;
211     }
212     } else {
213     ## TODO: extension element
214     $not_allowed = 1;
215     }
216     $self->{onerror}->(node => $node, type => 'element not allowed')
217     if $not_allowed;
218     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
219     unshift @nodes, @$sib;
220     push @$new_todos, @$ch;
221     } elsif ($nt == 3 or $nt == 4) {
222     ## TODO: Are white spaces allowed?
223     $self->{onerror}->(node => $node, type => 'character not allowed');
224     } elsif ($nt == 5) {
225     unshift @nodes, @{$node->child_nodes};
226     }
227     }
228    
229     unless ($has_name) { # MUST
230     $self->{onerror}->(node => $todo->{node},
231     type => 'element missing:atom.name');
232     }
233    
234     return ($new_todos);
235     },
236     }; # $AtomPersonConstruct
237    
238 wakaba 1.2 our $Element;
239    
240     $Element->{$ATOM_NS}->{name} = {
241     ## NOTE: Strictly speaking, structure and semantics for atom:name
242     ## element outside of Person construct is not defined.
243     attrs_checker => $GetAtomAttrsChecker->({}),
244     checker => sub {
245     my ($self, $todo) = @_;
246    
247     my @nodes = (@{$todo->{node}->child_nodes});
248     my $new_todos = [];
249    
250     while (@nodes) {
251     my $node = shift @nodes;
252     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
253    
254     my $nt = $node->node_type;
255     if ($nt == 1) {
256     ## NOTE: No constraint.
257     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
258     unshift @nodes, @$sib;
259     push @$new_todos, @$ch;
260     } elsif ($nt == 3 or $nt == 4) {
261     #
262     } elsif ($nt == 5) {
263     unshift @nodes, @{$node->child_nodes};
264     }
265     }
266    
267     return ($new_todos);
268     },
269     };
270    
271     $Element->{$ATOM_NS}->{uri} = {
272     ## NOTE: Strictly speaking, structure and semantics for atom:uri
273     ## element outside of Person construct is not defined.
274     attrs_checker => $GetAtomAttrsChecker->({}),
275     checker => sub {
276     my ($self, $todo) = @_;
277    
278     my @nodes = (@{$todo->{node}->child_nodes});
279     my $new_todos = [];
280    
281     my $s = '';
282     while (@nodes) {
283     my $node = shift @nodes;
284     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
285    
286     my $nt = $node->node_type;
287     if ($nt == 1) {
288     ## NOTE: Not explicitly disallowed.
289     $self->{onerror}->(node => $node, type => 'element not allowed');
290     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
291     unshift @nodes, @$sib;
292     push @$new_todos, @$ch;
293     } elsif ($nt == 3 or $nt == 4) {
294     $s .= $node->data;
295     } elsif ($nt == 5) {
296     unshift @nodes, @{$node->child_nodes};
297     }
298     }
299    
300     ## NOTE: There MUST NOT be any white space.
301     Whatpm::URIChecker->check_iri_reference ($s, sub {
302     my %opt = @_;
303     $self->{onerror}->(node => $todo->{node}, level => $opt{level},
304     type => 'URI::'.$opt{type}.
305     (defined $opt{position} ? ':'.$opt{position} : ''));
306     });
307    
308     return ($new_todos);
309     },
310     };
311    
312     $Element->{$ATOM_NS}->{email} = {
313     ## NOTE: Strictly speaking, structure and semantics for atom:email
314     ## element outside of Person construct is not defined.
315     attrs_checker => $GetAtomAttrsChecker->({}),
316     checker => sub {
317     my ($self, $todo) = @_;
318    
319     my @nodes = (@{$todo->{node}->child_nodes});
320     my $new_todos = [];
321    
322     my $s = '';
323     while (@nodes) {
324     my $node = shift @nodes;
325     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
326    
327     my $nt = $node->node_type;
328     if ($nt == 1) {
329     ## NOTE: Not explicitly disallowed.
330     $self->{onerror}->(node => $node, type => 'element not allowed');
331     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
332     unshift @nodes, @$sib;
333     push @$new_todos, @$ch;
334     } elsif ($nt == 3 or $nt == 4) {
335     $s .= $node->data;
336     } elsif ($nt == 5) {
337     unshift @nodes, @{$node->child_nodes};
338     }
339     }
340    
341     ## TODO: addr-spec
342     $self->{onerror}->(node => $todo->{node}, type => 'addr-spec',
343     level => 'unsupported');
344    
345     return ($new_todos);
346     },
347     };
348    
349 wakaba 1.1 ## MUST NOT be any white space
350     my $AtomDateConstruct = {
351     attrs_checker => $GetAtomAttrsChecker->({}),
352     checker => sub {
353     my ($self, $todo) = @_;
354    
355     my @nodes = (@{$todo->{node}->child_nodes});
356     my $new_todos = [];
357    
358     my $s = '';
359     while (@nodes) {
360     my $node = shift @nodes;
361     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
362    
363     my $nt = $node->node_type;
364     if ($nt == 1) {
365     ## NOTE: It does not explicitly say that there MUST NOT be any element.
366     $self->{onerror}->(node => $node, type => 'element not allowed');
367     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
368     unshift @nodes, @$sib;
369     push @$new_todos, @$ch;
370     } elsif ($nt == 3 or $nt == 4) {
371     $s .= $node->data;
372     } elsif ($nt == 5) {
373     unshift @nodes, @{$node->child_nodes};
374     }
375     }
376    
377 wakaba 1.4 ## MUST: RFC 3339 |date-time| with uppercase |T| and |Z|
378     if ($s =~ /\A([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(?>\.[0-9]+)?(?>Z|[+-]([0-9]{2}):([0-9]{2}))\z/) {
379     my ($y, $M, $d, $h, $m, $s, $zh, $zm)
380     = ($1, $2, $3, $4, $5, $6, $7, $8);
381     my $node = $todo->{node};
382    
383     ## Check additional constraints described or referenced in
384     ## comments of ABNF rules for |date-time|.
385     my $level = $self->{must_level};
386     if (0 < $M and $M < 13) {
387     $self->{onerror}->(node => $node, type => 'datetime:bad day',
388     level => $level)
389     if $d < 1 or
390     $d > [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]->[$M];
391     $self->{onerror}->(node => $node, type => 'datetime:bad day',
392     level => $level)
393     if $M == 2 and $d == 29 and
394     not ($y % 400 == 0 or ($y % 4 == 0 and $y % 100 != 0));
395     } else {
396     $self->{onerror}->(node => $node, type => 'datetime:bad month',
397     level => $level);
398     }
399     $self->{onerror}->(node => $node, type => 'datetime:bad hour',
400     level => $level) if $h > 23;
401     $self->{onerror}->(node => $node, type => 'datetime:bad minute',
402     level => $level) if $m > 59;
403     $self->{onerror}->(node => $node, type => 'datetime:bad second',
404     level => $level)
405     if $s > 60; ## NOTE: Validness of leap seconds are not checked.
406     $self->{onerror}->(node => $node, type => 'datetime:bad timezone hour',
407     level => $level) if $zh > 23;
408     $self->{onerror}->(node => $node, type => 'datetime:bad timezone minute',
409     level => $level) if $zm > 59;
410     } else {
411     $self->{onerror}->(node => $todo->{node},
412     type => 'datetime:syntax error',
413     level => $self->{must_level});
414     }
415     ## NOTE: SHOULD be accurate as possible (cannot be checked)
416 wakaba 1.1
417     return ($new_todos);
418     },
419     }; # $AtomDateConstruct
420    
421 wakaba 1.2 $Element->{$ATOM_NS}->{entry} = {
422 wakaba 1.1 is_root => 1,
423     attrs_checker => $GetAtomAttrsChecker->({}),
424     checker => sub {
425     my ($self, $todo) = @_;
426    
427     my @nodes = (@{$todo->{node}->child_nodes});
428     my $new_todos = [];
429    
430     ## TODO: MUST author+ unless (child::source/child::author)
431     ## or (parent::feed/child::author)
432    
433     my $has_element = {};
434     while (@nodes) {
435     my $node = shift @nodes;
436     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
437    
438     my $nt = $node->node_type;
439     if ($nt == 1) {
440     # MUST
441     my $nsuri = $node->namespace_uri;
442     $nsuri = '' unless defined $nsuri;
443     my $not_allowed;
444     if ($nsuri eq $ATOM_NS) {
445     my $ln = $node->manakai_local_name;
446     if ({ # MUST (0, 1)
447     content => 1,
448     id => 1,
449     published => 1,
450     rights => 1,
451     source => 1,
452     summary => 1,
453     ## TODO: MUST if child::content/@src | child::content/@type = IMT, !text/ !/xml !+xml
454     title => 1,
455     updated => 1,
456     }->{$ln}) {
457     unless ($has_element->{$ln}) {
458     $has_element->{$ln} = 1;
459     $not_allowed = $has_element->{entry};
460     } else {
461     $not_allowed = 1;
462     }
463     } elsif ($ln eq 'link') { # MAY
464 wakaba 1.3 if ($node->rel eq $LINK_REL . 'alternate') {
465     my $type = $node->get_attribute_ns (undef, 'type');
466     $type = '' unless defined $type;
467     my $hreflang = $node->get_attribute_ns (undef, 'hreflang');
468     $hreflang = '' unless defined $hreflang;
469     my $key = 'link:'.(defined $type ? ':'.$type : '').':'.
470     (defined $hreflang ? ':'.$hreflang : '');
471     unless ($has_element->{$key}) {
472     $has_element->{$key} = 1;
473     $has_element->{'link.alternate'} = 1;
474     } else {
475     $not_allowed = 1;
476     }
477     }
478    
479 wakaba 1.1 ## NOTE: MAY
480 wakaba 1.3 $not_allowed ||= $has_element->{entry};
481 wakaba 1.1 } elsif ({ # MAY
482     author => 1,
483     category => 1,
484     contributor => 1,
485     }->{$ln}) {
486     $not_allowed = $has_element->{entry};
487     } else {
488     $not_allowed = 1;
489     }
490     } else {
491     ## TODO: extension element
492     $not_allowed = 1;
493     }
494     $self->{onerror}->(node => $node, type => 'element not allowed')
495     if $not_allowed;
496     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
497     unshift @nodes, @$sib;
498     push @$new_todos, @$ch;
499     } elsif ($nt == 3 or $nt == 4) {
500     ## TODO: Are white spaces allowed?
501     $self->{onerror}->(node => $node, type => 'character not allowed');
502     } elsif ($nt == 5) {
503     unshift @nodes, @{$node->child_nodes};
504     }
505     }
506    
507     ## NOTE: metadata elements, followed by atom:entry* (no explicit MAY)
508    
509     ## TODO: If entry's with same id, then updated SHOULD be different
510    
511     unless ($has_element->{id}) { # MUST
512     $self->{onerror}->(node => $todo->{node},
513     type => 'element missing:atom.id');
514     }
515     unless ($has_element->{title}) { # MUST
516     $self->{onerror}->(node => $todo->{node},
517     type => 'element missing:atom.title');
518     }
519     unless ($has_element->{updated}) { # MUST
520     $self->{onerror}->(node => $todo->{node},
521     type => 'element missing:atom.updated');
522     }
523 wakaba 1.3 if (not $has_element->{content} and
524     not $has_element->{'link.alternate'}) {
525     $self->{onerror}->(node => $todo->{node},
526     type => 'element missing:atom.link.alternate');
527     }
528 wakaba 1.1
529     return ($new_todos);
530     },
531     };
532    
533     $Element->{$ATOM_NS}->{feed} = {
534     is_root => 1,
535     attrs_checker => $GetAtomAttrsChecker->({}),
536     checker => sub {
537     my ($self, $todo) = @_;
538    
539     my @nodes = (@{$todo->{node}->child_nodes});
540     my $new_todos = [];
541    
542     ## TODO: MUST author+ unless all entry child has author+.
543    
544     my $has_element = {};
545     while (@nodes) {
546     my $node = shift @nodes;
547     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
548    
549     my $nt = $node->node_type;
550     if ($nt == 1) {
551     my $nsuri = $node->namespace_uri;
552     $nsuri = '' unless defined $nsuri;
553     my $not_allowed;
554     if ($nsuri eq $ATOM_NS) {
555     my $ln = $node->manakai_local_name;
556     if ($ln eq 'entry') {
557     $has_element->{entry} = 1;
558     } elsif ({ # MUST (0, 1)
559     generator => 1,
560     icon => 1,
561     id => 1,
562     logo => 1,
563     rights => 1,
564     subtitle => 1,
565     title => 1,
566     updated => 1,
567     }->{$ln}) {
568     unless ($has_element->{$ln}) {
569     $has_element->{$ln} = 1;
570     $not_allowed = $has_element->{entry};
571     } else {
572     $not_allowed = 1;
573     }
574 wakaba 1.3 } elsif ($ln eq 'link') {
575     my $rel = $node->rel;
576     if ($rel eq $LINK_REL . 'alternate') {
577     my $type = $node->get_attribute_ns (undef, 'type');
578     $type = '' unless defined $type;
579     my $hreflang = $node->get_attribute_ns (undef, 'hreflang');
580     $hreflang = '' unless defined $hreflang;
581     my $key = 'link:'.(defined $type ? ':'.$type : '').':'.
582     (defined $hreflang ? ':'.$hreflang : '');
583     unless ($has_element->{$key}) {
584     $has_element->{$key} = 1;
585     } else {
586     $not_allowed = 1;
587     }
588     } elsif ($rel eq $LINK_REL . 'self') {
589     $has_element->{'link.self'} = 1;
590     }
591    
592     ## NOTE: MAY
593 wakaba 1.1 $not_allowed = $has_element->{entry};
594     } elsif ({ # MAY
595     author => 1,
596     category => 1,
597     contributor => 1,
598     }->{$ln}) {
599     $not_allowed = $has_element->{entry};
600     } else {
601     $not_allowed = 1;
602     }
603     } else {
604     ## TODO: extension element
605     $not_allowed = 1;
606     }
607     $self->{onerror}->(node => $node, type => 'element not allowed')
608     if $not_allowed;
609     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
610     unshift @nodes, @$sib;
611     push @$new_todos, @$ch;
612     } elsif ($nt == 3 or $nt == 4) {
613     ## TODO: Are white spaces allowed?
614     $self->{onerror}->(node => $node, type => 'character not allowed');
615     } elsif ($nt == 5) {
616     unshift @nodes, @{$node->child_nodes};
617     }
618     }
619    
620     ## NOTE: metadata elements, followed by atom:entry* (no explicit MAY)
621    
622     ## TODO: If entry's with same id, then updated SHOULD be different
623    
624     unless ($has_element->{id}) { # MUST
625     $self->{onerror}->(node => $todo->{node},
626     type => 'element missing:atom.id');
627     }
628     unless ($has_element->{title}) { # MUST
629     $self->{onerror}->(node => $todo->{node},
630     type => 'element missing:atom.title');
631     }
632     unless ($has_element->{updated}) { # MUST
633     $self->{onerror}->(node => $todo->{node},
634     type => 'element missing:atom.updated');
635     }
636 wakaba 1.3 unless ($has_element->{'link.self'}) {
637     $self->{onerror}->(node => $todo->{node}, level => 's',
638     type => 'child element missing:atom.link.self');
639     }
640 wakaba 1.1
641     return ($new_todos);
642     },
643     };
644    
645     $Element->{$ATOM_NS}->{content} = {
646     attrs_checker => $GetAtomAttrsChecker->({
647     src => sub { 1 }, # checked in |checker|
648     type => sub { 1 }, # checked in |checker|
649     }),
650     checker => sub {
651     my ($self, $todo) = @_;
652    
653     my $attr = $todo->{node}->get_attribute_node_ns (undef, 'type');
654     my $src_attr = $todo->{node}->get_attribute_node_ns (undef, 'src');
655     my $value;
656     if ($attr) {
657     $value = $attr->value;
658     if ($value eq 'text' or $value eq 'html' or $value eq 'xhtml') {
659     # MUST
660     } else {
661 wakaba 1.3 ## NOTE: MUST be a MIME media type. What is "MIME media type"?
662     my $value = $attr->value;
663     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
664     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
665     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
666     if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
667     my @type = ($1, $2);
668     my $param = $3;
669     while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
670     if (defined $2) {
671     push @type, $1 => $2;
672     } else {
673     my $n = $1;
674     my $v = $2;
675     $v =~ s/\\(.)/$1/gs;
676     push @type, $n => $v;
677     }
678     }
679     require Whatpm::IMTChecker;
680     Whatpm::IMTChecker->check_imt (sub {
681     my %opt = @_;
682     $self->{onerror}->(node => $attr, level => $opt{level},
683     type => 'IMT:'.$opt{type});
684     }, @type);
685     } else {
686     $self->{onerror}->(node => $attr, type => 'IMT:syntax error');
687     }
688 wakaba 1.1 }
689     } elsif ($src_attr) {
690     $value = '';
691     $self->{onerror}->(node => $todo->{node},
692     type => 'attribute missing:type', level => 's');
693     } else {
694     $value = 'text';
695     }
696    
697     ## TODO: This implementation is not optimal.
698    
699     if ($src_attr) {
700 wakaba 1.2 ## NOTE: There MUST NOT be any white space.
701     Whatpm::URIChecker->check_iri_reference ($src_attr->value, sub {
702     my %opt = @_;
703     $self->{onerror}->(node => $todo->{node}, level => $opt{level},
704     type => 'URI::'.$opt{type}.
705     (defined $opt{position} ? ':'.$opt{position} : ''));
706     });
707 wakaba 1.1
708     ## NOTE: If @src, the element MUST be empty. What is "empty"?
709     ## Is |<e><!----></e>| empty? |<e>&e;</e>| where |&e;| has
710     ## empty replacement tree shuld be empty, since Atom is defined
711     ## in terms of XML Information Set where entities are expanded.
712     ## (but what if |&e;| is an unexpanded entity?)
713     }
714    
715     if ($value eq 'text') {
716     $self->{onerror}->(node => $attr, type => 'not IMT') if $src_attr;
717    
718     my @nodes = (@{$todo->{node}->child_nodes});
719     my $new_todos = [];
720    
721     while (@nodes) {
722     my $node = shift @nodes;
723     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
724    
725     my $nt = $node->node_type;
726     if ($nt == 1) {
727     # MUST NOT
728     $self->{onerror}->(node => $node, type => 'element not allowed');
729     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
730     unshift @nodes, @$sib;
731     push @$new_todos, @$ch;
732     } elsif ($nt == 3 or $nt == 4) {
733     $self->{onerror}->(node => $node, type => 'character not allowed')
734     if $src_attr;
735     } elsif ($nt == 5) {
736     unshift @nodes, @{$node->child_nodes};
737     }
738     }
739    
740     return ($new_todos);
741     } elsif ($value eq 'html') {
742     $self->{onerror}->(node => $attr, type => 'not IMT') if $src_attr;
743    
744     my @nodes = (@{$todo->{node}->child_nodes});
745     my $new_todos = [];
746    
747     while (@nodes) {
748     my $node = shift @nodes;
749     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
750    
751     my $nt = $node->node_type;
752     if ($nt == 1) {
753     # MUST NOT
754     $self->{onerror}->(node => $node, type => 'element not allowed');
755     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
756     unshift @nodes, @$sib;
757     push @$new_todos, @$ch;
758     } elsif ($nt == 3 or $nt == 4) {
759     $self->{onerror}->(node => $node, type => 'character not allowed')
760     if $src_attr;
761     } elsif ($nt == 5) {
762     unshift @nodes, @{$node->child_nodes};
763     }
764     }
765    
766     ## TODO: SHOULD be suitable for handling as HTML [HTML4]
767     # markup MUST be escaped
768     ## TODO: HTML SHOULD be valid as if within <div>
769    
770     return ($new_todos);
771     } elsif ($value eq 'xhtml') {
772     $self->{onerror}->(node => $attr, type => 'not IMT') if $src_attr;
773    
774     my @nodes = (@{$todo->{node}->child_nodes});
775     my $new_todos = [];
776    
777     my $has_div;
778     while (@nodes) {
779     my $node = shift @nodes;
780     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
781    
782     my $nt = $node->node_type;
783     if ($nt == 1) {
784     # MUST
785     my $nsuri = $node->namespace_uri;
786     if (defined $nsuri and
787     $nsuri eq q<http://www.w3.org/1999/xhtml> and
788     $node->manakai_local_name eq 'div' and
789     not $has_div) {
790     ## TODO: SHOULD be suitable for handling as HTML [XHTML10]
791     $has_div = 1;
792     $self->{onerror}->(node => $node, type => 'element not allowed')
793     if $src_attr;
794     } else {
795     $self->{onerror}->(node => $node, type => 'element not allowed');
796     }
797     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
798     unshift @nodes, @$sib;
799     push @$new_todos, @$ch;
800     } elsif ($nt == 3 or $nt == 4) {
801     ## TODO: Are white spaces allowed?
802     $self->{onerror}->(node => $node, type => 'character not allowed');
803     } elsif ($nt == 5) {
804     unshift @nodes, @{$node->child_nodes};
805     }
806     }
807    
808     unless ($has_div) {
809     $self->{onerror}->(node => $todo->{node},
810     type => 'element missing:div');
811     }
812    
813     return ($new_todos);
814     } elsif ($value =~ m![+/][Xx][Mm][Ll]\z!) {
815     ## ISSUE: There is no definition for "XML media type" in RFC 3023.
816     ## Is |application/xml-dtd| an XML media type?
817    
818     my @nodes = (@{$todo->{node}->child_nodes});
819     my $new_todos = [];
820    
821     while (@nodes) {
822     my $node = shift @nodes;
823     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
824    
825     my $nt = $node->node_type;
826     if ($nt == 1) {
827     ## MAY contain elements
828     $self->{onerror}->(node => $node, type => 'element not allowed')
829     if $src_attr;
830     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
831     unshift @nodes, @$sib;
832     push @$new_todos, @$ch;
833     } elsif ($nt == 3 or $nt == 4) {
834     ## TODO: Are white spaces allowed?
835     $self->{onerror}->(node => $node, type => 'character not allowed');
836     } elsif ($nt == 5) {
837     unshift @nodes, @{$node->child_nodes};
838     }
839     }
840    
841 wakaba 1.3 ## NOTE: SHOULD be suitable for handling as $value.
842 wakaba 1.1 ## If no @src, this would normally mean it contains a
843     ## single child element that would serve as the root element.
844 wakaba 1.3 $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
845     type => 'content:'.$value);
846 wakaba 1.1
847     return ($new_todos);
848     } elsif ($value =~ m!^[Tt][Ee][Xx][Tt]/!) {
849     my @nodes = (@{$todo->{node}->child_nodes});
850     my $new_todos = [];
851    
852     while (@nodes) {
853     my $node = shift @nodes;
854     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
855    
856     my $nt = $node->node_type;
857     if ($nt == 1) {
858     # MUST NOT
859     $self->{onerror}->(node => $node, type => 'element not allowed');
860     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
861     unshift @nodes, @$sib;
862     push @$new_todos, @$ch;
863     } elsif ($nt == 3 or $nt == 4) {
864     $self->{onerror}->(node => $node, type => 'character not allowed')
865     if $src_attr;
866     } elsif ($nt == 5) {
867     unshift @nodes, @{$node->child_nodes};
868     }
869     }
870    
871     ## NOTE: No further restriction (such as to conform to the type).
872    
873     return ($new_todos);
874     } else {
875     my @nodes = (@{$todo->{node}->child_nodes});
876     my $new_todos = [];
877    
878     if ($value =~ m!^(?>message|multipart)/!i) { # MUST NOT
879     $self->{onerror}->(node => $attr, type => 'IMT:composite');
880     }
881    
882     my $s = '';
883     while (@nodes) {
884     my $node = shift @nodes;
885     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
886    
887     my $nt = $node->node_type;
888     if ($nt == 1) {
889     ## not explicitly disallowed
890     $self->{onerror}->(node => $node, type => 'element not allowed');
891     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
892     unshift @nodes, @$sib;
893     push @$new_todos, @$ch;
894     } elsif ($nt == 3 or $nt == 4) {
895     $s .= $node->data;
896     $self->{onerror}->(node => $node, type => 'character not allowed')
897     if $src_attr;
898     } elsif ($nt == 5) {
899     unshift @nodes, @{$node->child_nodes};
900     }
901     }
902    
903     ## TODO: $s = valid Base64ed [RFC 3548] where
904     ## MAY leading and following "white space" (what?)
905     ## and lines separated by a single U+000A
906 wakaba 1.3
907     ## NOTE: SHOULD be suitable for the indicated media type.
908     $self->{onerror}->(node => $todo->{node}, level => 'unsupported',
909     type => 'content:'.$value);
910 wakaba 1.1
911     return ($new_todos);
912     }
913     },
914     };
915    
916     $Element->{$ATOM_NS}->{author} = $AtomPersonConstruct;
917    
918     $Element->{$ATOM_NS}->{category} = {
919     attrs_checker => $GetAtomAttrsChecker->({
920     label => sub { 1 }, # no value constraint
921 wakaba 1.2 scheme => sub { # NOTE: No MUST.
922     my ($self, $attr) = @_;
923     ## NOTE: There MUST NOT be any white space.
924     Whatpm::URIChecker->check_iri ($attr->value, sub {
925     my %opt = @_;
926     $self->{onerror}->(node => $attr, level => $opt{level},
927     type => 'URI::'.$opt{type}.
928     (defined $opt{position} ? ':'.$opt{position} : ''));
929     });
930     },
931 wakaba 1.1 term => sub { 1 }, # no value constraint
932     }),
933     checker => sub {
934     my ($self, $todo) = @_;
935    
936     unless ($todo->{node}->has_attribute_ns (undef, 'term')) {
937     $self->{onerror}->(node => $todo->{node},
938     type => 'attribute missing:term');
939     }
940    
941     my @nodes = (@{$todo->{node}->child_nodes});
942     my $new_todos = [];
943    
944     while (@nodes) {
945     my $node = shift @nodes;
946     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
947    
948     my $nt = $node->node_type;
949     if ($nt == 1) {
950     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
951     unshift @nodes, @$sib;
952     push @$new_todos, @$ch;
953     } elsif ($nt == 3 or $nt == 4) {
954     #
955     } elsif ($nt == 5) {
956     unshift @nodes, @{$node->child_nodes};
957     }
958     }
959    
960     return ($new_todos);
961     },
962     };
963    
964     $Element->{$ATOM_NS}->{contributor} = $AtomPersonConstruct;
965    
966     $Element->{$ATOM_NS}->{generator} = {
967     attrs_checker => $GetAtomAttrsChecker->({
968 wakaba 1.2 uri => sub { # MUST
969     my ($self, $attr) = @_;
970     ## NOTE: There MUST NOT be any white space.
971     Whatpm::URIChecker->check_iri_reference ($attr->value, sub {
972     my %opt = @_;
973     $self->{onerror}->(node => $attr, level => $opt{level},
974     type => 'URI::'.$opt{type}.
975     (defined $opt{position} ? ':'.$opt{position} : ''));
976     });
977     ## NOTE: Dereferencing SHOULD produce a representation
978     ## that is relevant to the agent.
979     },
980 wakaba 1.1 version => sub { 1 }, # no value constraint
981     }),
982     checker => sub {
983     my ($self, $todo) = @_;
984    
985     my @nodes = (@{$todo->{node}->child_nodes});
986     my $new_todos = [];
987    
988     while (@nodes) {
989     my $node = shift @nodes;
990     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
991    
992     my $nt = $node->node_type;
993     if ($nt == 1) {
994     ## not explicitly disallowed
995     $self->{onerror}->(node => $node, type => 'element not allowed');
996     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
997     unshift @nodes, @$sib;
998     push @$new_todos, @$ch;
999     } elsif ($nt == 3 or $nt == 4) {
1000     ## MUST be a string that is a human-readable name for
1001     ## the generating agent
1002     } elsif ($nt == 5) {
1003     unshift @nodes, @{$node->child_nodes};
1004     }
1005     }
1006    
1007     return ($new_todos);
1008     },
1009     };
1010    
1011     $Element->{$ATOM_NS}->{icon} = {
1012     attrs_checker => $GetAtomAttrsChecker->({}),
1013     checker => sub {
1014     my ($self, $todo) = @_;
1015    
1016     my @nodes = (@{$todo->{node}->child_nodes});
1017     my $new_todos = [];
1018    
1019 wakaba 1.2 my $s = '';
1020 wakaba 1.1 while (@nodes) {
1021     my $node = shift @nodes;
1022     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1023    
1024     my $nt = $node->node_type;
1025     if ($nt == 1) {
1026     ## not explicitly disallowed
1027     $self->{onerror}->(node => $node, type => 'element not allowed');
1028     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1029     unshift @nodes, @$sib;
1030     push @$new_todos, @$ch;
1031     } elsif ($nt == 3 or $nt == 4) {
1032 wakaba 1.2 $s .= $node->data;
1033 wakaba 1.1 } elsif ($nt == 5) {
1034     unshift @nodes, @{$node->child_nodes};
1035     }
1036     }
1037    
1038 wakaba 1.2 ## NOTE: No MUST.
1039     ## NOTE: There MUST NOT be any white space.
1040     Whatpm::URIChecker->check_iri_reference ($s, sub {
1041     my %opt = @_;
1042     $self->{onerror}->(node => $todo->{node}, level => $opt{level},
1043     type => 'URI::'.$opt{type}.
1044     (defined $opt{position} ? ':'.$opt{position} : ''));
1045     });
1046    
1047 wakaba 1.1 ## NOTE: Image SHOULD be 1:1 and SHOULD be small
1048    
1049     return ($new_todos);
1050     },
1051     };
1052    
1053     $Element->{$ATOM_NS}->{id} = {
1054     attrs_checker => $GetAtomAttrsChecker->({}),
1055     checker => sub {
1056     my ($self, $todo) = @_;
1057    
1058     my @nodes = (@{$todo->{node}->child_nodes});
1059     my $new_todos = [];
1060 wakaba 1.2
1061     my $s = '';
1062     while (@nodes) {
1063     my $node = shift @nodes;
1064     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1065    
1066     my $nt = $node->node_type;
1067     if ($nt == 1) {
1068     ## not explicitly disallowed
1069     $self->{onerror}->(node => $node, type => 'element not allowed');
1070     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1071     unshift @nodes, @$sib;
1072     push @$new_todos, @$ch;
1073     } elsif ($nt == 3 or $nt == 4) {
1074     $s .= $node->data;
1075     } elsif ($nt == 5) {
1076     unshift @nodes, @{$node->child_nodes};
1077     }
1078     }
1079    
1080     ## NOTE: There MUST NOT be any white space.
1081     Whatpm::URIChecker->check_iri ($s, sub { # MUST
1082     my %opt = @_;
1083     $self->{onerror}->(node => $todo->{node}, level => $opt{level},
1084     type => 'URI::'.$opt{type}.
1085     (defined $opt{position} ? ':'.$opt{position} : ''));
1086     });
1087     ## TODO: SHOULD be normalized
1088    
1089     return ($new_todos);
1090     },
1091     };
1092    
1093     $Element->{$ATOM_NS}->{link} = {
1094     attrs_checker => $GetAtomAttrsChecker->({
1095     href => sub {
1096     my ($self, $attr) = @_;
1097     ## NOTE: There MUST NOT be any white space.
1098     Whatpm::URIChecker->check_iri_reference ($attr->value, sub {
1099     my %opt = @_;
1100     $self->{onerror}->(node => $attr, level => $opt{level},
1101     type => 'URI::'.$opt{type}.
1102     (defined $opt{position} ? ':'.$opt{position} : ''));
1103     });
1104     },
1105 wakaba 1.4 hreflang => $AtomLanguageTagAttrChecker,
1106 wakaba 1.2 length => sub { }, # No MUST; in octets.
1107     rel => sub { # MUST
1108     my ($self, $attr) = @_;
1109     my $value = $attr->value;
1110     if ($value =~ /\A(?>[0-9A-Za-z._~!\$&'()*+,;=\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}-]|%[0-9A-Fa-f][0-9A-Fa-f]|\@)+\z/) {
1111 wakaba 1.3 $value = $LINK_REL . $value;
1112 wakaba 1.2 }
1113    
1114     ## NOTE: There MUST NOT be any white space.
1115     Whatpm::URIChecker->check_iri ($value, sub {
1116     my %opt = @_;
1117     $self->{onerror}->(node => $attr, level => $opt{level},
1118     type => 'URI::'.$opt{type}.
1119     (defined $opt{position} ? ':'.$opt{position} : ''));
1120     });
1121    
1122     ## TODO: Warn if unregistered
1123     },
1124 wakaba 1.3 type => sub {
1125     ## NOTE: MUST be a MIME media type. What is "MIME media type"?
1126     my ($self, $attr) = @_;
1127     my $value = $attr->value;
1128     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
1129     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
1130     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
1131     if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
1132     my @type = ($1, $2);
1133     my $param = $3;
1134     while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
1135     if (defined $2) {
1136     push @type, $1 => $2;
1137     } else {
1138     my $n = $1;
1139     my $v = $2;
1140     $v =~ s/\\(.)/$1/gs;
1141     push @type, $n => $v;
1142     }
1143     }
1144     require Whatpm::IMTChecker;
1145     Whatpm::IMTChecker->check_imt (sub {
1146     my %opt = @_;
1147     $self->{onerror}->(node => $attr, level => $opt{level},
1148     type => 'IMT:'.$opt{type});
1149     }, @type);
1150     } else {
1151     $self->{onerror}->(node => $attr, type => 'IMT:syntax error');
1152     }
1153     },
1154 wakaba 1.2 }),
1155     checker => sub {
1156     my ($self, $todo) = @_;
1157    
1158     unless ($todo->{node}->has_attribute_ns (undef, 'href')) { # MUST
1159     $self->{onerror}->(node => $todo->{node},
1160     type => 'attribute missing:href');
1161     }
1162    
1163 wakaba 1.3 if ($todo->{node}->rel eq $LINK_REL . 'enclosure' and
1164 wakaba 1.2 not $todo->{node}->has_attribute_ns (undef, 'length')) {
1165     $self->{onerror}->(node => $todo->{node}, level => 's',
1166     type => 'attribute missing:length');
1167     }
1168    
1169     my @nodes = (@{$todo->{node}->child_nodes});
1170     my $new_todos = [];
1171 wakaba 1.1
1172     while (@nodes) {
1173     my $node = shift @nodes;
1174     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1175    
1176     my $nt = $node->node_type;
1177     if ($nt == 1) {
1178 wakaba 1.2 my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1179     unshift @nodes, @$sib;
1180     push @$new_todos, @$ch;
1181     } elsif ($nt == 3 or $nt == 4) {
1182     #
1183     } elsif ($nt == 5) {
1184     unshift @nodes, @{$node->child_nodes};
1185     }
1186     }
1187    
1188     return ($new_todos);
1189     },
1190     };
1191    
1192     $Element->{$ATOM_NS}->{logo} = {
1193     attrs_checker => $GetAtomAttrsChecker->({}),
1194     checker => sub {
1195     my ($self, $todo) = @_;
1196    
1197     my @nodes = (@{$todo->{node}->child_nodes});
1198     my $new_todos = [];
1199    
1200     my $s = '';
1201     while (@nodes) {
1202     my $node = shift @nodes;
1203     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1204    
1205     my $nt = $node->node_type;
1206     if ($nt == 1) {
1207 wakaba 1.1 ## not explicitly disallowed
1208     $self->{onerror}->(node => $node, type => 'element not allowed');
1209     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1210     unshift @nodes, @$sib;
1211     push @$new_todos, @$ch;
1212     } elsif ($nt == 3 or $nt == 4) {
1213 wakaba 1.2 $s .= $node->data;
1214 wakaba 1.1 } elsif ($nt == 5) {
1215     unshift @nodes, @{$node->child_nodes};
1216     }
1217     }
1218    
1219 wakaba 1.2 ## NOTE: There MUST NOT be any white space.
1220     Whatpm::URIChecker->check_iri_reference ($s, sub {
1221     my %opt = @_;
1222     $self->{onerror}->(node => $todo->{node}, level => $opt{level},
1223     type => 'URI::'.$opt{type}.
1224     (defined $opt{position} ? ':'.$opt{position} : ''));
1225     });
1226    
1227     ## NOTE: Image SHOULD be 2:1
1228    
1229     return ($new_todos);
1230     },
1231     };
1232    
1233     $Element->{$ATOM_NS}->{published} = $AtomDateConstruct;
1234    
1235     $Element->{$ATOM_NS}->{rights} = $AtomDateConstruct;
1236 wakaba 1.3 ## NOTE: SHOULD NOT be used to convey machine-readable information.
1237 wakaba 1.2
1238     $Element->{$ATOM_NS}->{source} = {
1239     attrs_checker => $GetAtomAttrsChecker->({}),
1240     checker => sub {
1241     my ($self, $todo) = @_;
1242    
1243     my @nodes = (@{$todo->{node}->child_nodes});
1244     my $new_todos = [];
1245     my $has_element = {};
1246     while (@nodes) {
1247     my $node = shift @nodes;
1248     $self->_remove_minuses ($node) and next if ref $node eq 'HASH';
1249    
1250     my $nt = $node->node_type;
1251     if ($nt == 1) {
1252     my $nsuri = $node->namespace_uri;
1253     $nsuri = '' unless defined $nsuri;
1254     my $not_allowed;
1255     if ($nsuri eq $ATOM_NS) {
1256     my $ln = $node->manakai_local_name;
1257     if ($ln eq 'entry') {
1258     $has_element->{entry} = 1;
1259     } elsif ({
1260     generator => 1,
1261     icon => 1,
1262     id => 1,
1263     logo => 1,
1264     rights => 1,
1265     subtitle => 1,
1266     title => 1,
1267     updated => 1,
1268     }->{$ln}) {
1269     unless ($has_element->{$ln}) {
1270     $has_element->{$ln} = 1;
1271     $not_allowed = $has_element->{entry};
1272     } else {
1273     $not_allowed = 1;
1274     }
1275     } elsif ($ln eq 'link') {
1276 wakaba 1.3 if ($node->rel eq $LINK_REL . 'alternate') {
1277     my $type = $node->get_attribute_ns (undef, 'type');
1278     $type = '' unless defined $type;
1279     my $hreflang = $node->get_attribute_ns (undef, 'hreflang');
1280     $hreflang = '' unless defined $hreflang;
1281     my $key = 'link:'.(defined $type ? ':'.$type : '').':'.
1282     (defined $hreflang ? ':'.$hreflang : '');
1283     unless ($has_element->{$key}) {
1284     $has_element->{$key} = 1;
1285     } else {
1286     $not_allowed = 1;
1287     }
1288     }
1289     $not_allowed ||= $has_element->{entry};
1290 wakaba 1.2 } elsif ({
1291     author => 1,
1292     category => 1,
1293     contributor => 1,
1294     }->{$ln}) {
1295     $not_allowed = $has_element->{entry};
1296     } else {
1297     $not_allowed = 1;
1298     }
1299     } else {
1300     ## TODO: extension element
1301     $not_allowed = 1;
1302     }
1303     $self->{onerror}->(node => $node, type => 'element not allowed')
1304     if $not_allowed;
1305     my ($sib, $ch) = $self->_check_get_children ($node, $todo);
1306     unshift @nodes, @$sib;
1307     push @$new_todos, @$ch;
1308     } elsif ($nt == 3 or $nt == 4) {
1309     ## TODO: Are white spaces allowed?
1310     $self->{onerror}->(node => $node, type => 'character not allowed');
1311     } elsif ($nt == 5) {
1312     unshift @nodes, @{$node->child_nodes};
1313     }
1314     }
1315 wakaba 1.1
1316     return ($new_todos);
1317     },
1318     };
1319 wakaba 1.2
1320     $Element->{$ATOM_NS}->{subtitle} = $AtomTextConstruct;
1321    
1322     $Element->{$ATOM_NS}->{summary} = $AtomTextConstruct;
1323    
1324     $Element->{$ATOM_NS}->{title} = $AtomTextConstruct;
1325    
1326     $Element->{$ATOM_NS}->{updated} = $AtomDateConstruct;
1327    
1328     ## TODO: signature element
1329    
1330     ## TODO: simple extension element and structured extension element
1331 wakaba 1.1
1332     $Whatpm::ContentChecker::Namespace->{$ATOM_NS}->{loaded} = 1;
1333    
1334     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24