/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** No Commercial Usage ** This file contains pre-release code and may not be distributed. ** You may use this file in accordance with the terms and conditions ** contained in the Technology Preview License Agreement accompanying ** this package. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** ** ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include <qfileinfo.h> #include <qregexp.h> #include <qdebug.h> #include "quoter.h" QT_BEGIN_NAMESPACE static void replaceMultipleNewlines(QString &s) { const int n = s.size(); bool slurping = false; int j = -1; const QChar newLine = QLatin1Char('\n'); QChar *d = s.data(); for (int i = 0; i != n; ++i) { const QChar c = d[i]; bool hit = (c == newLine); if (slurping && hit) continue; d[++j] = c; slurping = hit; } s.resize(++j); } // This is equivalent to line.split( QRegExp("\n(?!\n|$)") ) but much faster static QStringList splitLines(const QString &line) { QStringList result; int i = line.size(); while (true) { int j = i - 1; while (j >= 0 && line.at(j) == QLatin1Char('\n')) --j; while (j >= 0 && line.at(j) != QLatin1Char('\n')) --j; result.prepend(line.mid(j + 1, i - j - 1)); if (j < 0) break; i = j; } return result; } /* Transforms 'int x = 3 + 4' into 'int x=3+4'. A white space is kept between 'int' and 'x' because it is meaningful in C++. */ static void trimWhiteSpace( QString& str ) { enum { Normal, MetAlnum, MetSpace } state = Normal; const int n = str.length(); int j = -1; QChar *d = str.data(); for ( int i = 0; i != n; ++i ) { const QChar c = d[i]; if ( c.isLetterOrNumber() ) { if ( state == Normal ) { state = MetAlnum; } else { if ( state == MetSpace ) str[++j] = c; state = Normal; } str[++j] = c; } else if ( c.isSpace() ) { if ( state == MetAlnum ) state = MetSpace; } else { state = Normal; str[++j] = c; } } str.resize(++j); } Quoter::Quoter() : silent( false ) { /* We're going to hard code these delimiters: * C++, Qt, Qt Script, Java: //! [<id>] * .pro files: #! [<id>] * .xq, .xml, .html files: <!-- [<id>] --> */ commentHash["pro"] = "#!"; commentHash["py"] = "#!"; commentHash["html"] = "<!--"; commentHash["qrc"] = "<!--"; commentHash["ui"] = "<!--"; commentHash["xml"] = "<!--"; commentHash["xq"] = "<!--"; } void Quoter::reset() { silent = false; plainLines.clear(); markedLines.clear(); codeLocation = Location::null; } void Quoter::quoteFromFile( const QString& userFriendlyFilePath, const QString& plainCode, const QString& markedCode ) { silent = false; /* Split the source code into logical lines. Empty lines are treated specially. Before: p->alpha(); p->beta(); p->gamma(); p->delta(); After: p->alpha(); p->beta();\n p->gamma();\n\n p->delta(); Newlines are preserved because they affect codeLocation. */ codeLocation = Location( userFriendlyFilePath ); plainLines = splitLines(plainCode); markedLines = splitLines(markedCode); if (markedLines.count() != plainLines.count()) { codeLocation.warning(tr("Something is wrong with qdoc's handling of marked code")); markedLines = plainLines; } /* Squeeze blanks (cat -s). */ QStringList::Iterator m = markedLines.begin(); while ( m != markedLines.end() ) { replaceMultipleNewlines( *m ); ++m; } codeLocation.start(); } QString Quoter::quoteLine( const Location& docLocation, const QString& command, const QString& pattern ) { if ( plainLines.isEmpty() ) { failedAtEnd( docLocation, command ); return QString(); } if ( pattern.isEmpty() ) { docLocation.warning( tr("Missing pattern after '\\%1'").arg(command) ); return QString(); } if ( match(docLocation, pattern, plainLines.first()) ) return getLine(); if ( !silent ) { docLocation.warning( tr("Command '\\%1' failed").arg(command) ); codeLocation.warning( tr("Pattern '%1' didn't match here") .arg(pattern) ); silent = true; } return QString(); } QString Quoter::quoteSnippet(const Location &docLocation, const QString &identifier) { QString comment = commentForCode(); QString delimiter = comment + QString(" [%1]").arg(identifier); QString t; while (!plainLines.isEmpty()) { if (match(docLocation, delimiter, plainLines.first())) { getLine(); break; } getLine(); } while (!plainLines.isEmpty()) { QString line = plainLines.first(); if (match(docLocation, delimiter, line)) { QString lastLine = getLine(); int dIndex = lastLine.indexOf(delimiter); if (dIndex > 0) { QString leading = lastLine.left(dIndex); dIndex = leading.indexOf(comment); if (dIndex != -1) leading = leading.left(dIndex); if (!leading.trimmed().isEmpty()) t += leading; } return t; } // Remove special macros to support Qt namespacing. if (line.startsWith("QT_BEGIN_NAMESPACE")) { getLine(); } else if (line.startsWith("QT_END_NAMESPACE")) { getLine(); t += QLatin1Char('\n'); } else if (!line.startsWith(comment)) { // Ordinary code t += getLine(); } else { // Normal comments if (line.contains(QLatin1Char('\n'))) t += QLatin1Char('\n'); getLine(); } } failedAtEnd(docLocation, QString("snippet (%1)").arg(delimiter)); return t; } QString Quoter::quoteTo( const Location& docLocation, const QString& command, const QString& pattern ) { QString t; QString comment = commentForCode(); if ( pattern.isEmpty() ) { while ( !plainLines.isEmpty() ) { QString line = plainLines.first(); // Remove special macros to support Qt namespacing. if (line.startsWith("QT_BEGIN_NAMESPACE")) { getLine(); } else if (line.startsWith("QT_END_NAMESPACE")) { getLine(); t += QLatin1Char('\n'); } else if (!line.startsWith(comment)) // Ordinary code t += getLine(); else { // Normal comments if (line.contains(QLatin1Char('\n'))) t += QLatin1Char('\n'); getLine(); } } } else { while ( !plainLines.isEmpty() ) { if ( match(docLocation, pattern, plainLines.first()) ) { return t; } t += getLine(); } failedAtEnd( docLocation, command ); } return t; } QString Quoter::quoteUntil( const Location& docLocation, const QString& command, const QString& pattern ) { QString t = quoteTo( docLocation, command, pattern ); t += getLine(); return t; } QString Quoter::getLine() { if ( plainLines.isEmpty() ) return QString(); plainLines.removeFirst(); QString t = markedLines.takeFirst(); t += QLatin1Char('\n'); codeLocation.advanceLines( t.count( QLatin1Char('\n') ) ); return t; } bool Quoter::match( const Location& docLocation, const QString& pattern0, const QString& line ) { QString str = line; while ( str.endsWith(QLatin1Char('\n')) ) str.truncate( str.length() - 1 ); QString pattern = pattern0; if ( pattern.startsWith(QLatin1Char('/')) && pattern.endsWith(QLatin1Char('/')) && pattern.length() > 2 ) { QRegExp rx( pattern.mid(1, pattern.length() - 2) ); if ( !silent && !rx.isValid() ) { docLocation.warning( tr("Invalid regular expression '%1'") .arg(rx.pattern()) ); silent = true; } return str.indexOf( rx ) != -1; } trimWhiteSpace(str); trimWhiteSpace(pattern); return str.indexOf(pattern) != -1; } void Quoter::failedAtEnd( const Location& docLocation, const QString& command ) { if (!silent && !command.isEmpty()) { if ( codeLocation.filePath().isEmpty() ) { docLocation.warning( tr("Unexpected '\\%1'").arg(command) ); } else { docLocation.warning( tr("Command '\\%1' failed at end of file '%2'") .arg(command).arg(codeLocation.filePath()) ); } silent = true; } } QString Quoter::commentForCode() const { QString suffix = QFileInfo(codeLocation.fileName()).suffix(); return commentHash.value(suffix, "//!"); } QT_END_NAMESPACE