1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/html/htmlpars.cpp
3 // Purpose: wxHtmlParser class (generic parser)
4 // Author: Vaclav Slavik
5 // Copyright: (c) 1999 Vaclav Slavik
6 // Licence: wxWindows licence
7 /////////////////////////////////////////////////////////////////////////////
15 #if wxUSE_HTML && wxUSE_STREAMS
18 #include "wx/dynarray.h"
22 #include "wx/wxcrtvararg.h"
25 #include "wx/tokenzr.h"
26 #include "wx/wfstream.h"
28 #include "wx/fontmap.h"
29 #include "wx/html/htmldefs.h"
30 #include "wx/html/htmlpars.h"
31 #include "wx/vector.h"
34 #include "wx/msw/wince/missing.h" // for bsearch()
37 // DLL options compatibility check:
38 WX_CHECK_BUILD_OPTIONS("wxHTML")
40 const wxChar
*wxTRACE_HTML_DEBUG
= wxT("htmldebug");
42 //-----------------------------------------------------------------------------
43 // wxHtmlParser helpers
44 //-----------------------------------------------------------------------------
50 wxHtmlTextPiece(const wxString::const_iterator
& start
,
51 const wxString::const_iterator
& end
)
52 : m_start(start
), m_end(end
) {}
53 wxString::const_iterator m_start
, m_end
;
56 // NB: this is an empty class and not typedef because of forward declaration
57 class wxHtmlTextPieces
: public wxVector
<wxHtmlTextPiece
>
61 class wxHtmlParserState
66 wxHtmlTextPieces
*m_textPieces
;
68 const wxString
*m_source
;
69 wxHtmlParserState
*m_nextState
;
72 //-----------------------------------------------------------------------------
74 //-----------------------------------------------------------------------------
76 IMPLEMENT_ABSTRACT_CLASS(wxHtmlParser
,wxObject
)
78 wxHtmlParser::wxHtmlParser()
83 m_entitiesParser
= new wxHtmlEntitiesParser
;
91 wxHtmlParser::~wxHtmlParser()
93 while (RestoreState()) {}
96 WX_CLEAR_ARRAY(m_HandlersStack
);
97 WX_CLEAR_HASH_SET(wxHtmlTagHandlersSet
, m_HandlersSet
);
98 delete m_entitiesParser
;
102 wxObject
* wxHtmlParser::Parse(const wxString
& source
)
106 wxObject
*result
= GetProduct();
111 void wxHtmlParser::InitParser(const wxString
& source
)
114 m_stopParsing
= false;
117 void wxHtmlParser::DoneParser()
122 void wxHtmlParser::SetSource(const wxString
& src
)
125 // NB: This is allocated on heap because wxHtmlTag uses iterators and
126 // making a copy of m_Source string in SetSourceAndSaveState() and
127 // RestoreState() would invalidate them (because wxString::m_impl's
128 // memory would change completely twice and iterators use pointers
129 // into it). So instead, we keep the string object intact and only
130 // store/restore pointer to it, for which we need it to be allocated
133 m_Source
= new wxString(src
);
139 void wxHtmlParser::CreateDOMTree()
141 wxHtmlTagsCache
cache(*m_Source
);
142 m_TextPieces
= new wxHtmlTextPieces
;
143 CreateDOMSubTree(NULL
, m_Source
->begin(), m_Source
->end(), &cache
);
147 extern bool wxIsCDATAElement(const wxString
& tag
);
149 void wxHtmlParser::CreateDOMSubTree(wxHtmlTag
*cur
,
150 const wxString::const_iterator
& begin_pos
,
151 const wxString::const_iterator
& end_pos
,
152 wxHtmlTagsCache
*cache
)
154 if (end_pos
<= begin_pos
)
158 wxString::const_iterator i
= begin_pos
;
159 wxString::const_iterator textBeginning
= begin_pos
;
161 // If the tag contains CDATA text, we include the text between beginning
162 // and ending tag verbosely. Setting i=end_pos will skip to the very
163 // end of this function where text piece is added, bypassing any child
164 // tags parsing (CDATA element can't have child elements by definition):
165 if (cur
!= NULL
&& wxIsCDATAElement(cur
->GetName()))
176 // add text to m_TextPieces:
177 if (i
> textBeginning
)
178 m_TextPieces
->push_back(wxHtmlTextPiece(textBeginning
, i
));
180 // if it is a comment, skip it:
181 if ( SkipCommentTag(i
, m_Source
->end()) )
183 textBeginning
= i
= i
+ 1; // skip closing '>' too
186 // add another tag to the tree:
187 else if (i
< end_pos
-1 && *(i
+1) != wxT('/'))
191 chd
= new wxHtmlTag(cur
, m_Source
,
192 i
, end_pos
, cache
, m_entitiesParser
);
195 chd
= new wxHtmlTag(NULL
, m_Source
,
196 i
, end_pos
, cache
, m_entitiesParser
);
199 // if this is the first tag to be created make the root
200 // m_Tags point to it:
205 // if there is already a root tag add this tag as
207 chd
->m_Prev
= m_Tags
->GetLastSibling();
208 chd
->m_Prev
->m_Next
= chd
;
212 if (chd
->HasEnding())
214 CreateDOMSubTree(chd
,
215 chd
->GetBeginIter(), chd
->GetEndIter1(),
217 i
= chd
->GetEndIter2();
220 i
= chd
->GetBeginIter();
225 // ... or skip ending tag:
228 while (i
< end_pos
&& *i
!= wxT('>')) ++i
;
229 textBeginning
= i
< end_pos
? i
+1 : i
;
235 // add remaining text to m_TextPieces:
236 if (end_pos
> textBeginning
)
237 m_TextPieces
->push_back(wxHtmlTextPiece(textBeginning
, end_pos
));
240 void wxHtmlParser::DestroyDOMTree()
246 t2
= t1
->GetNextSibling();
250 m_Tags
= m_CurTag
= NULL
;
252 wxDELETE(m_TextPieces
);
255 void wxHtmlParser::DoParsing()
259 DoParsing(m_Source
->begin(), m_Source
->end());
262 void wxHtmlParser::DoParsing(const wxString::const_iterator
& begin_pos_
,
263 const wxString::const_iterator
& end_pos
)
265 wxString::const_iterator
begin_pos(begin_pos_
);
267 if (end_pos
<= begin_pos
)
270 wxHtmlTextPieces
& pieces
= *m_TextPieces
;
271 size_t piecesCnt
= pieces
.size();
273 while (begin_pos
< end_pos
)
275 while (m_CurTag
&& m_CurTag
->GetBeginIter() < begin_pos
)
276 m_CurTag
= m_CurTag
->GetNextTag();
277 while (m_CurTextPiece
< piecesCnt
&&
278 pieces
[m_CurTextPiece
].m_start
< begin_pos
)
281 if (m_CurTextPiece
< piecesCnt
&&
283 pieces
[m_CurTextPiece
].m_start
< m_CurTag
->GetBeginIter()))
286 AddText(GetEntitiesParser()->Parse(
287 wxString(pieces
[m_CurTextPiece
].m_start
,
288 pieces
[m_CurTextPiece
].m_end
)));
289 begin_pos
= pieces
[m_CurTextPiece
].m_end
;
294 if (m_CurTag
->HasEnding())
295 begin_pos
= m_CurTag
->GetEndIter2();
297 begin_pos
= m_CurTag
->GetBeginIter();
298 wxHtmlTag
*t
= m_CurTag
;
299 m_CurTag
= m_CurTag
->GetNextTag();
308 void wxHtmlParser::AddTag(const wxHtmlTag
& tag
)
312 wxHtmlTagHandlersHash::const_iterator h
= m_HandlersHash
.find(tag
.GetName());
313 if (h
!= m_HandlersHash
.end())
315 inner
= h
->second
->HandleTag(tag
);
322 DoParsing(tag
.GetBeginIter(), tag
.GetEndIter1());
326 void wxHtmlParser::AddTagHandler(wxHtmlTagHandler
*handler
)
328 wxString
s(handler
->GetSupportedTags());
329 wxStringTokenizer
tokenizer(s
, wxT(", "));
331 while (tokenizer
.HasMoreTokens())
332 m_HandlersHash
[tokenizer
.GetNextToken()] = handler
;
334 m_HandlersSet
.insert(handler
);
336 handler
->SetParser(this);
339 void wxHtmlParser::PushTagHandler(wxHtmlTagHandler
*handler
, const wxString
& tags
)
341 wxStringTokenizer
tokenizer(tags
, wxT(", "));
344 m_HandlersStack
.push_back(new wxHtmlTagHandlersHash(m_HandlersHash
));
346 while (tokenizer
.HasMoreTokens())
348 key
= tokenizer
.GetNextToken();
349 m_HandlersHash
[key
] = handler
;
353 void wxHtmlParser::PopTagHandler()
355 wxCHECK_RET( !m_HandlersStack
.empty(),
356 "attempt to remove HTML tag handler from empty stack" );
358 wxHtmlTagHandlersHash
*prev
= m_HandlersStack
.back();
359 m_HandlersStack
.pop_back();
360 m_HandlersHash
= *prev
;
364 void wxHtmlParser::SetSourceAndSaveState(const wxString
& src
)
366 wxHtmlParserState
*s
= new wxHtmlParserState
;
368 s
->m_curTag
= m_CurTag
;
370 s
->m_textPieces
= m_TextPieces
;
371 s
->m_curTextPiece
= m_CurTextPiece
;
372 s
->m_source
= m_Source
;
374 s
->m_nextState
= m_SavedStates
;
386 bool wxHtmlParser::RestoreState()
388 if (!m_SavedStates
) return false;
393 wxHtmlParserState
*s
= m_SavedStates
;
394 m_SavedStates
= s
->m_nextState
;
396 m_CurTag
= s
->m_curTag
;
398 m_TextPieces
= s
->m_textPieces
;
399 m_CurTextPiece
= s
->m_curTextPiece
;
400 m_Source
= s
->m_source
;
406 wxString
wxHtmlParser::GetInnerSource(const wxHtmlTag
& tag
)
408 return wxString(tag
.GetBeginIter(), tag
.GetEndIter1());
411 //-----------------------------------------------------------------------------
413 //-----------------------------------------------------------------------------
415 IMPLEMENT_ABSTRACT_CLASS(wxHtmlTagHandler
,wxObject
)
417 void wxHtmlTagHandler::ParseInnerSource(const wxString
& source
)
419 // It is safe to temporarily change the source being parsed,
420 // provided we restore the state back after parsing
421 m_Parser
->SetSourceAndSaveState(source
);
422 m_Parser
->DoParsing();
423 m_Parser
->RestoreState();
427 //-----------------------------------------------------------------------------
428 // wxHtmlEntitiesParser
429 //-----------------------------------------------------------------------------
431 IMPLEMENT_DYNAMIC_CLASS(wxHtmlEntitiesParser
,wxObject
)
433 wxHtmlEntitiesParser::wxHtmlEntitiesParser()
435 : m_conv(NULL
), m_encoding(wxFONTENCODING_SYSTEM
)
440 wxHtmlEntitiesParser::~wxHtmlEntitiesParser()
448 void wxHtmlEntitiesParser::SetEncoding(wxFontEncoding encoding
)
450 if (encoding
== m_encoding
)
455 m_encoding
= encoding
;
456 if (m_encoding
== wxFONTENCODING_SYSTEM
)
459 m_conv
= new wxCSConv(wxFontMapper::GetEncodingName(m_encoding
));
461 #endif // !wxUSE_UNICODE
463 wxString
wxHtmlEntitiesParser::Parse(const wxString
& input
) const
467 const wxString::const_iterator
end(input
.end());
468 wxString::const_iterator
c(input
.begin());
469 wxString::const_iterator
last(c
);
471 for ( ; c
< end
; ++c
)
475 if ( output
.empty() )
476 output
.reserve(input
.length());
479 output
.append(last
, c
);
484 const wxString::const_iterator ent_s
= c
;
487 for ( ; c
!= end
; ++c
)
490 if ( !((ch
>= wxT('a') && ch
<= wxT('z')) ||
491 (ch
>= wxT('A') && ch
<= wxT('Z')) ||
492 (ch
>= wxT('0') && ch
<= wxT('9')) ||
493 ch
== wxT('_') || ch
== wxT('#')) )
497 entity
.append(ent_s
, c
);
498 if (c
== end
|| *c
!= wxT(';')) --c
;
500 entity_char
= GetEntityChar(entity
);
502 output
<< entity_char
;
505 output
.append(ent_s
-1, c
+1);
506 wxLogTrace(wxTRACE_HTML_DEBUG
,
507 "Unrecognized HTML entity: '%s'",
512 if ( last
== input
.begin() ) // common case: no entity
515 output
.append(last
, end
);
520 wxChar
wxHtmlEntitiesParser::GetCharForCode(unsigned code
) const
524 wbuf
[0] = (wchar_t)code
;
526 wxMBConv
*conv
= m_conv
? m_conv
: &wxConvLocal
;
527 if (conv
->WC2MB(buf
, wbuf
, 2) == (size_t)-1)
533 struct wxHtmlEntityInfo
535 const wxStringCharType
*name
;
539 extern "C" int LINKAGEMODE
wxHtmlEntityCompare(const void *key
, const void *item
)
541 #if wxUSE_UNICODE_UTF8
542 return strcmp((char*)key
, ((wxHtmlEntityInfo
*)item
)->name
);
544 return wxStrcmp((wxChar
*)key
, ((wxHtmlEntityInfo
*)item
)->name
);
548 wxChar
wxHtmlEntitiesParser::GetEntityChar(const wxString
& entity
) const
553 return 0; // invalid entity reference
555 if (entity
[0] == wxT('#'))
557 // NB: parsed value is a number, so it's OK to use wx_str(), internal
558 // representation is the same for numbers
559 const wxStringCharType
*ent_s
= entity
.wx_str();
560 const wxStringCharType
*format
;
562 if (ent_s
[1] == wxS('x') || ent_s
[1] == wxS('X'))
571 if (wxSscanf(ent_s
, format
, &code
) != 1)
576 // store the literals in wx's internal representation (either char*
577 // in UTF-8 or wchar_t*) for best performance:
578 #define ENTITY(name, code) { wxS(name), code }
580 static wxHtmlEntityInfo substitutions
[] = {
581 ENTITY("AElig", 198),
582 ENTITY("Aacute", 193),
583 ENTITY("Acirc", 194),
584 ENTITY("Agrave", 192),
585 ENTITY("Alpha", 913),
586 ENTITY("Aring", 197),
587 ENTITY("Atilde", 195),
590 ENTITY("Ccedil", 199),
592 ENTITY("Dagger", 8225),
593 ENTITY("Delta", 916),
595 ENTITY("Eacute", 201),
596 ENTITY("Ecirc", 202),
597 ENTITY("Egrave", 200),
598 ENTITY("Epsilon", 917),
601 ENTITY("Gamma", 915),
602 ENTITY("Iacute", 205),
603 ENTITY("Icirc", 206),
604 ENTITY("Igrave", 204),
607 ENTITY("Kappa", 922),
608 ENTITY("Lambda", 923),
610 ENTITY("Ntilde", 209),
612 ENTITY("OElig", 338),
613 ENTITY("Oacute", 211),
614 ENTITY("Ocirc", 212),
615 ENTITY("Ograve", 210),
616 ENTITY("Omega", 937),
617 ENTITY("Omicron", 927),
618 ENTITY("Oslash", 216),
619 ENTITY("Otilde", 213),
623 ENTITY("Prime", 8243),
626 ENTITY("Scaron", 352),
627 ENTITY("Sigma", 931),
628 ENTITY("THORN", 222),
630 ENTITY("Theta", 920),
631 ENTITY("Uacute", 218),
632 ENTITY("Ucirc", 219),
633 ENTITY("Ugrave", 217),
634 ENTITY("Upsilon", 933),
637 ENTITY("Yacute", 221),
640 ENTITY("aacute", 225),
641 ENTITY("acirc", 226),
642 ENTITY("acute", 180),
643 ENTITY("aelig", 230),
644 ENTITY("agrave", 224),
645 ENTITY("alefsym", 8501),
646 ENTITY("alpha", 945),
651 ENTITY("aring", 229),
652 ENTITY("asymp", 8776),
653 ENTITY("atilde", 227),
655 ENTITY("bdquo", 8222),
657 ENTITY("brvbar", 166),
658 ENTITY("bull", 8226),
660 ENTITY("ccedil", 231),
661 ENTITY("cedil", 184),
665 ENTITY("clubs", 9827),
666 ENTITY("cong", 8773),
668 ENTITY("crarr", 8629),
670 ENTITY("curren", 164),
671 ENTITY("dArr", 8659),
672 ENTITY("dagger", 8224),
673 ENTITY("darr", 8595),
675 ENTITY("delta", 948),
676 ENTITY("diams", 9830),
677 ENTITY("divide", 247),
678 ENTITY("eacute", 233),
679 ENTITY("ecirc", 234),
680 ENTITY("egrave", 232),
681 ENTITY("empty", 8709),
682 ENTITY("emsp", 8195),
683 ENTITY("ensp", 8194),
684 ENTITY("epsilon", 949),
685 ENTITY("equiv", 8801),
689 ENTITY("euro", 8364),
690 ENTITY("exist", 8707),
692 ENTITY("forall", 8704),
693 ENTITY("frac12", 189),
694 ENTITY("frac14", 188),
695 ENTITY("frac34", 190),
696 ENTITY("frasl", 8260),
697 ENTITY("gamma", 947),
700 ENTITY("hArr", 8660),
701 ENTITY("harr", 8596),
702 ENTITY("hearts", 9829),
703 ENTITY("hellip", 8230),
704 ENTITY("iacute", 237),
705 ENTITY("icirc", 238),
706 ENTITY("iexcl", 161),
707 ENTITY("igrave", 236),
708 ENTITY("image", 8465),
709 ENTITY("infin", 8734),
712 ENTITY("iquest", 191),
713 ENTITY("isin", 8712),
715 ENTITY("kappa", 954),
716 ENTITY("lArr", 8656),
717 ENTITY("lambda", 955),
718 ENTITY("lang", 9001),
719 ENTITY("laquo", 171),
720 ENTITY("larr", 8592),
721 ENTITY("lceil", 8968),
722 ENTITY("ldquo", 8220),
724 ENTITY("lfloor", 8970),
725 ENTITY("lowast", 8727),
728 ENTITY("lsaquo", 8249),
729 ENTITY("lsquo", 8216),
732 ENTITY("mdash", 8212),
733 ENTITY("micro", 181),
734 ENTITY("middot", 183),
735 ENTITY("minus", 8722),
737 ENTITY("nabla", 8711),
739 ENTITY("ndash", 8211),
743 ENTITY("notin", 8713),
744 ENTITY("nsub", 8836),
745 ENTITY("ntilde", 241),
747 ENTITY("oacute", 243),
748 ENTITY("ocirc", 244),
749 ENTITY("oelig", 339),
750 ENTITY("ograve", 242),
751 ENTITY("oline", 8254),
752 ENTITY("omega", 969),
753 ENTITY("omicron", 959),
754 ENTITY("oplus", 8853),
758 ENTITY("oslash", 248),
759 ENTITY("otilde", 245),
760 ENTITY("otimes", 8855),
763 ENTITY("part", 8706),
764 ENTITY("permil", 8240),
765 ENTITY("perp", 8869),
769 ENTITY("plusmn", 177),
770 ENTITY("pound", 163),
771 ENTITY("prime", 8242),
772 ENTITY("prod", 8719),
773 ENTITY("prop", 8733),
776 ENTITY("rArr", 8658),
777 ENTITY("radic", 8730),
778 ENTITY("rang", 9002),
779 ENTITY("raquo", 187),
780 ENTITY("rarr", 8594),
781 ENTITY("rceil", 8969),
782 ENTITY("rdquo", 8221),
783 ENTITY("real", 8476),
785 ENTITY("rfloor", 8971),
788 ENTITY("rsaquo", 8250),
789 ENTITY("rsquo", 8217),
790 ENTITY("sbquo", 8218),
791 ENTITY("scaron", 353),
792 ENTITY("sdot", 8901),
795 ENTITY("sigma", 963),
796 ENTITY("sigmaf", 962),
798 ENTITY("spades", 9824),
800 ENTITY("sube", 8838),
806 ENTITY("supe", 8839),
807 ENTITY("szlig", 223),
809 ENTITY("there4", 8756),
810 ENTITY("theta", 952),
811 ENTITY("thetasym", 977),
812 ENTITY("thinsp", 8201),
813 ENTITY("thorn", 254),
814 ENTITY("tilde", 732),
815 ENTITY("times", 215),
816 ENTITY("trade", 8482),
817 ENTITY("uArr", 8657),
818 ENTITY("uacute", 250),
819 ENTITY("uarr", 8593),
820 ENTITY("ucirc", 251),
821 ENTITY("ugrave", 249),
823 ENTITY("upsih", 978),
824 ENTITY("upsilon", 965),
826 ENTITY("weierp", 8472),
828 ENTITY("yacute", 253),
833 ENTITY("zwnj", 8204),
836 static size_t substitutions_cnt
= 0;
838 if (substitutions_cnt
== 0)
839 while (substitutions
[substitutions_cnt
].code
!= 0)
842 wxHtmlEntityInfo
*info
;
844 // bsearch crashes under WinCE for some reason
847 for (i
= 0; i
< substitutions_cnt
; i
++)
849 if (entity
== substitutions
[i
].name
)
851 info
= & substitutions
[i
];
856 info
= (wxHtmlEntityInfo
*) bsearch(entity
.wx_str(), substitutions
,
858 sizeof(wxHtmlEntityInfo
),
859 wxHtmlEntityCompare
);
868 return GetCharForCode(code
);
871 wxFSFile
*wxHtmlParser::OpenURL(wxHtmlURLType type
,
872 const wxString
& url
) const
874 int flags
= wxFS_READ
;
875 if (type
== wxHTML_URL_IMAGE
)
876 flags
|= wxFS_SEEKABLE
;
878 return m_FS
? m_FS
->OpenFile(url
, flags
) : NULL
;
883 //-----------------------------------------------------------------------------
884 // wxHtmlParser::ExtractCharsetInformation
885 //-----------------------------------------------------------------------------
887 class wxMetaTagParser
: public wxHtmlParser
890 wxMetaTagParser() { }
892 wxObject
* GetProduct() { return NULL
; }
895 virtual void AddText(const wxString
& WXUNUSED(txt
)) {}
897 wxDECLARE_NO_COPY_CLASS(wxMetaTagParser
);
900 class wxMetaTagHandler
: public wxHtmlTagHandler
903 wxMetaTagHandler(wxString
*retval
) : wxHtmlTagHandler(), m_retval(retval
) {}
904 wxString
GetSupportedTags() { return wxT("META,BODY"); }
905 bool HandleTag(const wxHtmlTag
& tag
);
910 wxDECLARE_NO_COPY_CLASS(wxMetaTagHandler
);
913 bool wxMetaTagHandler::HandleTag(const wxHtmlTag
& tag
)
915 if (tag
.GetName() == wxT("BODY"))
917 m_Parser
->StopParsing();
923 if (tag
.GetParamAsString(wxT("HTTP-EQUIV"), &httpEquiv
) &&
924 httpEquiv
.IsSameAs(wxT("Content-Type"), false) &&
925 tag
.GetParamAsString(wxT("CONTENT"), &content
))
928 if (content
.Left(19) == wxT("text/html; charset="))
930 *m_retval
= content
.Mid(19);
931 m_Parser
->StopParsing();
939 wxString
wxHtmlParser::ExtractCharsetInformation(const wxString
& markup
)
942 wxMetaTagParser
*parser
= new wxMetaTagParser();
945 parser
->AddTagHandler(new wxMetaTagHandler(&charset
));
946 parser
->Parse(markup
);
954 wxHtmlParser::SkipCommentTag(wxString::const_iterator
& start
,
955 wxString::const_iterator end
)
957 wxASSERT_MSG( *start
== '<', wxT("should be called on the tag start") );
959 wxString::const_iterator p
= start
;
961 // Comments begin with "<!--" in HTML 4.0; anything shorter or not containing
962 // these characters is not a comment and we're not going to skip it.
963 if ( ++p
== end
|| *p
!= '!' )
965 if ( ++p
== end
|| *p
!= '-' )
967 if ( ++p
== end
|| *p
!= '-' )
970 // skip the start of the comment tag in any case, if we don't find the
971 // closing tag we should ignore broken markup
974 // comments end with "--[ \t\r\n]*>", i.e. white space is allowed between
975 // comment delimiter and the closing tag character (section 3.2.4 of
976 // http://www.w3.org/TR/html401/)
982 if ( (c
== wxT(' ') || c
== wxT('\n') ||
983 c
== wxT('\r') || c
== wxT('\t')) && dashes
>= 2 )
985 // ignore white space before potential tag end
989 if ( c
== wxT('>') && dashes
>= 2 )
991 // found end of comment
1005 #endif // wxUSE_HTML