OSDN Git Service

Move the directories
[kita/kita.git] / src / libkita / parser.cpp
1 /***************************************************************************
2  *   Copyright (C) 2009 by Kita Developers                                 *
3  *   ikemo@users.sourceforge.jp                                            *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  ***************************************************************************/
10 #include "parser.h"
11
12 #include <QtCore/QString>
13
14 #include "datinfo.h"
15 #include "globalconfig.h"
16 #include "kita-utf16.h"
17 #include "kita-utf8.h"
18
19 using namespace Kita;
20 using namespace Kita::Parser;
21
22 static const int KITA_RESDIGIT = 4;
23
24 /* if cdat == str, return str.length() */
25 static int isEqual(const QChar *cdat, const QString& str)
26 {
27     int i = 0;
28     const int size = str.size();
29     while (i < size && str.at(i) != '\0') {
30         if (*cdat != str.at(i)) return 0;
31         cdat++;i++;
32     }
33     return i;
34 }
35
36 /* parsing function for special char (such as &hearts; */
37
38 /* For example, if cdat = "&amp;", then
39  
40    pos (= length of cdat) = 5,
41    retstr = "&".                           */
42 static QString parseSpecialChar(
43
44     /* input */
45     const QChar *cdat,
46
47     /* output */
48     unsigned int& pos)
49 {
50     QString retstr;
51
52     if ((pos = isEqual(cdat , "&gt;"))) retstr = '>';
53     else if ((pos = isEqual(cdat , "&lt;"))) retstr = '<';
54     else if ((pos = isEqual(cdat , "&nbsp;"))) retstr = ' ';
55     else if ((pos = isEqual(cdat , "&amp;"))) retstr = '&';
56     else if ((pos = isEqual(cdat , "&quot;"))) retstr = '"';
57
58     else if ((pos = isEqual(cdat , "&hearts;")))
59         retstr = QString::fromUtf8(KITAUTF8_HEART);
60
61     else if ((pos = isEqual(cdat , "&diams;")))
62         retstr = QString::fromUtf8(KITAUTF8_DIA);
63
64     else if ((pos = isEqual(cdat , "&clubs;")))
65         retstr = QString::fromUtf8(KITAUTF8_CLUB);
66
67     else if ((pos = isEqual(cdat , "&spades;")))
68         retstr = QString::fromUtf8(KITAUTF8_SPADE);
69
70     return retstr;
71 }
72
73 /* get plain text from raw data   */
74 /*
75    This function replaces "<br>" to "\n", removes HTML tags and
76    replaces special chars.
77 */
78 void Parser::datToText(
79
80     /* input */
81     const QString &rawData,
82
83     /* output */
84     QString& text
85 )
86 {
87     text.clear();
88
89     unsigned int startPos, pos;
90     const QChar *chpt = rawData.unicode();
91     unsigned int length = rawData.length();
92
93     for (unsigned int i = startPos = 0 ; i < length ; i++) {
94
95         switch (chpt[ i ].unicode()) {
96
97         case '<':
98
99             /* " <br> " */
100             if (chpt[ i + 1 ] == 'b' && chpt[ i + 2 ] == 'r' && chpt[ i + 3 ] == '>') {
101
102                 unsigned int i2 = i - startPos;
103                 if (i > 0 && chpt[ i - 1 ] == ' ') i2--; /* remove space before <br> */
104                 text += rawData.mid(startPos, i2) + '\n';
105                 startPos = i + 4;
106                 if (chpt[ startPos ] == ' ') startPos++; /* remove space after <br> */
107                 i = startPos - 1;
108             }
109
110             /*----------------------------------------*/
111
112             /* remove HTML tags <[^>]*>  */
113             else {
114
115                 if (i - startPos) text += rawData.mid(startPos, i - startPos);
116                 while (chpt[ i ] != '>' && i < length) i++;
117                 startPos = i + 1;
118             }
119
120             break;
121
122             /*----------------------------------*/
123
124         case '&':
125
126             /* special char */
127             {
128                 QString tmpstr;
129                 tmpstr = parseSpecialChar(chpt + i, pos);
130
131                 if (!tmpstr.isEmpty()) {
132                     text += rawData.mid(startPos, i - startPos) + tmpstr;
133                     startPos = i + pos;
134                     i = startPos - 1;
135                 }
136             }
137
138             break;
139         }
140     }
141
142     text += rawData.mid(startPos);
143 }
144
145 /* parsing function for anchor (>>digits)   */
146
147 /* This function parses res anchor.
148  
149    For example, if cdat = "&gt;12-20", then
150  
151    linkstr = ">12-20",
152    refNum[0] = 12,
153    refNum[1] = 20,
154    pos (= length of cdat) = 9,
155    ret = true;
156  
157 */
158 static bool parseResAnchor(
159
160     /* input */
161     const QChar *cdat, const unsigned int length,
162
163     /* output */
164     QString& linkstr, int* refNum, unsigned int& pos)
165 {
166
167     struct LocalFunc {
168         static bool isHYPHEN(unsigned short c)
169         {
170
171             /* UTF-16 */
172             if (c == '-'
173                     || (c >= 0x2010 && c <= 0x2015)
174                     || (c == 0x2212)
175                     || (c == 0xFF0D)          /* UTF8: 0xEFBC8D */
176               ) {
177                 return true;
178             }
179
180             return false;
181         }
182     };
183
184     bool ret = false;
185
186     if (length == 0) return false;
187
188     linkstr.clear();
189     refNum[ 0 ] = 0;
190     refNum[ 1 ] = 0;
191     pos = 0;
192
193     /* check '>' twice */
194     for (int i = 0; i < 2; i++) {
195
196         if (cdat[ pos ].unicode() == UTF16_BRACKET) {
197             linkstr += cdat[ pos ];
198             pos++;
199         } else if (cdat[ pos ] == '&' && cdat[ pos + 1 ] == 'g'  /* &gt; */
200                     && cdat[ pos + 2 ] == 't' && cdat[ pos + 3 ] == ';') {
201             linkstr += '>';
202             pos += 4;
203         }
204
205     }
206
207     /* check ',' */
208     if (!pos) {
209         if (cdat[ pos ] == ',' || cdat[ pos ].unicode() == UTF16_COMMA) {
210             linkstr += ',';
211             pos ++;
212         }
213     }
214
215     /* check '=' */
216     if (!pos) {
217         if (cdat[ pos ] == '=' || cdat[ pos ].unicode() == UTF16_EQ) {
218             linkstr += '=';
219             pos ++;
220         }
221     }
222
223     /* check digits */
224     int hyphen = 0;
225
226     for (int i = 0 ; i < KITA_RESDIGIT + 1 && pos < length ; i++, pos++) {
227
228         unsigned short c = cdat[ pos ].unicode();
229
230         if ((c < UTF16_0 || c > UTF16_9)
231                 && (c < '0' || c > '9')
232                 && (!LocalFunc::isHYPHEN(c)
233                      || (i == 0 && LocalFunc::isHYPHEN(c))
234                      || (hyphen && LocalFunc::isHYPHEN(c)))
235           ) break;
236
237         linkstr += cdat[ pos ];
238
239         if (LocalFunc::isHYPHEN(c)) {
240             hyphen = 1;
241             i = -1;
242         } else {
243             if (c >= UTF16_0) c = '0' + cdat[ pos ].unicode() - UTF16_0;
244             refNum[ hyphen ] *= 10;
245             refNum[ hyphen ] += c - '0';
246         }
247
248         ret = true;
249     }
250
251     return ret;
252 }
253
254 /* parse name */
255
256 /* output:
257   
258    resdat.name
259    resdat.nameHTML
260    
261 */
262 static void parseName(const QString& rawStr, RESDAT& resdat)
263 {
264     unsigned int i = 0, pos;
265     int refNum[ 2 ];
266     QString linkurl, linkstr;
267
268     datToText(rawStr, resdat.name);
269
270     const QChar * chpt = resdat.name.unicode();
271     unsigned int length = resdat.name.length();
272     resdat.nameHTML.clear();
273
274     /* anchor */
275     while (parseResAnchor(chpt + i, length - i, linkstr, refNum, pos)) {
276
277         linkurl = QString("#%1").arg(refNum[ 0 ]);
278         if (refNum[ 1 ]) linkurl += QString("-%1").arg(refNum[ 1 ]);
279
280         resdat.nameHTML += "<a href=\"" + linkurl + "\">";
281         resdat.nameHTML += linkstr;
282         resdat.nameHTML += "</a>";
283
284         ANCNUM anctmp;
285         if (refNum[ 1 ] < refNum[ 0 ]) refNum[ 1 ] = refNum[ 0 ];
286         anctmp.from = refNum[ 0 ];
287         anctmp.to = refNum[ 1 ];
288         resdat.anclist += anctmp;
289
290         i += pos;
291     }
292
293     /* non-digits strings */
294     if (i < length) {
295
296         resdat.nameHTML += "<span class=\"name_noaddr\">";
297         resdat.nameHTML += resdat.name.mid(i);
298         resdat.nameHTML += "</span>";
299     }
300
301 }
302
303 /* parse date, ID, host */
304
305 /* output :
306    
307    resdat.dateTime
308    resdat.date
309    resdat.id
310    resdat.host
311  
312 */
313 static void parseDateId(const QString& rawStr, RESDAT& resdat)
314 {
315     resdat.date = rawStr;
316     resdat.id.clear();
317     resdat.host.clear();
318     resdat.be.clear();
319     resdat.bepointmark.clear();
320
321     const QChar *chpt = rawStr.unicode();
322     unsigned int pos = 0, startpos = 0;
323     unsigned int length = rawStr.length();
324
325     while (chpt[ pos ] != '\0' &&
326             !(chpt[ pos ] == 'I' && chpt[ pos + 1 ] == 'D') &&
327             !(chpt[ pos ] == 'B' && chpt[ pos + 1 ] == 'E')) {
328         pos++;
329     }
330     resdat.date = rawStr.left(pos);
331
332     /* id */
333     if (chpt[ pos ] == 'I' && chpt[ pos + 1 ] == 'D') {
334         pos += 3;
335         startpos = pos;
336         while (chpt[ pos ] != ' ' && pos++ < length) {};
337         resdat.id = rawStr.mid(startpos, pos - startpos);
338         pos++;
339     }
340
341     //    qDebug("date %s, ID %s", (const char*)resdat.date.local8Bit(), resdat.id.ascii());
342
343     if (pos >= length) return ;
344
345     /* be */
346     if (chpt[ pos ] == 'B' && chpt[ pos + 1 ] == 'E') {
347         pos += 3;
348         startpos = pos;
349         while (chpt[ pos ] != '-' && pos++ < length) {};
350         resdat.be = rawStr.mid(startpos, pos - startpos);
351         pos++;
352         if (pos < length && chpt[ pos ] == '#') {
353             startpos = pos;
354             while (chpt[ pos ] == '#' && pos++ < length) {};
355             resdat.bepointmark = rawStr.mid(startpos, pos - startpos);
356         }
357     }
358
359     if (pos >= length) return ;
360
361     /* host */
362     if (chpt[ pos ] == 'H' && chpt[ pos + 1 ] == 'O') {
363         pos += 5;
364         startpos = pos;
365         while (chpt[ pos ] != ' ' && pos++ < length) {};
366         resdat.host = rawStr.mid(startpos, pos - startpos);
367         pos++;
368         //      qDebug("host %s", resdat.host.ascii());
369     }
370 }
371
372 /* parsing function for link   */
373
374 /* For example,
375  
376    cdat = "ttp://foo.com",
377  
378    then
379  
380    linkstr = "ttp://foo.com",
381    linkurl = "http://foo.com",
382    pos (= length of cdat) = 13,
383  
384    and return true.
385                                 */
386 static bool parseLink(
387
388     /* input */
389     const QChar *cdat, const unsigned int length,
390
391     /* output */
392     QString& linkstr, QString& linkurl, unsigned int& pos
393 )
394 {
395
396     /*-----------------------------*/
397
398     linkstr.clear();
399     linkurl.clear();
400
401     QString retlinkstr;
402     QString prefix;
403     QString scheme;
404
405     if (isEqual(cdat , "http://")) {
406         prefix = "http://";
407         scheme = "http://";
408     } else if (isEqual(cdat , "ttp://")) {
409         prefix = "ttp://";
410         scheme = "http://";
411     } else if (isEqual(cdat , "tp://")) {
412         prefix = "tp://";
413         scheme = "http://";
414     } else if (isEqual(cdat , "https://")) {
415         prefix = "https://";
416         scheme = "https://";
417     } else if (isEqual(cdat , "ttps://")) {
418         prefix = "ttps://";
419         scheme = "https://";
420     } else if (isEqual(cdat , "tps://")) {
421         prefix = "tps://";
422         scheme = "https://";
423     } else {
424         return false;
425     }
426
427     pos = prefix.length();
428     while (cdat[ pos ] >= '!' && cdat[ pos ] <= '~' &&
429             cdat[ pos ] != ' ' && cdat[ pos ] != '<' && cdat[ pos ] != '>'
430             && pos < length) {
431         retlinkstr += cdat[ pos++ ];
432     }
433     if (pos > length) return false;
434
435     if (!retlinkstr.isEmpty()) datToText(retlinkstr, linkstr);
436
437     linkurl = scheme + linkstr;
438     linkstr = prefix + linkstr;
439
440     return true;
441 }
442
443 /* create res anchor  */
444 /* This function is called from parseBody internally.
445    See also parseBody.                                */
446 static bool createResAnchor(const QString &rawStr, RESDAT& resdat,
447         const QChar *chpt, unsigned int &i, unsigned int &startPos)
448 {
449     QString linkstr, linkurl;
450     int refNum[ 2 ];
451     unsigned int pos;
452     unsigned int length = rawStr.length();
453
454     /* parse anchor */
455     if (!parseResAnchor(chpt + i, length - i, linkstr, refNum, pos)) {
456
457         i += pos - 1;
458         return false;
459     }
460
461     /* create anchor */
462     resdat.bodyHTML += rawStr.mid(startPos, i - startPos);
463     linkurl = QString("#%1").arg(refNum[ 0 ]);
464     if (refNum[ 1 ]) linkurl += QString("-%1").arg(refNum[ 1 ]);
465
466     resdat.bodyHTML += "<a href=\"" + linkurl + "\">";
467     resdat.bodyHTML += linkstr;
468     resdat.bodyHTML += "</a>";
469
470     /* add anchor to ancList */
471     ANCNUM anctmp;
472     if (refNum[ 1 ] < refNum[ 0 ]) refNum[ 1 ] = refNum[ 0 ];
473     anctmp.from = refNum[ 0 ];
474     anctmp.to = refNum[ 1 ];
475     resdat.anclist += anctmp;
476
477     startPos = i + pos;
478     i = startPos - 1;
479
480     return true;
481 }
482
483 /* parse body */
484
485 /* output :
486    
487    resdat.bodyHTML
488  
489 */
490 static void parseBody(const QString &rawStr, RESDAT& resdat)
491 {
492     resdat.bodyHTML.clear();
493
494     unsigned int startPos;
495     QString linkstr, linkurl;
496     const QChar *chpt = rawStr.unicode();
497     unsigned int length = rawStr.length();
498
499     bool ancChain = false;
500
501     /* ancChain is chain for anchor. For examle, if anchor "&gt;2"
502        appeared, ancChain is set to true. Moreover, if next strings
503        are "=5", anchor for 5 is also set. Thus, we can obtain anchors
504        for strings "&gt;2=5" as follows:
505
506        <a href="#2">&gt;2</a><a href="#5">=5</a>
507     */
508
509     int offset = 0;
510     if (chpt[ 0 ] == ' ') offset = 1; /* remove one space after <> */
511     for (unsigned int i = startPos = offset ; i < length ; i++) {
512
513         switch (chpt[ i ].unicode()) {
514
515         case '<':
516
517             /* " <br> " */
518             if (chpt[ i + 1 ] == 'b' && chpt[ i + 2 ] == 'r' && chpt[ i + 3 ] == '>') {
519
520                 /* reset anchor chain */
521                 ancChain = false;
522
523                 unsigned int i2 = i - startPos;
524                 if (i > 0 && chpt[ i - 1 ] == ' ') i2--; /* remove space before <br> */
525                 resdat.bodyHTML += rawStr.mid(startPos, i2);
526
527                 resdat.bodyHTML += "<br>";
528
529                 startPos = i + 4;
530                 if (chpt[ startPos ] == ' ') startPos++; /* remove space after <br> */
531                 i = startPos - 1;
532             }
533
534             /*----------------------------------------*/
535
536             /* remove HTML tags <[^>]*>  */
537             else {
538
539                 if (i - startPos) resdat.bodyHTML += rawStr.mid(startPos, i - startPos);
540                 while (chpt[ i ] != '>' && i < length) i++;
541                 startPos = i + 1;
542             }
543
544             break;
545
546             /*----------------------------------------*/
547
548         case 'h':     /* "http://" or "ttp://" or "tp:" */
549         case 't':
550             {
551                 unsigned int pos = 0;
552                 if (parseLink(chpt + i, length - i, linkstr, linkurl, pos)) {
553                     resdat.bodyHTML += rawStr.mid(startPos, i - startPos);
554                     resdat.bodyHTML += "<a href=\"" + linkurl + "\">";
555                     resdat.bodyHTML += linkstr;
556                     resdat.bodyHTML += "</a>";
557
558                     startPos = i + pos;
559                     i = startPos - 1;
560                 }
561             }
562
563             break;
564
565             /*----------------------------------*/
566
567         case '&':
568
569             /* &gt; */
570             if (chpt[ i + 1 ] == 'g' && chpt[ i + 2 ] == 't' && chpt[ i + 3 ] == ';')
571                 ancChain = createResAnchor(rawStr, resdat, chpt, i, startPos);
572
573             break;
574
575             /*----------------------------------------*/
576
577             /* unicode '>'  */
578         case UTF16_BRACKET:
579
580             ancChain = createResAnchor(rawStr, resdat, chpt, i, startPos);
581             break;
582
583             /*----------------------------------*/
584
585         default:
586
587             if (ancChain) ancChain = createResAnchor(rawStr, resdat, chpt, i, startPos);
588         }
589     }
590
591     resdat.bodyHTML += rawStr.mid(startPos);
592 }
593
594 /* Main Parser */
595
596 /*
597   struct RESDAT is defined in datinfo.h.
598   This function is called from DatToHtml() and DatInfo::parseDat()
599   
600   input:
601  
602   resdat.num     ... number
603   resdat.linestr ... raw line strings
604  
605   output:
606   
607   resdat.*
608   subject
609 */
610 bool Parser::parseResDat(RESDAT& resdat, QString& subject)
611 {
612     if (resdat.parsed) return true;
613
614     resdat.parsed = true;
615     resdat.broken = false;
616     resdat.anclist.clear();
617
618     /* search the staring positions of each section to split raw data. */
619     const QChar *chpt = resdat.linestr.unicode();
620     unsigned int length = resdat.linestr.length();
621     unsigned int section = 0;
622     unsigned int sectionPos[ 5 ];
623     for (unsigned int i = 0 ; i < length ; i++) {
624
625         /* sections are splitted by "<>" */
626         if (chpt[ i ] == '<' && chpt[ i + 1 ] == '>') {
627             section++;
628
629             if (section >= 5) {
630                 resdat.broken = true;
631                 return true;
632             }
633
634             sectionPos[ section ] = i + 2;
635             i++;
636         }
637     }
638
639     /* broken data */
640     if (section != 4) {
641         resdat.broken = true;
642         return true;
643     }
644
645     //    qDebug("[%d] %d %d %d %d",section, sectionPos[1],sectionPos[2],sectionPos[3],sectionPos[4]);
646
647     /* name */
648     length = sectionPos[ 1 ] - 2 ;
649     parseName(resdat.linestr.mid(0, length), resdat);
650
651     /* mail */
652     length = sectionPos[ 2 ] - 2 - sectionPos[ 1 ];
653     datToText(resdat.linestr.mid(sectionPos[ 1 ], length), resdat.address);
654
655     /* date, ID, host  */
656     length = sectionPos[ 3 ] - 2 - sectionPos[ 2 ];
657     parseDateId(resdat.linestr.mid(sectionPos[ 2 ], length), resdat);
658
659     /* body */
660     length = sectionPos[ 4 ] - 2 - sectionPos[ 3 ];
661     parseBody(resdat.linestr.mid(sectionPos[ 3 ], length), resdat);
662
663     /* subject */
664     subject = resdat.linestr.mid(sectionPos[ 4 ]);
665
666     return true;
667 }