/[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.10 - (hide annotations) (download)
Thu Mar 20 07:37:00 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.9: +7 -1 lines
++ whatpm/t/ChangeLog	20 Mar 2008 07:36:54 -0000
	* content-model-1.dat: Some test results are fixed.

	* content-model-atom-1.dat: Some test results are fixed.
	Test data for |atom:author| for |atom:feed| are added.

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

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

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

++ whatpm/Whatpm/ContentChecker/ChangeLog	20 Mar 2008 07:35:41 -0000
	* Atom.pm: Support for |author| for |feed| checking.

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 ## 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 wakaba 1.10 $item->{parent_state}->{has_no_author_entry} = 1;#for atom:feed's check
480 wakaba 1.9 } # A
481     }
482    
483 wakaba 1.1 ## TODO: If entry's with same id, then updated SHOULD be different
484    
485 wakaba 1.8 unless ($element_state->{has_element}->{id}) { # MUST
486     $self->{onerror}->(node => $item->{node},
487     type => 'element missing:atom|id');
488     }
489     unless ($element_state->{has_element}->{title}) { # MUST
490     $self->{onerror}->(node => $item->{node},
491     type => 'element missing:atom|title');
492     }
493     unless ($element_state->{has_element}->{updated}) { # MUST
494     $self->{onerror}->(node => $item->{node},
495     type => 'element missing:atom|updated');
496     }
497     if (not $element_state->{has_element}->{content} and
498     not $element_state->{has_element}->{'link.alternate'}) {
499     $self->{onerror}->(node => $item->{node},
500     type => 'element missing:atom|link|alternate');
501 wakaba 1.1 }
502     },
503     };
504    
505     $Element->{$ATOM_NS}->{feed} = {
506 wakaba 1.8 %AtomChecker,
507 wakaba 1.1 is_root => 1,
508 wakaba 1.8 check_child_element => sub {
509     my ($self, $item, $child_el, $child_nsuri, $child_ln,
510     $child_is_transparent, $element_state) = @_;
511    
512     ## NOTE: metadata elements, followed by atom:entry* (no explicit MAY)
513    
514     if ($self->{minus_elements}->{$child_nsuri}->{$child_ln}) {
515     $self->{onerror}->(node => $child_el,
516     type => 'element not allowed:minus',
517     level => $self->{must_level});
518     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
519     #
520     } elsif ($child_nsuri eq $ATOM_NS) {
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.10
586     if ($element_state->{has_no_author_entry}) {
587     $self->{onerror}->(node => $item->{node},
588     type => 'element missing:atom|author',
589     level => $self->{must_level});
590     }
591 wakaba 1.1
592     ## TODO: If entry's with same id, then updated SHOULD be different
593    
594 wakaba 1.8 unless ($element_state->{has_element}->{id}) { # MUST
595     $self->{onerror}->(node => $item->{node},
596     type => 'element missing:atom|id');
597 wakaba 1.1 }
598 wakaba 1.8 unless ($element_state->{has_element}->{title}) { # MUST
599     $self->{onerror}->(node => $item->{node},
600     type => 'element missing:atom|title');
601 wakaba 1.1 }
602 wakaba 1.8 unless ($element_state->{has_element}->{updated}) { # MUST
603     $self->{onerror}->(node => $item->{node},
604     type => 'element missing:atom|updated');
605 wakaba 1.1 }
606 wakaba 1.8 unless ($element_state->{has_element}->{'link.self'}) {
607     $self->{onerror}->(node => $item->{node}, level => 's',
608     type => 'element missing:atom|link|self');
609 wakaba 1.3 }
610 wakaba 1.1
611 wakaba 1.8 $AtomChecker{check_end}->(@_);
612 wakaba 1.1 },
613     };
614    
615     $Element->{$ATOM_NS}->{content} = {
616 wakaba 1.8 %AtomChecker,
617     check_start => sub {
618     my ($self, $item, $element_state) = @_;
619     $element_state->{type} = 'text';
620     },
621     check_attrs => $GetAtomAttrsChecker->({
622     src => sub {
623     my ($self, $attr, $item, $element_state) = @_;
624    
625     $element_state->{has_src} = 1;
626    
627     ## NOTE: There MUST NOT be any white space.
628     Whatpm::URIChecker->check_iri_reference ($attr->value, sub {
629     my %opt = @_;
630     $self->{onerror}->(node => $item->{node}, level => $opt{level},
631     type => 'URI::'.$opt{type}.
632     (defined $opt{position} ? ':'.$opt{position} : ''));
633     });
634     },
635     type => sub {
636     my ($self, $attr, $item, $element_state) = @_;
637    
638     $element_state->{has_type} = 1;
639 wakaba 1.1
640 wakaba 1.8 my $value = $attr->value;
641 wakaba 1.1 if ($value eq 'text' or $value eq 'html' or $value eq 'xhtml') {
642     # MUST
643     } else {
644 wakaba 1.3 ## NOTE: MUST be a MIME media type. What is "MIME media type"?
645     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
646     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
647     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
648     if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
649     my @type = ($1, $2);
650     my $param = $3;
651     while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
652     if (defined $2) {
653     push @type, $1 => $2;
654     } else {
655     my $n = $1;
656     my $v = $2;
657     $v =~ s/\\(.)/$1/gs;
658     push @type, $n => $v;
659     }
660     }
661     require Whatpm::IMTChecker;
662     Whatpm::IMTChecker->check_imt (sub {
663     my %opt = @_;
664     $self->{onerror}->(node => $attr, level => $opt{level},
665     type => 'IMT:'.$opt{type});
666     }, @type);
667     } else {
668 wakaba 1.8 $self->{onerror}->(node => $attr, type => 'IMT:syntax error',
669     level => $self->{must_level});
670 wakaba 1.3 }
671 wakaba 1.1 }
672    
673 wakaba 1.8 if ($value =~ m![+/][Xx][Mm][Ll]\z!) {
674     ## ISSUE: There is no definition for "XML media type" in RFC 3023.
675     ## Is |application/xml-dtd| an XML media type?
676     $value = 'xml';
677     } elsif ($value =~ m!^[Tt][Ee][Xx][Tt]/!) {
678     $value = 'mime_text';
679     } elsif ($value =~ m!^(?>message|multipart)/!i) {
680     $self->{onerror}->(node => $attr, type => 'IMT:composite',
681     level => $self->{must_level});
682     }
683 wakaba 1.1
684 wakaba 1.8 $element_state->{type} = $value;
685     },
686     }, {
687     src => FEATURE_RFC4287,
688     type => FEATURE_RFC4287,
689     }),
690     check_child_element => sub {
691     my ($self, $item, $child_el, $child_nsuri, $child_ln,
692     $child_is_transparent, $element_state) = @_;
693    
694     if ($self->{minus_elements}->{$child_nsuri}->{$child_ln}) {
695     $self->{onerror}->(node => $child_el,
696     type => 'element not allowed:minus',
697     level => $self->{must_level});
698     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
699     #
700     } else {
701     if ($element_state->{type} eq 'text' or
702     $element_state->{type} eq 'html' or
703     $element_state->{type} eq 'mime_text') {
704     # MUST NOT
705     $self->{onerror}->(node => $child_el,
706     type => 'element not allowed:atom|content',
707     level => $self->{must_level});
708     } elsif ($element_state->{type} eq 'xhtml') {
709     if ($element_state->{has_div}) {
710     $self->{onerror}->(node => $child_el,
711     type => 'element not allowed:atom|content',
712     level => $self->{must_level});
713     } else {
714     ## TODO: SHOULD be suitable for handling as HTML [XHTML10]
715     $element_state->{has_div} = 1;
716     }
717     } elsif ($element_state->{type} eq 'xml') {
718     ## MAY contain elements
719     if ($element_state->{has_src}) {
720     $self->{onerror}->(node => $child_el,
721     type => 'element not allowed:atom|content',
722     level => $self->{must_level});
723 wakaba 1.1 }
724 wakaba 1.8 } else {
725     ## NOTE: Elements are not explicitly disallowed.
726 wakaba 1.1 }
727 wakaba 1.8 }
728     },
729     ## NOTE: If @src, the element MUST be empty. What is "empty"?
730     ## Is |<e><!----></e>| empty? |<e>&e;</e>| where |&e;| has
731     ## empty replacement tree shuld be empty, since Atom is defined
732     ## in terms of XML Information Set where entities are expanded.
733     ## (but what if |&e;| is an unexpanded entity?)
734     check_child_text => sub {
735     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
736     if ($has_significant) {
737     if ($element_state->{has_src}) {
738     $self->{onerror}->(node => $child_node,
739     type => 'character not allowed',
740     level => $self->{must_level});
741     } elsif ($element_state->{type} eq 'xhtml' or
742     $element_state->{type} eq 'xml') {
743     $self->{onerror}->(node => $child_node,
744     type => 'character not allowed:atom|content',
745     level => $self->{must_level});
746 wakaba 1.1 }
747 wakaba 1.8 }
748 wakaba 1.1
749 wakaba 1.8 ## type=html
750 wakaba 1.1 ## TODO: SHOULD be suitable for handling as HTML [HTML4]
751     # markup MUST be escaped
752     ## TODO: HTML SHOULD be valid as if within <div>
753    
754 wakaba 1.8 ## NOTE: type=text/* has no further restriction (i.e. the content don't
755     ## have to conform to the definition of the type).
756     },
757     check_end => sub {
758     my ($self, $item, $element_state) = @_;
759 wakaba 1.1
760 wakaba 1.8 if ($element_state->{has_src}) {
761     if (not $element_state->{has_type}) {
762     $self->{onerror}->(node => $item->{node},
763     type => 'attribute missing:type',
764     level => $self->{should_level});
765     }
766     if ($element_state->{type} eq 'text' or
767     $element_state->{type} eq 'html' or
768     $element_state->{type} eq 'xhtml') {
769     $self->{onerror}
770     ->(node => $item->{node}->get_attribute_node_ns (undef, 'type'),
771     type => 'not IMT', level => $self->{must_level});
772 wakaba 1.1 }
773 wakaba 1.8 }
774 wakaba 1.1
775 wakaba 1.8 if ($element_state->{type} eq 'xhtml') {
776     unless ($element_state->{has_div}) {
777     $self->{onerror}->(node => $item->{node},
778     type => 'element missing:div',
779     level => $self->{must_level});
780 wakaba 1.1 }
781 wakaba 1.8 } elsif ($element_state->{type} eq 'xml') {
782 wakaba 1.3 ## NOTE: SHOULD be suitable for handling as $value.
783 wakaba 1.1 ## If no @src, this would normally mean it contains a
784     ## single child element that would serve as the root element.
785 wakaba 1.8 $self->{onerror}->(node => $item->{node},
786     level => $self->{unsupported_level},
787     type => 'atom|content not supported',
788     value => $item->{node}->get_attribute_ns
789     (undef, 'type'));
790     } elsif ($element_state->{type} eq 'text' or
791     $element_state->{type} eq 'html' or
792     $element_state->{type} eq 'mime-text') {
793     #
794 wakaba 1.1 } else {
795     ## TODO: $s = valid Base64ed [RFC 3548] where
796     ## MAY leading and following "white space" (what?)
797     ## and lines separated by a single U+000A
798 wakaba 1.3
799     ## NOTE: SHOULD be suitable for the indicated media type.
800 wakaba 1.8 $self->{onerror}->(node => $item->{node},
801     level => $self->{unsupported_level},
802     type => 'atom|content not supported',
803     value => $item->{node}->get_attribute_ns
804     (undef, 'type'));
805     }
806 wakaba 1.1
807 wakaba 1.8 $AtomChecker{check_end}->(@_);
808 wakaba 1.1 },
809     };
810 wakaba 1.6 ## TODO: Tests for <html:nest/> in <atom:content/>
811 wakaba 1.1
812 wakaba 1.8 $Element->{$ATOM_NS}->{author} = \%AtomPersonConstruct;
813 wakaba 1.1
814     $Element->{$ATOM_NS}->{category} = {
815 wakaba 1.8 %AtomChecker,
816     check_attrs => $GetAtomAttrsChecker->({
817 wakaba 1.1 label => sub { 1 }, # no value constraint
818 wakaba 1.2 scheme => sub { # NOTE: No MUST.
819     my ($self, $attr) = @_;
820     ## NOTE: There MUST NOT be any white space.
821     Whatpm::URIChecker->check_iri ($attr->value, sub {
822     my %opt = @_;
823     $self->{onerror}->(node => $attr, level => $opt{level},
824     type => 'URI::'.$opt{type}.
825     (defined $opt{position} ? ':'.$opt{position} : ''));
826     });
827     },
828 wakaba 1.8 term => sub {
829     my ($self, $attr, $item, $element_state) = @_;
830    
831     ## NOTE: No value constraint.
832    
833     $element_state->{has_term} = 1;
834     },
835     }, {
836     label => FEATURE_RFC4287,
837     scheme => FEATURE_RFC4287,
838     term => FEATURE_RFC4287,
839 wakaba 1.1 }),
840 wakaba 1.8 check_end => sub {
841     my ($self, $item, $element_state) = @_;
842     unless ($element_state->{has_term}) {
843     $self->{onerror}->(node => $item->{node},
844 wakaba 1.1 type => 'attribute missing:term');
845     }
846    
847 wakaba 1.8 $AtomChecker{check_end}->(@_);
848 wakaba 1.1 },
849 wakaba 1.8 ## NOTE: Meaning of content is not defined.
850 wakaba 1.1 };
851    
852 wakaba 1.8 $Element->{$ATOM_NS}->{contributor} = \%AtomPersonConstruct;
853 wakaba 1.6
854     ## TODO: Anything below does not support <html:nest/> yet.
855 wakaba 1.1
856     $Element->{$ATOM_NS}->{generator} = {
857 wakaba 1.8 %AtomChecker,
858     check_attrs => $GetAtomAttrsChecker->({
859 wakaba 1.2 uri => sub { # MUST
860     my ($self, $attr) = @_;
861     ## NOTE: There MUST NOT be any white space.
862     Whatpm::URIChecker->check_iri_reference ($attr->value, sub {
863     my %opt = @_;
864     $self->{onerror}->(node => $attr, level => $opt{level},
865     type => 'URI::'.$opt{type}.
866     (defined $opt{position} ? ':'.$opt{position} : ''));
867     });
868     ## NOTE: Dereferencing SHOULD produce a representation
869     ## that is relevant to the agent.
870     },
871 wakaba 1.1 version => sub { 1 }, # no value constraint
872 wakaba 1.8 }, {
873     uri => FEATURE_RFC4287,
874     version => FEATURE_RFC4287,
875 wakaba 1.1 }),
876    
877 wakaba 1.8 ## NOTE: Elements are not explicitly disallowed.
878 wakaba 1.1
879 wakaba 1.8 ## NOTE: Content MUST be a string that is a human-readable name for
880     ## the generating agent.
881 wakaba 1.1 };
882    
883     $Element->{$ATOM_NS}->{icon} = {
884 wakaba 1.8 %AtomChecker,
885     check_start => sub {
886     my ($self, $item, $element_state) = @_;
887     $element_state->{value} = '';
888     },
889     ## NOTE: Elements are not explicitly disallowed.
890     check_child_text => sub {
891     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
892     $element_state->{value} .= $child_node->data;
893     },
894     check_end => sub {
895     my ($self, $item, $element_state) = @_;
896 wakaba 1.1
897 wakaba 1.2 ## NOTE: No MUST.
898     ## NOTE: There MUST NOT be any white space.
899 wakaba 1.8 Whatpm::URIChecker->check_iri_reference ($element_state->{value}, sub {
900 wakaba 1.2 my %opt = @_;
901 wakaba 1.8 $self->{onerror}->(node => $item->{node}, level => $opt{level},
902 wakaba 1.2 type => 'URI::'.$opt{type}.
903     (defined $opt{position} ? ':'.$opt{position} : ''));
904     });
905    
906 wakaba 1.1 ## NOTE: Image SHOULD be 1:1 and SHOULD be small
907    
908 wakaba 1.8 $AtomChecker{check_end}->(@_);
909 wakaba 1.1 },
910     };
911    
912     $Element->{$ATOM_NS}->{id} = {
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.2
926     ## NOTE: There MUST NOT be any white space.
927 wakaba 1.8 Whatpm::URIChecker->check_iri ($element_state->{value}, sub {
928 wakaba 1.2 my %opt = @_;
929 wakaba 1.8 $self->{onerror}->(node => $item->{node}, level => $opt{level},
930 wakaba 1.2 type => 'URI::'.$opt{type}.
931     (defined $opt{position} ? ':'.$opt{position} : ''));
932     });
933     ## TODO: SHOULD be normalized
934    
935 wakaba 1.8 $AtomChecker{check_end}->(@_);
936 wakaba 1.2 },
937     };
938    
939     $Element->{$ATOM_NS}->{link} = {
940 wakaba 1.8 %AtomChecker,
941     check_attrs => $GetAtomAttrsChecker->({
942 wakaba 1.2 href => sub {
943     my ($self, $attr) = @_;
944     ## NOTE: There MUST NOT be any white space.
945     Whatpm::URIChecker->check_iri_reference ($attr->value, sub {
946     my %opt = @_;
947     $self->{onerror}->(node => $attr, level => $opt{level},
948     type => 'URI::'.$opt{type}.
949     (defined $opt{position} ? ':'.$opt{position} : ''));
950     });
951     },
952 wakaba 1.4 hreflang => $AtomLanguageTagAttrChecker,
953 wakaba 1.2 length => sub { }, # No MUST; in octets.
954     rel => sub { # MUST
955     my ($self, $attr) = @_;
956     my $value = $attr->value;
957     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/) {
958 wakaba 1.3 $value = $LINK_REL . $value;
959 wakaba 1.2 }
960    
961     ## NOTE: There MUST NOT be any white space.
962     Whatpm::URIChecker->check_iri ($value, sub {
963     my %opt = @_;
964     $self->{onerror}->(node => $attr, level => $opt{level},
965     type => 'URI::'.$opt{type}.
966     (defined $opt{position} ? ':'.$opt{position} : ''));
967     });
968    
969     ## TODO: Warn if unregistered
970     },
971 wakaba 1.5 title => sub { }, # No MUST
972 wakaba 1.3 type => sub {
973     ## NOTE: MUST be a MIME media type. What is "MIME media type"?
974     my ($self, $attr) = @_;
975     my $value = $attr->value;
976     my $lws0 = qr/(?>(?>\x0D\x0A)?[\x09\x20])*/;
977     my $token = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+/;
978     my $qs = qr/"(?>[\x00-\x0C\x0E-\x21\x23-\x5B\x5D-\x7E]|\x0D\x0A[\x09\x20]|\x5C[\x00-\x7F])*"/;
979     if ($value =~ m#\A$lws0($token)$lws0/$lws0($token)$lws0((?>;$lws0$token$lws0=$lws0(?>$token|$qs)$lws0)*)\z#) {
980     my @type = ($1, $2);
981     my $param = $3;
982     while ($param =~ s/^;$lws0($token)$lws0=$lws0(?>($token)|($qs))$lws0//) {
983     if (defined $2) {
984     push @type, $1 => $2;
985     } else {
986     my $n = $1;
987     my $v = $2;
988     $v =~ s/\\(.)/$1/gs;
989     push @type, $n => $v;
990     }
991     }
992     require Whatpm::IMTChecker;
993     Whatpm::IMTChecker->check_imt (sub {
994     my %opt = @_;
995     $self->{onerror}->(node => $attr, level => $opt{level},
996     type => 'IMT:'.$opt{type});
997     }, @type);
998     } else {
999     $self->{onerror}->(node => $attr, type => 'IMT:syntax error');
1000     }
1001     },
1002 wakaba 1.8 }, {
1003     href => FEATURE_RFC4287,
1004     hreflang => FEATURE_RFC4287,
1005     length => FEATURE_RFC4287,
1006     rel => FEATURE_RFC4287,
1007     title => FEATURE_RFC4287,
1008     type => FEATURE_RFC4287,
1009 wakaba 1.2 }),
1010 wakaba 1.8 check_start => sub {
1011     my ($self, $item, $element_state) = @_;
1012 wakaba 1.2
1013 wakaba 1.8 unless ($item->{node}->has_attribute_ns (undef, 'href')) { # MUST
1014     $self->{onerror}->(node => $item->{node},
1015 wakaba 1.2 type => 'attribute missing:href');
1016     }
1017    
1018 wakaba 1.8 if ($item->{node}->rel eq $LINK_REL . 'enclosure' and
1019     not $item->{node}->has_attribute_ns (undef, 'length')) {
1020     $self->{onerror}->(node => $item->{node}, level => 's',
1021 wakaba 1.2 type => 'attribute missing:length');
1022     }
1023     },
1024     };
1025    
1026     $Element->{$ATOM_NS}->{logo} = {
1027 wakaba 1.8 %AtomChecker,
1028     ## NOTE: Child elements are not explicitly disallowed
1029     check_start => sub {
1030     my ($self, $item, $element_state) = @_;
1031     $element_state->{value} = '';
1032     },
1033     ## NOTE: Elements are not explicitly disallowed.
1034     check_child_text => sub {
1035     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1036     $element_state->{value} .= $child_node->data;
1037     },
1038     check_end => sub {
1039     my ($self, $item, $element_state) = @_;
1040 wakaba 1.1
1041 wakaba 1.2 ## NOTE: There MUST NOT be any white space.
1042 wakaba 1.8 Whatpm::URIChecker->check_iri_reference ($element_state->{value}, sub {
1043 wakaba 1.2 my %opt = @_;
1044 wakaba 1.8 $self->{onerror}->(node => $item->{node}, level => $opt{level},
1045 wakaba 1.2 type => 'URI::'.$opt{type}.
1046     (defined $opt{position} ? ':'.$opt{position} : ''));
1047     });
1048    
1049     ## NOTE: Image SHOULD be 2:1
1050    
1051 wakaba 1.8 $AtomChecker{check_end}->(@_);
1052 wakaba 1.2 },
1053     };
1054    
1055 wakaba 1.8 $Element->{$ATOM_NS}->{published} = \%AtomDateConstruct;
1056 wakaba 1.2
1057 wakaba 1.8 $Element->{$ATOM_NS}->{rights} = \%AtomDateConstruct;
1058 wakaba 1.3 ## NOTE: SHOULD NOT be used to convey machine-readable information.
1059 wakaba 1.2
1060     $Element->{$ATOM_NS}->{source} = {
1061 wakaba 1.8 %AtomChecker,
1062     check_child_element => sub {
1063     my ($self, $item, $child_el, $child_nsuri, $child_ln,
1064     $child_is_transparent, $element_state) = @_;
1065    
1066     if ($self->{minus_elements}->{$child_nsuri}->{$child_ln}) {
1067     $self->{onerror}->(node => $child_el,
1068     type => 'element not allowed:minus',
1069     level => $self->{must_level});
1070     } elsif ($self->{plus_elements}->{$child_nsuri}->{$child_ln}) {
1071     #
1072     } elsif ($child_nsuri eq $ATOM_NS) {
1073     my $not_allowed;
1074     if ($child_ln eq 'entry') {
1075     $element_state->{has_element}->{entry} = 1;
1076     } elsif ({
1077     generator => 1,
1078     icon => 1,
1079     id => 1,
1080     logo => 1,
1081     rights => 1,
1082     subtitle => 1,
1083     title => 1,
1084     updated => 1,
1085     }->{$child_ln}) {
1086     unless ($element_state->{has_element}->{$child_ln}) {
1087     $element_state->{has_element}->{$child_ln} = 1;
1088     $not_allowed = $element_state->{has_element}->{entry};
1089     } else {
1090     $not_allowed = 1;
1091     }
1092     } elsif ($child_ln eq 'link') {
1093     if ($child_ln->rel eq $LINK_REL . 'alternate') {
1094     my $type = $child_ln->get_attribute_ns (undef, 'type');
1095     $type = '' unless defined $type;
1096     my $hreflang = $child_ln->get_attribute_ns (undef, 'hreflang');
1097     $hreflang = '' unless defined $hreflang;
1098     my $key = 'link:'.(defined $type ? ':'.$type : '').':'.
1099     (defined $hreflang ? ':'.$hreflang : '');
1100     unless ($element_state->{has_element}->{$key}) {
1101     $element_state->{has_element}->{$key} = 1;
1102 wakaba 1.2 } else {
1103     $not_allowed = 1;
1104     }
1105     }
1106 wakaba 1.8 $not_allowed ||= $element_state->{has_element}->{entry};
1107     } elsif ({
1108     category => 1,
1109     contributor => 1,
1110     }->{$child_ln}) {
1111     $not_allowed = $element_state->{has_element}->{entry};
1112 wakaba 1.9 } elsif ($child_ln eq 'author') {
1113     $not_allowed = $element_state->{has_element}->{entry};
1114     $item->{parent_state}->{has_author} = 1; # parent::atom:entry's flag
1115 wakaba 1.8 } else {
1116     $not_allowed = 1;
1117     }
1118     if ($not_allowed) {
1119     $self->{onerror}->(node => $child_el, type => 'element not allowed');
1120 wakaba 1.2 }
1121 wakaba 1.8 } else {
1122     ## TODO: extension element
1123     $self->{onerror}->(node => $child_el, type => 'element not allowed');
1124     }
1125     },
1126     check_child_text => sub {
1127     my ($self, $item, $child_node, $has_significant, $element_state) = @_;
1128     if ($has_significant) {
1129     $self->{onerror}->(node => $child_node, type => 'character not allowed',
1130     level => $self->{must_level});
1131 wakaba 1.2 }
1132 wakaba 1.1 },
1133     };
1134 wakaba 1.2
1135 wakaba 1.8 $Element->{$ATOM_NS}->{subtitle} = \%AtomTextConstruct;
1136 wakaba 1.2
1137 wakaba 1.8 $Element->{$ATOM_NS}->{summary} = \%AtomTextConstruct;
1138 wakaba 1.2
1139 wakaba 1.8 $Element->{$ATOM_NS}->{title} = \%AtomTextConstruct;
1140 wakaba 1.2
1141 wakaba 1.8 $Element->{$ATOM_NS}->{updated} = \%AtomDateConstruct;
1142 wakaba 1.2
1143     ## TODO: signature element
1144    
1145     ## TODO: simple extension element and structured extension element
1146 wakaba 1.1
1147     $Whatpm::ContentChecker::Namespace->{$ATOM_NS}->{loaded} = 1;
1148    
1149     1;

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24