/[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.13 - (hide annotations) (download)
Thu Mar 20 08:27:38 2008 UTC (16 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.12: +17 -11 lines
++ whatpm/Whatpm/ContentChecker/ChangeLog	20 Mar 2008 08:27:34 -0000
	* Atom.pm: Support for |<* type=html>| in Text construct.

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

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

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24