1 /***************************************************************************
2                              labelparser.cpp
3                              ----------------
4     begin                : Dec 14 2004
5                            Copyright (C) 2004, The University of Toronto
6     email                : netterfield@astro.utoronto.ca
7  ***************************************************************************/
8 
9 /***************************************************************************
10  *                                                                         *
11  *   This program is free software; you can redistribute it and/or modify  *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  ***************************************************************************/
17 
18 #include "labelparser.h"
19 
20 #include <assert.h>
21 #include <stdlib.h>
22 
23 #include <qregexp.h>
24 #include <qstring.h>
25 
26 using namespace Label;
27 
28 // Debug output for Parsing - 0 Off 1 On
29 #define DEBUG_PARSING 0
30 
31 #if DEBUG_PARSING
32 #define dumpattr(node, text) do { printf("%s: bold:%d italic:%d underline:%d\n", text, (node)->attributes.bold, (node)->attributes.italic, (node)->attributes.underline); } while(0)
33 #else
34 #define dumpattr(node, text)
35 #endif
36 
Chunk(Chunk * parent,VOffset dir,bool isGroup,bool inherit)37 Chunk::Chunk(Chunk *parent, VOffset dir, bool isGroup, bool inherit)
38 : next(0L), prev(0L), up(0L), down(0L), group(0L), scalar(false), linebreak(false), tab(false), vector(false), formated(false), vOffset(dir) {
39   assert(parent || vOffset == None);
40   if (parent) {  // attach and inherit
41     switch (vOffset) {
42       case None:
43         if (isGroup) {
44           parent->group = this;
45         } else {
46           while (parent->next) {
47             parent = parent->next;
48           }
49           parent->next = this;
50         }
51         break;
52       case Up:
53         assert(!parent->up);
54         parent->up = this;
55         break;
56       case Down:
57         assert(!parent->down);
58         parent->down = this;
59         break;
60     }
61 
62     if (inherit) {
63       // inherit these properties from the parent
64       attributes = parent->attributes;
65     }
66 
67     prev = parent;
68   }
69 }
70 
71 
~Chunk()72 Chunk::~Chunk() {
73   // These are set to 0 by the child if they're non-zero
74   delete next;
75   delete up;
76   delete down;
77   delete group;
78   group = 0L;
79   if (prev) {
80     switch (vOffset) {
81       case None:
82         prev->next = 0L;
83         // Note: kind of does the wrong thing if we're a group...  no issue though
84         break;
85       case Up:
86         prev->up = 0L;
87         break;
88       case Down:
89         prev->down = 0L;
90         break;
91     }
92     prev = 0L;
93   }
94 }
95 
96 
locked() const97 bool Chunk::locked() const {
98   return scalar || group || linebreak || tab || vector;
99 }
100 
101 
Parsed()102 Parsed::Parsed() : chunk(0L) {
103 }
104 
105 
~Parsed()106 Parsed::~Parsed() {
107   delete chunk;
108   chunk = 0L;
109 }
110 
111 
setNormalChar(QChar c,Chunk ** tail)112 inline void setNormalChar(QChar c, Chunk **tail) {
113   if (*tail && !(*tail)->locked()) {
114     (*tail)->text += c;
115   } else {
116     Chunk *f = new Chunk(*tail, Chunk::None, false, true);
117     f->text += c;
118     *tail = f;
119   }
120 }
121 
122 
parseColor(const QString & txt,int * skip)123 inline QColor parseColor(const QString& txt, int *skip) {
124   const int end = txt.indexOf('}');
125   if (skip) {
126     *skip = end;
127   }
128 
129   if (end == -1) {
130     return QColor();
131   }
132 
133   const QString endPart = txt.left(end);
134 
135   QColor color(endPart); // This one is slow.  If we support our own formats
136                          // outside of QColor, make sure that this is called
137                          // -after- we try our own formats.  Every cycle
138                          // counts in here.
139 #if 0
140   // This is rr,gg,bb support.  I'm not sure about supporting H,S,V or even
141   // about compatibility with LaTeX so for now we don't support it.  If it's
142   // ever re-enabled, make sure that testcases are added.
143   if (!color.isValid()) {
144     // the color is in the format "r,g,b"
145     QStringList components = QStringList::split(',', endPart, true);
146     if (components.count() == 3) {
147       int colors[3] = { 0, 0, 0 };
148       int base = 10;
149 
150       // assume the colors are given as decimal numbers unless we have a hex value in the string
151       if (endPart.find(QRegExp("[A-F]", false)) != -1) {
152         base = 16;
153       }
154 
155       bool ok = true;
156       colors[0] = components[0].toInt(&ok, base);
157       if (ok) {
158         colors[1] = components[1].toInt(&ok, base);
159       }
160       if (ok) {
161         colors[2] = components[2].toInt(&ok, base);
162       }
163 
164       if (ok) {
165         color.setRgb(colors[0], colors[1], colors[2]);
166       } // Should error out?
167     }
168   }
169 #endif
170   return color;
171 }
172 
173 
174 static Chunk *parseInternal(Chunk *ctail, const QString& txt, uint& start, uint cnt, bool interpretNewLine);
175 
176 #define EXPAND_GREEK(L_U, L_L, REST, SKIP, UCODE)    \
177   case L_L:                                   \
178     x=0x20;                                   \
179   case L_U:                                   \
180     if (txt.mid(from + 1).startsWith(REST)) { \
181       *skip = SKIP;                           \
182       setNormalChar(QChar(UCODE+x), tail);    \
183       return true;                            \
184     }                                         \
185   break;
186 
187 
parseOutChar(const QString & txt,uint from,int * skip,Chunk ** tail,bool interpretNewLine)188 inline bool parseOutChar(const QString& txt, uint from, int *skip, Chunk **tail, bool interpretNewLine) {
189   // STOP! Changes you make here should be made into cclineedit.cpp as well for completion.
190   QChar c = txt[from];
191   bool upper = false;
192   *skip = 1;
193   short x = 0;
194 
195 #if DEBUG_PARSING
196   qDebug() << "----- parsing " << txt;
197 #endif
198 
199   switch (c.unicode()) {
200     EXPAND_GREEK('B', 'b', "eta",  4, 0x392)
201     EXPAND_GREEK('D', 'd', "elta", 5, 0x394)
202     EXPAND_GREEK('Z', 'z', "eta",  4, 0x396)
203     EXPAND_GREEK('K', 'k', "appa", 5, 0x39a)
204     EXPAND_GREEK('M', 'm', "u",    2, 0x39c)
205     EXPAND_GREEK('X', 'x', "i",    2, 0x39e)
206     EXPAND_GREEK('R', 'r', "ho",   3, 0x3a1)
207 
208     case 'a':
209       x = 0x20;
210     case 'A':
211       if (txt.mid(from + 1).startsWith(QLatin1String("lpha"))) {
212         *skip = 5;
213         setNormalChar(QChar(0x391+x), tail);
214         return true;
215       } else if (txt.mid(from + 1).startsWith(QLatin1String("pprox"))) {
216         *skip = 6;
217         setNormalChar(QChar(0x2248), tail);
218         return true;
219       }
220       break;
221 
222 
223     case 'c':
224       x = 0x20;
225     case 'C':
226       if (txt.mid(from + 1).startsWith(QLatin1String("hi"))) {
227         *skip = 3;
228         setNormalChar(QChar(0x3a7+x), tail);
229         return true;
230       } else if (txt.mid(from + 1).startsWith(QLatin1String("dot"))) {
231         *skip = 4;
232         setNormalChar(QChar(0x2219), tail);
233         return true;
234       }
235       break;
236 
237     case 'e':
238       x = 0x20;
239     case 'E':
240       if (txt.mid(from + 1).startsWith(QLatin1String("psilon"))) {
241         *skip = 7;
242         setNormalChar(QChar(0x395+x), tail);
243         return true;
244       } else if (txt.mid(from + 1).startsWith(QLatin1String("ta"))) {
245         *skip = 3;
246         setNormalChar(QChar(0x397+x), tail);
247         return true;
248       } else if (txt.mid(from + 1).startsWith(QLatin1String("ll"))) {
249         *skip = 3;
250         setNormalChar(QChar(0x2113), tail);
251         return true;
252       }
253       break;
254 
255     case 'g':
256       x = 0x20;
257     case 'G':
258       if (txt.mid(from + 1).startsWith(QLatin1String("amma"))) {
259         *skip = 5;
260         setNormalChar(QChar(0x393+x), tail);
261         return true;
262       } else if (txt.mid(from + 1).startsWith(QLatin1String("eq"))) {
263         *skip = 3;
264         setNormalChar(QChar(0x2265), tail);
265         return true;
266       } else if (txt.mid(from + 1).startsWith('e')) {
267         *skip = 2;
268         setNormalChar(QChar(0x2265), tail);
269         return true;
270       }
271       break;
272 
273     case 'i':
274       x = 0x20;
275     case 'I':
276       if (txt.mid(from + 1).startsWith(QLatin1String("ota"))) {
277         *skip = 4;
278         setNormalChar(QChar(0x399+x), tail);
279         return true;
280       } else if (!upper && txt.mid(from + 1).startsWith(QLatin1String("nf"))) {
281         *skip = 3;
282         setNormalChar(QChar(0x221E), tail);
283         return true;
284       } else if (!upper && txt.mid(from + 1).startsWith(QLatin1String("nt"))) {
285         *skip = 3;
286         setNormalChar(QChar(0x222B), tail);
287         return true;
288       }
289       break;
290 
291     case 'l':
292       x = 0x20;
293     case 'L':
294       if (txt.mid(from + 1).startsWith(QLatin1String("ambda"))) {
295         *skip = 6;
296         setNormalChar(QChar(0x39b+x), tail);
297         return true;
298       } else if (txt.mid(from + 1).startsWith(QLatin1String("eq"))) {
299         *skip = 3;
300         setNormalChar(QChar(0x2264), tail);
301         return true;
302       } else if (txt.mid(from + 1).startsWith('e')) {
303         *skip = 2;
304         setNormalChar(QChar(0x2264), tail);
305         return true;
306       }
307       break;
308 
309     case 'n':
310       x = 0x20;
311     case 'N':
312       if (txt.mid(from + 1).startsWith('u')) {
313         *skip = 2;
314         setNormalChar(QChar(0x39D+x), tail);
315         return true;
316       } else if (txt.mid(from + 1).startsWith('e')) {
317         *skip = 2;
318         setNormalChar(QChar(0x2260), tail);
319         return true;
320       } else if (interpretNewLine) {
321         *skip = 1;
322         if (!*tail || !(*tail)->text.isEmpty() || (*tail)->locked()) {
323           *tail = new Chunk(*tail, Chunk::None, false, true);
324         }
325         (*tail)->linebreak = true;
326         *tail = new Chunk(*tail, Chunk::None, false, true);
327         return true;
328       } else {
329         *skip = 1;
330         setNormalChar(QChar(0x20), tail);
331         return true;
332       }
333       break;
334 
335     case 'o':
336       x = 0x20;
337     case 'O':
338       if (txt.mid(from + 1).startsWith(QLatin1String("verline{"))) {
339         if ((*tail)->group) {
340           *tail = new Chunk(*tail, Chunk::None, false, true);
341         }
342         Chunk *working = new Chunk(*tail, Chunk::None, true, true);
343         dumpattr(working, "start group for overline");
344         uint parseStart = from + 9;
345         working->attributes.overline = true;
346         parseInternal(working, txt, parseStart, txt.length(), interpretNewLine);
347         *skip = parseStart - from + 1;
348         dumpattr(working, "end group for overline");
349         return true;
350       } else if (txt.mid(from + 1).startsWith(QLatin1String("micron"))) {
351         *skip = 7;
352         setNormalChar(QChar(0x39F+x), tail);
353         return true;
354       } else if (txt.mid(from + 1).startsWith(QLatin1String("mega"))) {
355         *skip = 5;
356         setNormalChar(QChar(0x3A9+x), tail);
357         return true;
358       } else if (txt.mid(from + 1).startsWith(QLatin1String("dot"))) {
359         *skip = 4;
360         setNormalChar(QChar(0x2299), tail);
361         return true;
362       }
363       break;
364 
365     case 'p':
366       x = 0x20;
367     case 'P':
368       if (txt.mid(from + 1).startsWith('i')) {
369         *skip = 2;
370         setNormalChar(QChar(0x3a0+x), tail);
371         return true;
372       } else if (txt.mid(from + 1).startsWith(QLatin1String("hi"))) {
373         *skip = 3;
374         setNormalChar(QChar(0x3A6+x), tail);
375         return true;
376       } else if (txt.mid(from + 1).startsWith(QLatin1String("si"))) {
377         *skip = 3;
378         setNormalChar(QChar(0x3A8+x), tail);
379         return true;
380       } else if (txt.mid(from + 1).startsWith(QLatin1String("artial"))) {
381         *skip = 7;
382         setNormalChar(QChar(0x2202), tail);
383         return true;
384       } else if (txt.mid(from + 1).startsWith(QLatin1String("rod"))) {
385         *skip = 4;
386         setNormalChar(QChar(0x220F), tail);
387         return true;
388       } else if (txt.mid(from + 1).startsWith('m')) {
389         *skip = 2;
390         setNormalChar(QChar(0xb1), tail);
391         return true;
392       }
393       break;
394 
395     case 't':
396       x = 0x20;
397     case 'T':
398       if (txt.mid(from + 1).startsWith(QLatin1String("extcolor{"))) { // \textcolor{color}{text}
399         if ((*tail)->group) {
400           *tail = new Chunk(*tail, Chunk::None, false, true);
401         }
402         Chunk *working = new Chunk(*tail, Chunk::None, true, true);
403         dumpattr(working, "start group for textcolor");
404         uint parseStart = from + 10;
405         int firstSkip = 0;
406         working->attributes.color = parseColor(txt.mid(parseStart), &firstSkip);
407         if (!working->attributes.color.isValid() || txt[parseStart + firstSkip + 1] != '{') {
408           return false;
409         }
410         parseStart += firstSkip + 2;
411         parseInternal(working, txt, parseStart, txt.length(), interpretNewLine);
412         *skip = parseStart - from + 1;
413         dumpattr(working, "end group for textcolor");
414         return true;
415       } else if (txt.mid(from + 1).startsWith(QLatin1String("extbf{"))) {
416         if ((*tail)->group) {
417           *tail = new Chunk(*tail, Chunk::None, false, true);
418         }
419         Chunk *working = new Chunk(*tail, Chunk::None, true, true);
420         dumpattr(working, "start group for textbf");
421         uint parseStart = from + 7;
422         working->attributes.bold = true;
423         parseInternal(working, txt, parseStart, txt.length(), interpretNewLine);
424         *skip = parseStart - from + 1;
425         dumpattr(working, "end group for textbf");
426         return true;
427       } else if (txt.mid(from + 1).startsWith(QLatin1String("extit{"))) {
428         if ((*tail)->group) {
429           *tail = new Chunk(*tail, Chunk::None, false, true);
430         }
431         Chunk *working = new Chunk(*tail, Chunk::None, true, true);
432         dumpattr(working, "start group for textit");
433         uint parseStart = from + 7;
434         working->attributes.italic = true;
435         parseInternal(working, txt, parseStart, txt.length(), interpretNewLine);
436         *skip = parseStart - from + 1;
437         dumpattr(working, "end group for textit");
438         return true;
439       } else if (txt.mid(from + 1).startsWith(QLatin1String("heta"))) {
440         *skip = 5;
441         setNormalChar(QChar(0x398+x), tail);
442         return true;
443       } else if (txt.mid(from + 1).startsWith(QLatin1String("au"))) {
444         *skip = 3;
445         setNormalChar(QChar(0x3A4+x), tail);
446         return true;
447       } else {
448         *skip = 1;
449         if (!*tail || !(*tail)->text.isEmpty() || (*tail)->locked()) {
450           *tail = new Chunk(*tail, Chunk::None, false, true);
451         }
452         (*tail)->tab = true;
453         *tail = new Chunk(*tail, Chunk::None, false, true);
454         return true;
455       }
456       break;
457 
458     case 's':
459       x = 0x20;
460     case 'S':
461       if (txt.mid(from + 1).startsWith(QLatin1String("igma"))) {
462         *skip = 5;
463         setNormalChar(QChar(0x3A3+x), tail);
464         return true;
465       } else if (!upper && txt.mid(from + 1).startsWith(QLatin1String("um"))) {
466         *skip = 3;
467         setNormalChar(QChar(0x2211), tail);
468         return true;
469       } else if (!upper && txt.mid(from + 1).startsWith(QLatin1String("qrt"))) {
470         *skip = 4;
471         setNormalChar(QChar(0x221A), tail);
472         return true;
473       }
474       break;
475 
476     case 'u':
477       x = 0x20;
478     case 'U':
479       if (txt.mid(from + 1).startsWith(QLatin1String("nderline{"))) {
480         if ((*tail)->group) {
481           *tail = new Chunk(*tail, Chunk::None, false, true);
482         }
483         Chunk *working = new Chunk(*tail, Chunk::None, true, true);
484         dumpattr(working, "start group for underline");
485         uint parseStart = from + 10;
486         working->attributes.underline = true;
487         parseInternal(working, txt, parseStart, txt.length(), interpretNewLine);
488         *skip = parseStart - from + 1;
489         dumpattr(working, "end group for underline");
490         return true;
491       } else if (txt.mid(from + 1).startsWith(QLatin1String("psilon"))) {
492         *skip = 7;
493         setNormalChar(QChar(0x3A5+x), tail);
494         return true;
495       }
496       break;
497 
498     default:
499       break;
500   }
501 
502   return false;
503 }
504 
505 
parseInternal(Chunk * ctail,const QString & txt,uint & start,uint cnt,bool interpretNewLine)506 static Chunk *parseInternal(Chunk *ctail, const QString& txt, uint& start, uint cnt, bool interpretNewLine) {
507   Chunk *chead = ctail;
508 
509   if (ctail->group) {
510     ctail = new Chunk(ctail, Chunk::None, false, true);
511   }
512   for (uint& i = start; i < cnt; ++i) {
513     QChar c = txt[i];
514     Chunk::VOffset dir = Chunk::Down;
515     switch (c.unicode()) {
516       case '\n':
517         if (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
518           while (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
519             ctail = ctail->prev;
520           }
521           ctail = new Chunk(ctail, Chunk::None, false, true);
522         }
523         if (!ctail->text.isEmpty() || ctail->locked()) {
524           if (ctail->vOffset != Chunk::None) {
525             ctail = new Chunk(ctail->prev, Chunk::None, false, true);
526           } else {
527             ctail = new Chunk(ctail, Chunk::None, false, true);
528           }
529         }
530         ctail->linebreak = true;
531         ctail = new Chunk(ctail, Chunk::None, false, true);
532         break;
533       case '\t':
534         if (!ctail->text.isEmpty() || ctail->locked()) {
535           if (ctail->vOffset != Chunk::None) {
536             ctail = new Chunk(ctail->prev, Chunk::None, false, true);
537           } else {
538             ctail = new Chunk(ctail, Chunk::None, false, true);
539           }
540         }
541         ctail->tab = true;
542         ctail = new Chunk(ctail, Chunk::None, false, true);
543         break;
544       case 0x5c:   // \ /**/
545 
546         if (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
547           while (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
548             ctail = ctail->prev;
549           }
550           ctail = new Chunk(ctail, Chunk::None, false, true);
551         }
552 
553 
554         if (ctail->vOffset != Chunk::None && !ctail->text.isEmpty()) {
555           ctail = new Chunk(ctail->prev, Chunk::None, false, true);
556         }
557         ++i;
558         if (i == cnt) {
559           setNormalChar('\\', &ctail);
560         } else {
561           int skip = 0;
562           if (!parseOutChar(txt, i, &skip, &ctail, interpretNewLine)) {
563             setNormalChar(txt[i], &ctail);
564           } else {
565             i += skip - 1;
566           }
567         }
568         break;
569       case 0x5e:   // ^
570         dir = Chunk::Up;
571       case 0x5f:   // _ (dir is set to Down at beginning of loop)
572         if (ctail->text.isEmpty() && !ctail->group) {
573           setNormalChar(c, &ctail);
574         } else {
575           if (ctail->vOffset != Chunk::None) {
576             if (ctail->vOffset != dir) {
577               ctail = new Chunk(ctail->prev, dir, false, true);
578             } else if (ctail->group) {
579               ctail = new Chunk(ctail, dir, false, true);
580             } else {
581               ctail = new Chunk(ctail, dir, false, true);
582             }
583           } else {
584             ctail = new Chunk(ctail, dir, false, true);
585           }
586         }
587         break;
588       case 0x7b:   // {
589         if (ctail->text.isEmpty() && !ctail->group) {
590           bool rc = false;
591           new Chunk(ctail, Chunk::None, true, true);
592           dumpattr(ctail->group, "start group with non-group and empty text");
593           rc = 0L != parseInternal(ctail->group, txt, ++i, cnt, interpretNewLine);
594           assert(rc);
595           dumpattr(ctail->group, "after start group with non-group and empty text");
596           if (!rc) {
597             return 0L;
598           }
599         } else {
600           bool rc = false;
601           if (ctail->vOffset == Chunk::None) {
602             rc = 0L != parseInternal(new Chunk(ctail, Chunk::None, true, true), txt, ++i, cnt, interpretNewLine);
603           } else {
604             rc = 0L != parseInternal(new Chunk(ctail->prev, Chunk::None, true, true), txt, ++i, cnt,  interpretNewLine);
605           }
606           if (!rc) {
607             return 0L;
608           }
609         }
610         break;
611       case 0x7d:   // }
612         if (chead->prev && chead->prev->group == chead) {
613           return chead;
614         } else {
615           setNormalChar(c, &ctail);
616         }
617         break;
618       case '[':
619         {
620           bool vector = false;
621           int vectorIndexStart = -1;
622           int vectorIndexEnd = -1;
623           int bracketStack = 1;
624           int pos = -1;
625           bool format = false;
626           int formatIndexStart = 0;
627           int formatIndexEnd = 0;
628 
629           bool equation = txt[i + 1] == '=';
630           for (uint searchPt = i + 1; bracketStack != 0 && searchPt < cnt; ++searchPt) {
631             if (txt[searchPt] == ']') {
632               if (--bracketStack == 0) {
633                 pos = searchPt;
634                 break;
635               } else if (bracketStack == 1 && vector && vectorIndexEnd == -1) {
636                 vectorIndexEnd = searchPt - 1;
637               }
638             } else if (txt[searchPt] == '[') {
639               ++bracketStack;
640               if (!vector && !equation) {
641                 vector = true;
642                 vectorIndexStart = searchPt + 1;
643               }
644             }
645           }
646 
647           if (pos < 0 || pos == int(i) + 1 /* empty [] */) {
648             return 0L;
649           }
650 
651           if (pos+3 < (int)cnt) { // ]{%f} is min size.
652             if ((txt[pos+1]=='{') && ((txt[pos+2] == '%') || (txt[pos+2] == 'T'))) {
653               formatIndexStart = pos+1;
654               for (uint searchPt = pos + 2; searchPt < cnt; ++searchPt) {
655                 if (txt[searchPt] == '}') {
656                   formatIndexEnd = searchPt;
657                   format = true;
658                   break;
659                 }
660               }
661             }
662           }
663 
664           if (ctail->locked() || !ctail->text.isEmpty()) {
665             if (ctail->vOffset != Chunk::None) {
666               ctail = new Chunk(ctail->prev, Chunk::None, false, true);
667             } else {
668               ctail = new Chunk(ctail, Chunk::None, false, true);
669             }
670           }
671 
672           if (vector) {
673             ctail->text = txt.mid(i + 1, vectorIndexStart - i - 2).trimmed();
674             ctail->expression = txt.mid(vectorIndexStart, vectorIndexEnd - vectorIndexStart + 1);
675             ctail->vector = true;
676           } else {
677             ctail->text = txt.mid(i + 1, pos - i - 1).trimmed();
678             ctail->scalar = true;
679           }
680           if (format) {
681             i = uint(formatIndexEnd);
682             ctail->formated = true;
683             ctail->format = txt.mid(formatIndexStart+1, formatIndexEnd - formatIndexStart-1);
684 
685           } else {
686             i = uint(pos);
687           }
688         }
689         break;
690       default:
691 #if 0
692         if (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
693           ctail = new Chunk(ctail->prev, Chunk::None, false, true);
694         }
695 #endif
696         if (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
697           while (ctail->vOffset != Chunk::None && (!ctail->text.isEmpty() || ctail->locked())) {
698             ctail = ctail->prev;
699           }
700           ctail = new Chunk(ctail, Chunk::None, false, true);
701         }
702         setNormalChar(c, &ctail);
703         break;
704     }
705   }
706 
707   return chead;
708 }
709 
parse(const QString & txt,const QColor & color,bool interpret,bool interpretNewLine)710 Parsed *Label::parse(const QString& txt, const QColor &color, bool interpret, bool interpretNewLine) {
711   Parsed *parsed = new Parsed;
712   Chunk *ctail = parsed->chunk = new Chunk(0L);
713   ctail->attributes.color = color;
714   if (!interpret) {
715     ctail->text = txt;
716     return parsed;
717   }
718 
719   uint start = 0;
720   uint cnt = txt.length();
721   Chunk *rc = parseInternal(ctail, txt, start, cnt, interpretNewLine);
722   if (!rc) {
723     // Parse error - how to recover?
724     delete parsed;
725     parsed = 0L;
726   }
727   return parsed;
728 }
729 
730 
731 // vim: ts=2 sw=2 et
732