/[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.11 - (hide annotations) (download)
Thu Mar 20 08:04:58 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.10: +12 -5 lines
++ whatpm/Whatpm/ChangeLog	20 Mar 2008 08:04:21 -0000
	* HTML.pm.src (set_inner_html): Line/column number
	code was old one yet.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24