]> git.saurik.com Git - wxWidgets.git/blame - src/html/htmltag.cpp
Added wxRichTextTableBlock class to help with table UI operations
[wxWidgets.git] / src / html / htmltag.cpp
CommitLineData
5526e819 1/////////////////////////////////////////////////////////////////////////////
93763ad5 2// Name: src/html/htmltag.cpp
5526e819
VS
3// Purpose: wxHtmlTag class (represents single tag)
4// Author: Vaclav Slavik
5// Copyright: (c) 1999 Vaclav Slavik
65571936 6// Licence: wxWindows licence
5526e819
VS
7/////////////////////////////////////////////////////////////////////////////
8
3096bd2f 9#include "wx/wxprec.h"
5526e819 10
2b5f62a0 11#ifdef __BORLANDC__
93763ad5 12 #pragma hdrstop
5526e819
VS
13#endif
14
93763ad5
WS
15#if wxUSE_HTML
16
40989e46
WS
17#include "wx/html/htmltag.h"
18
b4f4d3dd 19#ifndef WX_PRECOMP
7cf41a5d 20 #include "wx/colour.h"
193d0c93 21 #include "wx/wxcrtvararg.h"
5526e819
VS
22#endif
23
daa616fc 24#include "wx/html/htmlpars.h"
f68e16c5
VZ
25#include "wx/html/styleparams.h"
26
4fe7567d
VS
27#include "wx/vector.h"
28
7e1e0960 29#include <stdio.h> // for vsscanf
5526e819
VS
30#include <stdarg.h>
31
5526e819
VS
32//-----------------------------------------------------------------------------
33// wxHtmlTagsCache
34//-----------------------------------------------------------------------------
35
5e8e25e7
VS
36struct wxHtmlCacheItem
37{
38 // this is "pos" value passed to wxHtmlTag's constructor.
39 // it is position of '<' character of the tag
b1a3a964
VS
40 wxString::const_iterator Key;
41
42 // Tag type
43 enum Type
44 {
45 Type_Normal, // normal tag with a matching ending tag
46 Type_NoMatchingEndingTag, // there's no ending tag for this tag
47 Type_EndingTag // this is ending tag </..>
48 };
49 Type type;
5e8e25e7
VS
50
51 // end positions for the tag:
52 // end1 is '<' of ending tag,
53 // end2 is '>' or both are
b1a3a964 54 wxString::const_iterator End1, End2;
5e8e25e7
VS
55
56 // name of this tag
57 wxChar *Name;
58};
59
4fe7567d
VS
60// NB: this is an empty class and not typedef because of forward declaration
61class wxHtmlTagsCacheData : public wxVector<wxHtmlCacheItem>
62{
63};
5e8e25e7 64
07cc7ddc 65bool wxIsCDATAElement(const wxChar *tag)
7c6cd4a8 66{
9a83f860
VZ
67 return (wxStrcmp(tag, wxT("SCRIPT")) == 0) ||
68 (wxStrcmp(tag, wxT("STYLE")) == 0);
7c6cd4a8
VS
69}
70
b1a3a964
VS
71bool wxIsCDATAElement(const wxString& tag)
72{
d9359369
VS
73 return (wxStrcmp(tag.wx_str(), wxS("SCRIPT")) == 0) ||
74 (wxStrcmp(tag.wx_str(), wxS("STYLE")) == 0);
b1a3a964
VS
75}
76
5526e819
VS
77wxHtmlTagsCache::wxHtmlTagsCache(const wxString& source)
78{
4fe7567d
VS
79 m_Cache = new wxHtmlTagsCacheData;
80 m_CachePos = 0;
81
8cd82622 82 wxChar tagBuffer[256];
5526e819 83
b1a3a964
VS
84 const wxString::const_iterator end = source.end();
85 for ( wxString::const_iterator pos = source.begin(); pos < end; ++pos )
4f9297b0 86 {
48d8ea6d
VZ
87 if (*pos != wxT('<'))
88 continue;
4609ee2e 89
48d8ea6d 90 // possible tag start found:
4fe7567d 91
48d8ea6d
VZ
92 // don't cache comment tags
93 if ( wxHtmlParser::SkipCommentTag(pos, end) )
94 continue;
8cd82622 95
36258204 96 // Remember the starting tag position.
48d8ea6d 97 wxString::const_iterator stpos = pos++;
48d8ea6d 98
36258204 99 // And look for the ending one.
48d8ea6d
VZ
100 int i;
101 for ( i = 0;
102 pos < end && i < (int)WXSIZEOF(tagBuffer) - 1 &&
103 *pos != wxT('>') && !wxIsspace(*pos);
104 ++i, ++pos )
105 {
106 tagBuffer[i] = (wxChar)wxToupper(*pos);
107 }
108 tagBuffer[i] = wxT('\0');
8cd82622 109
48d8ea6d
VZ
110 while (pos < end && *pos != wxT('>'))
111 ++pos;
5526e819 112
36258204
VZ
113 if ( pos == end )
114 {
115 // We didn't find a closing bracket, this is not a valid tag after
116 // all. Notice that we need to roll back pos to avoid creating an
117 // invalid iterator when "++pos" is done in the loop statement.
118 --pos;
119
120 continue;
121 }
122
123 // We have a valid tag, add it to the cache.
124 size_t tg = Cache().size();
125 Cache().push_back(wxHtmlCacheItem());
126 Cache()[tg].Key = stpos;
127 Cache()[tg].Name = new wxChar[i+1];
128 memcpy(Cache()[tg].Name, tagBuffer, (i+1)*sizeof(wxChar));
129
48d8ea6d
VZ
130 if ((stpos+1) < end && *(stpos+1) == wxT('/')) // ending tag:
131 {
132 Cache()[tg].type = wxHtmlCacheItem::Type_EndingTag;
133 // find matching begin tag:
134 for (i = tg; i >= 0; i--)
a914db0f 135 {
48d8ea6d 136 if ((Cache()[i].type == wxHtmlCacheItem::Type_NoMatchingEndingTag) && (wxStrcmp(Cache()[i].Name, tagBuffer+1) == 0))
b1a3a964 137 {
48d8ea6d
VZ
138 Cache()[i].type = wxHtmlCacheItem::Type_Normal;
139 Cache()[i].End1 = stpos;
140 Cache()[i].End2 = pos + 1;
141 break;
b1a3a964 142 }
5526e819 143 }
48d8ea6d
VZ
144 }
145 else
146 {
147 Cache()[tg].type = wxHtmlCacheItem::Type_NoMatchingEndingTag;
7448de8d 148
48d8ea6d
VZ
149 if (wxIsCDATAElement(tagBuffer))
150 {
151 // store the orig pos in case we are missing the closing
152 // tag (see below)
153 const wxString::const_iterator old_pos = pos;
154 bool foundCloseTag = false;
155
156 // find next matching tag
157 int tag_len = wxStrlen(tagBuffer);
158 while (pos < end)
7c6cd4a8 159 {
48d8ea6d
VZ
160 // find the ending tag
161 while (pos + 1 < end &&
162 (*pos != '<' || *(pos+1) != '/'))
163 ++pos;
164 if (*pos == '<')
165 ++pos;
166
167 // see if it matches
168 int match_pos = 0;
169 while (pos < end && match_pos < tag_len )
7c6cd4a8 170 {
48d8ea6d
VZ
171 wxChar c = *pos;
172 if ( c == '>' || c == '<' )
173 break;
174
175 // cast to wxChar needed to suppress warning in
176 // Unicode build
177 if ((wxChar)wxToupper(c) == tagBuffer[match_pos])
b1a3a964 178 {
48d8ea6d 179 ++match_pos;
7c6cd4a8 180 }
48d8ea6d
VZ
181 else if (c == wxT(' ') || c == wxT('\n') ||
182 c == wxT('\r') || c == wxT('\t'))
313ffa19 183 {
48d8ea6d 184 // need to skip over these
7c6cd4a8 185 }
48d8ea6d 186 else
313ffa19 187 {
48d8ea6d 188 match_pos = 0;
7c6cd4a8 189 }
48d8ea6d 190 ++pos;
7c6cd4a8 191 }
48d8ea6d
VZ
192
193 // found a match
194 if (match_pos == tag_len)
195 {
196 pos = pos - tag_len - 3;
197 foundCloseTag = true;
198 break;
199 }
200 else // keep looking for the closing tag
313ffa19 201 {
48d8ea6d 202 ++pos;
313ffa19 203 }
7c6cd4a8 204 }
48d8ea6d
VZ
205 if (!foundCloseTag)
206 {
207 // we didn't find closing tag; this means the markup
208 // is incorrect and the best thing we can do is to
209 // ignore the unclosed tag and continue parsing as if
210 // it didn't exist:
211 pos = old_pos;
212 }
5526e819
VS
213 }
214 }
5526e819
VS
215 }
216
217 // ok, we're done, now we'll free .Name members of cache - we don't need it anymore:
4fe7567d
VS
218 for ( wxHtmlTagsCacheData::iterator i = Cache().begin();
219 i != Cache().end(); ++i )
4f9297b0 220 {
5276b0a5 221 wxDELETEA(i->Name);
5526e819
VS
222 }
223}
224
4fe7567d
VS
225wxHtmlTagsCache::~wxHtmlTagsCache()
226{
227 delete m_Cache;
228}
229
b1a3a964
VS
230void wxHtmlTagsCache::QueryTag(const wxString::const_iterator& at,
231 const wxString::const_iterator& inputEnd,
232 wxString::const_iterator *end1,
233 wxString::const_iterator *end2,
234 bool *hasEnding)
5526e819 235{
4fe7567d 236 if (Cache().empty())
36258204
VZ
237 {
238 *end1 =
239 *end2 = inputEnd;
240 *hasEnding = true;
4fe7567d 241 return;
36258204 242 }
4fe7567d
VS
243
244 if (Cache()[m_CachePos].Key != at)
4f9297b0 245 {
4fe7567d 246 int delta = (at < Cache()[m_CachePos].Key) ? -1 : 1;
8cd82622
VZ
247 do
248 {
b1a3a964
VS
249 m_CachePos += delta;
250
251 if ( m_CachePos < 0 || m_CachePos >= (int)Cache().size() )
10b9be32 252 {
b1a3a964
VS
253 if ( m_CachePos < 0 )
254 m_CachePos = 0;
255 else
256 m_CachePos = Cache().size() - 1;
10b9be32
VZ
257 // something is very wrong with HTML, give up by returning an
258 // impossibly large value which is going to be ignored by the
259 // caller
260 *end1 =
b1a3a964
VS
261 *end2 = inputEnd;
262 *hasEnding = true;
10b9be32
VZ
263 return;
264 }
daa616fc 265 }
4fe7567d 266 while (Cache()[m_CachePos].Key != at);
5526e819 267 }
61679695
VS
268
269 switch ( Cache()[m_CachePos].type )
270 {
271 case wxHtmlCacheItem::Type_Normal:
272 *end1 = Cache()[m_CachePos].End1;
273 *end2 = Cache()[m_CachePos].End2;
274 *hasEnding = true;
275 break;
276
277 case wxHtmlCacheItem::Type_EndingTag:
278 wxFAIL_MSG("QueryTag called for ending tag - can't be");
279 // but if it does happen, fall through, better than crashing
280
281 case wxHtmlCacheItem::Type_NoMatchingEndingTag:
282 // If input HTML is invalid and there's no closing tag for this
283 // one, pretend that it runs all the way to the end of input
284 *end1 = inputEnd;
285 *end2 = inputEnd;
286 *hasEnding = false;
287 break;
288 }
5526e819
VS
289}
290
291
292
293
294//-----------------------------------------------------------------------------
295// wxHtmlTag
296//-----------------------------------------------------------------------------
297
211dfedd 298wxHtmlTag::wxHtmlTag(wxHtmlTag *parent,
b1a3a964
VS
299 const wxString *source,
300 const wxString::const_iterator& pos,
301 const wxString::const_iterator& end_pos,
daa616fc 302 wxHtmlTagsCache *cache,
7da48d49 303 wxHtmlEntitiesParser *entParser)
5526e819 304{
211dfedd
VS
305 /* Setup DOM relations */
306
307 m_Next = NULL;
308 m_FirstChild = m_LastChild = NULL;
309 m_Parent = parent;
310 if (parent)
311 {
312 m_Prev = m_Parent->m_LastChild;
313 if (m_Prev == NULL)
314 m_Parent->m_FirstChild = this;
315 else
316 m_Prev->m_Next = this;
317 m_Parent->m_LastChild = this;
318 }
319 else
320 m_Prev = NULL;
321
322 /* Find parameters and their values: */
8cd82622 323
76de2296 324 wxChar c wxDUMMY_INITIALIZE(0);
5526e819
VS
325
326 // fill-in name, params and begin pos:
b1a3a964 327 wxString::const_iterator i(pos+1);
5526e819 328
b076dc01 329 // find tag's name and convert it to uppercase:
8cd82622 330 while ((i < end_pos) &&
b1a3a964 331 ((c = *(i++)) != wxT(' ') && c != wxT('\r') &&
daa616fc 332 c != wxT('\n') && c != wxT('\t') &&
ad20c567 333 c != wxT('>') && c != wxT('/')))
a914db0f 334 {
8cd82622 335 if ((c >= wxT('a')) && (c <= wxT('z')))
daa616fc
VS
336 c -= (wxT('a') - wxT('A'));
337 m_Name << c;
5526e819
VS
338 }
339
b076dc01 340 // if the tag has parameters, read them and "normalize" them,
8cd82622 341 // i.e. convert to uppercase, replace whitespaces by spaces and
b076dc01 342 // remove whitespaces around '=':
b1a3a964 343 if (*(i-1) != wxT('>'))
daa616fc
VS
344 {
345 #define IS_WHITE(c) (c == wxT(' ') || c == wxT('\r') || \
346 c == wxT('\n') || c == wxT('\t'))
347 wxString pname, pvalue;
348 wxChar quote;
8cd82622 349 enum
a914db0f 350 {
8cd82622 351 ST_BEFORE_NAME = 1,
daa616fc
VS
352 ST_NAME,
353 ST_BEFORE_EQ,
354 ST_BEFORE_VALUE,
355 ST_VALUE
356 } state;
8cd82622 357
daa616fc
VS
358 quote = 0;
359 state = ST_BEFORE_NAME;
360 while (i < end_pos)
361 {
b1a3a964 362 c = *(i++);
daa616fc 363
8cd82622 364 if (c == wxT('>') && !(state == ST_VALUE && quote != 0))
a914db0f 365 {
daa616fc 366 if (state == ST_BEFORE_EQ || state == ST_NAME)
b076dc01 367 {
daa616fc 368 m_ParamNames.Add(pname);
b1a3a964 369 m_ParamValues.Add(wxGetEmptyString());
b076dc01 370 }
daa616fc
VS
371 else if (state == ST_VALUE && quote == 0)
372 {
373 m_ParamNames.Add(pname);
367c84b9
VS
374 if (entParser)
375 m_ParamValues.Add(entParser->Parse(pvalue));
376 else
377 m_ParamValues.Add(pvalue);
daa616fc
VS
378 }
379 break;
5526e819 380 }
daa616fc 381 switch (state)
a914db0f 382 {
daa616fc
VS
383 case ST_BEFORE_NAME:
384 if (!IS_WHITE(c))
385 {
386 pname = c;
387 state = ST_NAME;
388 }
389 break;
390 case ST_NAME:
391 if (IS_WHITE(c))
392 state = ST_BEFORE_EQ;
393 else if (c == wxT('='))
394 state = ST_BEFORE_VALUE;
395 else
396 pname << c;
397 break;
398 case ST_BEFORE_EQ:
399 if (c == wxT('='))
400 state = ST_BEFORE_VALUE;
401 else if (!IS_WHITE(c))
402 {
403 m_ParamNames.Add(pname);
b1a3a964 404 m_ParamValues.Add(wxGetEmptyString());
daa616fc
VS
405 pname = c;
406 state = ST_NAME;
407 }
408 break;
409 case ST_BEFORE_VALUE:
410 if (!IS_WHITE(c))
411 {
412 if (c == wxT('"') || c == wxT('\''))
b1a3a964 413 quote = c, pvalue = wxGetEmptyString();
daa616fc
VS
414 else
415 quote = 0, pvalue = c;
416 state = ST_VALUE;
417 }
418 break;
419 case ST_VALUE:
420 if ((quote != 0 && c == quote) ||
421 (quote == 0 && IS_WHITE(c)))
422 {
423 m_ParamNames.Add(pname);
424 if (quote == 0)
425 {
426 // VS: backward compatibility, no real reason,
427 // but wxHTML code relies on this... :(
428 pvalue.MakeUpper();
429 }
367c84b9
VS
430 if (entParser)
431 m_ParamValues.Add(entParser->Parse(pvalue));
432 else
433 m_ParamValues.Add(pvalue);
daa616fc
VS
434 state = ST_BEFORE_NAME;
435 }
436 else
437 pvalue << c;
438 break;
72aa4a98 439 }
5526e819 440 }
8cd82622 441
daa616fc 442 #undef IS_WHITE
7448de8d
WS
443 }
444 m_Begin = i;
b1a3a964 445 cache->QueryTag(pos, source->end(), &m_End1, &m_End2, &m_hasEnding);
7448de8d
WS
446 if (m_End1 > end_pos) m_End1 = end_pos;
447 if (m_End2 > end_pos) m_End2 = end_pos;
b1a3a964
VS
448
449#if WXWIN_COMPATIBILITY_2_8
450 m_sourceStart = source->begin();
451#endif
f68e16c5
VZ
452
453 // Try to parse any style parameters that can be handled simply by
454 // converting them to the equivalent HTML 3 attributes: this is a far cry
455 // from perfect but better than nothing.
456 static const struct EquivAttr
457 {
458 const char *style;
459 const char *attr;
460 } equivAttrs[] =
461 {
462 { "text-align", "ALIGN" },
463 { "width", "WIDTH" },
464 { "vertical-align", "VALIGN" },
465 { "background", "BGCOLOR" },
c7155388 466 { "background-color", "BGCOLOR" },
f68e16c5
VZ
467 };
468
469 wxHtmlStyleParams styleParams(*this);
470 for ( unsigned n = 0; n < WXSIZEOF(equivAttrs); n++ )
471 {
472 const EquivAttr& ea = equivAttrs[n];
473 if ( styleParams.HasParam(ea.style) && !HasParam(ea.attr) )
474 {
475 m_ParamNames.Add(ea.attr);
476 m_ParamValues.Add(styleParams.GetParam(ea.style));
477 }
478 }
5526e819
VS
479}
480
211dfedd
VS
481wxHtmlTag::~wxHtmlTag()
482{
0d58bb65
VS
483 wxHtmlTag *t1, *t2;
484 t1 = m_FirstChild;
485 while (t1)
486 {
487 t2 = t1->GetNextSibling();
488 delete t1;
489 t1 = t2;
490 }
211dfedd
VS
491}
492
5526e819
VS
493bool wxHtmlTag::HasParam(const wxString& par) const
494{
8703bc01 495 return (m_ParamNames.Index(par, false) != wxNOT_FOUND);
5526e819
VS
496}
497
614f9713 498wxString wxHtmlTag::GetParam(const wxString& par, bool with_quotes) const
5526e819 499{
8703bc01 500 int index = m_ParamNames.Index(par, false);
daa616fc 501 if (index == wxNOT_FOUND)
b1a3a964 502 return wxGetEmptyString();
614f9713 503 if (with_quotes)
4f9297b0 504 {
daa616fc
VS
505 // VS: backward compatibility, seems to be never used by wxHTML...
506 wxString s;
507 s << wxT('"') << m_ParamValues[index] << wxT('"');
508 return s;
5526e819 509 }
daa616fc
VS
510 else
511 return m_ParamValues[index];
5526e819
VS
512}
513
90350682 514int wxHtmlTag::ScanParam(const wxString& par,
d7640339
VS
515 const char *format,
516 void *param) const
517{
518 wxString parval = GetParam(par);
519 return wxSscanf(parval, format, param);
520}
521
522int wxHtmlTag::ScanParam(const wxString& par,
523 const wchar_t *format,
90350682 524 void *param) const
5526e819 525{
5526e819 526 wxString parval = GetParam(par);
161f4f73 527 return wxSscanf(parval, format, param);
5526e819
VS
528}
529
f68e16c5
VZ
530/* static */
531bool wxHtmlTag::ParseAsColour(const wxString& str, wxColour *clr)
8bd72d90 532{
9a83f860 533 wxCHECK_MSG( clr, false, wxT("invalid colour argument") );
8cd82622 534
86766dfd 535 // handle colours defined in HTML 4.0 first:
9a83f860 536 if (str.length() > 1 && str[0] != wxT('#'))
8bd72d90 537 {
d9359369
VS
538 #define HTML_COLOUR(name, r, g, b) \
539 if (str.IsSameAs(wxS(name), false)) \
86766dfd 540 { clr->Set(r, g, b); return true; }
8bd72d90
VS
541 HTML_COLOUR("black", 0x00,0x00,0x00)
542 HTML_COLOUR("silver", 0xC0,0xC0,0xC0)
543 HTML_COLOUR("gray", 0x80,0x80,0x80)
544 HTML_COLOUR("white", 0xFF,0xFF,0xFF)
545 HTML_COLOUR("maroon", 0x80,0x00,0x00)
546 HTML_COLOUR("red", 0xFF,0x00,0x00)
547 HTML_COLOUR("purple", 0x80,0x00,0x80)
548 HTML_COLOUR("fuchsia", 0xFF,0x00,0xFF)
549 HTML_COLOUR("green", 0x00,0x80,0x00)
550 HTML_COLOUR("lime", 0x00,0xFF,0x00)
551 HTML_COLOUR("olive", 0x80,0x80,0x00)
552 HTML_COLOUR("yellow", 0xFF,0xFF,0x00)
553 HTML_COLOUR("navy", 0x00,0x00,0x80)
554 HTML_COLOUR("blue", 0x00,0x00,0xFF)
555 HTML_COLOUR("teal", 0x00,0x80,0x80)
556 HTML_COLOUR("aqua", 0x00,0xFF,0xFF)
557 #undef HTML_COLOUR
8bd72d90 558 }
5716a1ab 559
86766dfd
VS
560 // then try to parse #rrggbb representations or set from other well
561 // known names (note that this doesn't strictly conform to HTML spec,
562 // but it doesn't do real harm -- but it *must* be done after the standard
563 // colors are handled above):
564 if (clr->Set(str))
565 return true;
566
8703bc01 567 return false;
8bd72d90
VS
568}
569
f68e16c5
VZ
570bool wxHtmlTag::GetParamAsColour(const wxString& par, wxColour *clr) const
571{
572 const wxString str = GetParam(par);
573 return !str.empty() && ParseAsColour(str, clr);
574}
575
8bd72d90
VS
576bool wxHtmlTag::GetParamAsInt(const wxString& par, int *clr) const
577{
3f6901ad
VZ
578 if ( !HasParam(par) )
579 return false;
580
8bd72d90 581 long i;
3f6901ad
VZ
582 if ( !GetParam(par).ToLong(&i) )
583 return false;
584
8bd72d90 585 *clr = (int)i;
3f6901ad 586 return true;
8bd72d90
VS
587}
588
e0e4b2b0
VZ
589bool
590wxHtmlTag::GetParamAsIntOrPercent(const wxString& par,
591 int* value,
592 bool& isPercent) const
593{
594 const wxString param = GetParam(par);
595 if ( param.empty() )
596 return false;
597
598 wxString num;
599 if ( param.EndsWith("%", &num) )
600 {
601 isPercent = true;
602 }
603 else
604 {
605 isPercent = false;
606 num = param;
607 }
608
609 long lValue;
610 if ( !num.ToLong(&lValue) )
611 return false;
612
613 if ( lValue > INT_MAX || lValue < INT_MIN )
614 return false;
615
616 *value = static_cast<int>(lValue);
617
618 return true;
619}
620
daa616fc
VS
621wxString wxHtmlTag::GetAllParams() const
622{
3103e8a9 623 // VS: this function is for backward compatibility only,
daa616fc
VS
624 // never used by wxHTML
625 wxString s;
626 size_t cnt = m_ParamNames.GetCount();
627 for (size_t i = 0; i < cnt; i++)
628 {
629 s << m_ParamNames[i];
630 s << wxT('=');
631 if (m_ParamValues[i].Find(wxT('"')) != wxNOT_FOUND)
632 s << wxT('\'') << m_ParamValues[i] << wxT('\'');
633 else
634 s << wxT('"') << m_ParamValues[i] << wxT('"');
635 }
636 return s;
637}
638
211dfedd
VS
639wxHtmlTag *wxHtmlTag::GetFirstSibling() const
640{
641 if (m_Parent)
642 return m_Parent->m_FirstChild;
643 else
644 {
645 wxHtmlTag *cur = (wxHtmlTag*)this;
8cd82622 646 while (cur->m_Prev)
211dfedd
VS
647 cur = cur->m_Prev;
648 return cur;
649 }
650}
651
652wxHtmlTag *wxHtmlTag::GetLastSibling() const
653{
654 if (m_Parent)
655 return m_Parent->m_LastChild;
656 else
657 {
658 wxHtmlTag *cur = (wxHtmlTag*)this;
8cd82622 659 while (cur->m_Next)
211dfedd
VS
660 cur = cur->m_Next;
661 return cur;
662 }
663}
664
665wxHtmlTag *wxHtmlTag::GetNextTag() const
666{
667 if (m_FirstChild) return m_FirstChild;
668 if (m_Next) return m_Next;
669 wxHtmlTag *cur = m_Parent;
670 if (!cur) return NULL;
8cd82622 671 while (cur->m_Parent && !cur->m_Next)
211dfedd
VS
672 cur = cur->m_Parent;
673 return cur->m_Next;
674}
675
4d223b67 676#endif