diff --git a/src/actionproxy.cpp b/src/actionproxy.cpp
index 7a75323..2c8895f 100644
--- a/src/actionproxy.cpp
+++ b/src/actionproxy.cpp
@@ -1,116 +1,117 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2008-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#include "actionproxy.h"
#include
#if 0
#include
ActionProxy::ActionProxy(QObject* parent, QObject* receiver, const char* slot)
: QObject(parent)
, m_currentAction(0)
, m_disabled(false)
, m_checked(false)
// , m_checkable(false)
{
if (receiver)
connect(this, SIGNAL(triggered(bool)), receiver, slot);
connect(this, &ActionProxy::toggled, this, &ActionProxy::handleToggled);
}
ActionProxy::~ActionProxy()
{
// if the view is closed...
}
void ActionProxy::registerAction(QAction* a)
{
if (a == m_currentAction)
return;
m_currentAction = a;
a->setChecked(m_checked);
a->setDisabled(m_disabled);
a->setStatusTip(m_statusTip);
m_keySequence = a->shortcut();
connect(a, SIGNAL(triggered(bool)), this, SIGNAL(triggered(bool)));
connect(a, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool)));
}
void ActionProxy::unregisterAction(/*QAction**/)
{
disconnect(m_currentAction, SIGNAL(triggered(bool)), this, SIGNAL(triggered(bool)));
disconnect(m_currentAction, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool)));
m_currentAction->setStatusTip(QString());
m_currentAction = 0;
}
void ActionProxy::handleToggled(bool checked)
{
m_checked = checked;
}
void ActionProxy::setDisabled(bool disabled)
{
if (m_currentAction)
m_currentAction->setDisabled(disabled);
m_disabled = disabled;
}
void ActionProxy::setChecked(bool checked)
{
if (m_currentAction)
m_currentAction->setChecked(checked); //handleToggled is called implicitly via signal/slot mechanism
else
m_checked = checked;
}
#endif
void StatusBarProxy::insert(int key, const QString& str)
{
if (m_currentStatusBar)
if (key < m_statusBarLabels.size()) m_statusBarLabels.at(key)->setText(str);
QMap::insert(key, str);
}
void StatusBarProxy::registerStatusBar(QStatusBar* bar, const QVector& statusBarLabels)
{
m_currentStatusBar = bar;
m_statusBarLabels = statusBarLabels;
for (int i = 0; i < statusBarLabels.size(); i++)
statusBarLabels.at(i)->setText(QString());
QMap::const_iterator i = constBegin();
while (i != constEnd()) {
if (i.key() < statusBarLabels.size()) statusBarLabels.at(i.key())->setText(i.value());
++i;
}
}
diff --git a/src/actionproxy.h b/src/actionproxy.h
index 1c029ca..1461915 100644
--- a/src/actionproxy.h
+++ b/src/actionproxy.h
@@ -1,116 +1,117 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2008-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef ACTIONPROXY_H
#define ACTIONPROXY_H
#include
#include
#include
#include
class QLabel;
class QStatusBar;
#if 0
/**
* used for connecting qactions to subwindows:
* forwards signals and saves/restores state on subwindow switch
*/
class ActionProxy: public QObject
{
Q_OBJECT
public:
ActionProxy(QObject* parent, QObject* receiver = 0, const char* slot = 0);
~ActionProxy();
void registerAction(QAction*);
void unregisterAction(/*QAction**/);
void setStatusTip(const QString& st)
{
m_statusTip = st; //for TM suggestions
}
QKeySequence shortcut()
{
return m_keySequence;
};//for TM suggestions
public slots:
void setDisabled(bool);
void setEnabled(bool enabled)
{
setDisabled(!enabled);
}
void setChecked(bool);
private slots:
void handleToggled(bool);
signals:
void triggered(bool = false);
void toggled(bool);
private:
QAction* m_currentAction;
bool m_disabled;
bool m_checked;
QString m_statusTip;
QKeySequence m_keySequence;
};
#endif
class StatusBarProxy: public QMap
{
public:
StatusBarProxy(): m_currentStatusBar(0) {}
~StatusBarProxy() {}
void insert(int, const QString&);
void registerStatusBar(QStatusBar*, const QVector& statusBarLabels);
void unregisterStatusBar()
{
m_currentStatusBar = 0;
}
private:
QStatusBar* m_currentStatusBar;
QVector m_statusBarLabels;
};
#define ID_STATUS_CURRENT 0
#define ID_STATUS_TOTAL 1
#define ID_STATUS_FUZZY 2
#define ID_STATUS_UNTRANS 3
#define ID_STATUS_ISFUZZY 4
#define ID_STATUS_PROGRESS 5
//#define TOTAL_ID_STATUSES 6
//#define ID_STATUS_READONLY 6
//#define ID_STATUS_CURSOR 7
#endif
diff --git a/src/alttransview.cpp b/src/alttransview.cpp
index ddb1d43..85b446e 100644
--- a/src/alttransview.cpp
+++ b/src/alttransview.cpp
@@ -1,310 +1,311 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#include "alttransview.h"
#include "lokalize_debug.h"
#include "diff.h"
#include "catalog.h"
#include "cmd.h"
#include "project.h"
#include "xlifftextedit.h"
#include "tmview.h" //TextBrowser
#include "mergecatalog.h"
#include "prefs_lokalize.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AltTransView::AltTransView(QWidget* parent, Catalog* catalog, const QVector& actions)
: QDockWidget(i18nc("@title:window", "Alternate Translations"), parent)
, m_browser(new TM::TextBrowser(this))
, m_catalog(catalog)
, m_normTitle(i18nc("@title:window", "Alternate Translations"))
, m_hasInfoTitle(m_normTitle + QStringLiteral(" [*]"))
, m_hasInfo(false)
, m_everShown(false)
, m_actions(actions)
{
setObjectName(QStringLiteral("msgIdDiff"));
setWidget(m_browser);
hide();
m_browser->setReadOnly(true);
m_browser->viewport()->setBackgroundRole(QPalette::Background);
QTimer::singleShot(0, this, &AltTransView::initLater);
}
void AltTransView::initLater()
{
setAcceptDrops(true);
KConfig config;
KConfigGroup group(&config, "AltTransView");
m_everShown = group.readEntry("EverShown", false);
QSignalMapper* signalMapper = new QSignalMapper(this);
int i = m_actions.size();
while (--i >= 0) {
connect(m_actions.at(i), &QAction::triggered, signalMapper, QOverload<>::of(&QSignalMapper::map));
signalMapper->setMapping(m_actions.at(i), i);
}
connect(signalMapper, QOverload::of(&QSignalMapper::mapped), this, &AltTransView::slotUseSuggestion);
connect(m_browser, &TM::TextBrowser::textInsertRequested, this, &AltTransView::textInsertRequested);
//connect(m_browser, &TM::TextBrowser::customContextMenuRequested, this, &AltTransView::contextMenu);
}
AltTransView::~AltTransView()
{
}
void AltTransView::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasUrls() && Catalog::extIsSupported(event->mimeData()->urls().first().path()))
event->acceptProposedAction();
}
void AltTransView::dropEvent(QDropEvent *event)
{
event->acceptProposedAction();
attachAltTransFile(event->mimeData()->urls().first().toLocalFile());
//update
m_prevEntry.entry = -1;
QTimer::singleShot(0, this, &AltTransView::process);
}
void AltTransView::attachAltTransFile(const QString& path)
{
MergeCatalog* altCat = new MergeCatalog(m_catalog, m_catalog, /*saveChanges*/false);
altCat->loadFromUrl(path);
m_catalog->attachAltTransCatalog(altCat);
}
void AltTransView::addAlternateTranslation(int entry, const QString& trans)
{
AltTrans altTrans;
altTrans.target = trans;
m_catalog->attachAltTrans(entry, altTrans);
m_prevEntry = DocPos();
QTimer::singleShot(0, this, &AltTransView::process);
}
void AltTransView::fileLoaded()
{
m_prevEntry.entry = -1;
QString absPath = m_catalog->url();
QString relPath = QDir(Project::instance()->projectDir()).relativeFilePath(absPath);
QFileInfo info(Project::instance()->altTransDir() % '/' % relPath);
if (info.canonicalFilePath() != absPath && info.exists())
attachAltTransFile(info.canonicalFilePath());
else
qCWarning(LOKALIZE_LOG) << "alt trans file doesn't exist:" << Project::instance()->altTransDir() % '/' % relPath;
}
void AltTransView::slotNewEntryDisplayed(const DocPosition& pos)
{
m_entry = DocPos(pos);
QTimer::singleShot(0, this, &AltTransView::process);
}
void AltTransView::process()
{
if (m_entry == m_prevEntry) return;
if (m_catalog->numberOfEntries() <= m_entry.entry)
return;//because of Qt::QueuedConnection
m_prevEntry = m_entry;
m_browser->clear();
m_entryPositions.clear();
const QVector& entries = m_catalog->altTrans(m_entry.toDocPosition());
m_entries = entries;
if (entries.isEmpty()) {
if (m_hasInfo) {
m_hasInfo = false;
setWindowTitle(m_normTitle);
}
return;
}
if (!m_hasInfo) {
m_hasInfo = true;
setWindowTitle(m_hasInfoTitle);
}
if (!isVisible() && !Settings::altTransViewEverShownWithData()) {
if (KMessageBox::questionYesNo(this, i18n("There is useful data available in Alternate Translations view.\n\n"
"For Gettext PO files it displays difference between current source text "
"and the source text corresponding to the fuzzy translation found by msgmerge when updating PO based on POT template.\n\n"
"Do you want to show the view with the data?"), m_normTitle) == KMessageBox::Yes)
show();
Settings::setAltTransViewEverShownWithData(true);
}
CatalogString source = m_catalog->sourceWithTags(m_entry.toDocPosition());
QTextBlockFormat blockFormatBase;
QTextBlockFormat blockFormatAlternate; blockFormatAlternate.setBackground(QPalette().alternateBase());
QTextCharFormat noncloseMatchCharFormat;
QTextCharFormat closeMatchCharFormat; closeMatchCharFormat.setFontWeight(QFont::Bold);
int i = 0;
int limit = entries.size();
forever {
const AltTrans& entry = entries.at(i);
QTextCursor cur = m_browser->textCursor();
QString html;
html.reserve(1024);
if (!entry.source.isEmpty()) {
html += QStringLiteral("");
QString result = userVisibleWordDiff(entry.source.string, source.string, Project::instance()->accel(), Project::instance()->markup()).toHtmlEscaped();
//result.replace("&","&");
//result.replace("<","<");
//result.replace(">",">");
result.replace(QStringLiteral("{KBABELADD}"), QStringLiteral(""));
result.replace(QStringLiteral("{/KBABELADD}"), QStringLiteral(" "));
result.replace(QStringLiteral("{KBABELDEL}"), QStringLiteral(""));
result.replace(QStringLiteral("{/KBABELDEL}"), QStringLiteral(" "));
result.replace(QStringLiteral("\\n"), QStringLiteral("\\n "));
html += result;
html += QStringLiteral(" ");
cur.insertHtml(html); html.clear();
}
if (!entry.target.isEmpty()) {
if (Q_LIKELY(i < m_actions.size())) {
m_actions.at(i)->setStatusTip(entry.target.string);
html += QString(QStringLiteral("[%1] ")).arg(m_actions.at(i)->shortcut().toString(QKeySequence::NativeText));
} else
html += QStringLiteral("[ - ] ");
cur.insertText(html); html.clear();
insertContent(cur, entry.target);
}
m_entryPositions.insert(cur.anchor(), i);
html += i ? QStringLiteral("
") : QStringLiteral("
");
cur.insertHtml(html);
if (Q_UNLIKELY(++i >= limit))
break;
cur.insertBlock(i % 2 ? blockFormatAlternate : blockFormatBase);
}
if (!m_everShown) {
m_everShown = true;
show();
KConfig config;
KConfigGroup group(&config, "AltTransView");
group.writeEntry("EverShown", true);
}
}
bool AltTransView::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast(event);
if (m_entryPositions.isEmpty()) {
QString tooltip = i18nc("@info:tooltip", "Sometimes, if source text is changed, its translation becomes deprecated and is either marked as needing review (i.e. looses approval status), "
"or (only in case of XLIFF file) moved to the alternate translations section accompanying the unit.
"
"This toolview also shows the difference between current source string and the previous source string, so that you can easily see which changes should be applied to existing translation to make it reflect current source.
"
"Double-clicking any word in this toolview inserts it into translation.
"
"Drop translation file onto this toolview to use it as a source for additional alternate translations.
"
);
QToolTip::showText(helpEvent->globalPos(), tooltip);
return true;
}
int block1 = m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).blockNumber();
int block = *m_entryPositions.lowerBound(m_browser->cursorForPosition(m_browser->viewport()->mapFromGlobal(helpEvent->globalPos())).anchor());
if (block1 != block)
qCWarning(LOKALIZE_LOG) << "block numbers don't match";
if (block >= m_entries.size())
return false;
QString origin = m_entries.at(block).origin;
if (origin.isEmpty())
return false;
QString tooltip = i18nc("@info:tooltip", "Origin: %1", origin);
QToolTip::showText(helpEvent->globalPos(), tooltip);
return true;
}
return QWidget::event(event);
}
void AltTransView::slotUseSuggestion(int i)
{
if (Q_UNLIKELY(i >= m_entries.size()))
return;
TM::TMEntry tmEntry;
tmEntry.target = m_entries.at(i).target;
CatalogString source = m_catalog->sourceWithTags(m_entry.toDocPosition());
tmEntry.diff = userVisibleWordDiff(m_entries.at(i).source.string, source.string, Project::instance()->accel(), Project::instance()->markup());
CatalogString target = TM::targetAdapted(tmEntry, source);
qCWarning(LOKALIZE_LOG) << "0" << target.string;
if (Q_UNLIKELY(target.isEmpty()))
return;
m_catalog->beginMacro(i18nc("@item Undo action", "Use alternate translation"));
QString old = m_catalog->targetWithTags(m_entry.toDocPosition()).string;
if (!old.isEmpty()) {
//FIXME test!
removeTargetSubstring(m_catalog, m_entry.toDocPosition(), 0, old.size());
//m_catalog->push(new DelTextCmd(m_catalog,m_pos,m_catalog->msgstr(m_pos)));
}
qCWarning(LOKALIZE_LOG) << "1" << target.string;
//m_catalog->push(new InsTextCmd(m_catalog,m_pos,target)/*,true*/);
insertCatalogString(m_catalog, m_entry.toDocPosition(), target, 0);
m_catalog->endMacro();
emit refreshRequested();
}
diff --git a/src/alttransview.h b/src/alttransview.h
index f522bad..fd04d92 100644
--- a/src/alttransview.h
+++ b/src/alttransview.h
@@ -1,85 +1,86 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2008 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef ALTTRANSVIEW_H
#define ALTTRANSVIEW_H
#define ALTTRANS_SHORTCUTS 9
#include "pos.h"
#include "alttrans.h"
#include
namespace TM
{
class TextBrowser;
}
class Catalog;
class QAction;
class AltTransView: public QDockWidget
{
Q_OBJECT
public:
explicit AltTransView(QWidget*, Catalog*, const QVector&);
~AltTransView() override;
public slots:
void slotNewEntryDisplayed(const DocPosition&);
void fileLoaded();
void attachAltTransFile(const QString&);
void addAlternateTranslation(int entry, const QString&);
private slots:
//void contextMenu(const QPoint & pos);
void process();
void initLater();
void slotUseSuggestion(int);
signals:
void refreshRequested();
void textInsertRequested(const QString&);
private:
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent *event) override;
bool event(QEvent *event) override;
private:
TM::TextBrowser* m_browser;
Catalog* m_catalog;
QString m_normTitle;
QString m_hasInfoTitle;
bool m_hasInfo;
bool m_everShown;
DocPos m_entry;
DocPos m_prevEntry;
QVector m_entries;
QMap m_entryPositions;
QVector m_actions;//need them to get shortcuts
};
#endif
diff --git a/src/binunitsview.cpp b/src/binunitsview.cpp
index c5c5f98..c0510b3 100644
--- a/src/binunitsview.cpp
+++ b/src/binunitsview.cpp
@@ -1,213 +1,214 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2009-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#include "binunitsview.h"
#include "phaseswindow.h" //MyTreeView
#include "catalog.h"
#include "cmd.h"
#include "project.h"
#include
#include
#include
#include
#include
#include
//BEGIN BinUnitsModel
BinUnitsModel::BinUnitsModel(Catalog* catalog, QObject* parent)
: QAbstractListModel(parent)
, m_catalog(catalog)
{
connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &BinUnitsModel::fileLoaded);
connect(catalog, &Catalog::signalEntryModified, this, &BinUnitsModel::entryModified);
connect(KDirWatch::self(), &KDirWatch::dirty, this, &BinUnitsModel::updateFile);
}
void BinUnitsModel::fileLoaded()
{
beginResetModel();
m_imageCache.clear();
endResetModel();
}
void BinUnitsModel::entryModified(const DocPosition& pos)
{
if (pos.entry < m_catalog->numberOfEntries())
return;
QModelIndex item = index(pos.entry - m_catalog->numberOfEntries(), TargetFilePath);
emit dataChanged(item, item);
}
void BinUnitsModel::updateFile(QString path)
{
QString relPath = QDir(Project::instance()->projectDir()).relativeFilePath(path);
DocPosition pos(m_catalog->numberOfEntries());
int limit = m_catalog->numberOfEntries() + m_catalog->binUnitsCount();
while (pos.entry < limit) {
if (m_catalog->target(pos) == relPath || m_catalog->source(pos) == relPath) {
int row = pos.entry - m_catalog->numberOfEntries();
m_imageCache.remove(relPath);
emit dataChanged(index(row, SourceFilePath), index(row, TargetFilePath));
return;
}
pos.entry++;
}
}
void BinUnitsModel::setTargetFilePath(int row, const QString& path)
{
DocPosition pos(row + m_catalog->numberOfEntries());
QString old = m_catalog->target(pos);
if (!old.isEmpty()) {
m_catalog->push(new DelTextCmd(m_catalog, pos, old));
m_imageCache.remove(old);
}
m_catalog->push(new InsTextCmd(m_catalog, pos, QDir(Project::instance()->projectDir()).relativeFilePath(path)));
QModelIndex item = index(row, TargetFilePath);
emit dataChanged(item, item);
}
int BinUnitsModel::rowCount(const QModelIndex& parent) const
{
if (parent.isValid())
return 0;
return m_catalog->binUnitsCount();
}
QVariant BinUnitsModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::DecorationRole) {
DocPosition pos(index.row() + m_catalog->numberOfEntries());
if (index.column() < Approved) {
QString path = index.column() == SourceFilePath ? m_catalog->source(pos) : m_catalog->target(pos);
if (!m_imageCache.contains(path)) {
QString absPath = Project::instance()->absolutePath(path);
KDirWatch::self()->addFile(absPath); //TODO remember watched files to react only on them in dirty() signal handler
m_imageCache.insert(path, QImage(absPath).scaled(128, 128, Qt::KeepAspectRatio));
}
return m_imageCache.value(path);
}
} else if (role == Qt::TextAlignmentRole)
return int(Qt::AlignLeft | Qt::AlignTop);
if (role != Qt::DisplayRole)
return QVariant();
static const char* noyes[] = {I18N_NOOP("no"), I18N_NOOP("yes")};
DocPosition pos(index.row() + m_catalog->numberOfEntries());
switch (index.column()) {
case SourceFilePath: return m_catalog->source(pos);
case TargetFilePath: return m_catalog->target(pos);
case Approved: return noyes[m_catalog->isApproved(pos)];
}
return QVariant();
}
QVariant BinUnitsModel::headerData(int section, Qt::Orientation, int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
switch (section) {
case SourceFilePath: return i18nc("@title:column", "Source");
case TargetFilePath: return i18nc("@title:column", "Target");
case Approved: return i18nc("@title:column", "Approved");
}
return QVariant();
}
//END BinUnitsModel
BinUnitsView::BinUnitsView(Catalog* catalog, QWidget* parent)
: QDockWidget(i18nc("@title toolview name", "Binary Units"), parent)
, m_catalog(catalog)
, m_model(new BinUnitsModel(catalog, this))
, m_view(new MyTreeView(this))
{
setObjectName(QStringLiteral("binUnits"));
hide();
setWidget(m_view);
m_view->setModel(m_model);
m_view->setRootIsDecorated(false);
m_view->setAlternatingRowColors(true);
m_view->viewport()->setBackgroundRole(QPalette::Background);
connect(m_view, &MyTreeView::doubleClicked, this, &BinUnitsView::mouseDoubleClicked);
connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &BinUnitsView::fileLoaded);
}
void BinUnitsView::fileLoaded()
{
setVisible(m_catalog->binUnitsCount());
}
void BinUnitsView::selectUnit(const QString& id)
{
QModelIndex item = m_model->index(m_catalog->unitById(id) - m_catalog->numberOfEntries());
m_view->setCurrentIndex(item);
m_view->scrollTo(item);
show();
}
void BinUnitsView::contextMenuEvent(QContextMenuEvent *event)
{
QModelIndex item = m_view->currentIndex();
if (!item.isValid())
return;
QMenu menu;
QAction* setTarget = menu.addAction(i18nc("@action:inmenu", "Set the file"));
QAction* useSource = menu.addAction(i18nc("@action:inmenu", "Use source file"));
// menu.addSeparator();
// QAction* openSource=menu.addAction(i18nc("@action:inmenu","Open source file in external program"));
// QAction* openTarget=menu.addAction(i18nc("@action:inmenu","Open target file in external program"));
QAction* result = menu.exec(event->globalPos());
if (!result)
return;
QString sourceFilePath = item.sibling(item.row(), BinUnitsModel::SourceFilePath).data().toString();
if (result == useSource)
m_model->setTargetFilePath(item.row(), sourceFilePath);
else if (result == setTarget) {
QString targetFilePath = QFileDialog::getOpenFileName(this, QString(), Project::instance()->projectDir());
if (!targetFilePath.isEmpty())
m_model->setTargetFilePath(item.row(), targetFilePath);
}
event->accept();
}
void BinUnitsView::mouseDoubleClicked(const QModelIndex& item)
{
//FIXME child processes don't notify us about changes ;(
if (item.column() < BinUnitsModel::Approved)
new KRun(QUrl::fromLocalFile(Project::instance()->absolutePath(item.data().toString())), this);
}
diff --git a/src/binunitsview.h b/src/binunitsview.h
index a19645e..f054a7c 100644
--- a/src/binunitsview.h
+++ b/src/binunitsview.h
@@ -1,95 +1,96 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2009 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef BINUNITSVIEW_H
#define BINUNITSVIEW_H
class Catalog;
class BinUnitsModel;
class MyTreeView;
#include "pos.h"
#include
#include
#include
class BinUnitsView: public QDockWidget
{
Q_OBJECT
public:
explicit BinUnitsView(Catalog* catalog, QWidget *parent);
public slots:
void selectUnit(const QString& id);
private:
void contextMenuEvent(QContextMenuEvent *event) override;
private slots:
void mouseDoubleClicked(const QModelIndex&);
void fileLoaded();
private:
Catalog* m_catalog;
BinUnitsModel* m_model;
MyTreeView* m_view;
};
class BinUnitsModel: public QAbstractListModel
{
Q_OBJECT
public:
enum BinUnitsModelColumns {
SourceFilePath = 0,
TargetFilePath,
Approved,
ColumnCount
};
BinUnitsModel(Catalog* catalog, QObject* parent);
~BinUnitsModel() {}
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override
{
Q_UNUSED(parent);
return ColumnCount;
}
QVariant data(const QModelIndex&, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const override;
void setTargetFilePath(int row, const QString&);
private slots:
void fileLoaded();
void entryModified(const DocPosition&);
void updateFile(QString path);
private:
Catalog* m_catalog;
mutable QHash m_imageCache;
};
#endif // BINUNITSVIEW_H
diff --git a/src/catalog/alttrans.h b/src/catalog/alttrans.h
index 8f45b67..862fb57 100644
--- a/src/catalog/alttrans.h
+++ b/src/catalog/alttrans.h
@@ -1,48 +1,49 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2009 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef ALTTRANS_H
#define ALTTRANS_H
#include "catalogstring.h"
#include "tmentry.h"
struct AltTrans {
///@see http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#alttranstype
enum Type {Proposal, PreviousVersion, Rejected, Reference, Accepted, Other};
Type type;
CatalogString source;
CatalogString target;
short score;
QString lang;
QString origin;
QString phase;
AltTrans(const CatalogString& s = CatalogString(), const QString& o = QString()): type(Other), source(s), score(0), origin(o) {}
};
#endif
diff --git a/src/catalog/catalog.cpp b/src/catalog/catalog.cpp
index b168898..79146c7 100644
--- a/src/catalog/catalog.cpp
+++ b/src/catalog/catalog.cpp
@@ -1,1067 +1,1068 @@
/* ****************************************************************************
This file is part of Lokalize
This file contains parts of KBabel code
Copyright (C) 1999-2000 by Matthias Kiefer
2001-2005 by Stanislav Visnovsky
2006 by Nicolas Goutte
2007-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#include "catalog.h"
#include "catalog_private.h"
#include "project.h"
#include "projectmodel.h" //to notify about modification
#include "catalogstorage.h"
#include "gettextstorage.h"
#include "gettextimport.h"
#include "gettextexport.h"
#include "xliffstorage.h"
#include "tsstorage.h"
#include "mergecatalog.h"
#include "version.h"
#include "prefs_lokalize.h"
#include "jobs.h"
#include "dbfilesmodel.h"
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_WIN
#define U QLatin1String
#else
#define U QStringLiteral
#endif
//QString Catalog::supportedMimeFilters("text/x-gettext-translation application/x-xliff application/x-linguist"); //" text/x-gettext-translation-template")
QString Catalog::supportedFileTypes(bool includeTemplates)
{
QString sep = QStringLiteral(";;");
QString all = i18n("All supported files (*.po *.pot *.xlf *.xliff *.ts)") + sep;
return all + (includeTemplates ? i18n("Gettext (*.po *.pot)") : i18n("Gettext (*.po)")) + sep + i18n("XLIFF (*.xlf *.xliff)") + sep + i18n("Linguist (*.ts)");
}
static const QString extensions[] = {U(".po"), U(".pot"), U(".xlf"), U(".xliff"), U(".ts")};
static const char* const xliff_states[] = {
I18N_NOOP("New"), I18N_NOOP("Needs translation"), I18N_NOOP("Needs full localization"), I18N_NOOP("Needs adaptation"), I18N_NOOP("Translated"),
I18N_NOOP("Needs translation review"), I18N_NOOP("Needs full localization review"), I18N_NOOP("Needs adaptation review"), I18N_NOOP("Final"),
I18N_NOOP("Signed-off")
};
const char* const* Catalog::states()
{
return xliff_states;
}
QStringList Catalog::supportedExtensions()
{
QStringList result;
int i = sizeof(extensions) / sizeof(QString);
while (--i >= 0)
result.append(extensions[i]);
return result;
}
bool Catalog::extIsSupported(const QString& path)
{
QStringList ext = supportedExtensions();
int i = ext.size();
while (--i >= 0 && !path.endsWith(ext.at(i)))
;
return i != -1;
}
Catalog::Catalog(QObject *parent)
: QUndoStack(parent)
, d(this)
, m_storage(0)
{
//cause refresh events for files modified from lokalize itself aint delivered automatically
connect(this, QOverload::of(&Catalog::signalFileSaved), Project::instance()->model(), QOverload::of(&ProjectModel::slotFileSaved), Qt::QueuedConnection);
QTimer* t = &(d._autoSaveTimer);
t->setInterval(2 * 60 * 1000);
t->setSingleShot(false);
connect(t, &QTimer::timeout, this, &Catalog::doAutoSave);
connect(this, QOverload<>::of(&Catalog::signalFileSaved), t, QOverload<>::of(&QTimer::start));
connect(this, QOverload<>::of(&Catalog::signalFileLoaded), t, QOverload<>::of(&QTimer::start));
connect(this, &Catalog::indexChanged, this, &Catalog::setAutoSaveDirty);
connect(Project::local(), &Project::configChanged, this, &Catalog::projectConfigChanged);
}
Catalog::~Catalog()
{
clear();
//delete m_storage; //deleted in clear();
}
void Catalog::clear()
{
setIndex(cleanIndex());//to keep TM in sync
QUndoStack::clear();
d._errorIndex.clear();
d._nonApprovedIndex.clear();
d._nonApprovedNonEmptyIndex.clear();
d._emptyIndex.clear();
delete m_storage; m_storage = 0;
d._filePath.clear();
d._lastModifiedPos = DocPosition();
d._modifiedEntries.clear();
while (!d._altTransCatalogs.isEmpty())
d._altTransCatalogs.takeFirst()->deleteLater();
d._altTranslations.clear();
/*
d.msgidDiffList.clear();
d.msgstr2MsgidDiffList.clear();
d.diffCache.clear();
*/
}
void Catalog::push(QUndoCommand* cmd)
{
generatePhaseForCatalogIfNeeded(this);
QUndoStack::push(cmd);
}
//BEGIN STORAGE TRANSLATION
int Catalog::capabilities() const
{
if (Q_UNLIKELY(!m_storage)) return 0;
return m_storage->capabilities();
}
int Catalog::numberOfEntries() const
{
if (Q_UNLIKELY(!m_storage)) return 0;
return m_storage->size();
}
static DocPosition alterForSinglePlural(const Catalog* th, DocPosition pos)
{
//if source lang is english (implied) and target lang has only 1 plural form (e.g. Chinese)
if (Q_UNLIKELY(th->numberOfPluralForms() == 1 && th->isPlural(pos)))
pos.form = 1;
return pos;
}
QString Catalog::msgid(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->source(alterForSinglePlural(this, pos));
}
QString Catalog::msgidWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->sourceWithPlurals(pos, truncateFirstLine);
}
QString Catalog::msgstr(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->target(pos);
}
QString Catalog::msgstrWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->targetWithPlurals(pos, truncateFirstLine);
}
CatalogString Catalog::sourceWithTags(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return CatalogString();
return m_storage->sourceWithTags(alterForSinglePlural(this, pos));
}
CatalogString Catalog::targetWithTags(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return CatalogString();
return m_storage->targetWithTags(pos);
}
CatalogString Catalog::catalogString(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return CatalogString();
return m_storage->catalogString(pos.part == DocPosition::Source ? alterForSinglePlural(this, pos) : pos);
}
QVector Catalog::notes(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QVector();
return m_storage->notes(pos);
}
QVector Catalog::developerNotes(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QVector();
return m_storage->developerNotes(pos);
}
Note Catalog::setNote(const DocPosition& pos, const Note& note)
{
if (Q_UNLIKELY(!m_storage))
return Note();
return m_storage->setNote(pos, note);
}
QStringList Catalog::noteAuthors() const
{
if (Q_UNLIKELY(!m_storage))
return QStringList();
return m_storage->noteAuthors();
}
void Catalog::attachAltTransCatalog(Catalog* altCat)
{
d._altTransCatalogs.append(altCat);
if (numberOfEntries() != altCat->numberOfEntries())
qCWarning(LOKALIZE_LOG) << altCat->url() << "has different number of entries";
}
void Catalog::attachAltTrans(int entry, const AltTrans& trans)
{
d._altTranslations.insert(entry, trans);
}
QVector Catalog::altTrans(const DocPosition& pos) const
{
QVector result;
if (m_storage)
result = m_storage->altTrans(pos);
foreach (Catalog* altCat, d._altTransCatalogs) {
if (pos.entry >= altCat->numberOfEntries()) {
qCDebug(LOKALIZE_LOG) << "ignoring" << altCat->url() << "this time because" << pos.entry << "<" << altCat->numberOfEntries();
continue;
}
if (altCat->source(pos) != source(pos)) {
qCDebug(LOKALIZE_LOG) << "ignoring" << altCat->url() << "this time because s don't match";
continue;
}
QString target = altCat->msgstr(pos);
if (!target.isEmpty() && altCat->isApproved(pos)) {
result << AltTrans();
result.last().target = target;
result.last().type = AltTrans::Reference;
result.last().origin = altCat->url();
}
}
if (d._altTranslations.contains(pos.entry))
result << d._altTranslations.value(pos.entry);
return result;
}
QStringList Catalog::sourceFiles(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QStringList();
return m_storage->sourceFiles(pos);
}
QString Catalog::id(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->id(pos);
}
QStringList Catalog::context(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QStringList();
return m_storage->context(pos);
}
QString Catalog::setPhase(const DocPosition& pos, const QString& phase)
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->setPhase(pos, phase);
}
void Catalog::setActivePhase(const QString& phase, ProjectLocal::PersonRole role)
{
//qCDebug(LOKALIZE_LOG)<<"setting active phase"<size();
while (pos.entry < limit) {
if (m_storage->isEmpty(pos))
d._emptyIndex << pos.entry;
if (!isApproved(pos))
{
d._nonApprovedIndex << pos.entry;
if (!m_storage->isEmpty(pos))
{
d._nonApprovedNonEmptyIndex << pos.entry;
}
}
++(pos.entry);
}
emit signalNumberOfFuzziesChanged();
emit signalNumberOfEmptyChanged();
}
QString Catalog::phase(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->phase(pos);
}
Phase Catalog::phase(const QString& name) const
{
return m_storage->phase(name);
}
QList Catalog::allPhases() const
{
return m_storage->allPhases();
}
QVector Catalog::phaseNotes(const QString& phase) const
{
return m_storage->phaseNotes(phase);
}
QVector Catalog::setPhaseNotes(const QString& phase, QVector notes)
{
return m_storage->setPhaseNotes(phase, notes);
}
QMap Catalog::allTools() const
{
return m_storage->allTools();
}
bool Catalog::isPlural(uint index) const
{
return m_storage && m_storage->isPlural(DocPosition(index));
}
bool Catalog::isApproved(uint index) const
{
if (Q_UNLIKELY(!m_storage))
return false;
bool extendedStates = m_storage->capabilities()&ExtendedStates;
return (extendedStates &&::isApproved(state(DocPosition(index)), activePhaseRole()))
|| (!extendedStates && m_storage->isApproved(DocPosition(index)));
}
TargetState Catalog::state(const DocPosition& pos) const
{
if (Q_UNLIKELY(!m_storage))
return NeedsTranslation;
if (m_storage->capabilities()&ExtendedStates)
return m_storage->state(pos);
else
return closestState(m_storage->isApproved(pos), activePhaseRole());
}
bool Catalog::isEmpty(uint index) const
{
return m_storage && m_storage->isEmpty(DocPosition(index));
}
bool Catalog::isEmpty(const DocPosition& pos) const
{
return m_storage && m_storage->isEmpty(pos);
}
bool Catalog::isEquivTrans(const DocPosition& pos) const
{
return m_storage && m_storage->isEquivTrans(pos);
}
int Catalog::binUnitsCount() const
{
return m_storage ? m_storage->binUnitsCount() : 0;
}
int Catalog::unitById(const QString& id) const
{
return m_storage ? m_storage->unitById(id) : 0;
}
QString Catalog::mimetype()
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->mimetype();
}
QString Catalog::fileType()
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->fileType();
}
CatalogType Catalog::type()
{
if (Q_UNLIKELY(!m_storage))
return Gettext;
return m_storage->type();
}
QString Catalog::sourceLangCode() const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->sourceLangCode();
}
QString Catalog::targetLangCode() const
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->targetLangCode();
}
void Catalog::setTargetLangCode(const QString& targetLangCode)
{
if (Q_UNLIKELY(!m_storage))
return;
bool notify = m_storage->targetLangCode() != targetLangCode;
m_storage->setTargetLangCode(targetLangCode);
if (notify) emit signalFileLoaded();
}
//END STORAGE TRANSLATION
//BEGIN OPEN/SAVE
#define DOESNTEXIST -1
#define ISNTREADABLE -2
#define UNKNOWNFORMAT -3
KAutoSaveFile* Catalog::checkAutoSave(const QString& url)
{
KAutoSaveFile* autoSave = 0;
QList staleFiles = KAutoSaveFile::staleFiles(QUrl::fromLocalFile(url));
foreach (KAutoSaveFile *stale, staleFiles) {
if (stale->open(QIODevice::ReadOnly) && !autoSave) {
autoSave = stale;
autoSave->setParent(this);
} else
stale->deleteLater();
}
if (autoSave)
qCInfo(LOKALIZE_LOG) << "autoSave" << autoSave->fileName();
return autoSave;
}
int Catalog::loadFromUrl(const QString& filePath, const QString& saidUrl, int* fileSize, bool fast)
{
QFileInfo info(filePath);
if (Q_UNLIKELY(!info.exists() || info.isDir()))
return DOESNTEXIST;
if (Q_UNLIKELY(!info.isReadable()))
return ISNTREADABLE;
bool readOnly = !info.isWritable();
QTime a; a.start();
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly))
return ISNTREADABLE;//TODO
CatalogStorage* storage = nullptr;
if (filePath.endsWith(QLatin1String(".po")) || filePath.endsWith(QLatin1String(".pot")))
storage = new GettextCatalog::GettextStorage;
else if (filePath.endsWith(QLatin1String(".xlf")) || filePath.endsWith(QLatin1String(".xliff")))
storage = new XliffStorage;
else if (filePath.endsWith(QLatin1String(".ts")))
storage = new TsStorage;
else {
//try harder
QTextStream in(&file);
int i = 0;
bool gettext = false;
while (!in.atEnd() && ++i < 64 && !gettext)
gettext = in.readLine().contains(QLatin1String("msgid"));
if (gettext) storage = new GettextCatalog::GettextStorage;
else return UNKNOWNFORMAT;
}
int line = storage->load(&file);
file.close();
if (Q_UNLIKELY(line != 0 || (!storage->size() && (line == -1)))) {
delete storage;
return line;
}
if (a.elapsed() > 100) qCDebug(LOKALIZE_LOG) << filePath << "opened in" << a.elapsed();
//ok...
clear();
//commit transaction
m_storage = storage;
updateApprovedEmptyIndexCache();
d._numberOfPluralForms = storage->numberOfPluralForms();
d._autoSaveDirty = true;
d._readOnly = readOnly;
d._filePath = saidUrl.isEmpty() ? filePath : saidUrl;
//set some sane role, a real phase with a nmae will be created later with the first edit command
setActivePhase(QString(), Project::local()->role());
if (!fast) {
KAutoSaveFile* autoSave = checkAutoSave(d._filePath);
d._autoSaveRecovered = autoSave;
if (autoSave) {
d._autoSave->deleteLater();
d._autoSave = autoSave;
//restore 'modified' status for entries
MergeCatalog* mergeCatalog = new MergeCatalog(this, this);
int errorLine = mergeCatalog->loadFromUrl(autoSave->fileName());
if (Q_LIKELY(errorLine == 0))
mergeCatalog->copyToBaseCatalog();
mergeCatalog->deleteLater();
d._autoSave->close();
} else
d._autoSave->setManagedFile(QUrl::fromLocalFile(d._filePath));
}
if (fileSize)
*fileSize = file.size();
emit signalFileLoaded();
emit signalFileLoaded(d._filePath);
return 0;
}
bool Catalog::save()
{
return saveToUrl(d._filePath);
}
//this function is not called if QUndoStack::isClean() !
bool Catalog::saveToUrl(QString localFilePath)
{
if (Q_UNLIKELY(!m_storage))
return true;
bool nameChanged = localFilePath.length();
if (Q_LIKELY(!nameChanged))
localFilePath = d._filePath;
QString localPath = QFileInfo(localFilePath).absolutePath();
if (!QFileInfo::exists(localPath))
if (!QDir::root().mkpath(localPath))
return false;
QFile file(localFilePath);
if (Q_UNLIKELY(!file.open(QIODevice::WriteOnly))) //i18n("Wasn't able to open file %1",filename.ascii());
return false;
bool belongsToProject = localFilePath.contains(Project::instance()->poDir());
if (Q_UNLIKELY(!m_storage->save(&file, belongsToProject)))
return false;
file.close();
d._autoSave->remove();
d._autoSaveRecovered = false;
setClean(); //undo/redo
if (nameChanged) {
d._filePath = localFilePath;
d._autoSave->setManagedFile(QUrl::fromLocalFile(localFilePath));
}
//Settings::self()->setCurrentGroup("Bookmarks");
//Settings::self()->addItemIntList(d._filePath.url(),d._bookmarkIndex);
emit signalFileSaved();
emit signalFileSaved(localFilePath);
return true;
/*
else if (status==NO_PERMISSIONS)
{
if (KMessageBox::warningContinueCancel(this,
i18n("You do not have permission to write to file:\n%1\n"
"Do you want to save to another file or cancel?", _currentURL.prettyUrl()),
i18n("Error"),KStandardGuiItem::save())==KMessageBox::Continue)
return fileSaveAs();
}
*/
}
void Catalog::doAutoSave()
{
if (isClean() || !(d._autoSaveDirty))
return;
if (Q_UNLIKELY(!m_storage))
return;
if (!d._autoSave->open(QIODevice::WriteOnly)) {
emit signalFileAutoSaveFailed(d._autoSave->fileName());
return;
}
qCInfo(LOKALIZE_LOG) << "doAutoSave" << d._autoSave->fileName();
m_storage->save(d._autoSave);
d._autoSave->close();
d._autoSaveDirty = false;
}
void Catalog::projectConfigChanged()
{
setActivePhase(activePhase(), Project::local()->role());
}
QByteArray Catalog::contents()
{
QBuffer buf;
buf.open(QIODevice::WriteOnly);
m_storage->save(&buf);
buf.close();
return buf.data();
}
//END OPEN/SAVE
/**
* helper method to keep db in a good shape :)
* called on
* 1) entry switch
* 2) automatic editing code like replace or undo/redo operation
**/
static void updateDB(
const QString& filePath,
const QString& ctxt,
const CatalogString& english,
const CatalogString& newTarget,
int form,
bool approved,
const QString& dbName
//const DocPosition&,//for back tracking
)
{
TM::UpdateJob* j = new TM::UpdateJob(filePath, ctxt, english, newTarget, form, approved,
dbName);
TM::threadPool()->start(j);
}
//BEGIN UNDO/REDO
const DocPosition& Catalog::undo()
{
QUndoStack::undo();
return d._lastModifiedPos;
}
const DocPosition& Catalog::redo()
{
QUndoStack::redo();
return d._lastModifiedPos;
}
void Catalog::flushUpdateDBBuffer()
{
if (!Settings::autoaddTM())
return;
DocPosition pos = d._lastModifiedPos;
if (pos.entry == -1 || pos.entry >= numberOfEntries()) {
//nothing to flush
//qCWarning(LOKALIZE_LOG)<<"nothing to flush or new file opened";
return;
}
QString dbName;
if (Project::instance()->targetLangCode() == targetLangCode()) {
dbName = Project::instance()->projectID();
} else {
dbName = sourceLangCode() % '-' % targetLangCode();
qCInfo(LOKALIZE_LOG) << "updating" << dbName << "because target language of project db does not match" << Project::instance()->targetLangCode() << targetLangCode();
if (!TM::DBFilesModel::instance()->m_configurations.contains(dbName)) {
TM::OpenDBJob* openDBJob = new TM::OpenDBJob(dbName, TM::Local, true);
connect(openDBJob, &TM::OpenDBJob::done, TM::DBFilesModel::instance(), &TM::DBFilesModel::updateProjectTmIndex);
openDBJob->m_setParams = true;
openDBJob->m_tmConfig.markup = Project::instance()->markup();
openDBJob->m_tmConfig.accel = Project::instance()->accel();
openDBJob->m_tmConfig.sourceLangCode = sourceLangCode();
openDBJob->m_tmConfig.targetLangCode = targetLangCode();
TM::DBFilesModel::instance()->openDB(openDBJob);
}
}
int form = -1;
if (isPlural(pos.entry))
form = pos.form;
updateDB(url(),
context(pos.entry).first(),
sourceWithTags(pos),
targetWithTags(pos),
form,
isApproved(pos.entry),
dbName);
d._lastModifiedPos = DocPosition();
}
void Catalog::setLastModifiedPos(const DocPosition& pos)
{
if (pos.entry >= numberOfEntries()) //bin-units
return;
bool entryChanged = DocPos(d._lastModifiedPos) != DocPos(pos);
if (entryChanged)
flushUpdateDBBuffer();
d._lastModifiedPos = pos;
}
bool CatalogPrivate::addToEmptyIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos, bool alreadyEmpty)
{
if ((!pos.offset) && (storage->target(pos).isEmpty()) && (!alreadyEmpty)) {
insertInList(_emptyIndex, pos.entry);
return true;
}
return false;
}
void Catalog::targetDelete(const DocPosition& pos, int count)
{
if (Q_UNLIKELY(!m_storage))
return;
bool alreadyEmpty = m_storage->isEmpty(pos);
m_storage->targetDelete(pos, count);
if (d.addToEmptyIndexIfAppropriate(m_storage, pos, alreadyEmpty))
emit signalNumberOfEmptyChanged();
emit signalEntryModified(pos);
}
bool CatalogPrivate::removeFromUntransIndexIfAppropriate(CatalogStorage* storage, const DocPosition& pos)
{
if ((!pos.offset) && (storage->isEmpty(pos))) {
_emptyIndex.removeAll(pos.entry);
return true;
}
return false;
}
void Catalog::targetInsert(const DocPosition& pos, const QString& arg)
{
if (Q_UNLIKELY(!m_storage))
return;
if (d.removeFromUntransIndexIfAppropriate(m_storage, pos))
emit signalNumberOfEmptyChanged();
m_storage->targetInsert(pos, arg);
emit signalEntryModified(pos);
}
void Catalog::targetInsertTag(const DocPosition& pos, const InlineTag& tag)
{
if (Q_UNLIKELY(!m_storage))
return;
if (d.removeFromUntransIndexIfAppropriate(m_storage, pos))
emit signalNumberOfEmptyChanged();
m_storage->targetInsertTag(pos, tag);
emit signalEntryModified(pos);
}
InlineTag Catalog::targetDeleteTag(const DocPosition& pos)
{
if (Q_UNLIKELY(!m_storage))
return InlineTag();
bool alreadyEmpty = m_storage->isEmpty(pos);
InlineTag tag = m_storage->targetDeleteTag(pos);
if (d.addToEmptyIndexIfAppropriate(m_storage, pos, alreadyEmpty))
emit signalNumberOfEmptyChanged();
emit signalEntryModified(pos);
return tag;
}
void Catalog::setTarget(DocPosition pos, const CatalogString& s)
{
//TODO for case of markup present
m_storage->setTarget(pos, s.string);
}
TargetState Catalog::setState(const DocPosition& pos, TargetState state)
{
bool extendedStates = m_storage && m_storage->capabilities()&ExtendedStates;
bool approved =::isApproved(state, activePhaseRole());
if (Q_UNLIKELY(!m_storage
|| (extendedStates && m_storage->state(pos) == state)
|| (!extendedStates && m_storage->isApproved(pos) == approved)))
return this->state(pos);
TargetState prevState;
if (extendedStates) {
prevState = m_storage->setState(pos, state);
d._statesIndex[prevState].removeAll(pos.entry);
insertInList(d._statesIndex[state], pos.entry);
} else {
prevState = closestState(!approved, activePhaseRole());
m_storage->setApproved(pos, approved);
}
if (!approved)
{
insertInList(d._nonApprovedIndex, pos.entry);
if (!m_storage->isEmpty(pos))
insertInList(d._nonApprovedNonEmptyIndex, pos.entry);
}
else
{
d._nonApprovedIndex.removeAll(pos.entry);
d._nonApprovedNonEmptyIndex.removeAll(pos.entry);
}
emit signalNumberOfFuzziesChanged();
emit signalEntryModified(pos);
return prevState;
}
Phase Catalog::updatePhase(const Phase& phase)
{
return m_storage->updatePhase(phase);
}
void Catalog::setEquivTrans(const DocPosition& pos, bool equivTrans)
{
if (m_storage) m_storage->setEquivTrans(pos, equivTrans);
}
bool Catalog::setModified(DocPos entry, bool modified)
{
if (modified) {
if (d._modifiedEntries.contains(entry))
return false;
d._modifiedEntries.insert(entry);
} else
d._modifiedEntries.remove(entry);
return true;
}
bool Catalog::isModified(DocPos entry) const
{
return d._modifiedEntries.contains(entry);
}
bool Catalog::isModified(int entry) const
{
if (!isPlural(entry))
return isModified(DocPos(entry, 0));
int f = numberOfPluralForms();
while (--f >= 0)
if (isModified(DocPos(entry, f)))
return true;
return false;
}
//END UNDO/REDO
int findNextInList(const QLinkedList& list, int index)
{
int nextIndex = -1;
foreach (int key, list) {
if (Q_UNLIKELY(key > index)) {
nextIndex = key;
break;
}
}
return nextIndex;
}
int findPrevInList(const QLinkedList& list, int index)
{
int prevIndex = -1;
foreach (int key, list) {
if (Q_UNLIKELY(key >= index))
break;
prevIndex = key;
}
return prevIndex;
}
void insertInList(QLinkedList& list, int index)
{
QLinkedList::Iterator it = list.begin();
while (it != list.end() && index > *it)
++it;
list.insert(it, index);
}
void Catalog::setBookmark(uint idx, bool set)
{
if (set)
insertInList(d._bookmarkIndex, idx);
else
d._bookmarkIndex.removeAll(idx);
}
bool isApproved(TargetState state, ProjectLocal::PersonRole role)
{
static const TargetState marginStates[] = {Translated, Final, SignedOff};
return state >= marginStates[role];
}
bool isApproved(TargetState state)
{
static const TargetState marginStates[] = {Translated, Final, SignedOff};
return state == marginStates[0] || state == marginStates[1] || state == marginStates[2];
}
TargetState closestState(bool approved, ProjectLocal::PersonRole role)
{
Q_ASSERT(role != ProjectLocal::Undefined);
static const TargetState approvementStates[][3] = {
{NeedsTranslation, NeedsReviewTranslation, NeedsReviewTranslation},
{Translated, Final, SignedOff}
};
return approvementStates[approved][role];
}
bool Catalog::isObsolete(int entry) const
{
if (Q_UNLIKELY(!m_storage))
return false;
return m_storage->isObsolete(entry);
}
QString Catalog::originalOdfFilePath()
{
if (Q_UNLIKELY(!m_storage))
return QString();
return m_storage->originalOdfFilePath();
}
void Catalog::setOriginalOdfFilePath(const QString& odfFilePath)
{
if (Q_UNLIKELY(!m_storage))
return;
m_storage->setOriginalOdfFilePath(odfFilePath);
}
diff --git a/src/catalog/catalog.h b/src/catalog/catalog.h
index cca7666..8180f4a 100644
--- a/src/catalog/catalog.h
+++ b/src/catalog/catalog.h
@@ -1,369 +1,370 @@
/*****************************************************************************
This file is part of Lokalize
This file contains parts of KBabel code
Copyright (C) 1999-2000 by Matthias Kiefer
2001-2004 by Stanislav Visnovsky
2007-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#ifndef CATALOG_H
#define CATALOG_H
#include "pos.h"
#include "catalogstring.h"
#include "catalogcapabilities.h"
#include "note.h"
#include "state.h"
#include "phase.h"
#include "alttrans.h"
#include "catalog_private.h"
class CatalogStorage;
class MassReplaceJob;
class KAutoSaveFile;
#include
namespace GettextCatalog
{
class CatalogImportPlugin;
class CatalogExportPlugin;
}
bool isApproved(TargetState state, ProjectLocal::PersonRole role);
bool isApproved(TargetState state); //disregarding Phase
TargetState closestState(bool approved, ProjectLocal::PersonRole role);
int findPrevInList(const QLinkedList& list, int index);
int findNextInList(const QLinkedList& list, int index);
void insertInList(QLinkedList& list, int index); // insert index in the right place in the list
/**
* This class represents a catalog
* It uses CatalogStorage interface to work with catalogs in different formats
* Also it defines all necessary functions to set and get the entries
*
* @short Wrapper class that represents a translation catalog
* @author Nick Shaforostoff
*/
class Catalog: public QUndoStack
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.Lokalize.FileContainer")
public:
explicit Catalog(QObject* parent);
virtual ~Catalog();
QString msgid(const DocPosition&) const;
virtual QString msgstr(const DocPosition&) const;
QString msgidWithPlurals(const DocPosition&, bool truncateFirstLine) const;
QString msgstrWithPlurals(const DocPosition&, bool truncateFirstLine) const;
static QStringList supportedExtensions();
static bool extIsSupported(const QString& path);
static const char* const* states();
int capabilities() const;
void push(QUndoCommand* cmd);
public slots: //DBus interface
QString source(const DocPosition& pos) const
{
return msgid(pos);
}
QString target(const DocPosition& pos) const
{
return msgstr(pos);
}
// used by XLIFF storage)
CatalogString sourceWithTags(const DocPosition& pos) const;
CatalogString targetWithTags(const DocPosition& pos) const;
CatalogString catalogString(const DocPosition& pos) const;
/**
* @a pos.form is note number
* @returns previous note contents, if any
*/
Note setNote(const DocPosition& pos, const Note& note);
QVector notes(const DocPosition& pos) const;
QVector developerNotes(const DocPosition& pos) const;
QStringList noteAuthors() const;
QVector altTrans(const DocPosition& pos) const;
QStringList sourceFiles(const DocPosition& pos) const;
//QString msgctxt(uint index) const;
//the result is guaranteed to have at least 1 string
QStringList context(const DocPosition& pos) const;
QString id(const DocPosition& pos) const;
///@returns previous phase-name
QString setPhase(const DocPosition& pos, const QString& phase);
QString phase(const DocPosition& pos) const;
QString activePhase() const
{
return d._phase;
}
ProjectLocal::PersonRole activePhaseRole() const
{
return d._phaseRole;
}
void setActivePhase(const QString& phase, ProjectLocal::PersonRole role = ProjectLocal::Approver);
Phase phase(const QString& name) const;
QList allPhases() const;
QMap allTools() const;
QVector phaseNotes(const QString& phase) const;
///@arg pos.entry - number of phase, @arg pos.form - number of note
QVector setPhaseNotes(const QString& phase, QVector);
bool isPlural(uint index) const;
bool isPlural(const DocPosition& pos) const
{
return isPlural(pos.entry);
}
bool isApproved(uint index) const;
bool isApproved(const DocPosition& pos) const
{
return isApproved(pos.entry);
}
TargetState state(const DocPosition& pos) const;
bool isEquivTrans(const DocPosition&) const;
///@returns true if at least one form is untranslated
bool isEmpty(uint index) const;
bool isEmpty(const DocPosition&) const;
bool isModified(DocPos entry) const;
bool isModified(int entry) const;
bool isObsolete(int entry) const;
/// so DocPosition::entry may actually be < size()+binUnitsCount()
int binUnitsCount() const;
int unitById(const QString& id) const;
bool isBookmarked(uint index) const
{
return d._bookmarkIndex.contains(index);
}
void setBookmark(uint, bool);
int numberOfPluralForms() const
{
return d._numberOfPluralForms;
}
int numberOfEntries() const;
int numberOfNonApproved() const
{
return d._nonApprovedNonEmptyIndex.size();
}
int numberOfUntranslated() const
{
return d._emptyIndex.size();
}
public:
QString originalOdfFilePath();
void setOriginalOdfFilePath(const QString&);
int firstFuzzyIndex() const
{
return d._nonApprovedIndex.isEmpty() ? numberOfEntries() : d._nonApprovedIndex.first();
}
int lastFuzzyIndex() const
{
return d._nonApprovedIndex.isEmpty() ? -1 : d._nonApprovedIndex.last();
}
int nextFuzzyIndex(uint index) const
{
return findNextInList(d._nonApprovedIndex, index);
}
int prevFuzzyIndex(uint index) const
{
return findPrevInList(d._nonApprovedIndex, index);
}
int firstUntranslatedIndex() const
{
return d._emptyIndex.isEmpty() ? numberOfEntries() : d._emptyIndex.first();
}
int lastUntranslatedIndex() const
{
return d._emptyIndex.isEmpty() ? -1 : d._emptyIndex.last();
}
int nextUntranslatedIndex(uint index) const
{
return findNextInList(d._emptyIndex, index);
}
int prevUntranslatedIndex(uint index) const
{
return findPrevInList(d._emptyIndex, index);
}
int firstBookmarkIndex() const
{
return d._bookmarkIndex.isEmpty() ? numberOfEntries() : d._bookmarkIndex.first();
}
int lastBookmarkIndex() const
{
return d._bookmarkIndex.isEmpty() ? -1 : d._bookmarkIndex.last();
}
int nextBookmarkIndex(uint index) const
{
return findNextInList(d._bookmarkIndex, index);
}
int prevBookmarkIndex(uint index) const
{
return findPrevInList(d._bookmarkIndex, index);
}
bool autoSaveRecovered()
{
return d._autoSaveRecovered;
}
public:
void clear();
bool isEmpty()
{
return !m_storage;
}
bool isReadOnly()
{
return d._readOnly;
}
void attachAltTransCatalog(Catalog*);
void attachAltTrans(int entry, const AltTrans& trans);
virtual const DocPosition& undo();
virtual const DocPosition& redo();
void setTarget(DocPosition pos, const CatalogString& s); //for batch use only!
//void setErrorIndex(const QList& errors){d._errorIndex=errors;}
void setUrl(const QString& u)
{
d._filePath = u; //used for template load
}
public slots: //DBus interface
const QString& url() const
{
return d._filePath;
}
///@returns 0 if success, >0 erroneous line (parsing error)
int loadFromUrl(const QString& url, const QString& saidUrl = QString(), int* fileSize = nullptr, bool fast = false);
bool saveToUrl(QString url);
bool save();
QByteArray contents();
QString mimetype();
QString fileType();
CatalogType type();
QString sourceLangCode() const;
QString targetLangCode() const;
void setTargetLangCode(const QString& targetLangCode);
/**
* updates DB for _posBuffer and accompanying _originalForLastModified
*/
void flushUpdateDBBuffer();
protected:
virtual KAutoSaveFile* checkAutoSave(const QString& url);
protected slots:
void doAutoSave();
void setAutoSaveDirty()
{
d._autoSaveDirty = true;
}
void projectConfigChanged();
protected:
/**
* (EDITING)
* accessed from undo/redo code
* called _BEFORE_ modification
*/
void setLastModifiedPos(const DocPosition&);
/**
* (EDITING)
* accessed from undo/redo code
* accessed from mergeCatalog)
* it _does_ check if action should be taken
*/
void setApproved(const DocPosition& pos, bool approved);
void targetDelete(const DocPosition& pos, int count);
void targetInsert(const DocPosition& pos, const QString& arg);
InlineTag targetDeleteTag(const DocPosition& pos);
void targetInsertTag(const DocPosition& pos, const InlineTag& tag);
TargetState setState(const DocPosition& pos, TargetState state);
Phase updatePhase(const Phase& phase);
void setEquivTrans(const DocPosition&, bool equivTrans);
/// @returns true if entry wasn't modified before
bool setModified(DocPos entry, bool modif);
void updateApprovedEmptyIndexCache();
protected:
CatalogPrivate d;
CatalogStorage *m_storage;
friend class GettextCatalog::CatalogImportPlugin;
friend class GettextCatalog::CatalogExportPlugin;
friend class LokalizeUnitCmd;
friend class InsTextCmd;
friend class DelTextCmd;
friend class InsTagCmd;
friend class DelTagCmd;
friend class SetStateCmd;
friend class SetNoteCmd;
friend class UpdatePhaseCmd;
friend class MergeCatalog;
friend class SetEquivTransCmd;
friend class MassReplaceJob;
public:
//static QString supportedMimeFilters;
static QString supportedFileTypes(bool includeTemplates = true);
signals:
void signalEntryModified(const DocPosition&);
void activePhaseChanged();
void signalNumberOfFuzziesChanged();
void signalNumberOfEmptyChanged();
Q_SCRIPTABLE void signalFileLoaded();
void signalFileLoaded(const QString&);
Q_SCRIPTABLE void signalFileSaved();
void signalFileSaved(const QString&);
void signalFileAutoSaveFailed(const QString&);
};
#endif
diff --git a/src/catalog/catalog_private.h b/src/catalog/catalog_private.h
index c638f9c..182994c 100644
--- a/src/catalog/catalog_private.h
+++ b/src/catalog/catalog_private.h
@@ -1,126 +1,127 @@
/* ****************************************************************************
This file is part of Lokalize
This file is based on the one from KBabel
Copyright (C) 1999-2000 by Matthias Kiefer
2001-2004 by Stanislav Visnovsky
2007 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#ifndef CATALOGPRIVATE_H
#define CATALOGPRIVATE_H
#include "projectlocal.h"
#include "state.h"
#include "pos.h"
#include "alttrans.h"
#include
#include
#include
#include
#include
#include
#include
#include
class QTextCodec;
class CatalogStorage;
class Catalog;
class CatalogPrivate
{
public:
/** url of the po-file, that belongs to this catalog */
QString _filePath;
QString _packageName;
QString _packageDir;
/** identification string for used import filter*/
QString _importID;
QTextCodec *fileCodec;
int _numberOfPluralForms;
QTimer _autoSaveTimer;
KAutoSaveFile* _autoSave;
bool _autoSaveDirty;
bool _autoSaveRecovered;
bool _readOnly;
//for wrapping
short _maxLineLength;
QLinkedList _nonApprovedIndex;
QLinkedList _nonApprovedNonEmptyIndex;
QLinkedList _emptyIndex;
QLinkedList _errorIndex;
QLinkedList _bookmarkIndex;
QVector< QLinkedList > _statesIndex;
QLinkedList _altTransCatalogs;
QMap _altTranslations;
//for undo/redo
//keeps pos of the entry that was last modified
DocPosition _lastModifiedPos;
QSet _modifiedEntries;//just for the nice gui
QString _phase;
ProjectLocal::PersonRole _phaseRole;
explicit CatalogPrivate(QObject* parent)
: fileCodec(0)
, _numberOfPluralForms(-1)
, _autoSave(new KAutoSaveFile(parent))
, _autoSaveDirty(true)
, _autoSaveRecovered(false)
, _readOnly(false)
, _maxLineLength(80)
, _phaseRole(ProjectLocal::Undefined)
{
Q_UNUSED(parent)
_statesIndex.resize(StateCount);
}
bool addToEmptyIndexIfAppropriate(CatalogStorage*, const DocPosition& pos, bool alreadyEmpty);
bool removeFromUntransIndexIfAppropriate(CatalogStorage*, const DocPosition& pos);
};
#endif //CatalogPrivate_H
diff --git a/src/catalog/catalogcapabilities.h b/src/catalog/catalogcapabilities.h
index 041ae82..615c391 100644
--- a/src/catalog/catalogcapabilities.h
+++ b/src/catalog/catalogcapabilities.h
@@ -1,41 +1,42 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2009 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef CATALOGCAPABILITIES_H
#define CATALOGCAPABILITIES_H
enum CatalogCapabilities {
KeepsNoteAuthors = 1,
MultipleNotes = 2,
Phases = 4,
ExtendedStates = 8,
Tags = 16
};
enum CatalogType {
Gettext,
Xliff,
Ts
};
#endif
diff --git a/src/catalog/catalogstorage.h b/src/catalog/catalogstorage.h
index c118755..ca86e84 100644
--- a/src/catalog/catalogstorage.h
+++ b/src/catalog/catalogstorage.h
@@ -1,274 +1,275 @@
/*
Copyright 2008-2009 Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef CATALOGSTORAGE_H
#define CATALOGSTORAGE_H
#include "pos.h"
#include "catalogstring.h"
#include "note.h"
#include "state.h"
#include "phase.h"
#include "alttrans.h"
#include "catalogcapabilities.h"
#include
#include
/**
* Abstract interface for storage of translation file
*
* format-specific elements like \" for gettext PO should be eliminated
*
* @short Abstract interface for storage of translation file
* @author Nick Shaforostoff
*/
class CatalogStorage
{
public:
CatalogStorage();
virtual ~CatalogStorage();
virtual int capabilities() const = 0;
virtual int load(QIODevice* device) = 0;
virtual bool save(QIODevice* device, bool belongsToProject = false) = 0;
virtual int size() const = 0;
int numberOfEntries()const
{
return size();
}
int numberOfPluralForms() const
{
return m_numberOfPluralForms;
}
/**
* flat-model interface (ignores XLIFF grouping)
*
* format-specific texts like \" for gettext PO should be eliminated
**/
virtual QString source(const DocPosition& pos) const = 0;
virtual QString target(const DocPosition& pos) const = 0;
virtual QString sourceWithPlurals(const DocPosition& pos, bool truncateFirstLine) const = 0;
virtual QString targetWithPlurals(const DocPosition& pos, bool truncateFirstLine) const = 0;
virtual CatalogString sourceWithTags(DocPosition pos) const = 0;
virtual CatalogString targetWithTags(DocPosition pos) const = 0;
virtual CatalogString catalogString(const DocPosition& pos) const = 0;
/**
* edit operations used by undo/redo system and sync-mode
**/
virtual void targetDelete(const DocPosition& pos, int count) = 0;
virtual void targetInsert(const DocPosition& pos, const QString& arg) = 0;
virtual void setTarget(const DocPosition& pos, const QString& arg) = 0; //called for mergeCatalog TODO switch to CatalogString
virtual void targetInsertTag(const DocPosition&, const InlineTag&) {}
virtual InlineTag targetDeleteTag(const DocPosition&)
{
return InlineTag();
}
virtual Phase updatePhase(const Phase&)
{
return Phase();
}
virtual QList allPhases() const
{
return QList();
}
virtual QMap allTools() const
{
return QMap();
}
/// all plural forms. pos.form doesn't matter
virtual QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const = 0;
virtual QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const = 0;
virtual QVector altTrans(const DocPosition& pos) const = 0;
virtual QVector notes(const DocPosition& pos) const = 0;
virtual Note setNote(DocPosition pos, const Note& note) = 0;
virtual QStringList noteAuthors() const
{
return QStringList();
}
virtual QVector developerNotes(const DocPosition& pos) const = 0;
virtual QStringList sourceFiles(const DocPosition& pos) const = 0;
virtual QString setPhase(const DocPosition& pos, const QString& phase)
{
Q_UNUSED(pos);
Q_UNUSED(phase);
return QString();
}
virtual QString phase(const DocPosition& pos) const
{
Q_UNUSED(pos);
return QString();
}
virtual Phase phase(const QString& name) const
{
Q_UNUSED(name);
return Phase();
}
virtual QVector phaseNotes(const QString& phase) const
{
Q_UNUSED(phase);
return QVector();
}
virtual QVector setPhaseNotes(const QString& phase, QVector notes)
{
Q_UNUSED(phase);
Q_UNUSED(notes);
return QVector();
}
//the result must be guaranteed to have at least 1 string
virtual QStringList context(const DocPosition&) const = 0;
//DocPosition.form - number of
//virtual QString context(const DocPosition&) const=0;
//virtual int contextCount(const DocPosition&) const=0;
/**
* user-invisible data for matching, e.g. during TM database lookup
* it is comprised of several strings
*
* database stores them and thus it is possible to
* fuzzy-match 'matchData' later
*
* it is responsibility of CatalogStorage implementations to
* separate/assemble the list properly according to the format specifics
*
* pos.form doesn't matter
**/
virtual QStringList matchData(const DocPosition&) const = 0;
/**
* entry id unique for this file
*
* pos.form doesn't matter
**/
virtual QString id(const DocPosition&) const = 0;
virtual bool isPlural(const DocPosition&) const = 0;
virtual bool isEmpty(const DocPosition&) const = 0;
virtual bool isEquivTrans(const DocPosition&) const
{
return true;
}
virtual void setEquivTrans(const DocPosition&, bool equivTrans)
{
Q_UNUSED(equivTrans)
}
virtual bool isApproved(const DocPosition&) const
{
return true;
}
virtual void setApproved(const DocPosition&, bool approved)
{
Q_UNUSED(approved)
}
virtual TargetState state(const DocPosition&) const
{
return New;
}
virtual TargetState setState(const DocPosition&, TargetState)
{
return New;
}
virtual bool isObsolete(int entry) const
{
Q_UNUSED(entry) return false;
}
virtual bool isTranslateable(int entry) const
{
Q_UNUSED(entry) return true;
}
virtual int binUnitsCount() const
{
return 0;
}
virtual int unitById(const QString& id) const
{
Q_UNUSED(id);
return 0;
}
const QString& url() const
{
return m_url;
}
void setUrl(const QString& u)
{
m_url = u; //TODO
}
virtual QString mimetype() const = 0;
virtual QString fileType() const = 0;
virtual CatalogType type() const = 0;
virtual QString originalOdfFilePath()
{
return QString();
}
virtual void setOriginalOdfFilePath(const QString&) {}
QString sourceLangCode() const
{
return m_sourceLangCode;
}
QString targetLangCode() const
{
return m_targetLangCode;
}
virtual void setTargetLangCode(const QString& langCode)
{
m_targetLangCode = langCode;
}
protected:
QString m_url;
QString m_sourceLangCode;
QString m_targetLangCode;
int m_numberOfPluralForms;
};
inline CatalogStorage::CatalogStorage()
: m_sourceLangCode(QStringLiteral("en_US"))
, m_numberOfPluralForms(0)
{
}
inline CatalogStorage::~CatalogStorage()
{
}
#endif
diff --git a/src/catalog/catalogstring.cpp b/src/catalog/catalogstring.cpp
index 8513d6d..e3bdf80 100644
--- a/src/catalog/catalogstring.cpp
+++ b/src/catalog/catalogstring.cpp
@@ -1,325 +1,326 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2008-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#include "catalogstring.h"
#include "lokalize_debug.h"
#include
const char* InlineTag::getElementName(InlineElement type)
{
static const char* inlineElementNames[(int)InlineElementCount] = {
"_unknown",
"bpt",
"ept",
"ph",
"it",
//"_NEVERSHOULDBECHOSEN",
"mrk",
"g",
"sub",
"_NEVERSHOULDBECHOSEN",
"x",
"bx",
"ex"
};
return inlineElementNames[(int)type];
}
InlineTag InlineTag::getPlaceholder() const
{
InlineTag tagRange = *this;
tagRange.start = -1;
tagRange.end = -1;
return tagRange;
}
InlineTag::InlineElement InlineTag::getElementType(const QByteArray& tag)
{
int i = InlineTag::InlineElementCount;
while (--i > 0)
if (getElementName(InlineElement(i)) == tag)
break;
return InlineElement(i);
}
QString InlineTag::displayName() const
{
static const char* inlineElementNames[(int)InlineElementCount] = {
"_unknown",
I18N_NOOP2("XLIFF inline tag name", "Start of paired tag"),
I18N_NOOP2("XLIFF inline tag name", "End of paired tag"),
I18N_NOOP2("XLIFF inline tag name", "Stand-alone tag"),
I18N_NOOP2("XLIFF inline tag name", "Isolated tag"),
//"_NEVERSHOULDBECHOSEN",
I18N_NOOP2("XLIFF inline tag name", "Marker"),
I18N_NOOP2("XLIFF inline tag name", "Generic group placeholder"),
I18N_NOOP2("XLIFF inline tag name", "Sub-flow"),
"_NEVERSHOULDBECHOSEN",
I18N_NOOP2("XLIFF inline tag name", "Generic placeholder"),
I18N_NOOP2("XLIFF inline tag name", "Start of paired placeholder"),
I18N_NOOP2("XLIFF inline tag name", "End of paired placeholder")
};
QString result = i18nc("XLIFF inline tag name", inlineElementNames[type]);
if (type == mrk) {
static const char* mrkTypes[] = {
"abbrev",
"abbreviated-form",
"abbreviation",
"acronym",
"appellation",
"collocation",
"common-name",
"datetime",
"equation",
"expanded-form",
"formula",
"head-term",
"initialism",
"international-scientific-term",
"internationalism",
"logical-expression",
"materials-management-unit",
"name",
"near-synonym",
"part-number",
"phrase",
"phraseological-unit",
"protected",
"romanized-form",
"seg",
"set-phrase",
"short-form",
"sku",
"standard-text",
"symbol",
"synonym",
"synonymous-phrase",
"term",
"transcribed-form",
"transliterated-form",
"truncated-term",
"variant"
};
static const char* mrkTypeNames[] = {
I18N_NOOP2("XLIFF mark type", "abbreviation"),
I18N_NOOP2("XLIFF mark type", "abbreviated form: a term resulting from the omission of any part of the full term while designating the same concept"),
I18N_NOOP2("XLIFF mark type", "abbreviation: an abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective')"),
I18N_NOOP2("XLIFF mark type", "acronym: an abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging')"),
I18N_NOOP2("XLIFF mark type", "appellation: a proper-name term, such as the name of an agency or other proper entity"),
I18N_NOOP2("XLIFF mark type", "collocation: a recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another"),
I18N_NOOP2("XLIFF mark type", "common name: a synonym for an international scientific term that is used in general discourse in a given language"),
I18N_NOOP2("XLIFF mark type", "date and/or time"),
I18N_NOOP2("XLIFF mark type", "equation: an expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign"),
I18N_NOOP2("XLIFF mark type", "expanded form: The complete representation of a term for which there is an abbreviated form"),
I18N_NOOP2("XLIFF mark type", "formula: figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula"),
I18N_NOOP2("XLIFF mark type", "head term: the concept designation that has been chosen to head a terminological record"),
I18N_NOOP2("XLIFF mark type", "initialism: an abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy')"),
I18N_NOOP2("XLIFF mark type", "international scientific term: a term that is part of an international scientific nomenclature as adopted by an appropriate scientific body"),
I18N_NOOP2("XLIFF mark type", "internationalism: a term that has the same or nearly identical orthographic or phonemic form in many languages"),
I18N_NOOP2("XLIFF mark type", "logical expression: an expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like"),
I18N_NOOP2("XLIFF mark type", "materials management unit: a unit to track object"),
I18N_NOOP2("XLIFF mark type", "name"),
I18N_NOOP2("XLIFF mark type", "near synonym: a term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others"),
I18N_NOOP2("XLIFF mark type", "part number: a unique alphanumeric designation assigned to an object in a manufacturing system"),
I18N_NOOP2("XLIFF mark type", "phrase"),
I18N_NOOP2("XLIFF mark type", "phraseological: a group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase"),
I18N_NOOP2("XLIFF mark type", "protected: the marked text should not be translated"),
I18N_NOOP2("XLIFF mark type", "romanized form: a form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet"),
I18N_NOOP2("XLIFF mark type", "segment: the marked text represents a segment"),
I18N_NOOP2("XLIFF mark type", "set phrase: a fixed, lexicalized phrase"),
I18N_NOOP2("XLIFF mark type", "short form: a variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs')"),
I18N_NOOP2("XLIFF mark type", "stock keeping unit: an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system"),
I18N_NOOP2("XLIFF mark type", "standard text: a fixed chunk of recurring text"),
I18N_NOOP2("XLIFF mark type", "symbol: a designation of a concept by letters, numerals, pictograms or any combination thereof"),
I18N_NOOP2("XLIFF mark type", "synonym: a term that represents the same or a very similar concept as the main entry term in a term entry"),
I18N_NOOP2("XLIFF mark type", "synonymous phrase: phraseological unit in a language that expresses the same semantic content as another phrase in that same language"),
I18N_NOOP2("XLIFF mark type", "term"),
I18N_NOOP2("XLIFF mark type", "transcribed form: a form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted"),
I18N_NOOP2("XLIFF mark type", "transliterated form: a form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system"),
I18N_NOOP2("XLIFF mark type", "truncated term: an abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza')"),
I18N_NOOP2("XLIFF mark type", "variant: one of the alternate forms of a term")
};
int i = sizeof(mrkTypes) / sizeof(char*);
while (--i >= 0 && mrkTypes[i] != id)
;
if (i != -1) {
result = i18nc("XLIFF mark type", mrkTypeNames[i]);
if (!result.isEmpty())
result[0] = result.at(0).toUpper();
}
}
if (!ctype.isEmpty())
result += " (" + ctype + ')';
return result;
}
QMap CatalogString::tagIdToIndex() const
{
QMap result;
int index = 0;
int count = tags.size();
for (int i = 0; i < count; ++i) {
if (!result.contains(tags.at(i).id))
result.insert(tags.at(i).id, index++);
}
return result;
}
QByteArray CatalogString::tagsAsByteArray()const
{
QByteArray result;
if (tags.size()) {
QDataStream stream(&result, QIODevice::WriteOnly);
stream << tags;
}
return result;
}
CatalogString::CatalogString(QString str, QByteArray tagsByteArray)
: string(str)
{
if (tagsByteArray.size()) {
QDataStream stream(tagsByteArray);
stream >> tags;
}
}
static void adjustTags(QList& tags, int position, int value)
{
int i = tags.size();
while (--i >= 0) {
InlineTag& t = tags[i];
if (t.start > position)
t.start += value;
if (t.end >= position) //cases when strict > is needed?
t.end += value;
}
}
void CatalogString::remove(int position, int len)
{
string.remove(position, len);
adjustTags(tags, position, -len);
}
void CatalogString::insert(int position, const QString& str)
{
string.insert(position, str);
adjustTags(tags, position, str.size());
}
QDataStream &operator<<(QDataStream &out, const InlineTag &t)
{
return out << int(t.type) << t.start << t.end << t.id;
}
QDataStream &operator>>(QDataStream &in, InlineTag &t)
{
int type;
in >> type >> t.start >> t.end >> t.id;
t.type = InlineTag::InlineElement(type);
return in;
}
QDataStream &operator<<(QDataStream &out, const CatalogString &myObj)
{
return out << myObj.string << myObj.tags;
}
QDataStream &operator>>(QDataStream &in, CatalogString &myObj)
{
return in >> myObj.string >> myObj.tags;
}
void adaptCatalogString(CatalogString& target, const CatalogString& ref)
{
//qCWarning(LOKALIZE_LOG) << "HERE" << target.string;
QHash id2tagIndex;
QMultiMap tagType2tagIndex;
int i = ref.tags.size();
while (--i >= 0) {
const InlineTag& t = ref.tags.at(i);
id2tagIndex.insert(t.id, i);
tagType2tagIndex.insert(t.type, i);
qCWarning(LOKALIZE_LOG) << "inserting" << t.id << t.type << i;
}
QList oldTags = target.tags;
target.tags.clear();
//we actually walking from beginning to end:
qSort(oldTags.begin(), oldTags.end(), qGreater());
i = oldTags.size();
while (--i >= 0) {
const InlineTag& targetTag = oldTags.at(i);
if (id2tagIndex.contains(targetTag.id)) {
qCWarning(LOKALIZE_LOG) << "matched" << targetTag.id << i;
target.tags.append(targetTag);
tagType2tagIndex.remove(targetTag.type, id2tagIndex.take(targetTag.id));
oldTags.removeAt(i);
}
}
//qCWarning(LOKALIZE_LOG) << "HERE 0" << target.string;
//now all the tags left have to ID (exact) matches
i = oldTags.size();
while (--i >= 0) {
InlineTag targetTag = oldTags.at(i);
if (tagType2tagIndex.contains(targetTag.type)) {
//try to match by position
//we're _taking_ first so the next one becomes new 'first' for the next time.
QList possibleRefMatches;
foreach (int i, tagType2tagIndex.values(targetTag.type))
possibleRefMatches << ref.tags.at(i);
qSort(possibleRefMatches);
qCWarning(LOKALIZE_LOG) << "setting id:" << targetTag.id << possibleRefMatches.first().id;
targetTag.id = possibleRefMatches.first().id;
target.tags.append(targetTag);
qCWarning(LOKALIZE_LOG) << "id??:" << targetTag.id << target.tags.first().id;
tagType2tagIndex.remove(targetTag.type, id2tagIndex.take(targetTag.id));
oldTags.removeAt(i);
}
}
//qCWarning(LOKALIZE_LOG) << "HERE 1" << target.string;
//now walk through unmatched tags and properly remove them.
foreach (const InlineTag& tag, oldTags) {
if (tag.isPaired())
target.remove(tag.end, 1);
target.remove(tag.start, 1);
}
//qCWarning(LOKALIZE_LOG) << "HERE 2" << target.string;
}
diff --git a/src/catalog/catalogstring.h b/src/catalog/catalogstring.h
index 31509bf..6ee288c 100644
--- a/src/catalog/catalogstring.h
+++ b/src/catalog/catalogstring.h
@@ -1,183 +1,184 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2008-2009 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef CATALOGSTRING_H
#define CATALOGSTRING_H
#include
#include
#include
#include
//#define TAGRANGE_IMAGE_SYMBOL 65532
#define TAGRANGE_IMAGE_SYMBOL QChar::ObjectReplacementCharacter
/**
* data structure used to pass info about inline elements
* a XLIFF tag is represented by a TAGRANGE_IMAGE_SYMBOL in the 'plainttext'
* and a struct TagRange
*
* describes which tag is behind TAGRANGE_IMAGE_SYMBOL char
* (or chars -- starting and ending) in source or target string
* start==end for non-paired tags
*/
struct InlineTag {
//sub = can contain -flow tag
//recursive = can contain other inline markup tags
///@see http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
enum InlineElement {
_unknown,
bpt, //sub
ept, //sub
ph, //sub
it, //sub
//_subContainersDelimiter,
mrk, //recursive, no id
g, //recursive
sub, //recursive, no id
_pairedXmlTagDelimiter,
x, //empty
bx, //empty
ex, //empty
InlineElementCount
};
int start;
int end;
InlineElement type;
QString id;
QString xid;
QString equivText;
QString ctype;
explicit InlineTag(): start(-1), end(-1), type(_unknown) {}
InlineTag(int start_, int end_, InlineElement type_, QString id_ = QString(), QString xid_ = QString(), QString equivText_ = QString(), QString ctype_ = QString())
: start(start_), end(end_), type(type_), id(id_), xid(xid_), equivText(equivText_), ctype(ctype_) {}
/**
* for situations when target doesn't contain tag
* (of the same type and with the same id) from source
* true means that the object corresponds to some tag in source,
* but target does not contain it.
*
* @see getPlaceholder()
*/
bool isEmpty()const
{
return start == -1;
}
/**
* used to denote tag that doesn't present in target,
* to have parallel numbering in view
*
* @returns TagRange object prototype to be inserted into target
* @see isEmpty()
*/
InlineTag getPlaceholder() const;
///@returns 0 if type is unknown
static InlineElement getElementType(const QByteArray&);
static const char* getElementName(InlineElement type);
const char* getElementName()const
{
return getElementName(type);
}
const char* name()const
{
return getElementName();
}
static bool isPaired(InlineElement type)
{
return type < InlineTag::_pairedXmlTagDelimiter;
}
bool isPaired()const
{
return isPaired(type);
}
QString displayName() const;
bool operator<(const InlineTag& other)const
{
return start < other.start;
}
};
Q_DECLARE_METATYPE(InlineTag)
Q_DECLARE_METATYPE(QList)
/**
* data structure used to pass info about inline elements
* a XLIFF tag is represented by a TAGRANGE_IMAGE_SYMBOL in the 'plainttext'
* and a struct TagRange
*
* string has each XLIFF markup tag represented by 1 symbol
* ranges is set to list describing which tag (type, id) at which position
*/
struct CatalogString {
QString string;
QList tags;
CatalogString() {}
CatalogString(QString str): string(str) {}
CatalogString(QString str, QByteArray tagsByteArray);
QMap tagIdToIndex() const; //assigns same indexes for tags with same ids
QByteArray tagsAsByteArray()const;
void remove(int position, int len);
void insert(int position, const QString& str);
void replace(int position, int len, const QString& str)
{
remove(position, len);
insert(position, str);
}
void clear()
{
string.clear();
tags.clear();
}
bool isEmpty() const
{
return string.isEmpty();
}
};
Q_DECLARE_METATYPE(CatalogString)
#include
QDataStream &operator<<(QDataStream &out, const InlineTag &myObj);
QDataStream &operator>>(QDataStream &in, InlineTag &myObj);
QDataStream &operator<<(QDataStream &out, const CatalogString &myObj);
QDataStream &operator>>(QDataStream &in, CatalogString &myObj);
/// prepares @arg target for using it as @arg ref translation
void adaptCatalogString(CatalogString& target, const CatalogString& ref);
#endif
diff --git a/src/catalog/cmd.cpp b/src/catalog/cmd.cpp
index 35b916a..94d9fc3 100644
--- a/src/catalog/cmd.cpp
+++ b/src/catalog/cmd.cpp
@@ -1,467 +1,468 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#include "cmd.h"
#include "lokalize_debug.h"
#include
#include "catalog_private.h"
#include "catalogitem_private.h"
#include "catalog.h"
#include "project.h"
#include
//BEGIN LokalizeUnitCmd
LokalizeUnitCmd::LokalizeUnitCmd(Catalog *catalog, const DocPosition& pos, const QString& name = QString())
: QUndoCommand(name)
, _catalog(catalog)
, _pos(pos)
, _firstModificationForThisEntry(false)
{}
static QString setPhaseForPart(Catalog* catalog, const QString& phase, DocPosition phasePos, DocPosition::Part part)
{
phasePos.part = part;
return catalog->setPhase(phasePos, phase);
}
void LokalizeUnitCmd::redo()
{
setJumpingPos();
doRedo();
_firstModificationForThisEntry = _catalog->setModified(DocPos(_pos), true);
// _prevPhase=setPhaseForPart(_catalog,_catalog->activePhase(),_pos,DocPosition::UndefPart);
}
void LokalizeUnitCmd::undo()
{
setJumpingPos();
doUndo();
if (_firstModificationForThisEntry)
_catalog->setModified(DocPos(_pos), false);
// setPhaseForPart(_catalog,_prevPhase,_pos,DocPosition::UndefPart);
}
void LokalizeUnitCmd::setJumpingPos()
{
_catalog->setLastModifiedPos(_pos);
}
//END LokalizeUnitCmd
//BEGIN LokalizeTargetCmd
LokalizeTargetCmd::LokalizeTargetCmd(Catalog *catalog, const DocPosition& pos, const QString& name = QString())
: LokalizeUnitCmd(catalog, pos, name)
{}
void LokalizeTargetCmd::redo()
{
LokalizeUnitCmd::redo();
_prevTargetPhase = setPhaseForPart(_catalog, _catalog->activePhase(), _pos, DocPosition::Target);
}
void LokalizeTargetCmd::undo()
{
LokalizeUnitCmd::undo();
setPhaseForPart(_catalog, _prevTargetPhase, _pos, DocPosition::Target);
}
//END LokalizeTargetCmd
//BEGIN InsTextCmd
InsTextCmd::InsTextCmd(Catalog *catalog, const DocPosition& pos, const QString& str)
: LokalizeTargetCmd(catalog, pos, i18nc("@item Undo action item", "Insertion"))
, _str(str)
{}
bool InsTextCmd::mergeWith(const QUndoCommand *other)
{
const DocPosition otherPos = static_cast(other)->pos();
if ((other->id() != id())
|| (otherPos.entry != _pos.entry)
|| (otherPos.form != _pos.form)
|| (otherPos.offset != _pos.offset + _str.size())
)
return false;
const QString& otherStr = static_cast(other)->_str;
if (otherStr.isEmpty() || _str.isEmpty()) //just a precaution
return false;
//be close to behaviour of LibreOffice
if (!_str.at(_str.size() - 1).isSpace() && otherStr.at(0).isSpace())
return false;
_str += otherStr;
return true;
}
void InsTextCmd::doRedo()
{
Catalog& catalog = *_catalog;
DocPosition pos = _pos; pos.offset += _str.size();
catalog.setLastModifiedPos(pos);
catalog.targetInsert(_pos, _str);
}
void InsTextCmd::doUndo()
{
_catalog->targetDelete(_pos, _str.size());
}
//END InsTextCmd
//BEGIN DelTextCmd
DelTextCmd::DelTextCmd(Catalog *catalog, const DocPosition &pos, const QString &str)
: LokalizeTargetCmd(catalog, pos, i18nc("@item Undo action item", "Deletion"))
, _str(str)
{}
bool DelTextCmd::mergeWith(const QUndoCommand *other)
{
const DocPosition otherPos = static_cast(other)->pos();
if (
(other->id() != id())
|| (otherPos.entry != _pos.entry)
|| (otherPos.form != _pos.form)
)
return false;
//Delete
if (otherPos.offset == _pos.offset) {
_str += static_cast(other)->_str;
return true;
}
//BackSpace
if (otherPos.offset == _pos.offset - static_cast(other)->_str.size()) {
_str.prepend(static_cast(other)->_str);
_pos.offset = otherPos.offset;
return true;
}
return false;
}
void DelTextCmd::doRedo()
{
_catalog->targetDelete(_pos, _str.size());
}
void DelTextCmd::doUndo()
{
//DocPosition pos=_pos; //pos.offset+=_str.size();
//_catalog.setLastModifiedPos(pos);
_catalog->targetInsert(_pos, _str);
}
//END DelTextCmd
//BEGIN SetStateCmd
void SetStateCmd::push(Catalog *catalog, const DocPosition& pos, bool approved)
{
catalog->push(new SetStateCmd(catalog, pos, closestState(approved, catalog->activePhaseRole())));
}
void SetStateCmd::instantiateAndPush(Catalog *catalog, const DocPosition& pos, TargetState state)
{
catalog->push(new SetStateCmd(catalog, pos, state));
}
SetStateCmd::SetStateCmd(Catalog *catalog, const DocPosition& pos, TargetState state)
: LokalizeUnitCmd(catalog, pos, i18nc("@item Undo action item", "Approvement toggling"))
, _state(state)
, _prevState(SignedOff) //shut up static analyzer
{}
void SetStateCmd::doRedo()
{
_prevState = _catalog->setState(_pos, _state);
}
void SetStateCmd::doUndo()
{
_catalog->setState(_pos, _prevState);
}
//END SetStateCmd
//BEGIN InsTagCmd
InsTagCmd::InsTagCmd(Catalog *catalog, const DocPosition& pos, const InlineTag& tag)
: LokalizeTargetCmd(catalog, pos, i18nc("@item Undo action item", "Markup Insertion"))
, _tag(tag)
{
_pos.offset = tag.start;
}
void InsTagCmd::doRedo()
{
Catalog& catalog = *_catalog;
DocPosition pos = _pos; pos.offset++; //between paired tags or after single tag
catalog.setLastModifiedPos(pos);
catalog.targetInsertTag(_pos, _tag);
}
void InsTagCmd::doUndo()
{
_catalog->targetDeleteTag(_pos);
}
//END InsTagCmd
//BEGIN DelTagCmd
DelTagCmd::DelTagCmd(Catalog *catalog, const DocPosition& pos)
: LokalizeTargetCmd(catalog, pos, i18nc("@item Undo action item", "Markup Deletion"))
{}
void DelTagCmd::doRedo()
{
_tag = _catalog->targetDeleteTag(_pos);
qCDebug(LOKALIZE_LOG) << "tag properties:" << _tag.start << _tag.end;
}
void DelTagCmd::doUndo()
{
Catalog& catalog = *_catalog;
DocPosition pos = _pos; pos.offset++; //between paired tags or after single tag
catalog.setLastModifiedPos(pos);
catalog.targetInsertTag(_pos, _tag);
}
//END DelTagCmd
//BEGIN SetNoteCmd
SetNoteCmd::SetNoteCmd(Catalog *catalog, const DocPosition& pos, const Note& note)
: LokalizeUnitCmd(catalog, pos, i18nc("@item Undo action item", "Note setting"))
, _note(note)
{
_pos.part = DocPosition::Comment;
}
static void setNote(Catalog& catalog, DocPosition& _pos, const Note& note, Note& resultNote)
{
resultNote = catalog.setNote(_pos, note);
int size = catalog.notes(_pos).size();
if (_pos.form >= size) _pos.form = -1;
#if 0
else if (_pos.form == -1) _pos.form = size - 1;
#endif
}
void SetNoteCmd::doRedo()
{
setNote(*_catalog, _pos, _note, _prevNote);
}
void SetNoteCmd::doUndo()
{
Note tmp; setNote(*_catalog, _pos, _prevNote, tmp);
}
void SetNoteCmd::setJumpingPos()
{
DocPosition pos = _pos;
pos.form = 0;
_catalog->setLastModifiedPos(pos);
}
//END SetNoteCmd
//BEGIN UpdatePhaseCmd
UpdatePhaseCmd::UpdatePhaseCmd(Catalog *catalog, const Phase& phase)
: QUndoCommand(i18nc("@item Undo action item", "Update/add workflow phase"))
, _catalog(catalog)
, _phase(phase)
{}
void UpdatePhaseCmd::redo()
{
_prevPhase = _catalog->updatePhase(_phase);
}
void UpdatePhaseCmd::undo()
{
_catalog->updatePhase(_prevPhase);
}
//END UpdatePhaseCmd
//BEGIN SetEquivTransCmd
SetEquivTransCmd::SetEquivTransCmd(Catalog *catalog, const DocPosition& pos, bool equivTrans)
: LokalizeTargetCmd(catalog, pos, i18nc("@item Undo action item", "Translation Equivalence Setting"))
, _equivTrans(equivTrans)
{}
void SetEquivTransCmd::doRedo()
{
_catalog->setEquivTrans(_pos, _equivTrans);
}
void SetEquivTransCmd::doUndo()
{
_catalog->setEquivTrans(_pos, !_equivTrans);
}
//END SetEquivTransCmd
bool fillTagPlaces(QMap& tagPlaces,
const CatalogString& catalogString,
int start,
int len
)
{
QString target = catalogString.string;
if (len == -1)
len = target.size();
int t = start;
while ((t = target.indexOf(TAGRANGE_IMAGE_SYMBOL, t)) != -1 && t < (start + len))
tagPlaces[t++] = 0;
int i = catalogString.tags.size();
while (--i >= 0) {
//qCWarning(LOKALIZE_LOG)<::const_iterator it = tagPlaces.constBegin();
while (it != tagPlaces.constEnd() && it.value())
++it;
return it == tagPlaces.constEnd();
}
bool removeTargetSubstring(Catalog* catalog, DocPosition pos, int delStart, int delLen)
{
CatalogString targetWithTags = catalog->targetWithTags(pos);
QString target = targetWithTags.string;
qCDebug(LOKALIZE_LOG) << "called with" << delStart << "delLen" << delLen << "target:" << target;
if (delLen == -1)
delLen = target.length() - delStart;
bool doTags = catalog->capabilities()&Tags;
QMap tagPlaces;
if (target.isEmpty() || (doTags && !fillTagPlaces(tagPlaces, targetWithTags, delStart, delLen))) {
qCWarning(LOKALIZE_LOG) << "error removing text" << target;
return false;
}
catalog->beginMacro(i18nc("@item Undo action item", "Remove text with markup"));
//all indexes are ok (or target is just plain text)
//modified=true;
//qCWarning(LOKALIZE_LOG)<<"all indexes are ok";
QMapIterator it(tagPlaces);
it.toBack();
while (it.hasPrevious()) {
it.previous();
if (it.value() != 1) continue;
pos.offset = it.key();
DelTagCmd* cmd = new DelTagCmd(catalog, pos);
catalog->push(cmd);
delLen -= 1 + cmd->tag().isPaired();
QString tmp = catalog->targetWithTags(pos).string;
tmp.replace(TAGRANGE_IMAGE_SYMBOL, u'*');
qCDebug(LOKALIZE_LOG) << "\tdeleting at" << it.key() << "current string:" << tmp << "delLen" << delLen;
}
//charsRemoved-=lenDecrement;
QString tmp = catalog->targetWithTags(pos).string;
tmp.replace(TAGRANGE_IMAGE_SYMBOL, u'*');
qCDebug(LOKALIZE_LOG) << "offset" << delStart << delLen << "current string:" << tmp;
pos.offset = delStart;
if (delLen) {
QString rText = catalog->targetWithTags(pos).string.mid(delStart, delLen);
rText.remove(TAGRANGE_IMAGE_SYMBOL);
qCDebug(LOKALIZE_LOG) << "rText" << rText << "delStart" << delStart << rText.size();
if (!rText.isEmpty())
catalog->push(new DelTextCmd(catalog, pos, rText));
}
tmp = catalog->targetWithTags(pos).string;
tmp.replace(TAGRANGE_IMAGE_SYMBOL, u'*');
qCDebug(LOKALIZE_LOG) << "current string:" << tmp;
catalog->endMacro();
return true;
}
void insertCatalogString(Catalog* catalog, DocPosition pos, const CatalogString& catStr, int start)
{
QMap posToTag;
int i = catStr.tags.size();
bool containsMarkup = i;
while (--i >= 0) {
const InlineTag& tag = catStr.tags.at(i);
//qCWarning(LOKALIZE_LOG)<<"\t"<beginMacro(i18nc("@item Undo action item", "Insert text with markup"));
i = 0;
int prev = 0;
while ((i = catStr.string.indexOf(TAGRANGE_IMAGE_SYMBOL, i)) != -1) {
qCDebug(LOKALIZE_LOG) << "TAGRANGE_IMAGE_SYMBOL" << i;
//text that was before tag we found
if (i - prev) {
pos.offset = start + prev;
catalog->push(new InsTextCmd(catalog, pos, catStr.string.mid(prev, i - prev)));
}
//now dealing with tag
qCDebug(LOKALIZE_LOG) << "posToTag.value(i)" << posToTag.value(i) << catStr.tags.size();
if (posToTag.value(i) < catStr.tags.size()) {
InlineTag tag = catStr.tags.at(posToTag.value(i));
qCDebug(LOKALIZE_LOG) << i << "testing for tag" << tag.name() << tag.start << tag.start;
if (tag.start == i) { //this is an opening tag (may be single tag)
pos.offset = start + i;
tag.start += start;
tag.end += start;
catalog->push(new InsTagCmd(catalog, pos, tag));
}
} else {
//HACK to keep positions in sync
pos.offset = start + i;
catalog->push(new InsTextCmd(catalog, pos, QStringLiteral(" ")));
}
prev = ++i;
}
pos.offset = start + prev;
if (catStr.string.length() - prev > 0)
catalog->push(new InsTextCmd(catalog, pos, catStr.string.mid(prev)));
if (containsMarkup) catalog->endMacro();
}
diff --git a/src/catalog/cmd.h b/src/catalog/cmd.h
index 8c621a4..3a11722 100644
--- a/src/catalog/cmd.h
+++ b/src/catalog/cmd.h
@@ -1,249 +1,250 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2007-2011 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
**************************************************************************** */
#ifndef CMD_H
#define CMD_H
#include
#include "pos.h"
#include "note.h"
#include "phase.h"
#include "state.h"
#include "catalogstring.h"
class Catalog;
enum Commands {
Insert, Delete,
InsertTag, DeleteTag,
ToggleApprovement, EquivTrans,
SetNote, UpdatePhase
};
class LokalizeUnitCmd: public QUndoCommand
{
public:
LokalizeUnitCmd(Catalog *catalog, const DocPosition& pos, const QString& name);
~LokalizeUnitCmd() override {}
void undo() override;
void redo() override;
DocPosition pos()const
{
return _pos;
}
protected:
virtual void doRedo() = 0;
virtual void doUndo() = 0;
/**
* may be overridden to set customized pos
* alternatively customized pos may be set manually in do*()
*/
virtual void setJumpingPos();
protected:
Catalog* _catalog;
DocPosition _pos;
bool _firstModificationForThisEntry;
// QString _prevPhase; currently xliffstorage doesn't support non-target phase setting
};
class LokalizeTargetCmd: public LokalizeUnitCmd
{
public:
LokalizeTargetCmd(Catalog *catalog, const DocPosition& pos, const QString& name);
~LokalizeTargetCmd() override {}
void undo() override;
void redo() override;
protected:
QString _prevTargetPhase;
};
/**
* how undo system works:
* undo() and redo() functions call appropriate private method of Catalog to change catalog contents,
* then set DocPosition (posBuffer var in Catalog), which is used to navigate editor to appr. place
* @short Do insert text
*/
class InsTextCmd: public LokalizeTargetCmd
{
public:
InsTextCmd(Catalog *catalog, const DocPosition& pos, const QString& str);
~InsTextCmd() override {}
int id() const override
{
return Insert;
}
bool mergeWith(const QUndoCommand *other) override;
void doRedo() override;
void doUndo() override;
private:
QString _str;
};
/// @see InsTextCmd
class DelTextCmd: public LokalizeTargetCmd
{
public:
DelTextCmd(Catalog *catalog, const DocPosition& pos, const QString& str);
~DelTextCmd() override {}
int id() const override
{
return Delete;
}
bool mergeWith(const QUndoCommand *other) override;
void doRedo() override;
void doUndo() override;
private:
QString _str;
};
class SetStateCmd: public LokalizeUnitCmd
{
private:
SetStateCmd(Catalog *catalog, const DocPosition& pos, TargetState state);
public:
~SetStateCmd() override {}
int id() const override
{
return ToggleApprovement;
}
void doRedo() override;
void doUndo() override;
static void push(Catalog *catalog, const DocPosition& pos, bool approved);
static void instantiateAndPush(Catalog *catalog, const DocPosition& pos, TargetState state);
TargetState _state;
TargetState _prevState;
};
/// @short Do insert tag
class InsTagCmd: public LokalizeTargetCmd
{
public:
/// offset is taken from @a tag and not from @a pos
InsTagCmd(Catalog *catalog, const DocPosition& pos, const InlineTag& tag);
~InsTagCmd() override {}
int id() const override
{
return InsertTag;
}
void doRedo() override;
void doUndo() override;
private:
InlineTag _tag;
};
/**
* TagRange is filled from document
*
* @short Do delete tag
*/
class DelTagCmd: public LokalizeTargetCmd
{
public:
DelTagCmd(Catalog *catalog, const DocPosition& pos);
~DelTagCmd() override {}
int id() const override
{
return DeleteTag;
}
void doRedo() override;
void doUndo() override;
InlineTag tag()const
{
return _tag; //used to get proprties of deleted tag
}
private:
InlineTag _tag;
};
/// @short Insert or remove (if content is empty) a note
class SetNoteCmd: public LokalizeUnitCmd
{
public:
/// @a pos.form is note number
SetNoteCmd(Catalog *catalog, const DocPosition& pos, const Note& note);
~SetNoteCmd() override {}
int id() const override
{
return SetNote;
}
protected:
void doRedo() override;
void doUndo() override;
void setJumpingPos() override;
private:
Note _note;
Note _prevNote;
};
/// @short Add or remove (if content is empty) a phase
class UpdatePhaseCmd: public QUndoCommand
{
public:
/// @a pos.form is note number
UpdatePhaseCmd(Catalog *catalog, const Phase& phase);
~UpdatePhaseCmd() override {}
int id() const override
{
return UpdatePhase;
}
void redo() override;
void undo() override;
private:
Catalog* _catalog;
Phase _phase;
Phase _prevPhase;
};
class SetEquivTransCmd: public LokalizeTargetCmd
{
public:
SetEquivTransCmd(Catalog *catalog, const DocPosition& pos, bool equivTrans);
~SetEquivTransCmd() override {}
int id() const override
{
return EquivTrans;
}
void doRedo() override;
void doUndo() override;
private:
bool _equivTrans;
};
/**
* CatalogString cmds helper function.
*
* tagPlaces: pos -> int:
* >0 if both start and end parts of tag were (to be) deleted
* 1 means this is start, 2 means this is end
* @returns false if it can't find second part of any paired tag in the range
*/
bool fillTagPlaces(QMap& tagPlaces, const CatalogString& catalogString, int start, int len);
bool removeTargetSubstring(Catalog* catalog, DocPosition pos, int delStart = 0, int delLen = -1);
void insertCatalogString(Catalog* catalog, DocPosition pos, const CatalogString& catStr, int start = 0);
#endif // CMD_H
diff --git a/src/catalog/gettext/catalogitem.cpp b/src/catalog/gettext/catalogitem.cpp
index 5008779..a3e104a 100644
--- a/src/catalog/gettext/catalogitem.cpp
+++ b/src/catalog/gettext/catalogitem.cpp
@@ -1,345 +1,346 @@
/* ****************************************************************************
This file is based on the one from KBabel
Copyright (C) 1999-2000 by Matthias Kiefer
2002 by Stanislav Visnovsky
Copyright (C) 2006 by Nicolas GOUTTE
2007-2012 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#include "catalogitem.h"
#include "lokalize_debug.h"
#include
using namespace GettextCatalog;
QString CatalogItem::msgctxt(const bool noNewlines) const
{
QString msgctxt = d._msgctxt;
if (noNewlines) return msgctxt.replace(QLatin1Char('\n'), QLatin1Char(' ')); //" " or "" ?
else return msgctxt;
}
const QString& CatalogItem::msgstr(const int form) const
{
if (Q_LIKELY(form < d._msgstrPlural.size()))
return d._msgstrPlural.at(form);
else
return d._msgstrPlural.last();
}
bool CatalogItem::prependEmptyForMsgid(const int form) const
{
Q_UNUSED(form)
return d._prependMsgIdEmptyLine;
}
bool CatalogItem::prependEmptyForMsgstr(const int form) const
{
Q_UNUSED(form)
return d._prependMsgStrEmptyLine;
}
const QVector& CatalogItem::msgstrPlural() const
{
return d._msgstrPlural;
}
const QVector& CatalogItem::msgidPlural() const
{
return d._msgidPlural;
}
QStringList CatalogItem::allPluralForms(CatalogItem::Part part, bool stripNewLines) const
{
QStringList result = (part == CatalogItem::Source ? d._msgidPlural : d._msgstrPlural).toList();
if (stripNewLines) {
result.replaceInStrings(QStringLiteral("\n"), QString());
}
return result;
}
void CatalogItem::setMsgctxt(const QString& msg)
{
d._msgctxt = msg;
d._msgctxt.squeeze();
d._keepEmptyMsgCtxt = msg.isEmpty();
}
void CatalogItem::setMsgid(const QString& msg, const int form)
{
if (form >= d._msgidPlural.size())
d._msgidPlural.resize(form + 1);
d._msgidPlural[form] = msg;
}
void CatalogItem::setMsgid(const QStringList& msg)
{
d._msgidPlural = msg.toVector(); //TODO
for (QVector::iterator it = d._msgidPlural.begin(); it != d._msgidPlural.end(); ++it)
it->squeeze();
}
void CatalogItem::setMsgid(const QStringList& msg, bool prependEmptyLine)
{
d._prependMsgIdEmptyLine = prependEmptyLine;
d._msgidPlural = msg.toVector(); //TODO
for (QVector::iterator it = d._msgidPlural.begin(); it != d._msgidPlural.end(); ++it)
it->squeeze();
}
void CatalogItem::setMsgid(const QVector& msg)
{
d._msgidPlural = msg;
for (QVector::iterator it = d._msgidPlural.begin(); it != d._msgidPlural.end(); ++it)
it->squeeze();
}
void CatalogItem::setMsgstr(const QString& msg, const int form)
{
if (form >= d._msgstrPlural.size())
d._msgstrPlural.resize(form + 1);
d._msgstrPlural[form] = msg;
}
void CatalogItem::setMsgstr(const QStringList& msg)
{
//TODO
d._msgstrPlural = msg.toVector();
}
void CatalogItem::setMsgstr(const QStringList& msg, bool prependEmptyLine)
{
d._prependMsgStrEmptyLine = prependEmptyLine;
d._msgstrPlural = msg.toVector();
}
void CatalogItem::setMsgstr(const QVector& msg)
{
d._msgstrPlural = msg;
}
void CatalogItem::setComment(const QString& com)
{
{
//static QMutex reMutex;
//QMutexLocker reLock(&reMutex); //avoid crash #281033
//now we have a bigger scale mutex in GettextStorage
static QRegExp fuzzyRegExp(QStringLiteral("((?:^|\n)#(?:,[^,]*)*),\\s*fuzzy"));
d._fuzzyCached = com.contains(fuzzyRegExp);
}
d._comment = com;
d._comment.squeeze();
}
bool CatalogItem::isUntranslated() const
{
return d.isUntranslated();
}
bool CatalogItem::isUntranslated(uint form) const
{
return d.isUntranslated(form);
}
#if 0
QStringList CatalogItem::errors() const
{
return d._errors;
}
bool CatalogItem::isCformat() const
{
// Allow "possible-c-format" (from xgettext --debug) or "c-format"
// Note the regexp (?: ) is similar to () but it does not capture (so it is faster)
return d._comment.indexOf(QRegExp(",\\s*(?:possible-)c-format")) == -1;
}
bool CatalogItem::isNoCformat() const
{
return d._comment.indexOf(QRegExp(",\\s*no-c-format")) == -1;
}
bool CatalogItem::isQtformat() const
{
return d._comment.indexOf(QRegExp(",\\s*qt-format")) == -1;
}
bool CatalogItem::isNoQtformat() const
{
return d._comment.indexOf(QRegExp(",\\s*no-qt-format")) == -1;
}
bool CatalogItem::isUntranslated() const
{
return d._msgstr.first().isEmpty();
}
int CatalogItem::totalLines() const
{
int lines = 0;
if (!d._comment.isEmpty()) {
lines = d._comment.count('\n') + 1;
}
int msgctxtLines = 0;
if (!d._msgctxt.isEmpty()) {
msgctxtLines = d._msgctxt.count('\n') + 1;
}
int msgidLines = 0;
QStringList::ConstIterator it;
for (it = d._msgid.begin(); it != d._msgid.end(); ++it) {
msgidLines += (*it).count('\n') + 1;
}
int msgstrLines = 0;
for (it = d._msgstr.begin(); it != d._msgstr.end(); ++it) {
msgstrLines += (*it).count('\n') + 1;
}
if (msgctxtLines > 1)
msgctxtLines++;
if (msgidLines > 1)
msgidLines++;
if (msgstrLines > 1)
msgstrLines++;
lines += (msgctxtLines + msgidLines + msgstrLines);
return lines;
}
void CatalogItem::setSyntaxError(bool on)
{
if (on && !d._errors.contains("syntax error"))
d._errors.append("syntax error");
else
d._errors.removeAll("syntax error");
}
#endif
QStringList CatalogItem::msgstrAsList() const
{
if (d._msgstrPlural.isEmpty()) {
qCWarning(LOKALIZE_LOG) << "This should never happen!";
return QStringList();
}
QStringList list(d._msgstrPlural.first().split('\n', QString::SkipEmptyParts));
if (d._msgstrPlural.first() == QLatin1String("\n"))
list.prepend(QString());
if (list.isEmpty())
list.append(QString());
return list;
}
void CatalogItem::setFuzzy()
{
d._fuzzyCached = true;
if (d._comment.isEmpty()) {
d._comment = QStringLiteral("#, fuzzy");
return;
}
int p = d._comment.indexOf(QLatin1String("#,"));
if (p != -1) {
d._comment.replace(p, 2, QStringLiteral("#, fuzzy,"));
return;
}
QString comment = d._comment;
static QRegExp a("\\#\\:[^\n]*\n");
p = a.indexIn(comment);
if (p != -1) {
d._comment = comment.insert(p + a.matchedLength(), QLatin1String("#, fuzzy\n"));
return;
}
p = d._comment.indexOf(QLatin1String("\n#|"));
if (p != -1) {
d._comment.insert(p, QLatin1String("\n#, fuzzy"));
return;
}
if (d._comment.startsWith(QLatin1String("#|"))) {
d._comment.prepend(QLatin1String("#, fuzzy\n"));
return;
}
if (!(d._comment.endsWith(QLatin1Char('\n'))))
d._comment += QLatin1Char('\n');
d._comment += QLatin1String("#, fuzzy");
}
void CatalogItem::unsetFuzzy()
{
d._fuzzyCached = false;
static const QRegExp rmFuzzyRe(QStringLiteral(",\\s*fuzzy"));
d._comment.remove(rmFuzzyRe);
// remove empty comment lines
d._comment.remove(QRegExp(QStringLiteral("\n#\\s*$")));
d._comment.remove(QRegExp(QStringLiteral("^#\\s*$")));
d._comment.remove(QRegExp(QStringLiteral("#\\s*\n")));
d._comment.remove(QRegExp(QStringLiteral("^#\\s*\n")));
}
#if 0
QString CatalogItem::nextError() const
{
return d._errors.first();
}
void CatalogItem::clearErrors()
{
d._errors.clear();
}
void CatalogItem::appendError(const QString& error)
{
if (!d._errors.contains(error))
d._errors.append(error);
}
void CatalogItem::removeError(const QString& error)
{
d._errors.removeAt(d._errors.indexOf(error));
}
#endif
// kate: space-indent on; indent-width 4; replace-tabs on;
diff --git a/src/catalog/gettext/catalogitem.h b/src/catalog/gettext/catalogitem.h
index aee69a4..cbd225b 100644
--- a/src/catalog/gettext/catalogitem.h
+++ b/src/catalog/gettext/catalogitem.h
@@ -1,181 +1,182 @@
/* ****************************************************************************
This file is part of Lokalize
This file is based on the one from KBabel
Copyright (C) 1999-2000 by Matthias Kiefer
Copyright (C) 2002-2003 by Stanislav Visnovsky
Copyright (C) 2006 by Nicolas GOUTTE
Copyright (C) 2007-2011 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#ifndef CATALOGITEM_H
#define CATALOGITEM_H
#include
#include "catalogitem_private.h"
namespace GettextCatalog
{
/**
* This class represents an entry in a catalog.
* It contains the comment, the Msgid and the Msgstr.
* It defines some functions to query the state of the entry
* (fuzzy, untranslated, cformat).
*
* @short Represents an entry in a Gettext catalog
* @author Matthias Kiefer
* @author Nick Shaforostoff
*/
class CatalogItem
{
public:
explicit CatalogItem() {}
CatalogItem(const CatalogItem& item): d(item.d) {}
~CatalogItem() {}
bool isFuzzy() const
{
return d._fuzzyCached; //", fuzzy" in comment
}
bool isCformat() const; //", c-format" or possible-c-format in comment (from the debug parameter of xgettext)
bool isNoCformat() const; //", no-c-format" in comment
bool isQtformat() const; //", qt-format" in comment
bool isNoQtformat() const; //", no-qt-format" in comment
bool isUntranslated() const;
bool isUntranslated(uint form) const;
inline bool isPlural() const
{
return d._plural;
}
inline void setPlural(bool plural = true)
{
d._plural = plural;
}
void setSyntaxError(bool);
/** returns the number of lines, the entry will need in a file */
int totalLines() const;
/** cleares the item */
inline void clear()
{
d.clear();
}
const QString& comment() const
{
return d._comment;
}
QString msgctxt(const bool noNewlines = false) const;
const QString& msgid(const int form = 0) const
{
return d.msgid(form);
}
const QString& msgstr(const int form = 0) const;
const QVector& msgstrPlural() const;
const QVector& msgidPlural() const;
enum Part {Source, Target};
QStringList allPluralForms(CatalogItem::Part, bool stripNewLines = false) const;
bool prependEmptyForMsgid(const int form = 0) const;
bool prependEmptyForMsgstr(const int form = 0) const;
bool keepEmptyMsgCtxt() const
{
return d._keepEmptyMsgCtxt;
}
QStringList msgstrAsList() const;
void setComment(const QString& com);
void setMsgctxt(const QString& msg);
void setMsgid(const QString& msg, const int form = 0);
void setMsgid(const QStringList& msg);
void setMsgid(const QStringList& msg, bool prependEmptyLine);
void setMsgid(const QVector& msg);
void setMsgstr(const QString& msg, const int form = 0);
void setMsgstr(const QStringList& msg);
void setMsgstr(const QStringList& msg, bool prependEmptyLine);
void setMsgstr(const QVector& msg);
void setValid(bool v)
{
d._valid = v;
}
bool isValid() const
{
return d._valid;
}
#if 0
/**
* @return the list of all errors of this item
*/
QStringList errors() const;
QString nextError() const;
void clearErrors();
void removeError(const QString& error);
void appendError(const QString& error);
/**
* makes some sanity checks and set status accordingly
* @return the new status of this item
* @see CatalogItem::Error
* @param accelMarker a char, that marks the keyboard accelerators
* @param contextInfo a regular expression, that determines what is
* the context information
* @param singularPlural a regular expression, that determines what is
* string with singular and plural form
* @param neededLines how many lines a string with singular-plural form
* must have
*/
int checkErrors(QChar accelMarker, const QRegExp& contextInfo
, const QRegExp& singularPlural, const int neededLines);
#endif
inline void operator=(const CatalogItem& rhs)
{
d.assign(rhs.d);
}
private:
CatalogItemPrivate d;
friend class GettextStorage;
void setFuzzy();
void unsetFuzzy();
};
}
#endif // CATALOGITEM_H
diff --git a/src/catalog/gettext/catalogitem_private.h b/src/catalog/gettext/catalogitem_private.h
index 82d63d6..bfef428 100644
--- a/src/catalog/gettext/catalogitem_private.h
+++ b/src/catalog/gettext/catalogitem_private.h
@@ -1,147 +1,148 @@
/* ****************************************************************************
This file is part of Lokalize
This file is based on the one from KBabel
Copyright (C) 1999-2000 by Matthias Kiefer
2002 by Stanislav Visnovsky
2007-2011 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#ifndef CATALOGITEMPRIVATE_H
#define CATALOGITEMPRIVATE_H
#include
#include
#include
namespace GettextCatalog
{
/**
* This class represents data for an entry in a catalog.
* It contains the comment, the Msgid and the Msgstr.
* It defines some functions to query the state of the entry
* (fuzzy, untranslated, cformat).
*
* @short Class, representing an entry in a catalog
* @author Matthias Kiefer
* @author Stanislav Visnovsky
* @author Nick Shaforostoff
*/
class CatalogItemPrivate
{
public:
bool _plural;
bool _valid;
bool _fuzzyCached;
bool _prependMsgIdEmptyLine;
bool _prependMsgStrEmptyLine;
bool _keepEmptyMsgCtxt;
QString _comment;
QString _msgctxt;
QVector _msgidPlural;
QVector _msgstrPlural;
//QVector _errors;
CatalogItemPrivate()
: _plural(false)
, _valid(true)
, _fuzzyCached(false)
, _prependMsgIdEmptyLine(false)
, _prependMsgStrEmptyLine(false)
, _keepEmptyMsgCtxt(false)
{}
void clear();
void assign(const CatalogItemPrivate& other);
bool isUntranslated() const;
bool isUntranslated(uint form) const;
const QString& msgid(const int form) const;
};
inline
void CatalogItemPrivate::clear()
{
_plural = false;
_valid = true;
_comment.clear();
_msgctxt.clear();
_msgidPlural.clear();
_msgstrPlural.clear();
//_errors.clear();
}
inline
void CatalogItemPrivate::assign(const CatalogItemPrivate& other)
{
_comment = other._comment;
_msgctxt = other._msgctxt;
_msgidPlural = other._msgidPlural;
_msgstrPlural = other._msgstrPlural;
_valid = other._valid;
//_errors=other._errors;
_plural = other._plural;
_fuzzyCached = other._fuzzyCached;
}
inline
bool CatalogItemPrivate::isUntranslated() const
{
int i = _msgstrPlural.size();
while (--i >= 0)
if (_msgstrPlural.at(i).isEmpty())
return true;
return false;
}
inline
bool CatalogItemPrivate::isUntranslated(uint form) const
{
if ((int)form < _msgstrPlural.size())
return _msgstrPlural.at(form).isEmpty();
else
return true;
}
inline
const QString& CatalogItemPrivate::msgid(const int form) const
{
//if original lang is english, we have only 2 formz
return (form < _msgidPlural.size()) ? _msgidPlural.at(form) : _msgidPlural.last();
}
}
#endif // CATALOGITEMPRIVATE_H
diff --git a/src/catalog/gettext/exportplugin.cpp b/src/catalog/gettext/exportplugin.cpp
index a0882ee..7a218fa 100644
--- a/src/catalog/gettext/exportplugin.cpp
+++ b/src/catalog/gettext/exportplugin.cpp
@@ -1,47 +1,48 @@
/* ****************************************************************************
This file is part of KAider
This file is based on the one from KBabel
Copyright (C) 2002-2003 by Stanislav Visnovsky
2007 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#include "catalogfileplugin.h"
using namespace GettextCatalog;
CatalogExportPlugin::CatalogExportPlugin()
// : d(new CatalogExportPluginPrivate)
{
}
CatalogExportPlugin::~CatalogExportPlugin()
{
// delete d;
}
diff --git a/src/catalog/gettext/gettextimport.cpp b/src/catalog/gettext/gettextimport.cpp
index fb6bc04..6f1e13f 100644
--- a/src/catalog/gettext/gettextimport.cpp
+++ b/src/catalog/gettext/gettextimport.cpp
@@ -1,704 +1,705 @@
/* ****************************************************************************
This file is part of Lokalize
This file is based on the one from KBabel
Copyright (C) 1999-2000 by Matthias Kiefer
2001-2003 by Stanislav Visnovsky
2006 by Nicolas GOUTTE
2007 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#include "gettextimport.h"
#include "lokalize_debug.h"
//#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "catalogitem.h"
using namespace GettextCatalog;
// GettextImportPlugin::GettextImportPlugin(ExtraDataSaver* extraDataSaver)
// : CatalogImportPlugin()
// , _extraDataSaver(extraDataSaver)
GettextImportPlugin::GettextImportPlugin()
: CatalogImportPlugin()
, _msgidMultiline(false)
, _msgstrMultiline(false)
, _gettextPluralForm(false)
, _testBorked(false)
, _obsolete(false)
, _msgctxtPresent(false)
, _rxMsgCtxt(QStringLiteral("^msgctxt\\s*\".*\"$"))
, _rxMsgId(QStringLiteral("^msgid\\s*\".*\"$"))
, _rxMsgIdPlural(QStringLiteral("^msgid_plural\\s*\".*\"$"))
, _rxMsgIdPluralBorked(QStringLiteral("^msgid_plural\\s*\"?.*\"?$"))
, _rxMsgIdBorked(QStringLiteral("^msgid\\s*\"?.*\"?$"))
, _rxMsgIdRemQuotes(QStringLiteral("^msgid\\s*\""))
, _rxMsgLineRemEndQuote(QStringLiteral("\"$"))
, _rxMsgLineRemStartQuote(QStringLiteral("^\""))
, _rxMsgLine(QStringLiteral("^\".*\\n?\"$"))
, _rxMsgLineBorked(QStringLiteral("^\"?.+\\n?\"?$"))
, _rxMsgStr(QStringLiteral("^msgstr\\s*\".*\\n?\"$"))
, _rxMsgStrOther(QStringLiteral("^msgstr\\s*\"?.*\\n?\"?$"))
, _rxMsgStrPluralStart(QStringLiteral("^msgstr\\[0\\]\\s*\".*\\n?\"$"))
, _rxMsgStrPluralStartBorked(QStringLiteral("^msgstr\\[0\\]\\s*\"?.*\\n?\"?$"))
, _rxMsgStrPlural(QStringLiteral("^msgstr\\[[0-9]+\\]\\s*\".*\\n?\"$"))
, _rxMsgStrPluralBorked(QStringLiteral("^msgstr\\[[0-9]\\]\\s*\"?.*\\n?\"?$"))
, _rxMsgStrRemQuotes(QStringLiteral("^msgstr\\s*\"?"))
// , _rxMsgId (QStringLiteral("^msgid\\s*\"?.*\"?$"))
, _obsoleteStart(QStringLiteral("#~"))
, _msgctxtStart(QStringLiteral("msgctxt"))
{
}
ConversionStatus GettextImportPlugin::load(QIODevice* device)
{
_testBorked = false;
_errorLine = 0;
// find codec for file
// bool hadCodec;
QTextCodec* codec = codecForDevice(device/*, &hadCodec*/);
QTextStream stream(device);
stream.seek(0);
stream.setCodec(codec);
//QIODevice *dev = stream.device();
//int fileSize = dev->size();
// if somethings goes wrong with the parsing, we don't have deleted the old contents
CatalogItem tempHeader;
//qCDebug(LOKALIZE_LOG) << "start parsing...";
QTime aaa;
aaa.start();
// first read header
const ConversionStatus status = readEntry(stream);
bool recoveredErrorInHeader = false;
if (Q_UNLIKELY(status == RECOVERED_PARSE_ERROR)) {
qCDebug(LOKALIZE_LOG) << "Recovered error in header entry";
recoveredErrorInHeader = true;
} else if (Q_UNLIKELY(status != OK)) {
qCWarning(LOKALIZE_LOG) << "Parse error in header entry";
return status;
}
bool reconstructedHeader = !_msgid.isEmpty() && !_msgid.first().isEmpty();
//qCWarning(LOKALIZE_LOG) << "HEADER MSGID: " << _msgid;
//qCWarning(LOKALIZE_LOG) << "HEADER MSGSTR: " << _msgstr;
if (Q_UNLIKELY(reconstructedHeader)) {
// The header must have an empty msgid
qCWarning(LOKALIZE_LOG) << "Header entry has non-empty msgid. Creating a temporary header! " << _msgid;
tempHeader.setMsgid(QString());
QString tmp(
"Content-Type: text/plain; charset=UTF-8\\n" // Unknown charset
"Content-Transfer-Encoding: 8bit\\n"
"Mime-Version: 1.0");
tempHeader.setMsgstr(tmp);
// We keep the comment of the first entry, as it might really be a header comment (at least partially)
const QString comment("# Header entry was created by Lokalize.\n#\n" + _comment);
tempHeader.setComment(comment);
recoveredErrorInHeader = true;
} else {
tempHeader.setMsgid(_msgid);
tempHeader.setMsgstr(_msgstr);
tempHeader.setComment(_comment);
}
// if(tempHeader.isFuzzy())
// {
// tempHeader.removeFuzzy();
// }
// check if header seems to indicate docbook content generated by xml2pot
const bool docbookContent = tempHeader.msgstr().contains("application/x-xml2pot");
// now parse the rest of the file
uint counter = 0;
QList errorIndex;
//bool recoveredError=false;
bool docbookFile = false;
ExtraDataSaver _extraDataSaver;
ConversionStatus success = OK;
while (!stream.atEnd()) {
if (reconstructedHeader)
reconstructedHeader = false;
else
success = readEntry(stream);
if (Q_LIKELY(success == OK)) {
if (_obsolete)
_extraDataSaver(_comment);
else {
CatalogItem tempCatItem;
tempCatItem.setPlural(_gettextPluralForm);
tempCatItem.setMsgid(_msgid, _msgidMultiline);
tempCatItem.setMsgstr(_msgstr, _msgstrMultiline);
if (_msgctxtPresent) tempCatItem.setMsgctxt(_msgctxt);
tempCatItem.setComment(_comment);
// add new entry to the list of entries
appendCatalogItem(tempCatItem);
// check if first comment seems to indicate a docbook source file
if (counter == 0)
docbookFile = tempCatItem.comment().contains(".docbook");
}
} else if (Q_UNLIKELY(success == RECOVERED_PARSE_ERROR)) {
qCDebug(LOKALIZE_LOG) << "Recovered parse error in entry: " << counter;
//recoveredError=true;
errorIndex.append(counter);
CatalogItem tempCatItem;
tempCatItem.setPlural(_gettextPluralForm);
tempCatItem.setMsgid(_msgid);
tempCatItem.setMsgstr(_msgstr);
if (_msgctxtPresent) tempCatItem.setMsgctxt(_msgctxt);
tempCatItem.setComment(_comment);
// add new entry to the list of entries
appendCatalogItem(tempCatItem);
} else if (success == PARSE_ERROR) {
qCDebug(LOKALIZE_LOG) << "Parse error in entry: " << counter;
return PARSE_ERROR;
} else {
qCDebug(LOKALIZE_LOG) << "Unknown success status, assumig parse error " << success;
return PARSE_ERROR;
}
counter++;
}
// TODO: can we check that there is no useful entry?
if (Q_UNLIKELY(!counter && !recoveredErrorInHeader)) {
// Empty file? (Otherwise, there would be a try of getting an entry and the count would be 1 !)
qCDebug(LOKALIZE_LOG) << " Empty file?";
return PARSE_ERROR;
}
//qCDebug(LOKALIZE_LOG) << " ready";
// We have successfully loaded the file (perhaps with recovered errors)
// qCWarning(LOKALIZE_LOG) << " done in " << aaa.elapsed() <<_extraDataSaver->extraData.size() << endl;
setGeneratedFromDocbook(docbookContent || docbookFile);
setHeader(tempHeader);
setCatalogExtraData(_extraDataSaver.extraData);
setErrorIndex(errorIndex);
setCodec(codec);
//setMimeTypes( "text/x-gettext-translation" );
#if 0
if (Q_UNLIKELY(recoveredErrorInHeader)) {
qCDebug(LOKALIZE_LOG) << " Returning: header error";
return RECOVERED_HEADER_ERROR;
} else if (Q_UNLIKELY(recoveredError)) {
qCDebug(LOKALIZE_LOG) << " Returning: recovered parse error";
return RECOVERED_PARSE_ERROR;
} else
#endif
{
//qCDebug(LOKALIZE_LOG) << " Returning: OK! :-)";
return OK;
}
}
QTextCodec* GettextImportPlugin::codecForDevice(QIODevice* device/*, bool* hadCodec*/)
{
QTextStream stream(device);
stream.seek(0);
_errorLine = 0;
stream.setCodec("UTF-8");
stream.setAutoDetectUnicode(true); //this way we can
QTextCodec* codec = stream.codec(); //detect UTF-16
ConversionStatus status = readEntry(stream);
if (Q_UNLIKELY(status != OK && status != RECOVERED_PARSE_ERROR)) {
qCDebug(LOKALIZE_LOG) << "wasn't able to read header";
return codec;
}
QRegExp regexp(QStringLiteral("Content-Type:\\s*\\w+/[-\\w]+;?\\s*charset\\s*=\\s*(\\S+)\\s*\\\\n"));
if (regexp.indexIn(_msgstr.first()) == -1) {
qCDebug(LOKALIZE_LOG) << "no charset entry found";
return codec;
}
const QString charset = regexp.cap(1);
if (charset != QLatin1String("UTF-8")) qCDebug(LOKALIZE_LOG) << "charset:" << charset;
if (charset.isEmpty()) {
qCWarning(LOKALIZE_LOG) << "No charset defined! Assuming UTF-8!";
return codec;
}
// "CHARSET" is the default charset entry in a template (pot).
// characters in a template should be either pure ascii or
// at least utf8, so utf8-codec can be used for both.
if (charset.contains(QLatin1String("CHARSET"))) {
qCDebug(LOKALIZE_LOG) << QString("file seems to be a template: using utf-8 encoding.");
return QTextCodec::codecForName("utf8");;
}
QTextCodec* t = 0;
t = QTextCodec::codecForName(charset.toLatin1());
if (t)
return t;
else
qCWarning(LOKALIZE_LOG) << "charset found, but no codec available, using UTF-8 instead";
return codec;//UTF-8
}
ConversionStatus GettextImportPlugin::readEntry(QTextStream& stream)
{
ConversionStatus result = readEntryRaw(stream);
const QString FROM = QStringLiteral("\\\"");
const QString TO = QStringLiteral("\"");
_msgstr.replaceInStrings(FROM, TO);
_msgid.replaceInStrings(FROM, TO);
_msgctxt.replace(FROM, TO);
return result;
}
ConversionStatus GettextImportPlugin::readEntryRaw(QTextStream& stream)
{
//qCDebug(LOKALIZE_LOG) << " START";
enum {Begin, Comment, Msgctxt, Msgid, Msgstr} part = Begin;
_trailingNewLines = 0;
bool error = false;
bool recoverableError = false;
//bool seenMsgctxt=false;
_msgstr.clear();
_msgstr.append(QString());
_msgid.clear();
_msgid.append(QString());
_msgctxt.clear();
_msgctxtPresent = false;
_comment.clear();
_gettextPluralForm = false;
_obsolete = false;
QStringList::Iterator msgstrIt = _msgstr.begin();
QString line;
while (!stream.atEnd()) {
_errorLine++;
//line=stream.readLine();
if (!_bufferedLine.isEmpty()) {
line = _bufferedLine;
_bufferedLine.clear();
} else
line = stream.readLine();
static const QString lesslessless = QStringLiteral("<<<<<<<");
static const QString isisis = QStringLiteral("=======");
static const QString moremoremore = QStringLiteral(">>>>>>>");
if (Q_UNLIKELY(line.startsWith(lesslessless) || line.startsWith(isisis) || line.startsWith(moremoremore))) {
// We have found a CVS/SVN conflict marker. Abort.
// (It cannot be any useful data of the PO file, as otherwise the line would start with at least a quote)
qCWarning(LOKALIZE_LOG) << "CVS/SVN conflict marker found! Aborting!" << endl << line << endl;
return PARSE_ERROR;
}
// remove whitespaces from beginning and end of line
line = line.trimmed();
// remember wrapping state to save file nicely
int len = line.length();
if (len) {
_trailingNewLines = 0;
if (_maxLineLength < len && line.at(0) != '#')
_maxLineLength = len;
} else
++_trailingNewLines;
if (part == Begin) {
// ignore trailing newlines
if (!len)
continue;
if (line.startsWith(_obsoleteStart)) {
_obsolete = true;
part = Comment;
_comment = line;
} else if (line.startsWith('#')) {
part = Comment;
_comment = line;
} else if (line.startsWith(_msgctxtStart) && line.contains(_rxMsgCtxt)) {
part = Msgctxt;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgctxt\\s*\"")));
line.remove(_rxMsgLineRemEndQuote);
_msgctxt = line;
_msgctxtPresent = true;
//seenMsgctxt=true;
} else if (line.contains(_rxMsgId)) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgIdRemQuotes);
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
}
// one of the quotation marks is missing
else if (Q_UNLIKELY(/*_testBorked&&*/ line.contains(_rxMsgIdBorked))) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgid\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
if (!line.isEmpty())
recoverableError = true;
} else {
qCDebug(LOKALIZE_LOG) << "no comment, msgctxt or msgid found after a comment: " << line;
error = true;
break;
}
} else if (part == Comment) {
if (!len && _obsolete) return OK;
if (!len) continue;
else if (line.startsWith(_obsoleteStart)) {
_comment += ('\n' + line);
_obsolete = true;
} else if (line.startsWith('#')) {
_comment += ('\n' + line);
} else if (line.startsWith(_msgctxtStart) && line.contains(_rxMsgCtxt)) {
part = Msgctxt;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgctxt\\s*\"")));
line.remove(_rxMsgLineRemEndQuote);
_msgctxt = line;
_msgctxtPresent = true;
//seenMsgctxt=true;
} else if (line.contains(_rxMsgId)) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgIdRemQuotes);
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
}
// one of the quotation marks is missing
else if (Q_UNLIKELY(/*_testBorked&&*/line.contains(_rxMsgIdBorked))) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp("^msgid\\s*\"?"));
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
if (!line.isEmpty())
recoverableError = true;
} else {
qCDebug(LOKALIZE_LOG) << "no comment or msgid found after a comment while parsing: " << _comment;
error = true;
break;
}
} else if (part == Msgctxt) {
if (!len)
continue;
else if (line.contains(_rxMsgLine)) {
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgLineRemStartQuote);
line.remove(_rxMsgLineRemEndQuote);
// add Msgctxt line to item
if (_msgctxt.isEmpty())
_msgctxt = line;
else
_msgctxt += ('\n' + line);
_msgctxtPresent = true;
} else if (line.contains(_rxMsgId)) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgIdRemQuotes);
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
}
// one of the quotation marks is missing
else if (Q_UNLIKELY(/*_testBorked&&*/ line.contains(_rxMsgIdBorked))) {
part = Msgid;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgid\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgidMultiline = line.isEmpty();
(*(_msgid).begin()) = line;
if (!line.isEmpty())
recoverableError = true;
} else {
qCDebug(LOKALIZE_LOG) << "no msgid found after a msgctxt while parsing: " << _msgctxt;
error = true;
break;
}
} else if (part == Msgid) {
if (!len)
continue;
else if (line.contains(_rxMsgLine)) {
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgLineRemStartQuote);
line.remove(_rxMsgLineRemEndQuote);
QStringList::Iterator it;
if (_gettextPluralForm) {
it = _msgid.end();
--it;
} else
it = _msgid.begin();
// add Msgid line to item
if (it->isEmpty())
(*it) = line;
else
(*it) += ('\n' + line);
} else if (line.contains(_rxMsgIdPlural)) {
part = Msgid;
_gettextPluralForm = true;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgid_plural\\s*\"")));
line.remove(_rxMsgLineRemEndQuote);
_msgid.append(line);
}
// one of the quotation marks is missing
else if (Q_UNLIKELY(/*_testBorked&&*/ line.contains(_rxMsgIdPluralBorked))) {
part = Msgid;
_gettextPluralForm = true;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgid_plural\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgid.append(line);
if (!line.isEmpty())
recoverableError = true;
} else if (!_gettextPluralForm && (line.contains(_rxMsgStr))) {
part = Msgstr;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgStrRemQuotes);
line.remove(_rxMsgLineRemEndQuote);
_msgstrMultiline = line.isEmpty();
(*msgstrIt) = line;
} else if (!_gettextPluralForm && (line.contains(_rxMsgStrOther))) {
part = Msgstr;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgStrRemQuotes);
line.remove(_rxMsgLineRemEndQuote);
_msgstrMultiline = line.isEmpty();
(*msgstrIt) = line;
if (!line.isEmpty())
recoverableError = true;
} else if (_gettextPluralForm && (line.contains(_rxMsgStrPluralStart))) {
part = Msgstr;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgstr\\[0\\]\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgstrMultiline = line.isEmpty();
(*msgstrIt) = line;
} else if (Q_UNLIKELY(/*_testBorked&&*/ _gettextPluralForm && line.contains(_rxMsgStrPluralStartBorked))) {
part = Msgstr;
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgstr\\[0\\]\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgstrMultiline = line.isEmpty();
(*msgstrIt) = line;
if (!line.isEmpty())
recoverableError = true;
} else if (line.startsWith('#')) {
// ### TODO: could this be considered recoverable?
qCDebug(LOKALIZE_LOG) << "comment found after a msgid while parsing: " << _msgid.first();
error = true;
break;
} else if (line.startsWith(QStringLiteral("msgid"))) {
qCDebug(LOKALIZE_LOG) << "Another msgid found after a msgid while parsing: " << _msgid.first();
error = true;
break;
}
// a line of the msgid with a missing quotation mark
else if (Q_UNLIKELY(/*_testBorked&&*/line.contains(_rxMsgLineBorked))) {
recoverableError = true;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgLineRemStartQuote);
line.remove(_rxMsgLineRemEndQuote);
QStringList::Iterator it;
if (_gettextPluralForm) {
it = _msgid.end();
--it;
} else
it = _msgid.begin();
// add Msgid line to item
if (it->isEmpty())
(*it) = line;
else
(*it) += ('\n' + line);
} else {
qCDebug(LOKALIZE_LOG) << "no msgstr found after a msgid while parsing: " << _msgid.first();
error = true;
break;
}
} else if (part == Msgstr) {
if (!len)
break;
// another line of the msgstr
else if (line.contains(_rxMsgLine)) {
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgLineRemStartQuote);
line.remove(_rxMsgLineRemEndQuote);
if (!(*msgstrIt).isEmpty())
(*msgstrIt) += '\n';
(*msgstrIt) += line;
} else if (_gettextPluralForm && (line.contains(_rxMsgStrPlural))) {
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgstr\\[[0-9]+\\]\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgstr.append(line);
msgstrIt = _msgstr.end();
--msgstrIt;
} else if (line.startsWith('#') || line.startsWith(QStringLiteral("msgid"))) {
_errorLine--;
_bufferedLine = line;
break;
} else if (Q_UNLIKELY(/*_testBorked&&*/ _gettextPluralForm && (line.contains(_rxMsgStrPluralBorked)))) {
// remove quotes at beginning and the end of the lines
line.remove(QRegExp(QStringLiteral("^msgstr\\[[0-9]\\]\\s*\"?")));
line.remove(_rxMsgLineRemEndQuote);
_msgstr.append(line);
msgstrIt = _msgstr.end();
--msgstrIt;
if (!line.isEmpty())
recoverableError = true;
} else if (line.startsWith(QLatin1String("msgstr"))) {
qCDebug(LOKALIZE_LOG) << "Another msgstr found after a msgstr while parsing: " << line << _msgstr.last();
error = true;
break;
}
// another line of the msgstr with a missing quotation mark
else if (Q_UNLIKELY(/*_testBorked&&*/line.contains(_rxMsgLineBorked))) {
recoverableError = true;
// remove quotes at beginning and the end of the lines
line.remove(_rxMsgLineRemStartQuote);
line.remove(_rxMsgLineRemEndQuote);
if (!(*msgstrIt).isEmpty())
(*msgstrIt) += '\n';
(*msgstrIt) += line;
} else {
qCDebug(LOKALIZE_LOG) << "no msgid or comment found after a msgstr while parsing: " << _msgstr.last();
error = true;
break;
}
}
}
/*
if(_gettextPluralForm)
{
qCDebug(LOKALIZE_LOG) << "gettext plural form:\n"
<< "msgid:\n" << _msgid.first() << "\n"
<< "msgid_plural:\n" << _msgid.last() << "\n" << endl;
int counter=0;
for(QStringList::Iterator it = _msgstr.begin(); it != _msgstr.end(); ++it)
{
qCDebug(LOKALIZE_LOG) << "msgstr[" << counter << "]:\n"
<< (*it) << endl;
counter++;
}
}
*/
//qCDebug(LOKALIZE_LOG) << " NEAR RETURN";
if (Q_UNLIKELY(error))
return PARSE_ERROR;
else if (Q_UNLIKELY(recoverableError))
return RECOVERED_PARSE_ERROR;
else
return OK;
}
// kate: space-indent on; indent-width 4; replace-tabs on;
diff --git a/src/catalog/gettext/gettextstorage.cpp b/src/catalog/gettext/gettextstorage.cpp
index 3647f83..f5382a4 100644
--- a/src/catalog/gettext/gettextstorage.cpp
+++ b/src/catalog/gettext/gettextstorage.cpp
@@ -1,494 +1,495 @@
/*
Copyright 2008-2014 Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "gettextstorage.h"
#include "lokalize_debug.h"
#include "gettextheader.h"
#include "catalogitem_private.h"
#include "gettextimport.h"
#include "gettextexport.h"
#include "project.h"
#include "version.h"
#include "prefs_lokalize.h"
#include "diff.h"
#include
#include
#include
#include
#include
#include
QMutex regExMutex;
// static QString GNUPluralForms(const QString& lang);
using namespace GettextCatalog;
GettextStorage::GettextStorage()
: CatalogStorage()
, m_codec(0)
, m_maxLineLength(80)
, m_trailingNewLines(0)
, m_generatedFromDocbook(false)
{
}
GettextStorage::~GettextStorage()
{
}
//BEGIN OPEN/SAVE
int GettextStorage::load(QIODevice* device/*, bool readonly*/)
{
//GettextImportPlugin importer=GettextImportPlugin(readonly?(new ExtraDataSaver()):(new ExtraDataListSaver()));
GettextImportPlugin importer;
ConversionStatus status = OK;
int errorLine;
{
QMutexLocker locker(®ExMutex);
status = importer.open(device, this, &errorLine);
}
//for langs with more than 2 forms
//we create any form-entries additionally needed
uint i = 0;
uint lim = size();
while (i < lim) {
CatalogItem& item = m_entries[i];
if (item.isPlural()
&& item.msgstrPlural().count() < m_numberOfPluralForms
) {
QVector msgstr(item.msgstrPlural());
while (msgstr.count() < m_numberOfPluralForms)
msgstr.append(QString());
item.setMsgstr(msgstr);
}
++i;
}
//qCompress(m_storage->m_catalogExtraData.join("\n\n").toUtf8(),9);
return status == OK ? 0 : (errorLine + 1);
}
bool GettextStorage::save(QIODevice* device, bool belongsToProject)
{
QString header = m_header.msgstr();
QString comment = m_header.comment();
QString catalogProjectId;//=m_url.fileName();
//catalogProjectId=catalogProjectId.left(catalogProjectId.lastIndexOf('.'));
{
QMutexLocker locker(®ExMutex);
updateHeader(header, comment,
m_targetLangCode,
m_numberOfPluralForms,
catalogProjectId,
m_generatedFromDocbook,
belongsToProject,
/*forSaving*/true,
m_codec);
}
m_header.setMsgstr(header);
m_header.setComment(comment);
//GettextExportPlugin exporter(m_maxLineLength>70?m_maxLineLength:-1, m_trailingNewLines);// this is kinda hackish...
GettextExportPlugin exporter(Project::instance()->wordWrap(), m_trailingNewLines);
ConversionStatus status = OK;
status = exporter.save(device/*x-gettext-translation*/, this, m_codec);
return status == OK;
}
//END OPEN/SAVE
//BEGIN STORAGE TRANSLATION
int GettextStorage::size() const
{
return m_entries.size();
}
static const QChar altSep(156);
static InlineTag makeInlineTag(int i)
{
static const QString altSepText(QStringLiteral(" | "));
static const QString ctype = i18n("separator for different-length string alternatives");
return InlineTag(i, i, InlineTag::x, QString::number(i), QString(), altSepText, ctype);
}
static CatalogString makeCatalogString(const QString& string)
{
CatalogString result;
result.string = string;
int i = 0;
while ((i = result.string.indexOf(altSep, i)) != -1) {
result.string[i] = TAGRANGE_IMAGE_SYMBOL;
result.tags.append(makeInlineTag(i));
++i;
}
return result;
}
//flat-model interface (ignores XLIFF grouping)
CatalogString GettextStorage::sourceWithTags(DocPosition pos) const
{
return makeCatalogString(source(pos));
}
CatalogString GettextStorage::targetWithTags(DocPosition pos) const
{
return makeCatalogString(target(pos));
}
QString GettextStorage::source(const DocPosition& pos) const
{
return m_entries.at(pos.entry).msgid(pos.form);
}
QString GettextStorage::target(const DocPosition& pos) const
{
return m_entries.at(pos.entry).msgstr(pos.form);
}
QString GettextStorage::sourceWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
{
if (m_entries.at(pos.entry).isPlural()) {
const QVector plurals = m_entries.at(pos.entry).msgidPlural();
QString pluralString;
for (int i = 0; i < plurals.size(); i++) {
QString str = plurals.at(i);
if (truncateFirstLine)
{
int truncatePos = str.indexOf("\n");
if (truncatePos != -1)
str.truncate(truncatePos);
}
pluralString += str;
if (i != plurals.size() - 1) {
pluralString += '|';
}
}
return pluralString;
} else {
QString str = m_entries.at(pos.entry).msgid(pos.form);
if (truncateFirstLine)
{
int truncatePos = str.indexOf("\n");
if (truncatePos != -1)
str.truncate(truncatePos);
}
return str;
}
}
QString GettextStorage::targetWithPlurals(const DocPosition& pos, bool truncateFirstLine) const
{
if (m_entries.at(pos.entry).isPlural()) {
const QVector plurals = m_entries.at(pos.entry).msgstrPlural();
QString pluralString;
for (int i = 0; i < plurals.size(); i++) {
QString str = plurals.at(i);
if (truncateFirstLine)
{
int truncatePos = str.indexOf("\n");
if (truncatePos != -1)
str.truncate(truncatePos);
}
pluralString += str;
if (i != plurals.size() - 1) {
pluralString += '|';
}
}
return pluralString;
} else {
QString str = m_entries.at(pos.entry).msgstr(pos.form);
if (truncateFirstLine)
{
int truncatePos = str.indexOf("\n");
if (truncatePos != -1)
str.truncate(truncatePos);
}
return str;
}
}
void GettextStorage::targetDelete(const DocPosition& pos, int count)
{
m_entries[pos.entry].d._msgstrPlural[pos.form].remove(pos.offset, count);
}
void GettextStorage::targetInsert(const DocPosition& pos, const QString& arg)
{
m_entries[pos.entry].d._msgstrPlural[pos.form].insert(pos.offset, arg);
}
void GettextStorage::setTarget(const DocPosition& pos, const QString& arg)
{
m_entries[pos.entry].d._msgstrPlural[pos.form] = arg;
}
void GettextStorage::targetInsertTag(const DocPosition& pos, const InlineTag& tag)
{
Q_UNUSED(tag);
targetInsert(pos, altSep);
}
InlineTag GettextStorage::targetDeleteTag(const DocPosition& pos)
{
targetDelete(pos, 1);
return makeInlineTag(pos.offset);
}
QStringList GettextStorage::sourceAllForms(const DocPosition& pos, bool stripNewLines) const
{
return m_entries.at(pos.entry).allPluralForms(CatalogItem::Source, stripNewLines);
}
QStringList GettextStorage::targetAllForms(const DocPosition& pos, bool stripNewLines) const
{
return m_entries.at(pos.entry).allPluralForms(CatalogItem::Target, stripNewLines);
}
QVector GettextStorage::altTrans(const DocPosition& pos) const
{
static const QRegExp alt_trans_mark_re(QStringLiteral("^#\\|"));
QStringList prev = m_entries.at(pos.entry).comment().split('\n').filter(alt_trans_mark_re);
QString oldSingular;
QString oldPlural;
QString* cur = &oldSingular;
QStringList::iterator it = prev.begin();
static const QString msgid_plural_alt = QStringLiteral("#| msgid_plural \"");
while (it != prev.end()) {
if (it->startsWith(msgid_plural_alt))
cur = &oldPlural;
int start = it->indexOf('\"') + 1;
int end = it->lastIndexOf('\"');
if (start && end != -1) {
if (!cur->isEmpty())
(*cur) += '\n';
if (!(cur->isEmpty() && (end - start) == 0)) //for multiline msgs
(*cur) += it->midRef(start, end - start);
}
++it;
}
if (pos.form == 0)
cur = &oldSingular;
cur->replace(QStringLiteral("\\\""), QStringLiteral("\""));
QVector result;
if (!cur->isEmpty())
result << AltTrans(CatalogString(*cur), i18n("Previous source value, saved by Gettext during transition to a newer POT template"));
return result;
}
Note GettextStorage::setNote(DocPosition pos, const Note& note)
{
//qCWarning(LOKALIZE_LOG)<<"s"< l = notes(pos);
if (l.size()) oldNote = l.first();
QStringList comment = m_entries.at(pos.entry).comment().split('\n');
//remove previous comment;
QStringList::iterator it = comment.begin();
while (it != comment.end()) {
if (it->startsWith(QLatin1String("# ")))
it = comment.erase(it);
else
++it;
}
if (note.content.size())
comment.prepend(QStringLiteral("# ") + note.content.split('\n').join(QStringLiteral("\n# ")));
m_entries[pos.entry].setComment(comment.join(QStringLiteral("\n")));
//qCWarning(LOKALIZE_LOG)<<"e"< GettextStorage::notes(const DocPosition& docPosition, const QRegExp& re, int preLen) const
{
QVector result;
QString content;
QStringList note = m_entries.at(docPosition.entry).comment().split('\n').filter(re);
foreach (const QString &s, note) {
if (s.size() >= preLen) {
content += s.midRef(preLen);
content += QLatin1Char('\n');
}
}
if (!content.isEmpty()) {
content.chop(1);
result << Note(content);
}
return result;
//i18nc("@info PO comment parsing. contains filename","Place: ");
//i18nc("@info PO comment parsing","GUI place: ");
}
QVector GettextStorage::notes(const DocPosition& docPosition) const
{
static const QRegExp nre(QStringLiteral("^# "));
return notes(docPosition, nre, 2);
}
QVector GettextStorage::developerNotes(const DocPosition& docPosition) const
{
static const QRegExp dnre(QStringLiteral("^#\\. (?!i18n: file:)"));
return notes(docPosition, dnre, 3);
}
QStringList GettextStorage::sourceFiles(const DocPosition& pos) const
{
QStringList result;
QStringList commentLines = m_entries.at(pos.entry).comment().split('\n');
static const QRegExp i18n_file_re(QStringLiteral("^#. i18n: file: "));
foreach (const QString &uiLine, commentLines.filter(i18n_file_re)) {
foreach (const QStringRef &fileRef, uiLine.midRef(15).split(' ')) {
result << fileRef.toString();
}
}
bool hasUi = !result.isEmpty();
static const QRegExp cpp_re(QStringLiteral("^#: "));
foreach (const QString &cppLine, commentLines.filter(cpp_re)) {
if (hasUi && cppLine.startsWith(QLatin1String("#: rc.cpp"))) continue;
foreach (const QStringRef &fileRef, cppLine.midRef(3).split(' ')) {
result << fileRef.toString();
}
}
return result;
}
QStringList GettextStorage::context(const DocPosition& pos) const
{
return matchData(pos);
}
QStringList GettextStorage::matchData(const DocPosition& pos) const
{
QString ctxt = m_entries.at(pos.entry).msgctxt();
//KDE-specific
//Splits @info:whatsthis and actual note
/* if (ctxt.startsWith('@') && ctxt.contains(' '))
{
QStringList result(ctxt.section(' ',0,0,QString::SectionSkipEmpty));
result<poDir());
updateHeader(values,
comment,
m_targetLangCode,
m_numberOfPluralForms,
catalogProjectId,
m_generatedFromDocbook,
belongsToProject,
/*forSaving*/true,
m_codec);
m_header = newHeader;
m_header.setComment(comment);
m_header.setMsgstr(values);
// setClean(false);
//emit signalHeaderChanged();
return true;
}
qCWarning(LOKALIZE_LOG) << "header Not valid";
return false;
}
diff --git a/src/catalog/gettext/gettextstorage.h b/src/catalog/gettext/gettextstorage.h
index ef01be6..47d0105 100644
--- a/src/catalog/gettext/gettextstorage.h
+++ b/src/catalog/gettext/gettextstorage.h
@@ -1,137 +1,138 @@
/*
Copyright 2008-2014 Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#ifndef GETTEXTSTORAGE_H
#define GETTEXTSTORAGE_H
#include
#include
#include "catalogitem.h"
#include "catalogstorage.h"
/**
* Implementation of Gettext PO format support
*/
namespace GettextCatalog
{
/**
* @short Implementation of storage for Gettext PO
* @author Nick Shaforostoff
*/
class GettextStorage: public CatalogStorage
{
public:
GettextStorage();
~GettextStorage() override;
int capabilities() const override
{
return 0;
}
int load(QIODevice* device/*, bool readonly=false*/) override;
bool save(QIODevice* device, bool belongsToProject = false) override;
int size() const override;
//flat-model interface (ignores XLIFF grouping)
QString source(const DocPosition& pos) const override;
QString target(const DocPosition& pos) const override;
QString sourceWithPlurals(const DocPosition& pos, bool truncateFirstLine) const override;
QString targetWithPlurals(const DocPosition& pos, bool truncateFirstLine) const override;
CatalogString sourceWithTags(DocPosition pos) const override;
CatalogString targetWithTags(DocPosition pos) const override;
CatalogString catalogString(const DocPosition& pos) const override
{
return pos.part == DocPosition::Target ? targetWithTags(pos) : sourceWithTags(pos);
}
void targetDelete(const DocPosition& pos, int count) override;
void targetInsert(const DocPosition& pos, const QString& arg) override;
void setTarget(const DocPosition& pos, const QString& arg) override;//called for mergeCatalog
void targetInsertTag(const DocPosition&, const InlineTag&) override;
InlineTag targetDeleteTag(const DocPosition&) override;
QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const override;
QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const override;
QVector notes(const DocPosition& pos) const override;
Note setNote(DocPosition pos, const Note& note) override;
QVector altTrans(const DocPosition& pos) const override;
QStringList sourceFiles(const DocPosition& pos) const override;
QVector developerNotes(const DocPosition& pos) const override;
QStringList context(const DocPosition& pos) const override;
QStringList matchData(const DocPosition& pos) const override;
QString id(const DocPosition& pos) const override;
bool isPlural(const DocPosition& pos) const override;
bool isApproved(const DocPosition& pos) const override;
void setApproved(const DocPosition& pos, bool approved) override;
bool isEmpty(const DocPosition& pos) const override;
QString mimetype() const override
{
return QStringLiteral("text/x-gettext-translation");
}
QString fileType() const override
{
return QStringLiteral("Gettext (*.po)");
}
CatalogType type() const override
{
return Gettext;
}
private:
bool setHeader(const CatalogItem& newHeader);
void setCodec(QTextCodec* codec)
{
m_codec = codec;
}
QVector notes(const DocPosition& pos, const QRegExp& re, int preLen) const;
private:
QVector m_entries;
QVector m_obsoleteEntries;
CatalogItem m_header;
QTextCodec* m_codec;
short m_maxLineLength;
short m_trailingNewLines;
bool m_generatedFromDocbook;
QStringList m_catalogExtraData;
QByteArray m_catalogExtraDataCompressed;
friend class CatalogImportPlugin;
friend class GettextExportPlugin;
};
}
#endif
diff --git a/src/catalog/gettext/importplugin.cpp b/src/catalog/gettext/importplugin.cpp
index b07af07..7c265a0 100644
--- a/src/catalog/gettext/importplugin.cpp
+++ b/src/catalog/gettext/importplugin.cpp
@@ -1,151 +1,152 @@
/* ****************************************************************************
This file is part of Lokalize
This file is based on the one from KBabel
Copyright (C) 2002-2003 by Stanislav Visnovsky
2007-2014 by Nick Shaforostoff
+ 2018-2019 by Simon Depiets
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
In addition, as a special exception, the copyright holders give
permission to link the code of this program with any edition of
the Qt library by Trolltech AS, Norway (or with modified versions
of Qt that use the same license as Qt), and distribute linked
combinations including the two. You must obey the GNU General
Public License in all respects for all of the code used other than
Qt. If you modify this file, you may extend this exception to
your version of the file, but you are not obligated to do so. If
you do not wish to do so, delete this exception statement from
your version.
**************************************************************************** */
#include "lokalize_debug.h"
#include "catalogfileplugin.h"
#include "importplugin_private.h"
#include "gettextstorage.h"
#include
#include
#include
namespace GettextCatalog
{
CatalogImportPlugin::CatalogImportPlugin()
: _maxLineLength(0)
, _trailingNewLines(0)
, _errorLine(0)
, d(new CatalogImportPluginPrivate)
{
}
CatalogImportPlugin::~CatalogImportPlugin()
{
delete d;
}
void CatalogImportPlugin::appendCatalogItem(const CatalogItem& item, const bool obsolete)
{
if (item.msgid().isEmpty())
return;
if (obsolete)
d->_obsoleteEntries.append(item);
else
d->_entries.append(item);
}
void CatalogImportPlugin::setCatalogExtraData(const QStringList& data)
{
d->_catalogExtraData = data;
d->_updateCatalogExtraData = true;
}
void CatalogImportPlugin::setGeneratedFromDocbook(const bool generated)
{
d->_generatedFromDocbook = generated;
d->_updateGeneratedFromDocbook = true;
}
void CatalogImportPlugin::setErrorIndex(const QList& errors)
{
d->_errorList = errors;
d->_updateErrorList = true;
}
void CatalogImportPlugin::setHeader(const CatalogItem& item)
{
d->_header = item;
d->_updateHeader = true;
}
void CatalogImportPlugin::setCodec(QTextCodec* codec)
{
d->_codec = codec;
}
ConversionStatus CatalogImportPlugin::open(QIODevice* device, GettextStorage* catalog, int* line)
{
d->_catalog = catalog;
startTransaction();
ConversionStatus result = load(device);
if (result == OK || result == RECOVERED_PARSE_ERROR || result == RECOVERED_HEADER_ERROR)
commitTransaction();
if (line)
(*line) = _errorLine;
return result;
}
void CatalogImportPlugin::startTransaction()
{
d->_updateCodec = false;
d->_updateCatalogExtraData = false;
d->_updateGeneratedFromDocbook = false;
d->_updateErrorList = false;
d->_updateHeader = false;
d->_entries.clear();
}
void CatalogImportPlugin::commitTransaction()
{
GettextStorage* catalog = d->_catalog;
//catalog->clear();
// fill in the entries
QVector& entries = catalog->m_entries;
entries.reserve(d->_entries.count()); //d->_catalog->setEntries( e );
for (QLinkedList::const_iterator it = d->_entries.begin(); it != d->_entries.end(); ++it/*,++i*/)
entries.append(*it);
// The codec is specified in the header, so it must be updated before the header is.
catalog->setCodec(d->_codec);
catalog->m_catalogExtraData = d->_catalogExtraData;
catalog->m_generatedFromDocbook = d->_generatedFromDocbook;
catalog->setHeader(d->_header);
//if( d->_updateErrorList ) d->_catalog->setErrorIndex(d->_errorList);
catalog->m_maxLineLength = _maxLineLength;
catalog->m_trailingNewLines = _trailingNewLines;
}
}
diff --git a/src/catalog/gettextheader.cpp b/src/catalog/gettextheader.cpp
index e239786..17e1dcf 100644
--- a/src/catalog/gettextheader.cpp
+++ b/src/catalog/gettextheader.cpp
@@ -1,672 +1,673 @@
/* ****************************************************************************
This file is part of Lokalize
Copyright (C) 2008-2014 by Nick Shaforostoff