/[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.14 - (hide annotations) (download)
Thu Mar 20 08:54:00 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.13: +14 -5 lines
++ whatpm/t/ChangeLog	20 Mar 2008 08:53:32 -0000
	* ContentCheker.t: Replace dummy error type for subdoc
	checking invocations to ";SUBDOC".

	* content-model-1.dat, content-model-2.dat: Test
	results revised to support the aforementioned change.

	* content-model-atom-1.dat: Test results revised
	so that |type=html| in Text construct is now
	tested whether the subdoc code is invoked.

2008-03-20  Wakaba  <wakaba@suika.fam.cx>

	* content-model-atom-1.dat: Test data on cases of
	missing |atom:summary| in |atom:entry| are added.

2008-03-20  Wakaba  <wakaba@suika.fam.cx>

++ whatpm/Whatpm/ContentChecker/ChangeLog	20 Mar 2008 08:38:25 -0000
	* Atom.pm: Raise an error if required |atom:summary|
	element is missing from an |atom:entry| element.

2008-03-20  Wakaba  <wakaba@suika.fam.cx>

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24