1 module html.parser;
2 
3 
4 import std.algorithm;
5 import std.array;
6 import std.ascii;
7 import std.conv;
8 import std.traits;
9 
10 import html.entities;
11 import html.utils;
12 
13 
14 private enum ParserStates {
15 	Text = 1,
16 
17 	// tags
18 	PreTagName,
19 	TagName,
20 	SelfClosingTag,
21 	PreClosingTagName,
22 	ClosingTagName,
23 	PostClosingTagName,
24 
25 	//attributes
26 	PreAttrName,
27 	AttrName,
28 	PostAttrName,
29 	PreAttrValue,
30 	AttrValueDQ,
31 	AttrValueSQ,
32 	AttrValueNQ,
33 
34 	// decls
35 	PreDeclaration,
36 	Declaration,
37 	ProcessingInstruction,
38 	PreComment,
39 	Comment,
40 	PostComment1,
41 	PostComment2,
42 
43 	// entities
44 	PreEntity,
45 	PreNumericEntity,
46 	NamedEntity,
47 	NumericEntity,
48 	HexEntity,
49 
50 	// cdata
51 	PreCDATA,
52 	PreCDATA_C,
53 	PreCDATA_CD,
54 	PreCDATA_CDA,
55 	PreCDATA_CDAT,
56 	PreCDATA_CDATA,
57 	CDATA,
58 	PostCDATA1,
59 	PostCDATA2,
60 
61 	// scripts / style
62 	PreScriptOrStyle,
63 	PreScript_SC,
64 	PreScript_SCR,
65 	PreScript_SCRI,
66 	PreScript_SCRIP,
67 	PreScript_SCRIPT,
68 	PreStyle_ST,
69 	PreStyle_STY,
70 	PreStyle_STYL,
71 	PreStyle_STYLE,
72 	PreClosingScriptOrStyle,
73 	ClosingScript_SC,
74 	ClosingScript_SCR,
75 	ClosingScript_SCRI,
76 	ClosingScript_SCRIP,
77 	ClosingScript_SCRIPT,
78 	ClosingStyle_ST,
79 	ClosingStyle_STY,
80 	ClosingStyle_STYL,
81 	ClosingStyle_STYLE,
82 }
83 
84 
85 private enum ParserTextStates {
86 	Normal = 0,
87 	Script,
88 	Style,
89 }
90 
91 
92 enum ParserOptions {
93 	ParseEntities   	= 1 << 0,
94 	DecodeEntities  	= 1 << 1,
95 
96 	None = 0,
97 	Default = ParseEntities | DecodeEntities,
98 }
99 
100 
101 private auto parseNamedEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) {
102 	auto length = ptr - start - 1;
103 	if (start[1+length-1] == ';')
104 		--length;
105 
106 	if (!length)
107 		return false;
108 
109 	auto limit = min(1+MaxLegacyEntityNameLength, length);
110 	auto name = start[1..1+length];
111 
112 	while (true) {
113 		if (auto pindex = name in index_) {
114 			handler.onNamedEntity(name);
115 			static if ((options & ParserOptions.DecodeEntities) != 0) {
116 				auto offset = codeOffset(*pindex);
117 				handler.onEntity(name, cast(const(char)[])bytes_[offset..offset + codeLength(*pindex)]);
118 			}
119 
120 			start += 1 + name.length;
121 			return true;
122 		} else {
123 			if (limit <= MinEntityNameLength)
124 				return false;
125 			--limit;
126 			name = start[1..1+limit];
127 			continue;
128 		}
129 	}
130 }
131 
132 private auto parseNumericEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) {
133 	auto length = (ptr - start) - 2;
134 	if (start[1+length-1] == ';')
135 		--length;
136 
137 	if (!length)
138 		return false;
139 	auto name = start[2..2+length];
140 	handler.onNumericEntity(name);
141 
142 	static if ((options & ParserOptions.DecodeEntities) != 0) {
143 		auto cname = name[0..min(8, name.length)];
144 		auto code = parse!uint(cname, 10);
145 		handler.onEntity(start[2..2+length], decodeCodePoint(code));
146 	}
147 
148 	start = ptr;
149 	return true;
150 }
151 
152 private auto parseHexEntity(Handler, size_t options)(ref const(char)* start, ref const(char)* ptr, ref Handler handler) {
153 	auto length = (ptr - start) - 3;
154 	if (start[1+length-1] == ';')
155 		--length;
156 
157 	if (!length)
158 		return false;
159 
160 	auto name = start[3..3+length];
161 	handler.onHexEntity(name);
162 
163 	static if ((options & ParserOptions.DecodeEntities) != 0) {
164 		auto cname = name[0..min(6, name.length)];
165 		auto code = parse!uint(cname, 16);
166 		handler.onEntity(name, decodeCodePoint(code));
167 	}
168 
169 	start = ptr;
170 	return true;
171 }
172 
173 void parseHTML(Handler, size_t options = ParserOptions.Default)(const(char)[] source, ref Handler handler) {
174 	auto ptr = source.ptr;
175 	auto end = source.ptr + source.length;
176 	auto start = ptr;
177 
178 	ParserStates state = ParserStates.Text;
179 	ParserStates saved = ParserStates.Text;
180 	ParserTextStates textState = ParserTextStates.Normal;
181 
182 	enum ParseEntities = (options & (ParserOptions.ParseEntities | ParserOptions.DecodeEntities)) != 0;
183 	enum DecodeEntities = (options & ParserOptions.DecodeEntities) != 0;
184 
185 	while (ptr != end) {
186 		final switch(state) with (ParserStates) {
187 		case Text:
188 			final switch (textState) with (ParserTextStates) {
189 			case Normal:
190 				static if (ParseEntities) {
191 					while ((ptr != end) && (*ptr != '<') && (*ptr != '&'))
192 						++ptr;
193 				} else {
194 					while ((ptr != end) && (*ptr != '<'))
195 						++ptr;
196 				}
197 				break;
198 			case Script:
199 			case Style:
200 				while ((ptr != end) && ((*ptr != '<') || (ptr + 1 == end) || (*(ptr + 1) != '/')))
201 					++ptr;
202 			}
203 			if (ptr == end)
204 				continue;
205 
206 			static if (ParseEntities) {
207 				auto noEntity = *ptr != '&';
208 			} else {
209 				enum noEntity = true;
210 			}
211 
212 			if (noEntity) {
213 				if (start != ptr)
214 					handler.onText(start[0..ptr-start]);
215 				state = PreTagName;
216 				start = ptr;
217 			} else {
218 				static if (ParseEntities) {
219 					if (start != ptr)
220 						handler.onText(start[0..ptr-start]);
221 					saved = state;
222 					state = PreEntity;
223 					start = ptr;
224 				}
225 			}
226 			break;
227 
228 		case PreTagName:
229 			if (*ptr == '/') {
230 				state = PreClosingTagName;
231 			} else if ((*ptr == '>') || isWhite(*ptr) || (textState != ParserTextStates.Normal)) {
232 				state = Text;
233 			} else {
234 				switch (*ptr) {
235 				case '!':
236 					state = PreDeclaration;
237 					start = ptr + 1;
238 					break;
239 				case '?':
240 					state = ProcessingInstruction;
241 					start = ptr + 1;
242 					break;
243 				case '<':
244 					handler.onText(start[0..ptr-start]);
245 					start = ptr + 1;
246 					break;
247 				default:
248 					if ((*ptr == 's') || (*ptr == 'S')) {
249 						state = PreScriptOrStyle;
250 					} else {
251 						state = TagName;
252 					}
253 					start = ptr;
254 					break;
255 				}
256 			}
257 			break;
258 
259 		case PreScriptOrStyle:
260 			if ((*ptr == 'c') || (*ptr == 'C')) {
261 				state = PreScript_SC;
262 			} else if ((*ptr == 't') || (*ptr == 'T')) {
263 				state = PreStyle_ST;
264 			} else {
265 				state = TagName;
266 				goto case TagName;
267 			}
268 			break;
269 
270 		case TagName:
271 			while ((ptr != end) && (*ptr != '/') && (*ptr != '>') && !isWhite(*ptr))
272 				++ptr;
273 			if (ptr == end)
274 				continue;
275 
276 			handler.onOpenStart(start[0..ptr-start]);
277 			state = PreAttrName;
278 			continue;
279 
280 		case PreClosingTagName:
281 			while ((ptr != end) && isWhite(*ptr))
282 				++ptr;
283 			if (ptr == end)
284 				continue;
285 
286 			if (*ptr == '>') {
287 				state = Text;
288 			} else if (textState != ParserTextStates.Normal) {
289 				if ((*ptr == 's') || (*ptr == 'S')) {
290 					state = PreClosingScriptOrStyle;
291 				} else {
292 					state = Text;
293 					continue;
294 				}
295 			} else {
296 				state = ClosingTagName;
297 				start = ptr;
298 			}
299 			break;
300 
301 		case PreClosingScriptOrStyle:
302 			if ((textState == ParserTextStates.Script) && ((*ptr == 'c') || (*ptr == 'C'))) {
303 				state = ClosingScript_SC;
304 			} else if ((textState == ParserTextStates.Style) && ((*ptr == 't') || (*ptr == 'T'))) {
305 				state = ClosingStyle_ST;
306 			} else {
307 				state = Text;
308 				goto case Text;
309 			}
310 			break;
311 
312 		case ClosingTagName:
313 			while ((ptr != end) && (*ptr != '>') && !isWhite(*ptr))
314 				++ptr;
315 			if (ptr == end)
316 				continue;
317 
318 			handler.onClose(start[0..ptr-start]);
319 			state = PostClosingTagName;
320 			continue;
321 
322 		case PostClosingTagName:
323 			while ((ptr != end) && (*ptr != '>'))
324 				++ptr;
325 			if (ptr == end)
326 				continue;
327 
328 			state = Text;
329 			start = ptr + 1;
330 			break;
331 
332 		case SelfClosingTag:
333 			while ((ptr != end) && (*ptr != '>') && isWhite(*ptr))
334 				++ptr;
335 			if (ptr == end)
336 				continue;
337 
338 			if (*ptr == '>') {
339 				handler.onSelfClosing();
340 				state = Text;
341 				start = ptr + 1;
342 			} else {
343 				state = PreAttrName;
344 				continue;
345 			}
346 			break;
347 
348 		case PreAttrName:
349 			while ((ptr != end) && (*ptr != '>') && (*ptr != '/') && isWhite(*ptr))
350 				++ptr;
351 			if (ptr == end)
352 				continue;
353 
354 			if (*ptr == '>') {
355 				handler.onOpenEnd(start[0..ptr-start]);
356 				state = Text;
357 				start = ptr + 1;
358 			} else if (*ptr == '/') {
359 				state = SelfClosingTag;
360 			} else {
361 				state = AttrName;
362 				start = ptr;
363 			}
364 			break;
365 
366 		case AttrName:
367 			while ((ptr != end) && (*ptr != '=') && (*ptr != '>') && (*ptr != '/') && !isWhite(*ptr))
368 				++ptr;
369 			if (ptr == end)
370 				continue;
371 
372 			handler.onAttrName(start[0..ptr-start]);
373 			state = PostAttrName;
374 			start = ptr;
375 			continue;
376 
377 		case PostAttrName:
378 			while ((ptr != end) && (*ptr != '=') && (*ptr != '>') && (*ptr != '/') && isWhite(*ptr))
379 				++ptr;
380 			if (ptr == end)
381 				continue;
382 
383 			switch(*ptr) {
384 			case '=':
385 				state = PreAttrValue;
386 				break;
387 			case '/':
388 			case '>':
389 				handler.onAttrEnd();
390 				state = PreAttrName;
391 				continue;
392 			default:
393 				handler.onAttrEnd();
394 				state = PreAttrName;
395 				start = ptr;
396 				continue;
397 			}
398 			break;
399 
400 		case PreAttrValue:
401 			while ((ptr != end) && (*ptr != '\"') && (*ptr != '\'') && isWhite(*ptr))
402 				++ptr;
403 			if (ptr == end)
404 				continue;
405 
406 			switch(*ptr) {
407 			case '\"':
408 				state = AttrValueDQ;
409 				start = ptr + 1;
410 				break;
411 			case '\'':
412 				state = AttrValueSQ;
413 				start = ptr + 1;
414 				break;
415 			default:
416 				state = AttrValueNQ;
417 				start = ptr;
418 				continue;
419 			}
420 			break;
421 
422 		case AttrValueDQ:
423 			static if (ParseEntities) {
424 				while ((ptr != end) && (*ptr != '\"') && (*ptr != '&'))
425 					++ptr;
426 			} else {
427 				while ((ptr != end) && (*ptr != '\"'))
428 					++ptr;
429 			}
430 			if (ptr == end)
431 				continue;
432 
433 			static if (ParseEntities) {
434 				auto noEntity = *ptr != '&';
435 			} else {
436 				enum noEntity = true;
437 			}
438 
439 			if (noEntity) {
440 				handler.onAttrValue(start[0..ptr-start]);
441 				handler.onAttrEnd();
442 				state = PreAttrName;
443 			} else {
444 				static if (ParseEntities) {
445 					if (start != ptr)
446 						handler.onAttrValue(start[0..ptr-start]);
447 					saved = state;
448 					state = PreEntity;
449 					start = ptr;
450 				}
451 			}
452 			break;
453 
454 		case AttrValueSQ:
455 			static if (ParseEntities) {
456 				while ((ptr != end) && (*ptr != '\'') && (*ptr != '&'))
457 					++ptr;
458 			} else {
459 				while ((ptr != end) && (*ptr != '\''))
460 					++ptr;
461 			}
462 			if (ptr == end)
463 				continue;
464 
465 			static if (ParseEntities) {
466 				auto noEntity = *ptr != '&';
467 			} else {
468 				enum noEntity = true;
469 			}
470 
471 			if (noEntity) {
472 				handler.onAttrValue(start[0..ptr-start]);
473 				handler.onAttrEnd();
474 				state = PreAttrName;
475 			} else {
476 				static if (ParseEntities) {
477 					if (start != ptr)
478 						handler.onAttrValue(start[0..ptr-start]);
479 					saved = state;
480 					state = PreEntity;
481 					start = ptr;
482 				}
483 			}
484 			break;
485 
486 		case AttrValueNQ:
487 			static if (ParseEntities) {
488 				while ((ptr != end) && (*ptr != '>') && (*ptr != '&') && !isWhite(*ptr))
489 					++ptr;
490 			} else {
491 				while ((ptr != end) && (*ptr != '>') && !isWhite(*ptr))
492 					++ptr;
493 			}
494 			if (ptr == end)
495 				continue;
496 
497 			static if (ParseEntities) {
498 				auto noEntity = *ptr != '&';
499 			} else {
500 				enum noEntity = true;
501 			}
502 
503 			if (noEntity) {
504 				handler.onAttrValue(start[0..ptr-start]);
505 				handler.onAttrEnd();
506 				state = PreAttrName;
507 			} else {
508 				static if (ParseEntities) {
509 					if (start != ptr)
510 						handler.onAttrValue(start[0..ptr-start]);
511 					saved = state;
512 					state = PreEntity;
513 					start = ptr;
514 					break;
515 				}
516 			}
517 			continue;
518 
519 		case PreComment:
520 			if (*ptr == '-') {
521 				state = Comment;
522 				start = ptr + 1;
523 			} else {
524 				state = Declaration;
525 			}
526 			break;
527 
528 		case Comment:
529 			while ((ptr != end) && (*ptr != '-'))
530 				++ptr;
531 			if (ptr == end)
532 				continue;
533 
534 			state = PostComment1;
535 			break;
536 
537 		case PostComment1:
538 			state = (*ptr == '-') ? PostComment2 : Comment;
539 			break;
540 
541 		case PostComment2:
542 			if (*ptr == '>') {
543 				handler.onComment(start[0..ptr-start-2]);
544 				state = Text;
545 				start = ptr + 1;
546 			} else if (*ptr != '-') {
547 				state = Comment;
548 			}
549 			break;
550 
551 		case PreDeclaration:
552 			switch(*ptr) {
553 			case '[':
554 				state = PreCDATA;
555 				break;
556 			case '-':
557 				state = PreComment;
558 				break;
559 			default:
560 				state = Declaration;
561 				break;
562 			}
563 			break;
564 
565 		case PreCDATA:
566 			if ((*ptr == 'C') || (*ptr == 'c')) {
567 				state = PreCDATA_C;
568 			} else {
569 				state = Declaration;
570 				continue;
571 			}
572 			break;
573 
574 		case PreCDATA_C:
575 			if ((*ptr == 'D') || (*ptr == 'd')) {
576 				state = PreCDATA_CD;
577 			} else {
578 				state = Declaration;
579 				continue;
580 			}
581 			break;
582 
583 		case PreCDATA_CD:
584 			if ((*ptr == 'A') || (*ptr == 'a')) {
585 				state = PreCDATA_CDA;
586 			} else {
587 				state = Declaration;
588 				continue;
589 			}
590 			break;
591 
592 		case PreCDATA_CDA:
593 			if ((*ptr == 'T') || (*ptr == 't')) {
594 				state = PreCDATA_CDAT;
595 			} else {
596 				state = Declaration;
597 				continue;
598 			}
599 			break;
600 
601 		case PreCDATA_CDAT:
602 			if ((*ptr == 'A') || (*ptr == 'a')) {
603 				state = PreCDATA_CDATA;
604 			} else {
605 				state = Declaration;
606 				continue;
607 			}
608 			break;
609 
610 		case PreCDATA_CDATA:
611 			if (*ptr == '[') {
612 				state = CDATA;
613 				start = ptr + 1;
614 			} else {
615 				state = Declaration;
616 				continue;
617 			}
618 			break;
619 
620 		case CDATA:
621 			while ((ptr != end) && (*ptr != ']'))
622 				++ptr;
623 			if (ptr == end)
624 				continue;
625 
626 			state = PostCDATA1;
627 			break;
628 
629 		case PostCDATA1:
630 			state = (*ptr == ']') ? PostCDATA2 : CDATA;
631 			break;
632 
633 		case PostCDATA2:
634 			if (*ptr == '>') {
635 				handler.onCDATA(start[0..ptr-start-2]);
636 				state = Text;
637 				start = ptr + 1;
638 			} else if (*ptr != ']') {
639 				state = CDATA;
640 			}
641 			break;
642 
643 		case Declaration:
644 			while ((ptr != end) && (*ptr != '>'))
645 				++ptr;
646 			if (ptr == end)
647 				continue;
648 
649 			handler.onDeclaration(start[0..ptr-start]);
650 			state = Text;
651 			start = ptr + 1;
652 			break;
653 
654 		case ProcessingInstruction:
655 			while ((ptr != end) && (*ptr != '>'))
656 				++ptr;
657 			if (ptr == end)
658 				continue;
659 
660 			handler.onProcessingInstruction(start[0..ptr-start]);
661 			state = Text;
662 			start = ptr + 1;
663 			break;
664 
665 		case PreEntity:
666 			static if (ParseEntities) {
667 				if (*ptr == '#') {
668 					state = PreNumericEntity;
669 					break;
670 				} else {
671 					state = NamedEntity;
672 					continue;
673 				}
674 			} else {
675 				assert(0, "should never get here!");
676 			}
677 
678 		case NamedEntity:
679 			static if (ParseEntities) {
680 				while ((ptr != end) && (*ptr != ';') && isAlphaNum(*ptr) && (ptr - start < MaxEntityNameLength))
681 					++ptr;
682 				if (ptr == end)
683 					continue;
684 
685 				if ((saved == Text) || (*ptr != '=')) {
686 					if (parseNamedEntity!(Handler, options)(start, ptr, handler)) {
687 						if (*start == ';')
688 							++start;
689 					}
690 				}
691 				state = saved;
692 
693 				if (*ptr == ';') break;
694 				else continue;
695 			} else {
696 				break;
697 			}
698 
699 		case PreNumericEntity:
700 			static if (ParseEntities) {
701 				if ((*ptr == 'X') || (*ptr == 'x')) {
702 					state = HexEntity;
703 					break;
704 				} else {
705 					state = NumericEntity;
706 					continue;
707 				}
708 			} else {
709 				assert(0, "should never get here!");
710 			}
711 
712 		case NumericEntity:
713 			static if (ParseEntities) {
714 				while ((ptr != end) && (*ptr != ';') && isDigit(*ptr))
715 					++ptr;
716 				if (ptr == end)
717 					continue;
718 
719 				state = saved;
720 				if (parseNumericEntity!(Handler, options)(start, ptr, handler)) {
721 					if (*start == ';')
722 						++start;
723 				}
724 
725 				if (*ptr == ';') break;
726 				else continue;
727 			} else {
728 				break;
729 			}
730 
731 		case HexEntity:
732 			static if (ParseEntities) {
733 				while ((ptr != end) && (*ptr != ';') && isHexDigit(*ptr))
734 					++ptr;
735 				if (ptr == end)
736 					continue;
737 
738 				state = saved;
739 				if (parseHexEntity!(Handler, options)(start, ptr, handler)) {
740 					if (*start == ';')
741 						++start;
742 				}
743 
744 				if (*ptr == ';') break;
745 				else continue;
746 			} else {
747 				break;
748 			}
749 
750 		case PreScript_SC:
751 			if ((*ptr == 'r') || (*ptr == 'R')) {
752 				state = PreScript_SCR;
753 			} else {
754 				state = TagName;
755 				goto case TagName;
756 			}
757 			break;
758 
759 		case PreScript_SCR:
760 			if ((*ptr == 'i') || (*ptr == 'I')) {
761 				state = PreScript_SCRI;
762 			} else {
763 				state = TagName;
764 				goto case TagName;
765 			}
766 			break;
767 
768 		case PreScript_SCRI:
769 			if ((*ptr == 'p') || (*ptr == 'p')) {
770 				state = PreScript_SCRIP;
771 			} else {
772 				state = TagName;
773 				goto case TagName;
774 			}
775 			break;
776 
777 		case PreScript_SCRIP:
778 			if ((*ptr == 't') || (*ptr == 't')) {
779 				state = PreScript_SCRIPT;
780 			} else {
781 				state = TagName;
782 				goto case TagName;
783 			}
784 			break;
785 
786 		case PreScript_SCRIPT:
787 			if ((*ptr == '/') || (*ptr == '>') || isWhite(*ptr))
788 				textState = ParserTextStates.Script;
789 
790 			state = TagName;
791 			goto case TagName;
792 
793 		case PreStyle_ST:
794 			if ((*ptr == 'y') || (*ptr == 'Y')) {
795 				state = PreStyle_STY;
796 			} else {
797 				state = TagName;
798 				goto case TagName;
799 			}
800 			break;
801 
802 		case PreStyle_STY:
803 			if ((*ptr == 'l') || (*ptr == 'L')) {
804 				state = PreStyle_STYL;
805 			} else {
806 				state = TagName;
807 				goto case TagName;
808 			}
809 			break;
810 
811 		case PreStyle_STYL:
812 			if ((*ptr == 'e') || (*ptr == 'E')) {
813 				state = PreStyle_STYLE;
814 			} else {
815 				state = TagName;
816 				goto case TagName;
817 			}
818 			break;
819 
820 		case PreStyle_STYLE:
821 			if ((*ptr == '/') || (*ptr == '>') || isWhite(*ptr))
822 				textState = ParserTextStates.Style;
823 
824 			state = TagName;
825 			goto case TagName;
826 
827 		case ClosingScript_SC:
828 			if ((*ptr == 'r') || (*ptr == 'R')) {
829 				state = ClosingScript_SCR;
830 			} else {
831 				state = Text;
832 				goto case Text;
833 			}
834 			break;
835 
836 		case ClosingScript_SCR:
837 			if ((*ptr == 'i') || (*ptr == 'I')) {
838 				state = ClosingScript_SCRI;
839 			} else {
840 				state = Text;
841 				goto case Text;
842 			}
843 			break;
844 
845 		case ClosingScript_SCRI:
846 			if ((*ptr == 'p') || (*ptr == 'p')) {
847 				state = ClosingScript_SCRIP;
848 			} else {
849 				state = Text;
850 				goto case Text;
851 			}
852 			break;
853 
854 		case ClosingScript_SCRIP:
855 			if ((*ptr == 't') || (*ptr == 't')) {
856 				state = ClosingScript_SCRIPT;
857 			} else {
858 				state = Text;
859 				goto case Text;
860 			}
861 			break;
862 
863 		case ClosingScript_SCRIPT:
864 			if ((*ptr == '>') || isWhite(*ptr)) {
865 				textState = ParserTextStates.Normal;
866 				state = ClosingTagName;
867 				start += 2;
868 				continue;
869 			} else {
870 				state = Text;
871 				goto case Text;
872 			}
873 
874 		case ClosingStyle_ST:
875 			if ((*ptr == 'y') || (*ptr == 'Y')) {
876 				state = ClosingStyle_STY;
877 			} else {
878 				state = Text;
879 				goto case Text;
880 			}
881 			break;
882 
883 		case ClosingStyle_STY:
884 			if ((*ptr == 'l') || (*ptr == 'L')) {
885 				state = ClosingStyle_STYL;
886 			} else {
887 				state = Text;
888 				goto case Text;
889 			}
890 			break;
891 
892 		case ClosingStyle_STYL:
893 			if ((*ptr == 'e') || (*ptr == 'E')) {
894 				state = ClosingStyle_STYLE;
895 			} else {
896 				state = Text;
897 				goto case Text;
898 			}
899 			break;
900 
901 		case ClosingStyle_STYLE:
902 			if ((*ptr == '>') || isWhite(*ptr)) {
903 				textState = ParserTextStates.Normal;
904 				state = ClosingTagName;
905 				start += 2;
906 				continue;
907 			} else {
908 				state = Text;
909 				goto case Text;
910 			}
911 		}
912 
913 		++ptr;
914 	}
915 
916 	auto remaining = start[0..ptr-start];
917 	if (!remaining.empty) {
918 		switch(state) with (ParserStates) {
919 		case Comment:
920 			handler.onComment(remaining);
921 			break;
922 		case PostComment1:
923 			handler.onComment(remaining[0..$-1]);
924 			break;
925 		case PostComment2:
926 			handler.onComment(remaining[0..$-2]);
927 			break;
928 		case NamedEntity:
929 			static if (ParseEntities) {
930 				if (saved == Text) {
931 					if (ptr-start > 1)
932 						parseNamedEntity!(Handler, options)(start, ptr, handler);
933 					if (start < ptr)
934 						handler.onText(start[0..ptr-start]);
935 				}
936 			}
937 			break;
938 		case NumericEntity:
939 			static if (ParseEntities) {
940 				if (saved == Text) {
941 					if (ptr-start > 2)
942 						parseNumericEntity!(Handler, options)(start, ptr, handler);
943 					if (start < ptr)
944 						handler.onText(start[0..ptr-start]);
945 				}
946 			}
947 			break;
948 		case HexEntity:
949 			static if (ParseEntities) {
950 				if (saved == Text) {
951 					if (ptr-start > 3)
952 						parseHexEntity!(Handler, options)(start, ptr, handler);
953 					if (start < ptr)
954 						handler.onText(start[0..ptr-start]);
955 				}
956 			}
957 			break;
958 		default:
959 			if ((state != TagName) &&
960 				(state != PreAttrName) &&
961 				(state != PreAttrValue) &&
962 				(state != PostAttrName) &&
963 				(state != AttrName) &&
964 				(state != AttrValueDQ) &&
965 				(state != AttrValueSQ) &&
966 				(state != AttrValueNQ) &&
967 				(state != ClosingTagName)) {
968 					handler.onText(remaining);
969 				}
970 			break;
971 		}
972 	}
973 
974 	handler.onDocumentEnd();
975 }