/****************************************************************************
**
** Copyright (C) 2009 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