1 /* SGML token scanner utilities */
12 #include "dom/scanner.h"
13 #include "dom/sgml/scanner.h"
14 #include "dom/string.h"
15 #include "util/error.h"
18 /* Bitmap entries for the SGML character groups used in the scanner table */
20 enum sgml_char_group
{
21 SGML_CHAR_ENTITY
= (1 << 1),
22 SGML_CHAR_IDENT
= (1 << 2),
23 SGML_CHAR_NEWLINE
= (1 << 3),
24 SGML_CHAR_WHITESPACE
= (1 << 4),
25 SGML_CHAR_NOT_TEXT
= (1 << 5),
26 SGML_CHAR_NOT_ATTRIBUTE
= (1 << 6),
29 static struct dom_scan_table_info sgml_scan_table_info
[] = {
30 DOM_SCAN_TABLE_RANGE("0", '9', SGML_CHAR_IDENT
| SGML_CHAR_ENTITY
),
31 DOM_SCAN_TABLE_RANGE("A", 'Z', SGML_CHAR_IDENT
| SGML_CHAR_ENTITY
),
32 DOM_SCAN_TABLE_RANGE("a", 'z', SGML_CHAR_IDENT
| SGML_CHAR_ENTITY
),
33 /* For the octal number impared (me including) \241 is 161 --jonas */
34 DOM_SCAN_TABLE_RANGE("\241", 255, SGML_CHAR_IDENT
| SGML_CHAR_ENTITY
),
36 DOM_SCAN_TABLE_STRING("-_:.", SGML_CHAR_IDENT
| SGML_CHAR_ENTITY
),
37 DOM_SCAN_TABLE_STRING("#", SGML_CHAR_ENTITY
),
38 DOM_SCAN_TABLE_STRING(" \f\n\r\t\v", SGML_CHAR_WHITESPACE
),
39 DOM_SCAN_TABLE_STRING("\f\n", SGML_CHAR_NEWLINE
),
40 DOM_SCAN_TABLE_STRING("<&", SGML_CHAR_NOT_TEXT
),
41 DOM_SCAN_TABLE_STRING("<=>", SGML_CHAR_NOT_ATTRIBUTE
),
46 #define SGML_STRING_MAP(str, type, family) \
47 { STATIC_DOM_STRING(str), SGML_TOKEN_##type, SGML_TOKEN_##family }
49 static struct dom_scanner_string_mapping sgml_string_mappings
[] = {
50 SGML_STRING_MAP("--", NOTATION_COMMENT
, NOTATION
),
51 SGML_STRING_MAP("ATTLIST", NOTATION_ATTLIST
, NOTATION
),
52 SGML_STRING_MAP("DOCTYPE", NOTATION_DOCTYPE
, NOTATION
),
53 SGML_STRING_MAP("ELEMENT", NOTATION_ELEMENT
, NOTATION
),
54 SGML_STRING_MAP("ENTITY", NOTATION_ENTITY
, NOTATION
),
56 SGML_STRING_MAP("xml", PROCESS_XML
, PROCESS
),
57 SGML_STRING_MAP("xml-stylesheet", PROCESS_XML_STYLESHEET
, PROCESS
),
62 static struct dom_scanner_token
*scan_sgml_tokens(struct dom_scanner
*scanner
);
64 struct dom_scanner_info sgml_scanner_info
= {
70 #define check_sgml_table(c, bit) (sgml_scanner_info.scan_table[(c)] & (bit))
72 #define scan_sgml(scanner, s, bit) \
73 while ((s) < (scanner)->end && check_sgml_table(*(s), bit)) (s)++;
75 #define is_sgml_ident(c) check_sgml_table(c, SGML_CHAR_IDENT)
76 #define is_sgml_entity(c) check_sgml_table(c, SGML_CHAR_ENTITY)
77 #define is_sgml_space(c) check_sgml_table(c, SGML_CHAR_WHITESPACE)
78 #define is_sgml_newline(c) check_sgml_table(c, SGML_CHAR_NEWLINE)
79 #define is_sgml_text(c) !check_sgml_table(c, SGML_CHAR_NOT_TEXT)
80 #define is_sgml_token_start(c) check_sgml_table(c, SGML_CHAR_TOKEN_START)
81 #define is_sgml_attribute(c) !check_sgml_table(c, SGML_CHAR_NOT_ATTRIBUTE | SGML_CHAR_WHITESPACE)
84 skip_sgml_space(struct dom_scanner
*scanner
, unsigned char **string
)
86 unsigned char *pos
= *string
;
88 if (!scanner
->count_lines
) {
89 scan_sgml(scanner
, pos
, SGML_CHAR_WHITESPACE
);
91 while (pos
< scanner
->end
&& is_sgml_space(*pos
)) {
92 if (is_sgml_newline(*pos
))
101 #define check_sgml_incomplete(scanner, string) \
102 ((scanner)->check_complete \
103 && (scanner)->incomplete \
104 && (string) == (scanner)->end)
107 set_sgml_incomplete(struct dom_scanner
*scanner
, struct dom_scanner_token
*token
)
109 size_t left
= scanner
->end
- scanner
->position
;
113 token
->type
= SGML_TOKEN_INCOMPLETE
;
114 set_dom_string(&token
->string
, scanner
->position
, left
);
116 /* Stop the scanning. */
117 scanner
->position
= scanner
->end
;
122 check_sgml_error(struct dom_scanner
*scanner
)
124 unsigned int found_error
= scanner
->found_error
;
126 /* Toggle if we found an error previously. */
127 scanner
->found_error
= 0;
129 return scanner
->detect_errors
&& !found_error
;
132 static unsigned char *
133 get_sgml_error_end(struct dom_scanner
*scanner
, enum sgml_token_type type
,
137 case SGML_TOKEN_CDATA_SECTION
:
138 case SGML_TOKEN_NOTATION_ATTLIST
:
139 case SGML_TOKEN_NOTATION_DOCTYPE
:
140 case SGML_TOKEN_NOTATION_ELEMENT
:
141 if (scanner
->position
+ 9 < end
)
142 end
= scanner
->position
+ 9;
145 case SGML_TOKEN_NOTATION_COMMENT
:
146 /* Just include the '<!--' part. */
147 if (scanner
->position
+ 4 < end
)
148 end
= scanner
->position
+ 4;
151 case SGML_TOKEN_NOTATION_ENTITY
:
152 if (scanner
->position
+ 6 < end
)
153 end
= scanner
->position
+ 6;
156 case SGML_TOKEN_PROCESS_XML
:
157 if (scanner
->position
+ 5 < end
)
158 end
= scanner
->position
+ 5;
161 case SGML_TOKEN_PROCESS_XML_STYLESHEET
:
162 if (scanner
->position
+ 16 < end
)
163 end
= scanner
->position
+ 16;
174 static struct dom_scanner_token
*
175 set_sgml_error(struct dom_scanner
*scanner
, unsigned char *end
)
177 struct dom_scanner_token
*token
= scanner
->current
;
178 struct dom_scanner_token
*next
;
180 assert(!scanner
->found_error
);
182 if (scanner
->current
>= scanner
->table
+ DOM_SCANNER_TOKENS
) {
183 scanner
->found_error
= 1;
188 next
= scanner
->current
;
189 copy_struct(next
, token
);
192 token
->type
= SGML_TOKEN_ERROR
;
193 token
->lineno
= scanner
->lineno
;
194 set_dom_string(&token
->string
, scanner
->position
, end
- scanner
->position
);
200 /* Text token scanning */
202 /* I think it is faster to not check the table here --jonas */
203 #define foreach_sgml_cdata(scanner, str) \
204 for (; ((str) < (scanner)->end && *(str) != '<' && *(str) != '&'); (str)++)
207 scan_sgml_text_token(struct dom_scanner
*scanner
, struct dom_scanner_token
*token
)
209 unsigned char *string
= scanner
->position
;
210 unsigned char first_char
= *string
;
211 enum sgml_token_type type
= SGML_TOKEN_GARBAGE
;
212 int real_length
= -1;
214 /* In scan_sgml_tokens() we check that first_char != '<' */
215 assert(first_char
!= '<' && scanner
->state
== SGML_STATE_TEXT
);
217 token
->string
.string
= string
++;
219 if (first_char
== '&') {
222 if (is_sgml_entity(*string
)) {
223 scan_sgml(scanner
, string
, SGML_CHAR_ENTITY
);
224 type
= SGML_TOKEN_ENTITY
;
225 token
->string
.string
++;
226 real_length
= string
- token
->string
.string
;
229 foreach_sgml_cdata (scanner
, string
) {
230 if (*string
== ';') {
237 /* We want the biggest possible text token. */
239 if (check_sgml_incomplete(scanner
, string
)) {
240 set_sgml_incomplete(scanner
, token
);
244 if (check_sgml_error(scanner
)) {
245 token
= set_sgml_error(scanner
, string
);
252 if (is_sgml_space(first_char
)) {
253 if (scanner
->count_lines
254 && is_sgml_newline(first_char
))
257 skip_sgml_space(scanner
, &string
);
258 type
= string
< scanner
->end
&& is_sgml_text(*string
)
259 ? SGML_TOKEN_TEXT
: SGML_TOKEN_SPACE
;
261 type
= SGML_TOKEN_TEXT
;
264 if (scanner
->count_lines
) {
265 foreach_sgml_cdata (scanner
, string
) {
266 if (is_sgml_newline(*string
))
270 foreach_sgml_cdata (scanner
, string
) {
275 /* We want the biggest possible text token. */
276 if (check_sgml_incomplete(scanner
, string
)) {
277 set_sgml_incomplete(scanner
, token
);
283 token
->string
.length
= real_length
>= 0 ? real_length
: string
- token
->string
.string
;
284 token
->precedence
= get_sgml_precedence(type
);
285 scanner
->position
= string
;
289 /* Element scanning */
291 /* Check whether it is safe to skip the @token when looking for @skipto. */
293 check_sgml_precedence(int type
, int skipto
)
295 return get_sgml_precedence(type
) <= get_sgml_precedence(skipto
);
298 /* Skip until @skipto is found, without taking precedence into account. */
299 static inline unsigned char *
300 skip_sgml_chars(struct dom_scanner
*scanner
, unsigned char *string
,
301 unsigned char skipto
)
305 assert(string
>= scanner
->position
&& string
<= scanner
->end
);
307 if (!scanner
->count_lines
) {
308 size_t length
= scanner
->end
- string
;
310 return memchr(string
, skipto
, length
);
313 for (newlines
= 0; string
< scanner
->end
; string
++) {
314 if (is_sgml_newline(*string
))
316 if (*string
== skipto
) {
317 /* Only count newlines if we actually find the
318 * requested char. Else callers are assumed to discard
320 scanner
->lineno
+= newlines
;
328 /* XXX: Only element or ``in tag'' precedence is handled correctly however
329 * using this function for CDATA or text would be overkill. */
330 static inline unsigned char *
331 skip_sgml(struct dom_scanner
*scanner
, unsigned char **string
, unsigned char skipto
,
334 unsigned char *pos
= *string
;
336 for (; pos
< scanner
->end
; pos
++) {
337 if (*pos
== skipto
) {
342 if (!check_sgml_precedence(*pos
, skipto
))
345 if (check_quoting
&& isquote(*pos
)) {
348 end
= skip_sgml_chars(scanner
, pos
+ 1, *pos
);
351 } else if (scanner
->count_lines
&& is_sgml_newline(*pos
)) {
361 skip_sgml_comment(struct dom_scanner
*scanner
, unsigned char **string
,
362 int *possibly_incomplete
)
364 unsigned char *pos
= *string
;
367 for ( ; (pos
= skip_sgml_chars(scanner
, pos
, '>')); pos
++) {
368 /* It is always safe to access index -2 and -1 here since we
369 * are supposed to have '<!--' before this is called. We do
370 * however need to check that the '-->' are not overlapping any
371 * preceeding '-'. Additionally also handle the quirky '--!>'
372 * end sometimes found. */
373 if (pos
[-2] == '-') {
374 if (pos
[-1] == '-' && &pos
[-2] >= *string
) {
375 length
= pos
- *string
- 2;
376 *possibly_incomplete
= 0;
379 } else if (pos
[-1] == '!' && pos
[-3] == '-' && &pos
[-3] >= *string
) {
380 length
= pos
- *string
- 3;
381 *possibly_incomplete
= 0;
390 /* The token is incomplete but set the length to handle tag
391 * tag soup graciously. */
392 *possibly_incomplete
= 1;
393 length
= pos
- *string
;
401 skip_sgml_cdata_section(struct dom_scanner
*scanner
, unsigned char **string
,
402 int *possibly_incomplete
)
404 unsigned char *pos
= *string
;
407 for ( ; (pos
= skip_sgml_chars(scanner
, pos
, '>')); pos
++) {
408 /* It is always safe to access index -2 and -1 here since we
409 * are supposed to have '<![CDATA[' before this is called. */
410 if (pos
[-2] == ']' && pos
[-1] == ']') {
411 length
= pos
- *string
- 2;
412 *possibly_incomplete
= 0;
420 /* The token is incomplete but set the length to handle tag
421 * soup graciously. */
422 *possibly_incomplete
= 1;
423 length
= pos
- *string
;
430 #define scan_sgml_attribute(scanner, str) \
431 while ((str) < (scanner)->end && is_sgml_attribute(*(str))) \
435 scan_sgml_element_token(struct dom_scanner
*scanner
, struct dom_scanner_token
*token
)
437 unsigned char *string
= scanner
->position
;
438 unsigned char first_char
= *string
;
439 enum sgml_token_type type
= SGML_TOKEN_GARBAGE
;
440 int real_length
= -1;
441 int possibly_incomplete
= 1;
442 enum sgml_scanner_state scanner_state
= scanner
->state
;
444 token
->string
.string
= string
++;
446 if (first_char
== '<') {
447 skip_sgml_space(scanner
, &string
);
449 if (scanner
->state
== SGML_STATE_ELEMENT
) {
450 /* Already inside an element so insert a tag end token
451 * and continue scanning in next iteration. */
452 type
= SGML_TOKEN_TAG_END
;
453 scanner_state
= SGML_STATE_TEXT
;
455 /* We are creating a 'virtual' that has no source. */
456 possibly_incomplete
= 0;
457 string
= token
->string
.string
;
460 } else if (string
== scanner
->end
) {
461 /* It is incomplete so prevent out of bound acess to
462 * the scanned string. */
464 } else if (is_sgml_ident(*string
)) {
465 token
->string
.string
= string
;
466 scan_sgml(scanner
, string
, SGML_CHAR_IDENT
);
468 real_length
= string
- token
->string
.string
;
470 skip_sgml_space(scanner
, &string
);
471 if (string
< scanner
->end
&& *string
== '>') {
472 type
= SGML_TOKEN_ELEMENT
;
475 /* We found the end. */
476 possibly_incomplete
= 0;
479 /* Was any space skipped? */
480 if (is_sgml_space(string
[-1])) {
481 /* We found the end. */
482 possibly_incomplete
= 0;
484 type
= SGML_TOKEN_ELEMENT_BEGIN
;
485 scanner_state
= SGML_STATE_ELEMENT
;
488 } else if (*string
== '!') {
489 unsigned char *ident
;
490 enum sgml_token_type base
= SGML_TOKEN_NOTATION
;
493 skip_sgml_space(scanner
, &string
);
494 token
->string
.string
= ident
= string
;
496 if (string
+ 1 < scanner
->end
497 && string
[0] == '-' && string
[1] == '-') {
499 type
= SGML_TOKEN_NOTATION_COMMENT
;
500 token
->string
.string
= string
;
501 real_length
= skip_sgml_comment(scanner
, &string
,
502 &possibly_incomplete
);
503 assert(real_length
>= 0);
505 } else if (string
+ 6 < scanner
->end
506 && !memcmp(string
, "[CDATA[", 7)) {
509 type
= SGML_TOKEN_CDATA_SECTION
;
510 token
->string
.string
= string
;
511 real_length
= skip_sgml_cdata_section(scanner
, &string
,
512 &possibly_incomplete
);
513 assert(real_length
>= 0);
516 scan_sgml(scanner
, string
, SGML_CHAR_IDENT
);
517 type
= map_dom_scanner_string(scanner
, ident
, string
, base
);
518 if (skip_sgml(scanner
, &string
, '>', 0)) {
519 /* We found the end. */
520 possibly_incomplete
= 0;
524 } else if (*string
== '?') {
526 enum sgml_token_type base
= SGML_TOKEN_PROCESS
;
529 skip_sgml_space(scanner
, &string
);
530 token
->string
.string
= pos
= string
;
531 scan_sgml(scanner
, string
, SGML_CHAR_IDENT
);
533 type
= map_dom_scanner_string(scanner
, pos
, string
, base
);
535 scanner_state
= SGML_STATE_PROC_INST
;
537 real_length
= string
- token
->string
.string
;
538 skip_sgml_space(scanner
, &string
);
540 /* Make '<?xml ' cause the right kind of error. */
541 if (is_sgml_space(string
[-1])
542 && string
< scanner
->end
) {
543 /* We found the end. */
544 possibly_incomplete
= 0;
547 if (scanner
->check_complete
&& scanner
->incomplete
) {
548 /* We need to fit both the process target token
549 * and the process data token into the scanner
551 if (token
+ 1 >= scanner
->table
+ DOM_SCANNER_TOKENS
) {
552 possibly_incomplete
= 1;
554 } else if (!possibly_incomplete
) {
555 /* FIXME: We do this twice. */
556 for (pos
= string
+ 1;
557 (pos
= skip_sgml_chars(scanner
, pos
, '>'));
563 possibly_incomplete
= 1;
566 if (possibly_incomplete
)
567 string
= scanner
->end
;
570 } else if (*string
== '/') {
572 skip_sgml_space(scanner
, &string
);
574 if (string
== scanner
->end
) {
575 /* Prevent out of bound access. */
577 } else if (is_sgml_ident(*string
)) {
578 token
->string
.string
= string
;
579 scan_sgml(scanner
, string
, SGML_CHAR_IDENT
);
580 real_length
= string
- token
->string
.string
;
582 type
= SGML_TOKEN_ELEMENT_END
;
583 if (skip_sgml(scanner
, &string
, '>', 1)) {
584 /* We found the end. */
585 possibly_incomplete
= 0;
588 } else if (*string
== '>') {
591 type
= SGML_TOKEN_ELEMENT_END
;
593 /* We found the end. */
594 possibly_incomplete
= 0;
597 if (type
!= SGML_TOKEN_GARBAGE
) {
598 scanner_state
= SGML_STATE_TEXT
;
602 /* Alien < > stuff so ignore it */
603 if (skip_sgml(scanner
, &string
, '>', 0)) {
604 /* We found the end. */
605 possibly_incomplete
= 0;
609 } else if (first_char
== '=') {
611 /* We found the end. */
612 possibly_incomplete
= 0;
614 } else if (first_char
== '?' || first_char
== '>') {
615 if (first_char
== '?') {
616 if (skip_sgml(scanner
, &string
, '>', 0)) {
617 /* We found the end. */
618 possibly_incomplete
= 0;
621 assert(first_char
== '>');
623 /* We found the end. */
624 possibly_incomplete
= 0;
627 type
= SGML_TOKEN_TAG_END
;
628 assert(scanner
->state
== SGML_STATE_ELEMENT
);
629 scanner_state
= SGML_STATE_TEXT
;
631 } else if (first_char
== '/') {
632 /* We allow '/' inside elements and only consider it as an end
633 * tag if immediately preceeds the '>' char. This is to allow
635 * '<form action=/ >' where '/' is part of a path and
636 * '<form action=a />' where '/>' is truely a tag end
638 * For stricter parsing we should always require attribute
639 * values to be quoted.
641 if (string
== scanner
->end
) {
642 /* Prevent out of bound access. */
644 } else if (*string
== '>') {
647 type
= SGML_TOKEN_ELEMENT_EMPTY_END
;
648 assert(scanner
->state
== SGML_STATE_ELEMENT
);
649 scanner_state
= SGML_STATE_TEXT
;
651 /* We found the end. */
652 possibly_incomplete
= 0;
654 } else if (is_sgml_attribute(*string
)) {
655 scan_sgml_attribute(scanner
, string
);
656 type
= SGML_TOKEN_ATTRIBUTE
;
657 if (string
[-1] == '/' && string
[0] == '>') {
659 /* We found the end. */
660 possibly_incomplete
= 0;
664 } else if (isquote(first_char
)) {
665 unsigned char *string_end
= skip_sgml_chars(scanner
, string
, first_char
);
668 /* We don't want the delimiters in the token */
669 token
->string
.string
++;
670 real_length
= string_end
- token
->string
.string
;
671 string
= string_end
+ 1;
672 type
= SGML_TOKEN_STRING
;
674 /* We found the end. */
675 possibly_incomplete
= 0;
677 } else if (scanner
->check_complete
&& scanner
->incomplete
) {
678 /* Force an incomplete token. */
679 string
= scanner
->end
;
681 } else if (string
< scanner
->end
682 && is_sgml_attribute(*string
)) {
683 token
->string
.string
++;
684 scan_sgml_attribute(scanner
, string
);
685 type
= SGML_TOKEN_ATTRIBUTE
;
688 } else if (is_sgml_attribute(first_char
)) {
689 if (is_sgml_ident(first_char
)) {
690 scan_sgml(scanner
, string
, SGML_CHAR_IDENT
);
691 type
= SGML_TOKEN_IDENT
;
694 if (string
< scanner
->end
695 && is_sgml_attribute(*string
)) {
696 scan_sgml_attribute(scanner
, string
);
697 type
= SGML_TOKEN_ATTRIBUTE
;
698 if (string
[-1] == '/' && string
[0] == '>') {
699 /* We found the end. */
700 possibly_incomplete
= 0;
706 if (possibly_incomplete
) {
707 if (check_sgml_incomplete(scanner
, string
)) {
708 set_sgml_incomplete(scanner
, token
);
712 if (check_sgml_error(scanner
) && string
== scanner
->end
) {
715 end
= get_sgml_error_end(scanner
, type
, string
);
716 token
= set_sgml_error(scanner
, end
);
722 /* Only apply the state change if the token was not abandoned because
723 * it was incomplete. */
724 scanner
->state
= scanner_state
;
727 token
->string
.length
= real_length
>= 0 ? real_length
: string
- token
->string
.string
;
728 token
->precedence
= get_sgml_precedence(type
);
729 scanner
->position
= string
;
733 /* Processing instruction data scanning */
736 scan_sgml_proc_inst_token(struct dom_scanner
*scanner
, struct dom_scanner_token
*token
)
738 unsigned char *string
= scanner
->position
;
739 /* The length can be empty for '<??>'. */
742 token
->string
.string
= string
++;
744 /* Figure out where the processing instruction ends. This doesn't use
745 * skip_sgml() since we MUST ignore precedence here to allow '<' inside
746 * the data part to be skipped correctly. */
747 for ( ; (string
= skip_sgml_chars(scanner
, string
, '>')); string
++) {
748 if (string
[-1] == '?') {
750 length
= string
- token
->string
.string
- 2;
756 /* Makes the next succeed when checking for incompletion, and
757 * puts the rest of the text within the token. */
758 string
= scanner
->end
;
760 if (check_sgml_incomplete(scanner
, string
)) {
761 set_sgml_incomplete(scanner
, token
);
765 if (check_sgml_error(scanner
)) {
766 token
= set_sgml_error(scanner
, string
);
772 token
->type
= SGML_TOKEN_PROCESS_DATA
;
773 token
->string
.length
= length
>= 0 ? length
: string
- token
->string
.string
;
774 token
->precedence
= get_sgml_precedence(token
->type
);
775 scanner
->position
= string
;
776 scanner
->state
= SGML_STATE_TEXT
;
780 /* Scanner multiplexor */
782 static struct dom_scanner_token
*
783 scan_sgml_tokens(struct dom_scanner
*scanner
)
785 struct dom_scanner_token
*table_end
= scanner
->table
+ DOM_SCANNER_TOKENS
;
787 if (!begin_dom_token_scanning(scanner
))
788 return get_dom_scanner_token(scanner
);
790 /* Scan tokens until we fill the table */
791 for (scanner
->current
= scanner
->table
+ scanner
->tokens
;
792 scanner
->current
< table_end
&& scanner
->position
< scanner
->end
;
793 scanner
->current
++) {
794 if (scanner
->state
== SGML_STATE_ELEMENT
795 || (*scanner
->position
== '<'
796 && scanner
->state
!= SGML_STATE_PROC_INST
)) {
797 skip_sgml_space(scanner
, &scanner
->position
);
798 if (scanner
->position
>= scanner
->end
) break;
800 scan_sgml_element_token(scanner
, scanner
->current
);
802 /* Shall we scratch this token? */
803 if (scanner
->current
->type
== SGML_TOKEN_SKIP
) {
807 } else if (scanner
->state
== SGML_STATE_TEXT
) {
808 scan_sgml_text_token(scanner
, scanner
->current
);
811 scan_sgml_proc_inst_token(scanner
, scanner
->current
);
815 return end_dom_token_scanning(scanner
, scanner
->current
);