Scribus Developer Documentation - API
logo-pen

Scribus Developer Documentation - API

latexhelpers.cpp

Go to the documentation of this file.
00001 /*
00002 For general Scribus (>=1.3.2) copyright and licensing information please refer
00003 to the COPYING file provided with the program. Following this notice may exist
00004 a copyright and/or license notice that predates the release of Scribus 1.3.2
00005 for which a new license (GPL+exception) is in place.
00006 */
00007 /***************************************************************************
00008                         latexhelpers.cpp  -  description
00009                         -------------------
00010 copyright            : Scribus Team
00011 ***************************************************************************/
00012 
00013 /***************************************************************************
00014 *                                                                         *
00015 *   This program is free software; you can redistribute it and/or modify  *
00016 *   it under the terms of the GNU General Public License as published by  *
00017 *   the Free Software Foundation; either version 2 of the License, or     *
00018 *   (at your option) any later version.                                   *
00019 *                                                                         *
00020 ***************************************************************************/
00021 
00022 
00023 #include "latexhelpers.h"
00024 
00025 #include <QDebug>
00026 #include <QFile>
00027 #include <QMessageBox>
00028 
00029 #include "prefsmanager.h"
00030 #include "scpaths.h"
00031 
00032 
00033 LatexHighlighter::LatexHighlighter(QTextDocument *document)
00034     : QSyntaxHighlighter(document)
00035 {
00036     QTextCharFormat mathFormat;
00037     mathFormat.setForeground(QColor(0, 128, 0));
00038     mathFormat.setFontWeight(QFont::Bold);
00039     setFormatFor(Math, mathFormat);
00040 
00041     QTextCharFormat commandFormat;
00042     commandFormat.setForeground(QColor(255, 0, 0));
00043     setFormatFor(Command, commandFormat);
00044     
00045     QTextCharFormat blockNameFormat;
00046     blockNameFormat.setForeground(QColor(0, 0, 255));
00047     setFormatFor(BlockName, blockNameFormat);
00048     
00049     QTextCharFormat scribusFormat;
00050     scribusFormat.setForeground(QColor(192, 16, 112));
00051     scribusFormat.setFontWeight(QFont::Bold);
00052     setFormatFor(ScribusSpecial, scribusFormat);
00053 
00054     QTextCharFormat commentFormat;
00055     commentFormat.setForeground(QColor(128, 128, 128));
00056     commentFormat.setFontItalic(true);
00057     setFormatFor(Comment, commentFormat);
00058 }
00059 
00060 void LatexHighlighter::setFormatFor(Construct construct, const QTextCharFormat &format)
00061 {
00062     m_formats[construct] = format;
00063     rehighlight();
00064 }
00065 
00066 
00067 void LatexHighlighter::highlightBlock(const QString &text)
00068 {
00069     int state = previousBlockState();
00070     int len = text.length();
00071     int start = 0;
00072     int pos = 0;
00073     //qDebug() << "Block:" << text;
00074     while (pos < len) {
00075         switch (state) {
00076             case NormalState:
00077             default:
00078                 while (pos < len) {
00079                     QChar ch = text.at(pos);
00080                     if (ch == '%') {
00081                         //Comment
00082                         setFormat(pos, len - pos, m_formats[Comment]);
00083                         pos = len; //Do not handle other formats in comments
00084                         break;
00085                     } else if (ch == '\\') {
00086                         start = pos;
00087                         pos++;
00088                         if (pos >= len) break;
00089                         ch = text.at(pos);
00090                         if (ch == '[') {
00091                             state = InMath;
00092                             break;
00093                         } else if ((text.mid(pos, 5) == "begin") || (text.mid(pos, 3) == "end")) {
00094                             int saved_pos = pos;
00095                             if (text.at(pos) == 'b') {
00096                                 setFormat(pos-1, 6, m_formats[Command]);
00097                                 pos += 5;
00098                             } else {
00099                                 setFormat(pos-1, 4, m_formats[Command]);
00100                                 pos += 3;
00101                             }
00102                             
00103                             while (pos < len && text.at(pos++).isSpace()) /*Skip over whitespace */;
00104                             if (text.at(pos-1) == '{') {
00105                                 start = pos;
00106                                 while (pos < len && text.at(pos++) != '}') /*Do nothing */;
00107                                 pos--;
00108                                 //qDebug() << "Start:" << start << "Stop:" << (pos - start) << text.at(pos-1);
00109                                 setFormat(start, pos - start, m_formats[BlockName]);
00110                             } else {
00111                                 pos = saved_pos;
00112                             }
00113                         } else {
00114                             while (pos < len) {
00115                                 ch = text.at(pos++);
00116                                 //Allowed chars are A-Za-z@
00117                                 if (!(((ch >= 'A') && (ch <= 'Z')) ||
00118                                     ((ch >= 'a') && (ch <= 'z')) || (ch == '@'))) break;
00119                             }
00120                             //qDebug() << "Len: " << (pos - start);
00121                             if ((pos - start) == 2) {
00122                                 //Special char
00123                                 setFormat(start, pos - start, m_formats[ScribusSpecial]);
00124                             } else {
00125                                 setFormat(start, pos - start -1, m_formats[Command]);
00126                             }
00127                         }
00128                         break;
00129                     } else if (ch == '$') {
00130                         if (text.mid(pos, 9) == "$scribus_") {
00131                             //Scribus special code
00132                             start = pos;
00133                             pos++;
00134                             while (pos < len && text.at(pos++) != '$') /*Do nothing */;
00135                             setFormat(start, pos - start, m_formats[ScribusSpecial]);
00136                         } else {
00137                             state = InDisplayMath;
00138                             pos++;
00139                             break;
00140                         }
00141                     }
00142                     pos++;
00143                 }
00144                 break;
00145             case InDisplayMath:
00146                 start = pos;
00147                 if (start > 0) start--;
00148                 while (pos < len) {
00149                     if (text.at(pos) == '$') {
00150                         pos++;
00151                         state = NormalState;
00152                         break;
00153                     }
00154                     pos++;
00155                 }
00156                 setFormat(start, pos - start, m_formats[Math]);
00157                 break;
00158             case InMath:
00159                 start = pos;
00160                 if (start > 0) start--;
00161                 while (pos < len) {
00162                     if (text.mid(pos,2) == "\\]" ) {
00163                         pos += 2;
00164                         state = NormalState;
00165                         break;
00166                     }
00167                     pos++;
00168                 }
00169                 setFormat(start, pos - start, m_formats[Math]);
00170                 break;
00171         }
00172     }
00173     setCurrentBlockState(state);
00174 }
00175 
00176 //TODO: Pass this information to LatexEditor, so the second parser can be removed
00177 bool LatexConfigParser::parseConfigFile(QString fn)
00178 {
00179     m_error = "";
00180     m_filename = fn;
00181     QFile f(fn);
00182     f.open(QIODevice::ReadOnly);
00183     xml.setDevice(&f);
00184     
00185     while (!xml.atEnd()) {
00186         xml.readNext();
00187         if (xml.isWhitespace() || xml.isComment() || xml.isStartDocument() || xml.isEndDocument()) continue;
00188         if (xml.isStartElement() && xml.name() == "editorsettings") {
00189             m_description = xml.attributes().value("description").toString();
00190             m_icon = xml.attributes().value("icon").toString();
00191             if (m_description.isEmpty()) {
00192                 m_description = fn;
00193             }
00194             parseElements();
00195         } else {
00196             formatError("Unexpected element at root level"+xml.name().toString()+", Token String: "+
00197                                  xml.tokenString());
00198         }
00199     }
00200     if (xml.hasError()) {
00201         formatError(xml.errorString());
00202     }
00203     f.close();
00204     if (!m_error.isEmpty()) {
00205         return false;
00206     }
00207     return true;
00208 }
00209 
00210 void LatexConfigParser::parseElements()
00211 {
00212     while (!xml.atEnd()) {
00213         xml.readNext();
00214         if (xml.isEndElement() && xml.name() == "editorsettings") break;
00215         if (xml.isWhitespace() || xml.isComment() || xml.isEndElement()) continue;
00216         if (!xml.isStartElement()) {
00217             formatError("Unexpected element in <editorsettings>"+xml.name().toString()+", Token String: "+
00218                                  xml.tokenString());
00219             continue;
00220         }
00221     
00222         if (xml.name() == "executable") {
00223             m_executable = xml.attributes().value("command").toString();
00224         } else if (xml.name() == "imagefile") {
00225             m_imageExtension = xml.attributes().value("extension").toString();
00226         } else if (xml.name() == "highlighter") {
00227             m_highlight_kate = xml.attributes().value("kate").toString();
00228             m_highlight_file = xml.attributes().value("file").toString();
00229         } else if (xml.name() == "empty-frame-text") {
00230             m_emptyFrameText = xml.readI18nText(true);
00231         } else if (xml.name() == "preamble") {
00232             m_preamble = xml.readElementText();
00233         } else if (xml.name() == "postamble") {
00234             m_postamble = xml.readElementText();
00235         } else if (xml.name() == "tab") {
00236             parseTab();
00237         } else {
00238             formatError("Unknown tag in <editorsettings>: "+xml.name().toString());
00239         }
00240     }
00241 }
00242 
00243 void LatexConfigParser::formatError(QString message)
00244 {
00245     QString new_error = QString::number(xml.lineNumber()) + ":" + 
00246             QString::number(xml.columnNumber()) + ":" + message;
00247     qDebug() << m_filename << new_error;
00248     m_error += new_error + "\n";
00249 }
00250 
00251 void LatexConfigParser::parseTab()
00252 {
00253     QString type = xml.attributes().value("type").toString();
00254     bool itemstab = (type == "items");
00255     QString title = "";
00256     QString name, text, default_value;
00257     
00258     while (!xml.atEnd()) {
00259         xml.readNext();
00260         if (xml.isWhitespace() || xml.isComment()) continue;
00261         if (xml.isEndElement() && xml.name() == "tab") break;
00262         if (!xml.isStartElement()) {
00263             formatError("Unexpected element in <tab>: "+xml.name().toString()+", Token String: "+
00264                                  xml.tokenString());
00265             continue;
00266         }
00267         if (xml.name() == "title") {
00268             if (!title.isEmpty()) {
00269                 formatError("Second <title> tag in <tab>");
00270             }
00271             title = xml.readI18nText();
00272         } else if (xml.name() == "item") {
00273             if (!itemstab) {
00274                 formatError("Found <item> in a 'settings'-tab!");
00275             }
00276             QString value = xml.attributes().value("value").toString();
00277             QString img = xml.attributes().value("image").toString();
00278             text = xml.readI18nText();
00279             //qDebug() << "For future use: <item>" << value << img << text;
00280         } else if (xml.name() == "comment" || xml.name() == "font" 
00281                          || xml.name() == "spinbox" || xml.name() == "color" 
00282                                  || xml.name() == "text" || xml.name() == "list") {
00283             //TODO: Store this + attributes in a list
00284                              QString tagname = xml.name().toString();
00285                              name = xml.attributes().value("name").toString();
00286                              default_value = xml.attributes().value("default").toString();
00287                              if (xml.name() != "list") {
00288                                  text = xml.readI18nText();
00289                              } else {
00290                                  ignoreList();
00291                              }
00292                              if (!name.isEmpty()) {
00293                                  if (properties.contains(name)) {
00294                                      formatError("Redeclared setting with name: " + name);
00295                                  } else {
00296                                      properties.insert(name, default_value);
00297                                  }
00298                              }
00299                              qDebug() << "For future use:" << tagname << name << text << default_value;
00300                                  } else {
00301                                      formatError("Unexpected element in <tab>: " + xml.name().toString());
00302                                  }
00303     }
00304     
00305     if (title.isEmpty()) {
00306         formatError("Tab ended here, but no title was found!");
00307     }
00308 }
00309 
00310 void LatexConfigParser::ignoreList()
00311 {
00312     //TODO: Quick hack to avoid real parsing
00313     while (!xml.atEnd()) {
00314         xml.readNext();
00315         if (xml.isEndElement() && xml.name() == "list") break;
00316     }
00317 }
00318 
00319 QString I18nXmlStreamReader::readI18nText(bool unindent)
00320 {
00321     QString language = PrefsManager::instance()->guiLanguage();
00322     QString result;
00323     int matchquality = 0;
00324     bool i18n = false;
00325     if (!isStartElement()) raiseError("readI18nText called without startelement!");
00326     
00327     QString startTag = name().toString();
00328     while (!atEnd()) {
00329         readNext();
00330         if (isWhitespace() || isComment()) continue;
00331         if (isStartElement() && name() == startTag) {
00332             raiseError("Invalid nested elements.");
00333             return "Error";
00334         }
00335         if (isEndElement() && name() == startTag) {
00336             if (!unindent) {
00337                 return result.trimmed();
00338             } else {
00339                 QStringList splitted = result.split("\n");
00340                 int i;
00341                 int minspaces = 0xffff;
00342                 /* NOTE: First line contains no leading whitespace so we start at 1 */
00343                 for (i = 1; i < splitted.size(); i++) {
00344                     if (splitted[i].trimmed().isEmpty()) continue;
00345                     int spaces;
00346                     QString tmp = splitted[i];
00347                     for (spaces = 0; spaces < tmp.length(); spaces++) {
00348                         if (!tmp[spaces].isSpace()) break;
00349                     }
00350                     if (spaces < minspaces) minspaces = spaces;
00351                 }
00352                 for (i = 1; i < splitted.size(); i++) {
00353                     splitted[i] = splitted[i].mid(minspaces);
00354                 }
00355                 return splitted.join("\n").trimmed();
00356             }
00357         }
00358         if (i18n) {
00359             if (isEndElement()) {
00360                 if (name() == "i18n") {
00361                     i18n = false;
00362                 } else {
00363                     raiseError("Invalid end element "+ name().toString());
00364                 }
00365                 continue;
00366             }
00367             if (!isStartElement()) {
00368                 raiseError("Unexpected data!");
00369             }
00370             if (name() == language) {
00371                 matchquality = 2; //Perfect match
00372                 result = readElementText();
00373             } else if (language.startsWith(name().toString()) && matchquality <= 1) {
00374                 matchquality = 1; //Only beginning part matches
00375                 result = readElementText();
00376             } else if (result.isEmpty()) {
00377                 matchquality = 0;
00378                 result = readElementText();
00379             } else {
00380                 readElementText(); //Ignore the text
00381             }
00382         } else {
00383             if (isStartElement()) {
00384                 if (name() == "i18n") {
00385                     i18n = true;
00386                     continue;
00387                 } else {
00388                     raiseError("Tag " + name().toString() +
00389                             "found, but \"i18n\" or string data expected.");
00390                     continue;
00391                 }
00392             } else if (isCharacters()) {
00393                 result = result + text().toString();
00394             }
00395         }
00396     }
00397     raiseError("Unexpected end of XML file");
00398     return result;
00399 }
00400 
00401 LatexConfigCache* LatexConfigCache::_instance = 0;
00402 
00403 LatexConfigCache* LatexConfigCache::instance()
00404 {
00405     if (!_instance) {
00406         _instance = new LatexConfigCache();
00407     }
00408     return _instance;
00409 }
00410 
00411 LatexConfigParser* LatexConfigCache::parser(QString filename, bool warnOnError)
00412 {
00413     if (parsers.contains(filename)) {
00414         if (warnOnError && error[filename]) {
00415             //Recreate element as error might have been fixed.
00416             delete parsers[filename];
00417             createParser(filename, warnOnError);
00418         }
00419     } else {
00420         createParser(filename, warnOnError);
00421     }
00422     return parsers[filename];
00423 }
00424 
00425 void LatexConfigCache::createParser(QString filename, bool warnOnError)
00426 {
00427     qDebug() << "Creating new parser" << filename;
00428     LatexConfigParser *parser = new LatexConfigParser();
00429     bool hasError = !parser->parseConfigFile(filename);
00430     parsers[filename] = parser;
00431     error[filename] = hasError;
00432     if (hasError) {
00433         QMessageBox::critical(0, QObject::tr("Error"), "<qt>" + 
00434                 QObject::tr("Parsing the configfile %1 failed! Depending on the type of the error "
00435                         "latexframes might not work correctly!\n%2").arg(
00436                         filename, parser->error())
00437                         + "</qt>", 1, 0, 0);
00438     }
00439 }
00440 
00441 bool LatexConfigCache::hasError(QString filename)
00442 {
00443     if (!error.contains(filename)) {
00444         return true;
00445     }
00446     return error[filename];
00447 }
00448 
00449 QStringList LatexConfigCache::defaultConfigs()
00450 {
00451     //QStringList fileList;
00452     
00453     QString base = ScPaths::instance().shareDir()+"/editorconfig/";
00454     QDir dir(base);
00455     QStringList files;
00456     files = dir.entryList(QStringList("*.xml"));
00457     files.sort();
00458     int i;
00459     for (i = 0; i < files.size(); i++) {
00460         if (files[i].compare("sample.xml",Qt::CaseInsensitive)==0) {
00461             files.removeAt(i);
00462             i--;
00463         } else {
00464             files[i] = base + files[i];
00465         }
00466     }
00467     return files;
00468 }
Created with personalized Doxygen. This is documentation for GPL software Scribus - Open Source Desktop Publishing.