/[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.9 - (hide annotations) (download)
Thu Mar 20 05:59:55 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.8: +36 -4 lines
++ whatpm/t/ChangeLog	20 Mar 2008 05:59:40 -0000
2008-03-20  Wakaba  <wakaba@suika.fam.cx>

	* content-model-1.dat: Some test results related on |atom:entry|
	are fixed.

	* content-model-atom-1.dat: Test data for |atom:author|
	for |atom:entry| are added.

++ whatpm/Whatpm/ContentChecker/ChangeLog	20 Mar 2008 05:58:48 -0000
2008-03-20  Wakaba  <wakaba@suika.fam.cx>

	* Atom.pm: Support for |author| for |entry| checking.

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24