1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/html/htmlpars.cpp
3 // Purpose: wxHtmlParser class (generic parser)
4 // Author: Vaclav Slavik
6 // Copyright: (c) 1999 Vaclav Slavik
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
10 #include "wx/wxprec.h"
16 #if wxUSE_HTML && wxUSE_STREAMS
19 #include "wx/dynarray.h"
23 #include "wx/wxcrtvararg.h"
26 #include "wx/tokenzr.h"
27 #include "wx/wfstream.h"
29 #include "wx/fontmap.h"
30 #include "wx/html/htmldefs.h"
31 #include "wx/html/htmlpars.h"
32 #include "wx/vector.h"
35 #include "wx/msw/wince/missing.h" // for bsearch()
38 // DLL options compatibility check:
39 WX_CHECK_BUILD_OPTIONS("wxHTML")
41 const wxChar
*wxTRACE_HTML_DEBUG
= _T("htmldebug");
43 //-----------------------------------------------------------------------------
44 // wxHtmlParser helpers
45 //-----------------------------------------------------------------------------
51 wxHtmlTextPiece(int pos
, int lng
) : m_pos(pos
), m_lng(lng
) {}
55 // NB: this is an empty class and not typedef because of forward declaration
56 class wxHtmlTextPieces
: public wxVector
<wxHtmlTextPiece
>
60 class wxHtmlParserState
65 wxHtmlTextPieces
*m_textPieces
;
68 wxHtmlParserState
*m_nextState
;
71 //-----------------------------------------------------------------------------
73 //-----------------------------------------------------------------------------
75 IMPLEMENT_ABSTRACT_CLASS(wxHtmlParser
,wxObject
)
77 wxHtmlParser::wxHtmlParser()
78 : wxObject(), m_HandlersHash(wxKEY_STRING
),
79 m_FS(NULL
), m_HandlersStack(NULL
)
81 m_entitiesParser
= new wxHtmlEntitiesParser
;
89 wxHtmlParser::~wxHtmlParser()
91 while (RestoreState()) {}
96 wxList
& tmp
= *m_HandlersStack
;
97 wxList::iterator it
, en
;
98 for( it
= tmp
.begin(), en
= tmp
.end(); it
!= en
; ++it
)
99 delete (wxHashTable
*)*it
;
102 delete m_HandlersStack
;
103 m_HandlersHash
.Clear();
104 WX_CLEAR_LIST(wxList
, m_HandlersList
);
105 delete m_entitiesParser
;
108 wxObject
* wxHtmlParser::Parse(const wxString
& source
)
112 wxObject
*result
= GetProduct();
117 void wxHtmlParser::InitParser(const wxString
& source
)
120 m_stopParsing
= false;
123 void wxHtmlParser::DoneParser()
128 void wxHtmlParser::SetSource(const wxString
& src
)
137 void wxHtmlParser::CreateDOMTree()
139 wxHtmlTagsCache
cache(m_Source
);
140 m_TextPieces
= new wxHtmlTextPieces
;
141 CreateDOMSubTree(NULL
, 0, m_Source
.length(), &cache
);
145 extern bool wxIsCDATAElement(const wxChar
*tag
);
147 void wxHtmlParser::CreateDOMSubTree(wxHtmlTag
*cur
,
148 int begin_pos
, int end_pos
,
149 wxHtmlTagsCache
*cache
)
151 if (end_pos
<= begin_pos
) return;
155 int textBeginning
= begin_pos
;
157 // If the tag contains CDATA text, we include the text between beginning
158 // and ending tag verbosely. Setting i=end_pos will skip to the very
159 // end of this function where text piece is added, bypassing any child
160 // tags parsing (CDATA element can't have child elements by definition):
161 if (cur
!= NULL
&& wxIsCDATAElement(cur
->GetName().c_str()))
168 c
= m_Source
.GetChar(i
);
172 // add text to m_TextPieces:
173 if (i
- textBeginning
> 0)
174 m_TextPieces
->push_back(
175 wxHtmlTextPiece(textBeginning
, i
- textBeginning
));
177 // if it is a comment, skip it:
178 wxString::const_iterator iter
= m_Source
.begin() + i
;
179 if ( SkipCommentTag(iter
, m_Source
.end()) )
182 i
= iter
- m_Source
.begin() + 1; // skip closing '>' too
185 // add another tag to the tree:
186 else if (i
< end_pos
-1 && m_Source
.GetChar(i
+1) != wxT('/'))
190 chd
= new wxHtmlTag(cur
, m_Source
,
191 i
, end_pos
, cache
, m_entitiesParser
);
194 chd
= new wxHtmlTag(NULL
, m_Source
,
195 i
, end_pos
, cache
, m_entitiesParser
);
198 // if this is the first tag to be created make the root
199 // m_Tags point to it:
204 // if there is already a root tag add this tag as
206 chd
->m_Prev
= m_Tags
->GetLastSibling();
207 chd
->m_Prev
->m_Next
= chd
;
211 if (chd
->HasEnding())
213 CreateDOMSubTree(chd
,
214 chd
->GetBeginPos(), chd
->GetEndPos1(),
216 i
= chd
->GetEndPos2();
219 i
= chd
->GetBeginPos();
224 // ... or skip ending tag:
227 while (i
< end_pos
&& m_Source
.GetChar(i
) != wxT('>')) i
++;
234 // add remaining text to m_TextPieces:
235 if (end_pos
- textBeginning
> 0)
236 m_TextPieces
->push_back(
237 wxHtmlTextPiece(textBeginning
, end_pos
- textBeginning
));
240 void wxHtmlParser::DestroyDOMTree()
246 t2
= t1
->GetNextSibling();
250 m_Tags
= m_CurTag
= NULL
;
256 void wxHtmlParser::DoParsing()
260 DoParsing(0, m_Source
.length());
263 void wxHtmlParser::DoParsing(int begin_pos
, int end_pos
)
265 if (end_pos
<= begin_pos
) return;
267 wxHtmlTextPieces
& pieces
= *m_TextPieces
;
268 size_t piecesCnt
= pieces
.size();
270 while (begin_pos
< end_pos
)
272 while (m_CurTag
&& m_CurTag
->GetBeginPos() < begin_pos
)
273 m_CurTag
= m_CurTag
->GetNextTag();
274 while (m_CurTextPiece
< piecesCnt
&&
275 pieces
[m_CurTextPiece
].m_pos
< begin_pos
)
278 if (m_CurTextPiece
< piecesCnt
&&
280 pieces
[m_CurTextPiece
].m_pos
< m_CurTag
->GetBeginPos()))
283 AddText(GetEntitiesParser()->Parse(
284 m_Source
.Mid(pieces
[m_CurTextPiece
].m_pos
,
285 pieces
[m_CurTextPiece
].m_lng
)));
286 begin_pos
= pieces
[m_CurTextPiece
].m_pos
+
287 pieces
[m_CurTextPiece
].m_lng
;
292 if (m_CurTag
->HasEnding())
293 begin_pos
= m_CurTag
->GetEndPos2();
295 begin_pos
= m_CurTag
->GetBeginPos();
296 wxHtmlTag
*t
= m_CurTag
;
297 m_CurTag
= m_CurTag
->GetNextTag();
306 void wxHtmlParser::AddTag(const wxHtmlTag
& tag
)
311 h
= (wxHtmlTagHandler
*) m_HandlersHash
.Get(tag
.GetName());
314 inner
= h
->HandleTag(tag
);
321 DoParsing(tag
.GetBeginPos(), tag
.GetEndPos1());
325 void wxHtmlParser::AddTagHandler(wxHtmlTagHandler
*handler
)
327 wxString
s(handler
->GetSupportedTags());
328 wxStringTokenizer
tokenizer(s
, wxT(", "));
330 while (tokenizer
.HasMoreTokens())
331 m_HandlersHash
.Put(tokenizer
.GetNextToken(), handler
);
333 if (m_HandlersList
.IndexOf(handler
) == wxNOT_FOUND
)
334 m_HandlersList
.Append(handler
);
336 handler
->SetParser(this);
339 void wxHtmlParser::PushTagHandler(wxHtmlTagHandler
*handler
, const wxString
& tags
)
341 wxStringTokenizer
tokenizer(tags
, wxT(", "));
344 if (m_HandlersStack
== NULL
)
346 m_HandlersStack
= new wxList
;
349 m_HandlersStack
->Insert((wxObject
*)new wxHashTable(m_HandlersHash
));
351 while (tokenizer
.HasMoreTokens())
353 key
= tokenizer
.GetNextToken();
354 m_HandlersHash
.Delete(key
);
355 m_HandlersHash
.Put(key
, handler
);
359 void wxHtmlParser::PopTagHandler()
361 wxList::compatibility_iterator first
;
363 if ( !m_HandlersStack
||
365 !(first
= m_HandlersStack
->GetFirst())
367 ((first
= m_HandlersStack
->GetFirst()) == NULL
)
368 #endif // wxUSE_STL/!wxUSE_STL
371 wxLogWarning(_("Warning: attempt to remove HTML tag handler from empty stack."));
374 m_HandlersHash
= *((wxHashTable
*) first
->GetData());
375 delete (wxHashTable
*) first
->GetData();
376 m_HandlersStack
->Erase(first
);
379 void wxHtmlParser::SetSourceAndSaveState(const wxString
& src
)
381 wxHtmlParserState
*s
= new wxHtmlParserState
;
383 s
->m_curTag
= m_CurTag
;
385 s
->m_textPieces
= m_TextPieces
;
386 s
->m_curTextPiece
= m_CurTextPiece
;
387 s
->m_source
= m_Source
;
389 s
->m_nextState
= m_SavedStates
;
396 m_Source
= wxEmptyString
;
401 bool wxHtmlParser::RestoreState()
403 if (!m_SavedStates
) return false;
407 wxHtmlParserState
*s
= m_SavedStates
;
408 m_SavedStates
= s
->m_nextState
;
410 m_CurTag
= s
->m_curTag
;
412 m_TextPieces
= s
->m_textPieces
;
413 m_CurTextPiece
= s
->m_curTextPiece
;
414 m_Source
= s
->m_source
;
420 wxString
wxHtmlParser::GetInnerSource(const wxHtmlTag
& tag
)
422 return GetSource()->Mid(tag
.GetBeginPos(),
423 tag
.GetEndPos1() - tag
.GetBeginPos());
426 //-----------------------------------------------------------------------------
428 //-----------------------------------------------------------------------------
430 IMPLEMENT_ABSTRACT_CLASS(wxHtmlTagHandler
,wxObject
)
432 void wxHtmlTagHandler::ParseInnerSource(const wxString
& source
)
434 // It is safe to temporarily change the source being parsed,
435 // provided we restore the state back after parsing
436 m_Parser
->SetSourceAndSaveState(source
);
437 m_Parser
->DoParsing();
438 m_Parser
->RestoreState();
442 //-----------------------------------------------------------------------------
443 // wxHtmlEntitiesParser
444 //-----------------------------------------------------------------------------
446 IMPLEMENT_DYNAMIC_CLASS(wxHtmlEntitiesParser
,wxObject
)
448 wxHtmlEntitiesParser::wxHtmlEntitiesParser()
449 #if wxUSE_WCHAR_T && !wxUSE_UNICODE
450 : m_conv(NULL
), m_encoding(wxFONTENCODING_SYSTEM
)
455 wxHtmlEntitiesParser::~wxHtmlEntitiesParser()
457 #if wxUSE_WCHAR_T && !wxUSE_UNICODE
462 void wxHtmlEntitiesParser::SetEncoding(wxFontEncoding encoding
)
464 #if wxUSE_WCHAR_T && !wxUSE_UNICODE
465 if (encoding
== m_encoding
)
470 m_encoding
= encoding
;
471 if (m_encoding
== wxFONTENCODING_SYSTEM
)
474 m_conv
= new wxCSConv(wxFontMapper::GetEncodingName(m_encoding
));
480 wxString
wxHtmlEntitiesParser::Parse(const wxString
& input
) const
484 const wxString::const_iterator
end(input
.end());
485 wxString::const_iterator
c(input
.begin());
486 wxString::const_iterator
last(c
);
488 for ( ; c
< end
; ++c
)
492 if ( output
.empty() )
493 output
.reserve(input
.length());
496 output
.append(last
, c
);
501 const wxString::const_iterator ent_s
= c
;
505 ((*c
>= wxT('a') && *c
<= wxT('z')) ||
506 (*c
>= wxT('A') && *c
<= wxT('Z')) ||
507 (*c
>= wxT('0') && *c
<= wxT('9')) ||
508 *c
== wxT('_') || *c
== wxT('#')); ++c
) {}
509 entity
.append(ent_s
, c
);
510 if (c
== end
|| *c
!= wxT(';')) --c
;
512 entity_char
= GetEntityChar(entity
);
514 output
<< entity_char
;
517 output
.append(ent_s
-1, c
+1);
518 wxLogTrace(wxTRACE_HTML_DEBUG
,
519 "Unrecognized HTML entity: '%s'",
524 if ( last
== input
.begin() ) // common case: no entity
527 output
.append(last
, end
);
532 wxChar
wxHtmlEntitiesParser::GetCharForCode(unsigned code
) const
537 wbuf
[0] = (wchar_t)code
;
539 wxMBConv
*conv
= m_conv
? m_conv
: &wxConvLocal
;
540 if (conv
->WC2MB(buf
, wbuf
, 2) == (size_t)-1)
544 return (code
< 256) ? (wxChar
)code
: '?';
549 struct wxHtmlEntityInfo
551 const wxStringCharType
*name
;
555 extern "C" int LINKAGEMODE
wxHtmlEntityCompare(const void *key
, const void *item
)
557 #if wxUSE_UNICODE_UTF8
558 return strcmp((char*)key
, ((wxHtmlEntityInfo
*)item
)->name
);
560 return wxStrcmp((wxChar
*)key
, ((wxHtmlEntityInfo
*)item
)->name
);
564 wxChar
wxHtmlEntitiesParser::GetEntityChar(const wxString
& entity
) const
568 if (entity
[0] == wxT('#'))
570 // NB: parsed value is a number, so it's OK to use wx_str(), internal
571 // representation is the same for numbers
572 const wxStringCharType
*ent_s
= entity
.wx_str();
573 const wxStringCharType
*format
;
575 if (ent_s
[1] == wxSTRING_TEXT('x') || ent_s
[1] == wxSTRING_TEXT('X'))
577 format
= wxSTRING_TEXT("%x");
581 format
= wxSTRING_TEXT("%u");
584 if (wxSscanf(ent_s
, format
, &code
) != 1)
589 // store the literals in wx's internal representation (either char*
590 // in UTF-8 or wchar_t*) for best performance:
591 #define ENTITY(name, code) { wxSTRING_TEXT(name), code }
593 static wxHtmlEntityInfo substitutions
[] = {
594 ENTITY("AElig", 198),
595 ENTITY("Aacute", 193),
596 ENTITY("Acirc", 194),
597 ENTITY("Agrave", 192),
598 ENTITY("Alpha", 913),
599 ENTITY("Aring", 197),
600 ENTITY("Atilde", 195),
603 ENTITY("Ccedil", 199),
605 ENTITY("Dagger", 8225),
606 ENTITY("Delta", 916),
608 ENTITY("Eacute", 201),
609 ENTITY("Ecirc", 202),
610 ENTITY("Egrave", 200),
611 ENTITY("Epsilon", 917),
614 ENTITY("Gamma", 915),
615 ENTITY("Iacute", 205),
616 ENTITY("Icirc", 206),
617 ENTITY("Igrave", 204),
620 ENTITY("Kappa", 922),
621 ENTITY("Lambda", 923),
623 ENTITY("Ntilde", 209),
625 ENTITY("OElig", 338),
626 ENTITY("Oacute", 211),
627 ENTITY("Ocirc", 212),
628 ENTITY("Ograve", 210),
629 ENTITY("Omega", 937),
630 ENTITY("Omicron", 927),
631 ENTITY("Oslash", 216),
632 ENTITY("Otilde", 213),
636 ENTITY("Prime", 8243),
639 ENTITY("Scaron", 352),
640 ENTITY("Sigma", 931),
641 ENTITY("THORN", 222),
643 ENTITY("Theta", 920),
644 ENTITY("Uacute", 218),
645 ENTITY("Ucirc", 219),
646 ENTITY("Ugrave", 217),
647 ENTITY("Upsilon", 933),
650 ENTITY("Yacute", 221),
653 ENTITY("aacute", 225),
654 ENTITY("acirc", 226),
655 ENTITY("acute", 180),
656 ENTITY("aelig", 230),
657 ENTITY("agrave", 224),
658 ENTITY("alefsym", 8501),
659 ENTITY("alpha", 945),
663 ENTITY("aring", 229),
664 ENTITY("asymp", 8776),
665 ENTITY("atilde", 227),
667 ENTITY("bdquo", 8222),
669 ENTITY("brvbar", 166),
670 ENTITY("bull", 8226),
672 ENTITY("ccedil", 231),
673 ENTITY("cedil", 184),
677 ENTITY("clubs", 9827),
678 ENTITY("cong", 8773),
680 ENTITY("crarr", 8629),
682 ENTITY("curren", 164),
683 ENTITY("dArr", 8659),
684 ENTITY("dagger", 8224),
685 ENTITY("darr", 8595),
687 ENTITY("delta", 948),
688 ENTITY("diams", 9830),
689 ENTITY("divide", 247),
690 ENTITY("eacute", 233),
691 ENTITY("ecirc", 234),
692 ENTITY("egrave", 232),
693 ENTITY("empty", 8709),
694 ENTITY("emsp", 8195),
695 ENTITY("ensp", 8194),
696 ENTITY("epsilon", 949),
697 ENTITY("equiv", 8801),
701 ENTITY("euro", 8364),
702 ENTITY("exist", 8707),
704 ENTITY("forall", 8704),
705 ENTITY("frac12", 189),
706 ENTITY("frac14", 188),
707 ENTITY("frac34", 190),
708 ENTITY("frasl", 8260),
709 ENTITY("gamma", 947),
712 ENTITY("hArr", 8660),
713 ENTITY("harr", 8596),
714 ENTITY("hearts", 9829),
715 ENTITY("hellip", 8230),
716 ENTITY("iacute", 237),
717 ENTITY("icirc", 238),
718 ENTITY("iexcl", 161),
719 ENTITY("igrave", 236),
720 ENTITY("image", 8465),
721 ENTITY("infin", 8734),
724 ENTITY("iquest", 191),
725 ENTITY("isin", 8712),
727 ENTITY("kappa", 954),
728 ENTITY("lArr", 8656),
729 ENTITY("lambda", 955),
730 ENTITY("lang", 9001),
731 ENTITY("laquo", 171),
732 ENTITY("larr", 8592),
733 ENTITY("lceil", 8968),
734 ENTITY("ldquo", 8220),
736 ENTITY("lfloor", 8970),
737 ENTITY("lowast", 8727),
740 ENTITY("lsaquo", 8249),
741 ENTITY("lsquo", 8216),
744 ENTITY("mdash", 8212),
745 ENTITY("micro", 181),
746 ENTITY("middot", 183),
747 ENTITY("minus", 8722),
749 ENTITY("nabla", 8711),
751 ENTITY("ndash", 8211),
755 ENTITY("notin", 8713),
756 ENTITY("nsub", 8836),
757 ENTITY("ntilde", 241),
759 ENTITY("oacute", 243),
760 ENTITY("ocirc", 244),
761 ENTITY("oelig", 339),
762 ENTITY("ograve", 242),
763 ENTITY("oline", 8254),
764 ENTITY("omega", 969),
765 ENTITY("omicron", 959),
766 ENTITY("oplus", 8853),
770 ENTITY("oslash", 248),
771 ENTITY("otilde", 245),
772 ENTITY("otimes", 8855),
775 ENTITY("part", 8706),
776 ENTITY("permil", 8240),
777 ENTITY("perp", 8869),
781 ENTITY("plusmn", 177),
782 ENTITY("pound", 163),
783 ENTITY("prime", 8242),
784 ENTITY("prod", 8719),
785 ENTITY("prop", 8733),
788 ENTITY("rArr", 8658),
789 ENTITY("radic", 8730),
790 ENTITY("rang", 9002),
791 ENTITY("raquo", 187),
792 ENTITY("rarr", 8594),
793 ENTITY("rceil", 8969),
794 ENTITY("rdquo", 8221),
795 ENTITY("real", 8476),
797 ENTITY("rfloor", 8971),
800 ENTITY("rsaquo", 8250),
801 ENTITY("rsquo", 8217),
802 ENTITY("sbquo", 8218),
803 ENTITY("scaron", 353),
804 ENTITY("sdot", 8901),
807 ENTITY("sigma", 963),
808 ENTITY("sigmaf", 962),
810 ENTITY("spades", 9824),
812 ENTITY("sube", 8838),
818 ENTITY("supe", 8839),
819 ENTITY("szlig", 223),
821 ENTITY("there4", 8756),
822 ENTITY("theta", 952),
823 ENTITY("thetasym", 977),
824 ENTITY("thinsp", 8201),
825 ENTITY("thorn", 254),
826 ENTITY("tilde", 732),
827 ENTITY("times", 215),
828 ENTITY("trade", 8482),
829 ENTITY("uArr", 8657),
830 ENTITY("uacute", 250),
831 ENTITY("uarr", 8593),
832 ENTITY("ucirc", 251),
833 ENTITY("ugrave", 249),
835 ENTITY("upsih", 978),
836 ENTITY("upsilon", 965),
838 ENTITY("weierp", 8472),
840 ENTITY("yacute", 253),
845 ENTITY("zwnj", 8204),
848 static size_t substitutions_cnt
= 0;
850 if (substitutions_cnt
== 0)
851 while (substitutions
[substitutions_cnt
].code
!= 0)
854 wxHtmlEntityInfo
*info
= NULL
;
856 // bsearch crashes under WinCE for some reason
858 for (i
= 0; i
< substitutions_cnt
; i
++)
860 if (entity
== substitutions
[i
].name
)
862 info
= & substitutions
[i
];
867 info
= (wxHtmlEntityInfo
*) bsearch(entity
.wx_str(), substitutions
,
869 sizeof(wxHtmlEntityInfo
),
870 wxHtmlEntityCompare
);
879 return GetCharForCode(code
);
882 wxFSFile
*wxHtmlParser::OpenURL(wxHtmlURLType
WXUNUSED(type
),
883 const wxString
& url
) const
885 return m_FS
? m_FS
->OpenFile(url
) : NULL
;
890 //-----------------------------------------------------------------------------
891 // wxHtmlParser::ExtractCharsetInformation
892 //-----------------------------------------------------------------------------
894 class wxMetaTagParser
: public wxHtmlParser
897 wxMetaTagParser() { }
899 wxObject
* GetProduct() { return NULL
; }
902 virtual void AddText(const wxString
& WXUNUSED(txt
)) {}
904 DECLARE_NO_COPY_CLASS(wxMetaTagParser
)
907 class wxMetaTagHandler
: public wxHtmlTagHandler
910 wxMetaTagHandler(wxString
*retval
) : wxHtmlTagHandler(), m_retval(retval
) {}
911 wxString
GetSupportedTags() { return wxT("META,BODY"); }
912 bool HandleTag(const wxHtmlTag
& tag
);
917 DECLARE_NO_COPY_CLASS(wxMetaTagHandler
)
920 bool wxMetaTagHandler::HandleTag(const wxHtmlTag
& tag
)
922 if (tag
.GetName() == _T("BODY"))
924 m_Parser
->StopParsing();
928 if (tag
.HasParam(_T("HTTP-EQUIV")) &&
929 tag
.GetParam(_T("HTTP-EQUIV")).IsSameAs(_T("Content-Type"), false) &&
930 tag
.HasParam(_T("CONTENT")))
932 wxString content
= tag
.GetParam(_T("CONTENT")).Lower();
933 if (content
.Left(19) == _T("text/html; charset="))
935 *m_retval
= content
.Mid(19);
936 m_Parser
->StopParsing();
944 wxString
wxHtmlParser::ExtractCharsetInformation(const wxString
& markup
)
947 wxMetaTagParser
*parser
= new wxMetaTagParser();
950 parser
->AddTagHandler(new wxMetaTagHandler(&charset
));
951 parser
->Parse(markup
);
959 wxHtmlParser::SkipCommentTag(wxString::const_iterator
& start
,
960 wxString::const_iterator end
)
962 wxASSERT_MSG( *start
== '<', _T("should be called on the tag start") );
964 wxString::const_iterator p
= start
;
966 // comments begin with "<!--" in HTML 4.0
967 if ( p
> end
- 3 || *++p
!= '!' || *++p
!= '-' || *++p
!= '-' )
969 // not a comment at all
973 // skip the start of the comment tag in any case, if we don't find the
974 // closing tag we should ignore broken markup
977 // comments end with "--[ \t\r\n]*>", i.e. white space is allowed between
978 // comment delimiter and the closing tag character (section 3.2.4 of
979 // http://www.w3.org/TR/html401/)
985 if ( (c
== wxT(' ') || c
== wxT('\n') ||
986 c
== wxT('\r') || c
== wxT('\t')) && dashes
>= 2 )
988 // ignore white space before potential tag end
992 if ( c
== wxT('>') && dashes
>= 2 )
994 // found end of comment
1008 #endif // wxUSE_HTML