File indexing completed on 2024-12-22 04:52:50

0001 /*
0002    MorkParser.cpp - Mozilla Mork Format Parser/Reader
0003    SPDX-FileCopyrightText: 2007 <ScalingWeb.com>
0004    SPDX-FileContributor: Yuriy Soroka <ysoroka@scalingweb.com>
0005    SPDX-FileContributor: Anton Fedoruk <afedoruk@scalingweb.com>
0006 
0007    SPDX-License-Identifier: BSD-3-Clause
0008 */
0009 
0010 #include "MorkParser.h"
0011 #include <QFile>
0012 #include <QIODevice>
0013 //        =============================================================
0014 //        MorkParser::MorkParser
0015 #include "thunderbirdplugin_debug.h"
0016 
0017 MorkParser::MorkParser(int DefaultScope)
0018 {
0019     initVars();
0020     mDefaultScope = DefaultScope;
0021 }
0022 
0023 //        =============================================================
0024 //        MorkParser::open
0025 
0026 bool MorkParser::open(const QString &path)
0027 {
0028     initVars();
0029 
0030     QFile MorkFile(path);
0031 
0032     // Open file
0033     if (!MorkFile.exists() || !MorkFile.open(QIODevice::ReadOnly)) {
0034         mError = FailedToOpen;
0035         return false;
0036     }
0037 
0038     // Check magic header
0039     QByteArray MagicHeader = MorkFile.readLine();
0040 
0041     if (!MagicHeader.contains(MorkMagicHeader)) {
0042         mError = UnsupportedVersion;
0043         return false;
0044     }
0045 
0046     mMorkData = MorkFile.readAll();
0047     MorkFile.close();
0048 
0049     // Parse mork
0050     return parse();
0051 }
0052 
0053 //        =============================================================
0054 //        MorkParser::error
0055 
0056 MorkErrors MorkParser::error() const
0057 {
0058     return mError;
0059 }
0060 
0061 //        =============================================================
0062 //        MorkParser::initVars
0063 
0064 void MorkParser::initVars()
0065 {
0066     mError = NoError;
0067     mMorkPos = 0;
0068     nowParsing_ = NPValues;
0069     mCurrentCells = nullptr;
0070     mNextAddValueId = 0x7fffffff;
0071 }
0072 
0073 //        =============================================================
0074 //        MorkParser::parse
0075 
0076 bool MorkParser::parse()
0077 {
0078     bool Result = true;
0079     char cur = 0;
0080 
0081     // Run over mork chars and parse each term
0082     cur = nextChar();
0083 
0084     while (Result && cur) {
0085         if (!isWhiteSpace(cur)) {
0086             // Figure out what a term
0087             switch (cur) {
0088             case '<':
0089                 // Dict
0090                 Result = parseDict();
0091                 break;
0092             case '/':
0093                 // Comment
0094                 Result = parseComment();
0095                 break;
0096             case '{':
0097                 Result = parseTable();
0098                 // Table
0099                 break;
0100             case '[':
0101                 Result = parseRow(0, 0);
0102                 // Row
0103                 break;
0104             case '@':
0105                 Result = parseGroup();
0106                 // Group
0107                 break;
0108             default:
0109                 mError = DefectedFormat;
0110                 Result = false;
0111                 break;
0112             }
0113         }
0114 
0115         // Get next char
0116         cur = nextChar();
0117     }
0118 
0119     return Result;
0120 }
0121 
0122 //        =============================================================
0123 //        MorkParser::isWhiteSpace
0124 
0125 bool MorkParser::isWhiteSpace(char c) const
0126 {
0127     switch (c) {
0128     case ' ':
0129     case '\t':
0130     case '\r':
0131     case '\n':
0132     case '\f':
0133         return true;
0134     default:
0135         return false;
0136     }
0137 }
0138 
0139 //        =============================================================
0140 //        MorkParser::nextChar
0141 
0142 char MorkParser::nextChar()
0143 {
0144     char cur = 0;
0145 
0146     if (mMorkPos < mMorkData.length()) {
0147         cur = mMorkData[mMorkPos];
0148         mMorkPos++;
0149     }
0150 
0151     if (!cur) {
0152         cur = 0;
0153     }
0154 
0155     return cur;
0156 }
0157 
0158 //        =============================================================
0159 //        MorkParser::parseDict
0160 
0161 bool MorkParser::parseDict()
0162 {
0163     char cur = nextChar();
0164     bool Result = true;
0165     nowParsing_ = NPValues;
0166 
0167     while (Result && cur != '>' && cur) {
0168         if (!isWhiteSpace(cur)) {
0169             switch (cur) {
0170             case '<':
0171                 if (mMorkData.mid(mMorkPos - 1, strlen(MorkDictColumnMeta)) == MorkDictColumnMeta) {
0172                     nowParsing_ = NPColumns;
0173                     mMorkPos += strlen(MorkDictColumnMeta) - 1;
0174                 }
0175                 break;
0176             case '(':
0177                 Result = parseCell();
0178                 break;
0179             case '/':
0180                 Result = parseComment();
0181                 break;
0182             }
0183         }
0184 
0185         cur = nextChar();
0186     }
0187 
0188     return Result;
0189 }
0190 
0191 //        =============================================================
0192 //        MorkParser::parseComment
0193 
0194 bool MorkParser::parseComment()
0195 {
0196     char cur = nextChar();
0197     if ('/' != cur) {
0198         return false;
0199     }
0200 
0201     while (cur != '\r' && cur != '\n' && cur) {
0202         cur = nextChar();
0203     }
0204 
0205     return true;
0206 }
0207 
0208 //        =============================================================
0209 //        MorkParser::parseCell
0210 
0211 bool MorkParser::parseCell()
0212 {
0213     bool Result = true;
0214     bool bColumnOid = false;
0215     bool bValueOid = false;
0216     bool bColumn = true;
0217     int Corners = 0;
0218 
0219     // Column = Value
0220     QString Column;
0221     QString Text;
0222 
0223     char cur = nextChar();
0224 
0225     // Process cell start with column (bColumn == true)
0226     while (Result && cur != QLatin1Char(')') && cur) {
0227         switch (cur) {
0228         case '^':
0229             // Oids
0230             Corners++;
0231             if (Corners == 1) {
0232                 bColumnOid = true;
0233             } else if (Corners == 2) {
0234                 bColumn = false;
0235                 bValueOid = true;
0236             } else {
0237                 Text += QLatin1Char(cur);
0238             }
0239 
0240             break;
0241         case '=':
0242             // From column to value
0243             if (bColumn) {
0244                 bColumn = false;
0245             } else {
0246                 Text += QLatin1Char(cur);
0247             }
0248             break;
0249         case '\\': {
0250             // Get next two chars
0251             char NextChar = nextChar();
0252             if ('\r' != NextChar && '\n' != NextChar) {
0253                 Text += QLatin1Char(NextChar);
0254             } else {
0255                 nextChar();
0256             }
0257             break;
0258         }
0259         case '$': {
0260             // Get next two chars
0261             QString HexChar;
0262             HexChar += QLatin1Char(nextChar());
0263             HexChar += QLatin1Char(nextChar());
0264             Text += QLatin1Char((char)HexChar.toInt(nullptr, 16));
0265             break;
0266         }
0267         default:
0268             // Just a char
0269             if (bColumn) {
0270                 Column += QLatin1Char(cur);
0271             } else {
0272                 Text += QLatin1Char(cur);
0273             }
0274             break;
0275         }
0276 
0277         cur = nextChar();
0278     }
0279     Q_UNUSED(bColumnOid)
0280     // Apply column and text
0281     int ColumnId = Column.toInt(nullptr, 16);
0282 
0283     if (NPRows != nowParsing_) {
0284         // Dicts
0285         if (!Text.isEmpty()) {
0286             if (nowParsing_ == NPColumns) {
0287                 mColumns[ColumnId] = Text;
0288                 qCDebug(THUNDERBIRDPLUGIN_LOG) << " column :" << ColumnId << " Text " << Text;
0289             } else {
0290                 mValues[ColumnId] = Text;
0291                 qCDebug(THUNDERBIRDPLUGIN_LOG) << " ColumnId " << ColumnId << " Value : " << Text;
0292             }
0293         }
0294     } else {
0295         if (!Text.isEmpty()) {
0296             // Rows
0297             int ValueId = Text.toInt(nullptr, 16);
0298 
0299             if (bValueOid) {
0300                 (*mCurrentCells)[ColumnId] = ValueId;
0301             } else {
0302                 mNextAddValueId--;
0303                 mValues[mNextAddValueId] = Text;
0304                 (*mCurrentCells)[ColumnId] = mNextAddValueId;
0305             }
0306         }
0307     }
0308     return Result;
0309 }
0310 
0311 //        =============================================================
0312 //        MorkParser::parseTable
0313 
0314 bool MorkParser::parseTable()
0315 {
0316     bool Result = true;
0317     QString TextId;
0318     int Id = 0;
0319     int Scope = 0;
0320 
0321     char cur = nextChar();
0322 
0323     // Get id
0324     while (cur != QLatin1Char('{') && cur != QLatin1Char('[') && cur != QLatin1Char('}') && cur) {
0325         if (!isWhiteSpace(cur)) {
0326             TextId += QLatin1Char(cur);
0327         }
0328 
0329         cur = nextChar();
0330     }
0331 
0332     parseScopeId(TextId, Id, Scope);
0333 
0334     // Parse the table
0335     while (Result && cur != QLatin1Char('}') && cur) {
0336         if (!isWhiteSpace(cur)) {
0337             switch (cur) {
0338             case '{':
0339                 Result = parseMeta('}');
0340                 break;
0341             case '[':
0342                 Result = parseRow(Id, Scope);
0343                 break;
0344             case '-':
0345             case '+':
0346                 break;
0347             default: {
0348                 QString JustId;
0349                 while (!isWhiteSpace(cur) && cur) {
0350                     JustId += QLatin1Char(cur);
0351                     cur = nextChar();
0352 
0353                     if (cur == QLatin1Char('}')) {
0354                         return Result;
0355                     }
0356                 }
0357 
0358                 int JustIdNum = 0;
0359                 int JustScopeNum = 0;
0360                 parseScopeId(JustId, JustIdNum, JustScopeNum);
0361 
0362                 setCurrentRow(Scope, Id, JustScopeNum, JustIdNum);
0363                 break;
0364             }
0365             }
0366         }
0367 
0368         cur = nextChar();
0369     }
0370 
0371     return Result;
0372 }
0373 
0374 //        =============================================================
0375 //        MorkParser::parseScopeId
0376 
0377 void MorkParser::parseScopeId(const QString &textId, int &Id, int &Scope) const
0378 {
0379     int Pos = 0;
0380 
0381     if ((Pos = textId.indexOf(QLatin1Char(':'))) >= 0) {
0382         QString tId = textId.mid(0, Pos);
0383         QString tSc = textId.mid(Pos + 1, textId.length() - Pos);
0384 
0385         if (tSc.length() > 1 && tSc[0] == QLatin1Char('^')) {
0386             // Delete '^'
0387             tSc.remove(0, 1);
0388         }
0389 
0390         Id = tId.toInt(nullptr, 16);
0391         Scope = tSc.toInt(nullptr, 16);
0392     } else {
0393         Id = textId.toInt(nullptr, 16);
0394     }
0395 }
0396 
0397 //        =============================================================
0398 //        MorkParser::setCurrentRow
0399 
0400 void MorkParser::setCurrentRow(int TableScope, int TableId, int RowScope, int RowId)
0401 {
0402     if (!RowScope) {
0403         RowScope = mDefaultScope;
0404     }
0405 
0406     if (!TableScope) {
0407         TableScope = mDefaultScope;
0408     }
0409 
0410     mCurrentCells = &(mMork[abs(TableScope)][abs(TableId)][abs(RowScope)][abs(RowId)]);
0411 }
0412 
0413 //        =============================================================
0414 //        MorkParser::parseRow
0415 
0416 bool MorkParser::parseRow(int TableId, int TableScope)
0417 {
0418     bool Result = true;
0419     QString TextId;
0420     int Id = 0;
0421     int Scope = 0;
0422     nowParsing_ = NPRows;
0423 
0424     char cur = nextChar();
0425 
0426     // Get id
0427     while (cur != QLatin1Char('(') && cur != QLatin1Char(']') && cur != QLatin1Char('[') && cur) {
0428         if (!isWhiteSpace(cur)) {
0429             TextId += QLatin1Char(cur);
0430         }
0431 
0432         cur = nextChar();
0433     }
0434 
0435     parseScopeId(TextId, Id, Scope);
0436     setCurrentRow(TableScope, TableId, Scope, Id);
0437 
0438     // Parse the row
0439     while (Result && cur != ']' && cur) {
0440         if (!isWhiteSpace(cur)) {
0441             switch (cur) {
0442             case '(':
0443                 Result = parseCell();
0444                 break;
0445             case '[':
0446                 Result = parseMeta(']');
0447                 break;
0448             default:
0449                 Result = false;
0450                 break;
0451             }
0452         }
0453 
0454         cur = nextChar();
0455     }
0456 
0457     return Result;
0458 }
0459 
0460 //        =============================================================
0461 //        MorkParser::parseGroup
0462 
0463 bool MorkParser::parseGroup()
0464 {
0465     return parseMeta('@');
0466 }
0467 
0468 //        =============================================================
0469 //        MorkParser::parseMeta
0470 
0471 bool MorkParser::parseMeta(char c)
0472 {
0473     char cur = nextChar();
0474 
0475     while (cur != c && cur) {
0476         cur = nextChar();
0477     }
0478 
0479     return true;
0480 }
0481 
0482 //        =============================================================
0483 //        MorkParser::getTables
0484 
0485 MorkTableMap *MorkParser::getTables(int TableScope)
0486 {
0487     TableScopeMap::iterator iter;
0488     iter = mMork.find(TableScope);
0489 
0490     if (iter == mMork.end()) {
0491         return nullptr;
0492     }
0493 
0494     return &iter.value();
0495 }
0496 
0497 //        =============================================================
0498 //        MorkParser::getRows
0499 
0500 MorkRowMap *MorkParser::getRows(int RowScope, RowScopeMap *table)
0501 {
0502     RowScopeMap::iterator iter;
0503     iter = table->find(RowScope);
0504 
0505     if (iter == table->end()) {
0506         return nullptr;
0507     }
0508 
0509     return &iter.value();
0510 }
0511 
0512 //        =============================================================
0513 //        MorkParser::getValue
0514 
0515 QString MorkParser::getValue(int oid)
0516 {
0517     MorkDict::ConstIterator foundIter = mValues.constFind(oid);
0518 
0519     if (foundIter == mValues.constEnd()) {
0520         return {};
0521     }
0522 
0523     return *foundIter;
0524 }
0525 
0526 //        =============================================================
0527 //        MorkParser::getColumn
0528 
0529 QString MorkParser::getColumn(int oid)
0530 {
0531     MorkDict::ConstIterator foundIter = mColumns.constFind(oid);
0532 
0533     if (foundIter == mColumns.constEnd()) {
0534         return {};
0535     }
0536 
0537     return *foundIter;
0538 }