diff --git a/src/catalog/catalog.cpp b/src/catalog/catalog.cpp index e6641d8..1e83956 100644 --- a/src/catalog/catalog.cpp +++ b/src/catalog/catalog.cpp @@ -1,1053 +1,1069 @@ /* **************************************************************************** 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 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" #ifndef NOKDE #include "projectmodel.h" //to notify about modification #endif #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) { #ifndef NOKDE //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); #endif 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._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) const +{ + if (Q_UNLIKELY(!m_storage)) + return QString(); + return m_storage->sourceWithPlurals(pos); +} + 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) const +{ + if (Q_UNLIKELY(!m_storage)) + return QString(); + + return m_storage->targetWithPlurals(pos); +} + + 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 (!isApproved(pos)) d._nonApprovedIndex << pos.entry; if (m_storage->isEmpty(pos)) d._emptyIndex << 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) { #ifndef NOKDE 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; #else return 0; #endif } 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 = 0; 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()); #ifndef NOKDE 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)); } #endif 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(); #ifndef NOKDE d._autoSave->remove(); d._autoSaveRecovered = false; #endif setClean(); //undo/redo if (nameChanged) { d._filePath = localFilePath; #ifndef NOKDE d._autoSave->setManagedFile(QUrl::fromLocalFile(localFilePath)); #endif } //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() { #ifndef NOKDE 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; #endif } 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); else d._nonApprovedIndex.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 e1ab8aa..2171f1e 100644 --- a/src/catalog/catalog.h +++ b/src/catalog/catalog.h @@ -1,367 +1,369 @@ /***************************************************************************** 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 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: Catalog(QObject* parent); virtual ~Catalog(); QString msgid(const DocPosition&) const; virtual QString msgstr(const DocPosition&) const; + QString msgidWithPlurals(const DocPosition&) const; + QString msgstrWithPlurals(const DocPosition&) 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._nonApprovedIndex.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 = 0, 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/catalogstorage.h b/src/catalog/catalogstorage.h index 8836fc0..1f11a35 100644 --- a/src/catalog/catalogstorage.h +++ b/src/catalog/catalogstorage.h @@ -1,272 +1,274 @@ /* Copyright 2008-2009 Nick Shaforostoff 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) const = 0; + virtual QString targetWithPlurals(const DocPosition& pos) 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/gettext/catalogitem.cpp b/src/catalog/gettext/catalogitem.cpp index 86f73f5..5008779 100644 --- a/src/catalog/gettext/catalogitem.cpp +++ b/src/catalog/gettext/catalogitem.cpp @@ -1,341 +1,345 @@ /* **************************************************************************** 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 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 751e33d..aee69a4 100644 --- a/src/catalog/gettext/catalogitem.h +++ b/src/catalog/gettext/catalogitem.h @@ -1,180 +1,181 @@ /* **************************************************************************** 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 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/gettextstorage.cpp b/src/catalog/gettext/gettextstorage.cpp index afa3885..36d06a0 100644 --- a/src/catalog/gettext/gettextstorage.cpp +++ b/src/catalog/gettext/gettextstorage.cpp @@ -1,434 +1,466 @@ /* Copyright 2008-2014 Nick Shaforostoff 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) const +{ + if (m_entries.at(pos.entry).isPlural()) { + const QVector plurals = m_entries.at(pos.entry).msgidPlural(); + QString pluralString = QString(); + for (int i = 0; i < plurals.size(); i++) { + pluralString += plurals.at(i); + if (i != plurals.size() - 1) { + pluralString += "|"; + } + } + return pluralString; + } else { + return m_entries.at(pos.entry).msgid(pos.form); + } +} +QString GettextStorage::targetWithPlurals(const DocPosition& pos) const +{ + if (m_entries.at(pos.entry).isPlural()) { + const QVector plurals = m_entries.at(pos.entry).msgstrPlural(); + QString pluralString = QString(); + for (int i = 0; i < plurals.size(); i++) { + pluralString += plurals.at(i); + if (i != plurals.size() - 1) { + pluralString += "|"; + } + } + return pluralString; + } else { + return m_entries.at(pos.entry).msgstr(pos.form); + } +} 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 b470333..a82decb 100644 --- a/src/catalog/gettext/gettextstorage.h +++ b/src/catalog/gettext/gettextstorage.h @@ -1,135 +1,137 @@ /* Copyright 2008-2014 Nick Shaforostoff 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(); int capabilities() const { return 0; } int load(QIODevice* device/*, bool readonly=false*/); bool save(QIODevice* device, bool belongsToProject = false); int size() const; //flat-model interface (ignores XLIFF grouping) QString source(const DocPosition& pos) const; QString target(const DocPosition& pos) const; + QString sourceWithPlurals(const DocPosition& pos) const; + QString targetWithPlurals(const DocPosition& pos) const; CatalogString sourceWithTags(DocPosition pos) const; CatalogString targetWithTags(DocPosition pos) const; CatalogString catalogString(const DocPosition& pos) const { return pos.part == DocPosition::Target ? targetWithTags(pos) : sourceWithTags(pos); } void targetDelete(const DocPosition& pos, int count); void targetInsert(const DocPosition& pos, const QString& arg); void setTarget(const DocPosition& pos, const QString& arg);//called for mergeCatalog void targetInsertTag(const DocPosition&, const InlineTag&); InlineTag targetDeleteTag(const DocPosition&); QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const; QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const; QVector notes(const DocPosition& pos) const; Note setNote(DocPosition pos, const Note& note); QVector altTrans(const DocPosition& pos) const; QStringList sourceFiles(const DocPosition& pos) const; QVector developerNotes(const DocPosition& pos) const; QStringList context(const DocPosition& pos) const; QStringList matchData(const DocPosition& pos) const; QString id(const DocPosition& pos) const; bool isPlural(const DocPosition& pos) const; bool isApproved(const DocPosition& pos) const; void setApproved(const DocPosition& pos, bool approved); bool isEmpty(const DocPosition& pos) const; QString mimetype()const { return QStringLiteral("text/x-gettext-translation"); } QString fileType()const { return QStringLiteral("Gettext (*.po)"); } CatalogType type()const { 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/ts/tsstorage.cpp b/src/catalog/ts/tsstorage.cpp index 77a3fc4..7604c96 100644 --- a/src/catalog/ts/tsstorage.cpp +++ b/src/catalog/ts/tsstorage.cpp @@ -1,536 +1,545 @@ /* Copyright 2008-2014 Nick Shaforostoff 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 "tsstorage.h" #include "lokalize_debug.h" #include "gettextheader.h" #include "project.h" #include "version.h" #include "prefs_lokalize.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif //static const char* const noyes[]={"no","yes"}; static const QString names[] = {U("source"), U("translation"), U("oldsource"), U("translatorcomment"), U("comment"), U("name"), U("numerus")}; enum TagNames {SourceTag, TargetTag, OldSourceTag, NoteTag, DevNoteTag, NameTag, PluralTag}; static const QString attrnames[] = {U("location"), U("type"), U("obsolete")}; enum AttrNames {LocationAttr, TypeAttr, ObsoleteAttr}; static const QString attrvalues[] = {U("obsolete"), U("vanished")}; enum AttValues {ObsoleteVal, VanishedVal}; TsStorage::TsStorage() : CatalogStorage() { } TsStorage::~TsStorage() { } int TsStorage::capabilities() const { return 0;//MultipleNotes; } //BEGIN OPEN/SAVE int TsStorage::load(QIODevice* device) { QTime chrono; chrono.start(); QXmlSimpleReader reader; reader.setFeature(QStringLiteral("http://qt-project.org/xml/features/report-whitespace-only-CharData"), true); reader.setFeature(QStringLiteral("http://xml.org/sax/features/namespaces"), false); QXmlInputSource source(device); QString errorMsg; int errorLine;//+errorColumn; bool success = m_doc.setContent(&source, &reader, &errorMsg, &errorLine/*,errorColumn*/); if (!success) { qCWarning(LOKALIZE_LOG) << "parse error" << errorMsg << errorLine; return errorLine + 1; } QDomElement file = m_doc.elementsByTagName(QStringLiteral("TS")).at(0).toElement(); m_sourceLangCode = file.attribute(QStringLiteral("sourcelanguage")); m_targetLangCode = file.attribute(QStringLiteral("language")); m_numberOfPluralForms = numberOfPluralFormsForLangCode(m_targetLangCode); //Create entry mapping. //Along the way: for langs with more than 2 forms //we create any form-entries additionally needed entries = m_doc.elementsByTagName(QStringLiteral("message")); qCWarning(LOKALIZE_LOG) << chrono.elapsed() << "secs, " << entries.size() << "entries"; return 0; } bool TsStorage::save(QIODevice* device, bool belongsToProject) { Q_UNUSED(belongsToProject) QTextStream stream(device); m_doc.save(stream, 4); return true; } //END OPEN/SAVE //BEGIN STORAGE TRANSLATION int TsStorage::size() const { //return m_map.size(); return entries.size(); } /** * helper structure used during XLIFF XML walk-through */ struct TsContentEditingData { enum ActionType {Get, DeleteText, InsertText, CheckLength}; QString stringToInsert; int pos; int lengthOfStringToRemove; ActionType actionType; ///Get TsContentEditingData(ActionType type = Get) : pos(-1) , lengthOfStringToRemove(-1) , actionType(type) {} ///DeleteText TsContentEditingData(int p, int l) : pos(p) , lengthOfStringToRemove(l) , actionType(DeleteText) {} ///InsertText TsContentEditingData(int p, const QString& s) : stringToInsert(s) , pos(p) , lengthOfStringToRemove(-1) , actionType(InsertText) {} }; static QString doContent(QDomElement elem, int startingPos, TsContentEditingData* data); /** * walks through XLIFF XML and performs actions depending on TsContentEditingData: * - reads content * - deletes content, or * - inserts content */ static QString content(QDomElement elem, TsContentEditingData* data = 0) { return doContent(elem, 0, data); } static QString doContent(QDomElement elem, int startingPos, TsContentEditingData* data) { //actually startingPos is current pos QString result; if (elem.isNull() || (!result.isEmpty() && data && data->actionType == TsContentEditingData::CheckLength)) return QString(); bool seenCharacterDataAfterElement = false; QDomNode n = elem.firstChild(); while (!n.isNull()) { if (n.isCharacterData()) { seenCharacterDataAfterElement = true; QDomCharacterData c = n.toCharacterData(); QString cData = c.data(); if (data && data->pos != -1 && data->pos >= startingPos && data->pos <= startingPos + cData.size()) { // time to do some action! ;) int localStartPos = data->pos - startingPos; //BEGIN DELETE TEXT if (data->actionType == TsContentEditingData::DeleteText) { //(data->lengthOfStringToRemove!=-1) if (localStartPos + data->lengthOfStringToRemove > cData.size()) { //text is fragmented into several QDomCharacterData int localDelLen = cData.size() - localStartPos; //qCWarning(LOKALIZE_LOG)<<"text is fragmented into several QDomCharacterData. localDelLen:"<lengthOfStringToRemove = data->lengthOfStringToRemove - localDelLen; //data->pos=startingPos; //qCWarning(LOKALIZE_LOG)<<"\tsetup:"<pos<lengthOfStringToRemove; } else { //qCWarning(LOKALIZE_LOG)<<"simple delete"<lengthOfStringToRemove; c.deleteData(localStartPos, data->lengthOfStringToRemove); data->actionType = TsContentEditingData::CheckLength; return QString('a');//so it exits 100% } } //END DELETE TEXT //INSERT else if (data->actionType == TsContentEditingData::InsertText) { c.insertData(localStartPos, data->stringToInsert); data->actionType = TsContentEditingData::CheckLength; return QString('a');//so it exits 100% } cData = c.data(); } //else // if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/) // qCWarning(LOKALIZE_LOG)<<"arg!"<pos"<pos; result += cData; startingPos += cData.size(); } n = n.nextSibling(); } if (!seenCharacterDataAfterElement) { //add empty charData child so that user could add some text elem.appendChild(elem.ownerDocument().createTextNode(QString())); } return result; } //flat-model interface (ignores XLIFF grouping) CatalogString TsStorage::catalogString(QDomElement contentElement) const { CatalogString catalogString; TsContentEditingData data(TsContentEditingData::Get); catalogString.string = content(contentElement, &data); return catalogString; } CatalogString TsStorage::catalogString(const DocPosition& pos) const { return catalogString(pos.part == DocPosition::Target ? targetForPos(pos) : sourceForPos(pos.entry)); } CatalogString TsStorage::targetWithTags(DocPosition pos) const { return catalogString(targetForPos(pos)); } CatalogString TsStorage::sourceWithTags(DocPosition pos) const { return catalogString(sourceForPos(pos.entry)); } QString TsStorage::source(const DocPosition& pos) const { return content(sourceForPos(pos.entry)); } QString TsStorage::target(const DocPosition& pos) const { return content(targetForPos(pos)); } +QString TsStorage::sourceWithPlurals(const DocPosition& pos) const +{ + return source(pos); +} +QString TsStorage::targetWithPlurals(const DocPosition& pos) const +{ + return target(pos); +} + void TsStorage::targetDelete(const DocPosition& pos, int count) { TsContentEditingData data(pos.offset, count); content(targetForPos(pos), &data); } void TsStorage::targetInsert(const DocPosition& pos, const QString& arg) { qCWarning(LOKALIZE_LOG) << pos.entry << arg; QDomElement targetEl = targetForPos(pos); //BEGIN add <*target> if (targetEl.isNull()) { QDomNode unitEl = unitForPos(pos.entry); QDomNode refNode = unitEl.firstChildElement(names[SourceTag]); targetEl = unitEl.insertAfter(m_doc.createElement(names[TargetTag]), refNode).toElement(); if (pos.entry < size()) { targetEl.appendChild(m_doc.createTextNode(arg));//i bet that pos.offset is 0 ;) return; } } //END add <*target> if (arg.isEmpty()) return; //means we were called just to add tag TsContentEditingData data(pos.offset, arg); content(targetEl, &data); } void TsStorage::setTarget(const DocPosition& pos, const QString& arg) { Q_UNUSED(pos); Q_UNUSED(arg); //TODO } QVector TsStorage::altTrans(const DocPosition& pos) const { QVector result; QString oldsource = content(unitForPos(pos.entry).firstChildElement(names[OldSourceTag])); if (!oldsource.isEmpty()) result << AltTrans(CatalogString(oldsource), i18n("Previous source value, saved by lupdate tool")); return result; } QStringList TsStorage::sourceFiles(const DocPosition& pos) const { QStringList result; QDomElement elem = unitForPos(pos.entry).firstChildElement(attrnames[LocationAttr]); while (!elem.isNull()) { QString sourcefile = elem.attribute(QStringLiteral("filename")); QString linenumber = elem.attribute(QStringLiteral("line")); if (!(sourcefile.isEmpty() && linenumber.isEmpty())) result.append(sourcefile + ':' + linenumber); elem = elem.nextSiblingElement(attrnames[LocationAttr]); } //qSort(result); return result; } QVector TsStorage::notes(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(names[NoteTag]); while (!elem.isNull()) { Note note; note.content = elem.text(); result.append(note); elem = elem.nextSiblingElement(names[NoteTag]); } return result; } QVector TsStorage::developerNotes(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(names[DevNoteTag]); while (!elem.isNull()) { Note note; note.content = elem.text(); result.append(note); elem = elem.nextSiblingElement(names[DevNoteTag]); } return result; } Note TsStorage::setNote(DocPosition pos, const Note& note) { //qCWarning(LOKALIZE_LOG)< if needed QDomElement target = unitForPos(pos.entry).firstChildElement(names[TargetTag]); //asking directly to bypass plural state detection if (target.attribute(attrnames[TypeAttr]) == attrvalues[ObsoleteVal]) return; if (approved) target.removeAttribute(attrnames[TypeAttr]); else target.setAttribute(attrnames[TypeAttr], QStringLiteral("unfinished")); } bool TsStorage::isApproved(const DocPosition& pos) const { QDomElement target = unitForPos(pos.entry).firstChildElement(names[TargetTag]); return !target.hasAttribute(attrnames[TypeAttr]) || target.attribute(attrnames[TypeAttr]) == attrvalues[VanishedVal]; } bool TsStorage::isObsolete(int entry) const { QDomElement target = unitForPos(entry).firstChildElement(names[TargetTag]); QString v = target.attribute(attrnames[TypeAttr]); return v == attrvalues[ObsoleteVal] || v == attrvalues[VanishedVal]; } bool TsStorage::isEmpty(const DocPosition& pos) const { TsContentEditingData data(TsContentEditingData::CheckLength); return content(targetForPos(pos), &data).isEmpty(); } bool TsStorage::isEquivTrans(const DocPosition& pos) const { Q_UNUSED(pos) return true;//targetForPos(pos.entry).attribute("equiv-trans")!="no"; } void TsStorage::setEquivTrans(const DocPosition& pos, bool equivTrans) { Q_UNUSED(pos) Q_UNUSED(equivTrans) //targetForPos(pos.entry).setAttribute("equiv-trans",noyes[equivTrans]); } QDomElement TsStorage::unitForPos(int pos) const { return entries.at(pos).toElement(); } QDomElement TsStorage::targetForPos(DocPosition pos) const { QDomElement unit = unitForPos(pos.entry); QDomElement translation = unit.firstChildElement(names[TargetTag]); if (!unit.hasAttribute(names[PluralTag])) return translation; if (pos.form == -1) pos.form = 0; QDomNodeList forms = translation.elementsByTagName(QStringLiteral("numerusform")); while (pos.form >= forms.size()) translation.appendChild(unit.ownerDocument().createElement(QStringLiteral("numerusform"))); return forms.at(pos.form).toElement(); } QDomElement TsStorage::sourceForPos(int pos) const { return unitForPos(pos).firstChildElement(names[SourceTag]); } void TsStorage::setTargetLangCode(const QString& langCode) { m_targetLangCode = langCode; QDomElement file = m_doc.elementsByTagName(QStringLiteral("TS")).at(0).toElement(); if (m_targetLangCode != file.attribute(QStringLiteral("language")).replace('-', '_')) { QString l = langCode; file.setAttribute(QStringLiteral("language"), l.replace('_', '-')); } } //END STORAGE TRANSLATION diff --git a/src/catalog/ts/tsstorage.h b/src/catalog/ts/tsstorage.h index c60037a..3b56954 100644 --- a/src/catalog/ts/tsstorage.h +++ b/src/catalog/ts/tsstorage.h @@ -1,119 +1,121 @@ /* Copyright 2008-2014 Nick Shaforostoff 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 TSSTORAGE_H #define TSSTORAGE_H #include "catalogstorage.h" #include #include #include #include // class QDomDocument; class TsStorage: public CatalogStorage { public: TsStorage(); ~TsStorage(); int capabilities() const; int load(QIODevice* device); bool save(QIODevice* device, bool belongsToProject = false); int size() const; bool isEmpty() const; //flat-model interface (ignores TS grouping) QString source(const DocPosition& pos) const; QString target(const DocPosition& pos) const; + QString sourceWithPlurals(const DocPosition& pos) const; + QString targetWithPlurals(const DocPosition& pos) const; CatalogString targetWithTags(DocPosition pos) const; CatalogString sourceWithTags(DocPosition pos) const; CatalogString catalogString(const DocPosition& pos) const; /// all plural forms. pos.form doesn't matter TODO QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } void targetDelete(const DocPosition& pos, int count); void targetInsert(const DocPosition& pos, const QString& arg); void setTarget(const DocPosition& pos, const QString& arg);//called for mergeCatalog QStringList sourceFiles(const DocPosition& pos) const; QVector altTrans(const DocPosition& pos) const; ///@a pos.form is note number Note setNote(DocPosition pos, const Note& note); QVector notes(const DocPosition& pos) const; QVector developerNotes(const DocPosition& pos) const; QStringList context(const DocPosition& pos) const; QStringList matchData(const DocPosition& pos) const; QString id(const DocPosition& pos) const; bool isPlural(const DocPosition& pos) const; bool isEmpty(const DocPosition& pos) const; bool isEquivTrans(const DocPosition& pos) const; void setEquivTrans(const DocPosition& pos, bool equivTrans); bool isApproved(const DocPosition& pos) const; void setApproved(const DocPosition& pos, bool approved); bool isObsolete(int entry) const; QString mimetype()const { return QStringLiteral("application/x-linguist"); } QString fileType()const { return QStringLiteral("Qt Linguist (*.ts)"); } CatalogType type()const { return Ts; } void setTargetLangCode(const QString& langCode); private: QDomElement unitForPos(int pos) const; QDomElement targetForPos(DocPosition pos) const; QDomElement sourceForPos(int pos) const; CatalogString catalogString(QDomElement contentElement) const; private: mutable QDomDocument m_doc; QDomNodeList entries; }; #endif diff --git a/src/catalog/xliff/xliffstorage.cpp b/src/catalog/xliff/xliffstorage.cpp index b91e1d5..9ef7b19 100644 --- a/src/catalog/xliff/xliffstorage.cpp +++ b/src/catalog/xliff/xliffstorage.cpp @@ -1,1017 +1,1025 @@ /* Copyright 2008-2009 Nick Shaforostoff 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 "xliffstorage.h" #include "lokalize_debug.h" #include "gettextheader.h" #include "project.h" #include "version.h" #include "prefs_lokalize.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif static const QString noyes[] = {U("no"), U("yes")}; static const QString bintargettarget[] = {U("bin-target"), U("target")}; static const QString binsourcesource[] = {U("bin-source"), U("source")}; static const QString NOTE = U("note"); XliffStorage::XliffStorage() : CatalogStorage() { } XliffStorage::~XliffStorage() { } int XliffStorage::capabilities() const { return KeepsNoteAuthors | MultipleNotes | Phases | ExtendedStates | Tags; } //BEGIN OPEN/SAVE int XliffStorage::load(QIODevice* device) { QTime chrono; chrono.start(); QXmlSimpleReader reader; reader.setFeature(QStringLiteral("http://qt-project.org/xml/features/report-whitespace-only-CharData"), true); reader.setFeature(QStringLiteral("http://xml.org/sax/features/namespaces"), false); QXmlInputSource source(device); QString errorMsg; int errorLine;//+errorColumn; bool success = m_doc.setContent(&source, &reader, &errorMsg, &errorLine/*,errorColumn*/); QString FILE = QStringLiteral("file"); if (!success || m_doc.elementsByTagName(FILE).isEmpty()) { qCWarning(LOKALIZE_LOG) << errorMsg; return errorLine + 1; } QDomElement file = m_doc.elementsByTagName(FILE).at(0).toElement(); m_sourceLangCode = file.attribute(QStringLiteral("source-language")).replace(u'-', u'_'); m_targetLangCode = file.attribute(QStringLiteral("target-language")).replace(u'-', u'_'); m_numberOfPluralForms = numberOfPluralFormsForLangCode(m_targetLangCode); //Create entry mapping. //Along the way: for langs with more than 2 forms //we create any form-entries additionally needed entries = m_doc.elementsByTagName(QStringLiteral("trans-unit")); int size = entries.size(); m_map.clear(); m_map.reserve(size); for (int i = 0; i < size; ++i) { QDomElement parentElement = entries.at(i).parentNode().toElement(); //if (Q_UNLIKELY( e.isNull() ))//sanity // continue; m_map << i; m_unitsById[entries.at(i).toElement().attribute(QStringLiteral("id"))] = i; if (parentElement.tagName() == QLatin1String("group") && parentElement.attribute(QStringLiteral("restype")) == QLatin1String("x-gettext-plurals")) { m_plurals.insert(i); int localPluralNum = m_numberOfPluralForms; while (--localPluralNum > 0 && (++i) < size) { QDomElement p = entries.at(i).parentNode().toElement(); if (p.tagName() == QLatin1String("group") && p.attribute(QStringLiteral("restype")) == QLatin1String("x-gettext-plurals")) continue; parentElement.appendChild(entries.at(m_map.last()).cloneNode()); } } } binEntries = m_doc.elementsByTagName(QStringLiteral("bin-unit")); size = binEntries.size(); int offset = m_map.size(); for (int i = 0; i < size; ++i) m_unitsById[binEntries.at(i).toElement().attribute(QStringLiteral("id"))] = offset + i; // entries=m_doc.elementsByTagName("body"); // uint i=0; // uint lim=size(); // while (i msgstr(item.msgstrPlural()); // while (msgstr.count() tags; QString stringToInsert; int pos; int lengthOfStringToRemove; ActionType actionType; ///Get ContentEditingData(ActionType type = Get) : pos(-1) , lengthOfStringToRemove(-1) , actionType(type) {} ///DeleteText ContentEditingData(int p, int l) : pos(p) , lengthOfStringToRemove(l) , actionType(DeleteText) {} ///InsertText ContentEditingData(int p, const QString& s) : stringToInsert(s) , pos(p) , lengthOfStringToRemove(-1) , actionType(InsertText) {} ///InsertTag ContentEditingData(int p, const InlineTag& range) : pos(p) , lengthOfStringToRemove(-1) , actionType(InsertTag) { tags.append(range); } ///DeleteTag ContentEditingData(int p) : pos(p) , lengthOfStringToRemove(-1) , actionType(DeleteTag) {} }; static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data); /** * walks through XLIFF XML and performs actions depending on ContentEditingData: * - reads content * - deletes content, or * - inserts content */ static QString content(QDomElement elem, ContentEditingData* data = 0) { return doContent(elem, 0, data); } static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data) { //actually startingPos is current pos QString result; if (elem.isNull() || (!result.isEmpty() && data && data->actionType == ContentEditingData::CheckLength)) return QString(); bool seenCharacterDataAfterElement = false; QDomNode n = elem.firstChild(); while (!n.isNull()) { if (n.isCharacterData()) { seenCharacterDataAfterElement = true; QDomCharacterData c = n.toCharacterData(); QString cData = c.data(); if (data && data->pos != -1 && data->pos >= startingPos && data->pos <= startingPos + cData.size()) { // time to do some action! ;) int localStartPos = data->pos - startingPos; //BEGIN DELETE TEXT if (data->actionType == ContentEditingData::DeleteText) { //(data->lengthOfStringToRemove!=-1) if (localStartPos + data->lengthOfStringToRemove > cData.size()) { //text is fragmented into several QDomCharacterData int localDelLen = cData.size() - localStartPos; //qCWarning(LOKALIZE_LOG)<<"text is fragmented into several QDomCharacterData. localDelLen:"<lengthOfStringToRemove = data->lengthOfStringToRemove - localDelLen; //data->pos=startingPos; //qCWarning(LOKALIZE_LOG)<<"\tsetup:"<pos<lengthOfStringToRemove; } else { //qCWarning(LOKALIZE_LOG)<<"simple delete"<lengthOfStringToRemove; c.deleteData(localStartPos, data->lengthOfStringToRemove); data->actionType = ContentEditingData::CheckLength; return QString('a');//so it exits 100% } } //END DELETE TEXT //INSERT else if (data->actionType == ContentEditingData::InsertText) { c.insertData(localStartPos, data->stringToInsert); data->actionType = ContentEditingData::CheckLength; return QString('a');//so it exits 100% } //BEGIN INSERT TAG else if (data->actionType == ContentEditingData::InsertTag) { const InlineTag& tag = data->tags.first(); QString mid = cData.mid(localStartPos); qCDebug(LOKALIZE_LOG) << "inserting tag" << tag.name() << tag.id << tag.start << tag.end << mid << data->pos << startingPos; if (mid.size()) c.deleteData(localStartPos, mid.size()); QDomElement newNode = elem.insertAfter(elem.ownerDocument().createElement(tag.getElementName()), n).toElement(); newNode.setAttribute(QStringLiteral("id"), tag.id); if (!tag.xid.isEmpty()) newNode.setAttribute(QStringLiteral("xid"), tag.xid); if (tag.isPaired() && tag.end > (tag.start + 1)) { //qCWarning(LOKALIZE_LOG)<<"isPaired"; int len = tag.end - tag.start - 1; //-image symbol int localLen = qMin(len, mid.size()); if (localLen) { //appending text //qCWarning(LOKALIZE_LOG)<<"localLen. appending"<missingLen (or siblings end) int childrenCumulativeLen = 0; QDomNode sibling = newNode.nextSibling(); while (!sibling.isNull()) { //&&(childrenCumulativeLen missingLen) { if (tmp.isCharacterData()) { //divide the last string const QString& endData = tmp.toCharacterData().data(); QString last = endData.left(endData.size() - (childrenCumulativeLen - missingLen)); newNode.appendChild(elem.ownerDocument().createTextNode(last)); tmp.toCharacterData().deleteData(0, last.size()); //qCWarning(LOKALIZE_LOG)<<"end of add"<actionType = ContentEditingData::CheckLength; return QStringLiteral("a");//we're done here } //END INSERT TAG cData = c.data(); } //else // if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/) // qCWarning(LOKALIZE_LOG)<<"arg!"<pos"<pos; result += cData; startingPos += cData.size(); } else if (n.isElement()) { QDomElement el = n.toElement(); //BEGIN DELETE TAG if (data && data->actionType == ContentEditingData::DeleteTag && data->pos == startingPos) { //qCWarning(LOKALIZE_LOG)<<"start deleting tag"; data->tags.append(InlineTag(startingPos, -1, InlineTag::getElementType(el.tagName().toUtf8()), el.attribute("id"), el.attribute("xid"))); if (data->tags.first().isPaired()) { //get end position ContentEditingData subData(ContentEditingData::Get); QString subContent = doContent(el, startingPos, &subData); data->tags[0].end = 1 + startingPos + subContent.size(); //tagsymbol+text //qCWarning(LOKALIZE_LOG)<<"get end position"<actionType = ContentEditingData::CheckLength; return QStringLiteral("a");//we're done here } //END DELETE TAG if (!seenCharacterDataAfterElement) //add empty charData child so that user could add some text elem.insertBefore(elem.ownerDocument().createTextNode(QString()), n); seenCharacterDataAfterElement = false; if (data) { result += QChar(TAGRANGE_IMAGE_SYMBOL); ++startingPos; } int oldStartingPos = startingPos; //detect type of the tag InlineTag::InlineElement i = InlineTag::getElementType(el.tagName().toUtf8()); //1 or 2 images to represent it? //2 = there may be content inside if (InlineTag::isPaired(i)) { QString recursiveContent = doContent(el, startingPos, data); if (!recursiveContent.isEmpty()) { result += recursiveContent; startingPos += recursiveContent.size(); } if (data) { result += QChar(TAGRANGE_IMAGE_SYMBOL); ++startingPos; } } if (data && data->actionType == ContentEditingData::Get) { QString id = el.attribute(QStringLiteral("id")); if (i == InlineTag::mrk) //TODO attr map id = el.attribute(QStringLiteral("mtype")); //qCWarning(LOKALIZE_LOG)<<"tagName"<tags.append(InlineTag(oldStartingPos - 1, startingPos - 1, i, id, el.attribute(QStringLiteral("xid")))); } } n = n.nextSibling(); } if (!seenCharacterDataAfterElement) { //add empty charData child so that user could add some text elem.appendChild(elem.ownerDocument().createTextNode(QString())); } return result; } //flat-model interface (ignores XLIFF grouping) CatalogString XliffStorage::catalogString(QDomElement unit, DocPosition::Part part) const { static const QString names[] = {U("source"), U("target"), U("seg-source")}; CatalogString catalogString; ContentEditingData data(ContentEditingData::Get); int nameIndex = part == DocPosition::Target; if (nameIndex == 0 && !unit.firstChildElement(names[2]).isNull()) nameIndex = 2; catalogString.string = content(unit.firstChildElement(names[nameIndex]), &data); catalogString.tags = data.tags; return catalogString; } CatalogString XliffStorage::catalogString(const DocPosition& pos) const { return catalogString(unitForPos(pos.entry), pos.part); } CatalogString XliffStorage::targetWithTags(DocPosition pos) const { return catalogString(unitForPos(pos.entry), DocPosition::Target); } CatalogString XliffStorage::sourceWithTags(DocPosition pos) const { return catalogString(unitForPos(pos.entry), DocPosition::Source); } static QString genericContent(QDomElement elem, bool nonbin) { return nonbin ? content(elem) : elem.firstChildElement(QStringLiteral("external-file")).attribute(QStringLiteral("href")); } QString XliffStorage::source(const DocPosition& pos) const { return genericContent(sourceForPos(pos.entry), pos.entry < size()); } QString XliffStorage::target(const DocPosition& pos) const { return genericContent(targetForPos(pos.entry), pos.entry < size()); } +QString XliffStorage::sourceWithPlurals(const DocPosition& pos) const +{ + return source(pos); +} +QString XliffStorage::targetWithPlurals(const DocPosition& pos) const +{ + return target(pos); +} void XliffStorage::targetDelete(const DocPosition& pos, int count) { if (pos.entry < size()) { ContentEditingData data(pos.offset, count); content(targetForPos(pos.entry), &data); } else { //only bulk delete requests are generated targetForPos(pos.entry).firstChildElement(QStringLiteral("external-file")).setAttribute(QStringLiteral("href"), QString()); } } void XliffStorage::targetInsert(const DocPosition& pos, const QString& arg) { //qCWarning(LOKALIZE_LOG)<<"targetinsert"< if (targetEl.isNull()) { QDomNode unitEl = unitForPos(pos.entry); QDomNode refNode = unitEl.firstChildElement(QStringLiteral("seg-source")); //obey standard if (refNode.isNull()) refNode = unitEl.firstChildElement(binsourcesource[pos.entry < size()]); targetEl = unitEl.insertAfter(m_doc.createElement(bintargettarget[pos.entry < size()]), refNode).toElement(); targetEl.setAttribute(QStringLiteral("state"), QStringLiteral("new")); if (pos.entry < size()) { targetEl.appendChild(m_doc.createTextNode(arg));//i bet that pos.offset is 0 ;) return; } } //END add <*target> if (arg.isEmpty()) return; //means we were called just to add tag if (pos.entry >= size()) { QDomElement ef = targetEl.firstChildElement(QStringLiteral("external-file")); if (ef.isNull()) ef = targetEl.appendChild(m_doc.createElement(QStringLiteral("external-file"))).toElement(); ef.setAttribute(QStringLiteral("href"), arg); return; } ContentEditingData data(pos.offset, arg); content(targetEl, &data); } void XliffStorage::targetInsertTag(const DocPosition& pos, const InlineTag& tag) { targetInsert(pos, QString()); //adds if needed ContentEditingData data(tag.start, tag); content(targetForPos(pos.entry), &data); } InlineTag XliffStorage::targetDeleteTag(const DocPosition& pos) { ContentEditingData data(pos.offset); content(targetForPos(pos.entry), &data); if (data.tags[0].end == -1) data.tags[0].end = data.tags[0].start; return data.tags.first(); } void XliffStorage::setTarget(const DocPosition& pos, const QString& arg) { Q_UNUSED(pos); Q_UNUSED(arg); //TODO } QVector XliffStorage::altTrans(const DocPosition& pos) const { QVector result; QDomElement elem = unitForPos(pos.entry).firstChildElement(QStringLiteral("alt-trans")); while (!elem.isNull()) { AltTrans aTrans; aTrans.source = catalogString(elem, DocPosition::Source); aTrans.target = catalogString(elem, DocPosition::Target); aTrans.phase = elem.attribute(QStringLiteral("phase-name")); aTrans.origin = elem.attribute(QStringLiteral("origin")); aTrans.score = elem.attribute(QStringLiteral("match-quality")).toInt(); aTrans.lang = elem.firstChildElement(QStringLiteral("target")).attribute(QStringLiteral("xml:lang")); const char* const types[] = { "proposal", "previous-version", "rejected", "reference", "accepted" }; QString typeStr = elem.attribute(QStringLiteral("alttranstype")); int i = -1; while (++i < int(sizeof(types) / sizeof(char*)) && types[i] != typeStr) ; aTrans.type = AltTrans::Type(i); result << aTrans; elem = elem.nextSiblingElement(QStringLiteral("alt-trans")); } return result; } static QDomElement phaseElement(QDomDocument m_doc, const QString& name, QDomElement& phasegroup) { QDomElement file = m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); QDomElement header = file.firstChildElement(QStringLiteral("header")); phasegroup = header.firstChildElement(QStringLiteral("phase-group")); if (phasegroup.isNull()) { phasegroup = m_doc.createElement(QStringLiteral("phase-group")); //order following XLIFF spec QDomElement skl = header.firstChildElement(QStringLiteral("skl")); if (!skl.isNull()) header.insertAfter(phasegroup, skl); else header.insertBefore(phasegroup, header.firstChildElement()); } QDomElement phaseElem = phasegroup.firstChildElement(QStringLiteral("phase")); while (!phaseElem.isNull() && phaseElem.attribute(QStringLiteral("phase-name")) != name) phaseElem = phaseElem.nextSiblingElement(QStringLiteral("phase")); return phaseElem; } static Phase phaseFromElement(QDomElement phaseElem) { Phase phase; phase.name = phaseElem.attribute(QStringLiteral("phase-name")); phase.process = phaseElem.attribute(QStringLiteral("process-name")); phase.company = phaseElem.attribute(QStringLiteral("company-name")); phase.contact = phaseElem.attribute(QStringLiteral("contact-name")); phase.email = phaseElem.attribute(QStringLiteral("contact-email")); phase.phone = phaseElem.attribute(QStringLiteral("contact-phone")); phase.tool = phaseElem.attribute(QStringLiteral("tool-id")); phase.date = QDate::fromString(phaseElem.attribute(QStringLiteral("date")), Qt::ISODate); return phase; } Phase XliffStorage::updatePhase(const Phase& phase) { QDomElement phasegroup; QDomElement phaseElem = phaseElement(m_doc, phase.name, phasegroup); Phase prev = phaseFromElement(phaseElem); if (phaseElem.isNull() && !phase.name.isEmpty()) { phaseElem = phasegroup.appendChild(m_doc.createElement(QStringLiteral("phase"))).toElement(); phaseElem.setAttribute(QStringLiteral("phase-name"), phase.name); } phaseElem.setAttribute(QStringLiteral("process-name"), phase.process); if (!phase.company.isEmpty()) phaseElem.setAttribute(QStringLiteral("company-name"), phase.company); phaseElem.setAttribute(QStringLiteral("contact-name"), phase.contact); phaseElem.setAttribute(QStringLiteral("contact-email"), phase.email); //Q_ASSERT(phase.contact.length()); //is empty when exiting w/o saving if (!phase.phone.isEmpty()) phaseElem.setAttribute(QLatin1String("contact-phone"), phase.phone); phaseElem.setAttribute(QStringLiteral("tool-id"), phase.tool); if (phase.date.isValid()) phaseElem.setAttribute(QStringLiteral("date"), phase.date.toString(Qt::ISODate)); return prev; } QList XliffStorage::allPhases() const { QList result; QDomElement file = m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); QDomElement header = file.firstChildElement(QStringLiteral("header")); QDomElement phasegroup = header.firstChildElement(QStringLiteral("phase-group")); QDomElement phaseElem = phasegroup.firstChildElement(QStringLiteral("phase")); while (!phaseElem.isNull()) { result.append(phaseFromElement(phaseElem)); phaseElem = phaseElem.nextSiblingElement(QStringLiteral("phase")); } return result; } Phase XliffStorage::phase(const QString& name) const { QDomElement phasegroup; QDomElement phaseElem = phaseElement(m_doc, name, phasegroup); return phaseFromElement(phaseElem); } QMap XliffStorage::allTools() const { QMap result; QDomElement file = m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); QDomElement header = file.firstChildElement(QStringLiteral("header")); QDomElement toolElem = header.firstChildElement(QStringLiteral("tool")); while (!toolElem.isNull()) { Tool tool; tool.tool = toolElem.attribute(QStringLiteral("tool-id")); tool.name = toolElem.attribute(QStringLiteral("tool-name")); tool.version = toolElem.attribute(QStringLiteral("tool-version")); tool.company = toolElem.attribute(QStringLiteral("tool-company")); result.insert(tool.tool, tool); toolElem = toolElem.nextSiblingElement(QStringLiteral("tool")); } return result; } QStringList XliffStorage::sourceFiles(const DocPosition& pos) const { QStringList result; QDomElement elem = unitForPos(pos.entry).firstChildElement(QStringLiteral("context-group")); while (!elem.isNull()) { if (elem.attribute(QStringLiteral("purpose")).contains(QLatin1String("location"))) { QDomElement context = elem.firstChildElement(QStringLiteral("context")); while (!context.isNull()) { QString sourcefile; QString linenumber; const QString contextType = context.attribute(QStringLiteral("context-type")); if (contextType == QLatin1String("sourcefile")) sourcefile = context.text(); else if (contextType == QLatin1String("linenumber")) linenumber = context.text(); if (!(sourcefile.isEmpty() && linenumber.isEmpty())) result.append(sourcefile % ':' % linenumber); context = context.nextSiblingElement(QStringLiteral("context")); } } elem = elem.nextSiblingElement(QStringLiteral("context-group")); } //qSort(result); return result; } static void initNoteFromElement(Note& note, QDomElement elem) { note.content = elem.text(); note.from = elem.attribute(QStringLiteral("from")); note.lang = elem.attribute(QStringLiteral("xml:lang")); if (elem.attribute(QStringLiteral("annotates")) == QLatin1String("source")) note.annotates = Note::Source; else if (elem.attribute(QStringLiteral("annotates")) == QLatin1String("target")) note.annotates = Note::Target; bool ok; note.priority = elem.attribute(QStringLiteral("priority")).toInt(&ok); if (!ok) note.priority = 0; } QVector XliffStorage::notes(const DocPosition& pos) const { QList result; QDomElement elem = entries.at(m_map.at(pos.entry)).firstChildElement(NOTE); while (!elem.isNull()) { Note note; initNoteFromElement(note, elem); result.append(note); elem = elem.nextSiblingElement(NOTE); } qSort(result); return result.toVector(); } QVector XliffStorage::developerNotes(const DocPosition& pos) const { Q_UNUSED(pos); //TODO return QVector(); } Note XliffStorage::setNote(DocPosition pos, const Note& note) { //qCWarning(LOKALIZE_LOG)< result; QDomNodeList notes = m_doc.elementsByTagName(NOTE); int i = notes.size(); while (--i >= 0) { QString from = notes.at(i).toElement().attribute(QStringLiteral("from")); if (!from.isEmpty()) result.insert(from); } return result.toList(); } QVector phaseNotes(QDomDocument m_doc, const QString& phasename, bool remove = false) { QVector result; QDomElement phasegroup; QDomElement phaseElem = phaseElement(m_doc, phasename, phasegroup); QDomElement noteElem = phaseElem.firstChildElement(NOTE); while (!noteElem.isNull()) { Note note; initNoteFromElement(note, noteElem); result.append(note); QDomElement old = noteElem; noteElem = noteElem.nextSiblingElement(NOTE); if (remove) phaseElem.removeChild(old); } return result; } QVector XliffStorage::phaseNotes(const QString& phasename) const { return ::phaseNotes(m_doc, phasename, false); } QVector XliffStorage::setPhaseNotes(const QString& phasename, QVector notes) { QVector result =::phaseNotes(m_doc, phasename, true); QDomElement phasegroup; QDomElement phaseElem = phaseElement(m_doc, phasename, phasegroup); foreach (const Note& note, notes) { QDomElement elem = phaseElem.appendChild(m_doc.createElement(NOTE)).toElement(); elem.appendChild(m_doc.createTextNode(note.content)); if (!note.from.isEmpty()) elem.setAttribute(QStringLiteral("from"), note.from); if (note.priority) elem.setAttribute(QStringLiteral("priority"), note.priority); } return result; } QString XliffStorage::setPhase(const DocPosition& pos, const QString& phase) { QString PHASENAME = QStringLiteral("phase-name"); targetInsert(pos, QString()); //adds if needed QDomElement target = targetForPos(pos.entry); QString result = target.attribute(PHASENAME); if (phase.isEmpty()) target.removeAttribute(PHASENAME); else if (phase != result) target.setAttribute(PHASENAME, phase); return result; } QString XliffStorage::phase(const DocPosition& pos) const { QDomElement target = targetForPos(pos.entry); return target.attribute(QStringLiteral("phase-name")); } QStringList XliffStorage::context(const DocPosition& pos) const { Q_UNUSED(pos); //TODO return QStringList(QString()); } QStringList XliffStorage::matchData(const DocPosition& pos) const { Q_UNUSED(pos); return QStringList(); } QString XliffStorage::id(const DocPosition& pos) const { return unitForPos(pos.entry).attribute(QStringLiteral("id")); } bool XliffStorage::isPlural(const DocPosition& pos) const { return m_plurals.contains(pos.entry); } /* bool XliffStorage::isApproved(const DocPosition& pos) const { return entries.at(m_map.at(pos.entry)).toElement().attribute("approved")=="yes"; } void XliffStorage::setApproved(const DocPosition& pos, bool approved) { static const char* const noyes[]={"no","yes"}; entries.at(m_map.at(pos.entry)).toElement().setAttribute("approved",noyes[approved]); } */ static const QString xliff_states[] = { U("new"), U("needs-translation"), U("needs-l10n"), U("needs-adaptation"), U("translated"), U("needs-review-translation"), U("needs-review-l10n"), U("needs-review-adaptation"), U("final"), U("signed-off") }; TargetState stringToState(const QString& state) { int i = sizeof(xliff_states) / sizeof(QString); while (--i > 0 && state != xliff_states[i]) ; return TargetState(i); } TargetState XliffStorage::setState(const DocPosition& pos, TargetState state) { targetInsert(pos, QString()); //adds if needed QDomElement target = targetForPos(pos.entry); TargetState prev = stringToState(target.attribute(QStringLiteral("state"))); target.setAttribute(QStringLiteral("state"), xliff_states[state]); unitForPos(pos.entry).setAttribute(QStringLiteral("approved"), noyes[state == SignedOff]); return prev; } TargetState XliffStorage::state(const DocPosition& pos) const { QDomElement target = targetForPos(pos.entry); if (!target.hasAttribute(QStringLiteral("state")) && unitForPos(pos.entry).attribute(QStringLiteral("approved")) == QLatin1String("yes")) return SignedOff; return stringToState(target.attribute(QStringLiteral("state"))); } bool XliffStorage::isEmpty(const DocPosition& pos) const { ContentEditingData data(ContentEditingData::CheckLength); return content(targetForPos(pos.entry), &data).isEmpty(); } bool XliffStorage::isEquivTrans(const DocPosition& pos) const { return targetForPos(pos.entry).attribute(QStringLiteral("equiv-trans")) != QLatin1String("no"); } void XliffStorage::setEquivTrans(const DocPosition& pos, bool equivTrans) { targetForPos(pos.entry).setAttribute(QStringLiteral("equiv-trans"), noyes[equivTrans]); } QDomElement XliffStorage::unitForPos(int pos) const { if (pos < size()) return entries.at(m_map.at(pos)).toElement(); return binEntries.at(pos - size()).toElement(); } QDomElement XliffStorage::targetForPos(int pos) const { return unitForPos(pos).firstChildElement(bintargettarget[pos < size()]); } QDomElement XliffStorage::sourceForPos(int pos) const { return unitForPos(pos).firstChildElement(binsourcesource[pos < size()]); } int XliffStorage::binUnitsCount() const { return binEntries.size(); } int XliffStorage::unitById(const QString& id) const { return m_unitsById.contains(id) ? m_unitsById.value(id) : -1; } QString XliffStorage::originalOdfFilePath() { QDomElement file = m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); return file.attribute(QStringLiteral("original")); } void XliffStorage::setOriginalOdfFilePath(const QString& odfFilePath) { QDomElement file = m_doc.elementsByTagName(QStringLiteral("file")).at(0).toElement(); return file.setAttribute(QStringLiteral("original"), odfFilePath); } //END STORAGE TRANSLATION diff --git a/src/catalog/xliff/xliffstorage.h b/src/catalog/xliff/xliffstorage.h index c3479f8..99b0e82 100644 --- a/src/catalog/xliff/xliffstorage.h +++ b/src/catalog/xliff/xliffstorage.h @@ -1,140 +1,142 @@ /* Copyright 2008-2014 Nick Shaforostoff 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 XLIFFSTORAGE_H #define XLIFFSTORAGE_H #include "catalogstorage.h" #include #include #include #include #include // class QDomDocument; class XliffStorage: public CatalogStorage { public: XliffStorage(); ~XliffStorage(); int capabilities() const; int load(QIODevice* device); bool save(QIODevice* device, bool belongsToProject = false); int size() const; bool isEmpty() const; //flat-model interface (ignores XLIFF grouping) QString source(const DocPosition& pos) const; QString target(const DocPosition& pos) const; + QString sourceWithPlurals(const DocPosition& pos) const; + QString targetWithPlurals(const DocPosition& pos) const; CatalogString targetWithTags(DocPosition pos) const; CatalogString sourceWithTags(DocPosition pos) const; CatalogString catalogString(const DocPosition& pos) const; /// all plural forms. pos.form doesn't matter TODO QStringList sourceAllForms(const DocPosition& pos, bool stripNewLines = false) const { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } QStringList targetAllForms(const DocPosition& pos, bool stripNewLines = false) const { Q_UNUSED(pos) Q_UNUSED(stripNewLines) return QStringList(); } void targetDelete(const DocPosition& pos, int count); void targetInsert(const DocPosition& pos, const QString& arg); void setTarget(const DocPosition& pos, const QString& arg);//called for mergeCatalog void targetInsertTag(const DocPosition&, const InlineTag&); InlineTag targetDeleteTag(const DocPosition&); Phase updatePhase(const Phase& phase); QList allPhases() const; Phase phase(const QString& name) const; QMap allTools() const; QVector phaseNotes(const QString& phase) const; QVector setPhaseNotes(const QString& phase, QVector notes); QStringList sourceFiles(const DocPosition& pos) const; QVector altTrans(const DocPosition& pos) const; ///@a pos.form is note number Note setNote(DocPosition pos, const Note& note); QVector notes(const DocPosition& pos) const; QStringList noteAuthors() const; QVector developerNotes(const DocPosition& pos) const; QString setPhase(const DocPosition& pos, const QString& phase); QString phase(const DocPosition& pos) const; QStringList context(const DocPosition& pos) const; QStringList matchData(const DocPosition& pos) const; QString id(const DocPosition& pos) const; bool isPlural(const DocPosition& pos) const; bool isEmpty(const DocPosition& pos) const; bool isEquivTrans(const DocPosition& pos) const; void setEquivTrans(const DocPosition& pos, bool equivTrans); TargetState state(const DocPosition& pos) const; TargetState setState(const DocPosition& pos, TargetState state); int binUnitsCount() const; int unitById(const QString& id) const; QString mimetype()const { return QStringLiteral("application/x-xliff"); } QString fileType()const { return QStringLiteral("XLIFF (*.xliff *.xlf)"); } CatalogType type()const { return Xliff; } QString originalOdfFilePath(); void setOriginalOdfFilePath(const QString&); void setTargetLangCode(const QString& langCode); private: QDomElement unitForPos(int pos) const; QDomElement targetForPos(int pos) const; QDomElement sourceForPos(int pos) const; CatalogString catalogString(QDomElement unit, DocPosition::Part part) const; private: mutable QDomDocument m_doc; QVector m_map;//need mapping to treat plurals as 1 entry QSet m_plurals; QDomNodeList entries; QDomNodeList binEntries; QMap m_unitsById; }; #endif diff --git a/src/cataloglistview/catalogmodel.cpp b/src/cataloglistview/catalogmodel.cpp index 4aa12aa..fbcb03e 100644 --- a/src/cataloglistview/catalogmodel.cpp +++ b/src/cataloglistview/catalogmodel.cpp @@ -1,307 +1,308 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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 "catalogmodel.h" #include "lokalize_debug.h" #include "catalog.h" #include "project.h" #ifndef NOKDE #include #endif #include #include #include #include #define DYNAMICFILTER_LIMIT 256 QVector CatalogTreeModel::m_fonts; CatalogTreeModel::CatalogTreeModel(QObject* parent, Catalog* catalog) : QAbstractItemModel(parent) , m_catalog(catalog) , m_ignoreAccel(true) { if (m_fonts.isEmpty()) { QVector fonts(4, QApplication::font()); fonts[1].setItalic(true); //fuzzy fonts[2].setBold(true); //modified fonts[3].setItalic(true); //fuzzy fonts[3].setBold(true); //modified m_fonts.reserve(4); for (int i = 0; i < 4; i++) m_fonts << fonts.at(i); } connect(catalog, &Catalog::signalEntryModified, this, &CatalogTreeModel::reflectChanges); connect(catalog, QOverload<>::of(&Catalog::signalFileLoaded), this, &CatalogTreeModel::fileLoaded); } QModelIndex CatalogTreeModel::index(int row, int column, const QModelIndex& /*parent*/) const { return createIndex(row, column); } QModelIndex CatalogTreeModel::parent(const QModelIndex& /*index*/) const { return QModelIndex(); } int CatalogTreeModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return DisplayedColumnCount; } void CatalogTreeModel::fileLoaded() { beginResetModel(); endResetModel(); } void CatalogTreeModel::reflectChanges(DocPosition pos) { emit dataChanged(index(pos.entry, 0), index(pos.entry, DisplayedColumnCount - 1)); #if 0 I disabled dynamicSortFilter function //lazy sorting/filtering if (rowCount() < DYNAMICFILTER_LIMIT || m_prevChanged != pos) { qCWarning(LOKALIZE_LOG) << "first dataChanged emitment" << pos.entry; emit dataChanged(index(pos.entry, 0), index(pos.entry, DisplayedColumnCount - 1)); if (!(rowCount() < DYNAMICFILTER_LIMIT)) { qCWarning(LOKALIZE_LOG) << "second dataChanged emitment" << m_prevChanged.entry; emit dataChanged(index(m_prevChanged.entry, 0), index(m_prevChanged.entry, DisplayedColumnCount - 1)); } } m_prevChanged = pos; #endif } int CatalogTreeModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return m_catalog->numberOfEntries(); } QVariant CatalogTreeModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const { if (role != Qt::DisplayRole) return QVariant(); switch (section) { case Key: return i18nc("@title:column", "Entry"); case Source: return i18nc("@title:column Original text", "Source"); case Target: return i18nc("@title:column Text in target language", "Target"); case Notes: return i18nc("@title:column", "Notes"); case Context: return i18nc("@title:column", "Context"); case TranslationStatus: return i18nc("@title:column", "Translation Status"); } return QVariant(); } QVariant CatalogTreeModel::data(const QModelIndex& index, int role) const { if (m_catalog->numberOfEntries() <= index.row()) return QVariant(); if (role == Qt::SizeHintRole) { //no need to cache because of uniform row heights return QFontMetrics(QApplication::font()).size(Qt::TextSingleLine, QString::fromLatin1(" ")); } else if (role == Qt::FontRole/* && index.column()==Target*/) { bool fuzzy = !m_catalog->isApproved(index.row()); bool modified = m_catalog->isModified(index.row()); return m_fonts.at(fuzzy * 1 | modified * 2); } else if (role == Qt::ForegroundRole) { #ifndef NOKDE if (m_catalog->isBookmarked(index.row())) { static KColorScheme colorScheme(QPalette::Normal); return colorScheme.foreground(KColorScheme::LinkText); } if (m_catalog->isObsolete(index.row())) { static KColorScheme colorScheme(QPalette::Normal); return colorScheme.foreground(KColorScheme::InactiveText); } #else if (m_catalog->isBookmarked(index.row())) { return QApplication::palette().link(); } if (m_catalog->isObsolete(index.row())) { return QApplication::palette().brightText(); } #endif } else if (role == Qt::UserRole) { switch (index.column()) { case TranslationStatus: return m_catalog->isApproved(index.row()); case IsEmpty: return m_catalog->isEmpty(index.row()); case State: return int(m_catalog->state(index.row())); case IsModified: return m_catalog->isModified(index.row()); case IsPlural: return m_catalog->isPlural(index.row()); default: role = Qt::DisplayRole; } } else if (role == StringFilterRole) { //exclude UI strings if (index.column() >= TranslationStatus) return QVariant(); else if (index.column() == Source || index.column() == Target) { - static const DocPosition::Part parts[] = {DocPosition::Source, DocPosition::Target}; - QString str = m_catalog->catalogString(DocPosition(index.row(), parts[index.column() == Target])).string; + QString str = index.column() == Source ? m_catalog->msgidWithPlurals(index.row()) : m_catalog->msgstrWithPlurals(index.row()); return m_ignoreAccel ? str.remove(Project::instance()->accel()) : str; } role = Qt::DisplayRole; } if (role != Qt::DisplayRole) return QVariant(); switch (index.column()) { case Key: return index.row() + 1; - case Source: return m_catalog->msgid(index.row()); - case Target: return m_catalog->msgstr(index.row()); + case Source: + return m_catalog->msgidWithPlurals(index.row()); + case Target: + return m_catalog->msgstrWithPlurals(index.row()); case Notes: { QString result; foreach (const Note ¬e, m_catalog->notes(index.row())) result += note.content; return result; } case Context: return m_catalog->context(index.row()); case TranslationStatus: static QString statuses[] = {i18nc("@info:status 'non-fuzzy' in gettext terminology", "Ready"), i18nc("@info:status 'fuzzy' in gettext terminology", "Needs review"), i18nc("@info:status", "Untranslated") }; if (m_catalog->isEmpty(index.row())) return statuses[2]; return statuses[!m_catalog->isApproved(index.row())]; } return QVariant(); } CatalogTreeFilterModel::CatalogTreeFilterModel(QObject* parent) : QSortFilterProxyModel(parent) , m_filerOptions(AllStates) , m_individualRejectFilterEnable(false) , m_mergeCatalog(NULL) { setFilterKeyColumn(-1); setFilterCaseSensitivity(Qt::CaseInsensitive); setFilterRole(CatalogTreeModel::StringFilterRole); setDynamicSortFilter(false); } void CatalogTreeFilterModel::setSourceModel(QAbstractItemModel* sourceModel) { QSortFilterProxyModel::setSourceModel(sourceModel); connect(sourceModel, &QAbstractItemModel::modelReset, this, QOverload<>::of(&CatalogTreeFilterModel::setEntriesFilteredOut)); setEntriesFilteredOut(false); } void CatalogTreeFilterModel::setEntriesFilteredOut() { return setEntriesFilteredOut(false); } void CatalogTreeFilterModel::setEntriesFilteredOut(bool filteredOut) { m_individualRejectFilter.fill(filteredOut, sourceModel()->rowCount()); m_individualRejectFilterEnable = filteredOut; invalidateFilter(); } void CatalogTreeFilterModel::setEntryFilteredOut(int entry, bool filteredOut) { // if (entry>=m_individualRejectFilter.size()) // sourceModelReset(); m_individualRejectFilter[entry] = filteredOut; m_individualRejectFilterEnable = true; invalidateFilter(); } void CatalogTreeFilterModel::setFilerOptions(int o) { m_filerOptions = o; setFilterCaseSensitivity(o & CaseInsensitive ? Qt::CaseInsensitive : Qt::CaseSensitive); static_cast(sourceModel())->setIgnoreAccel(o & IgnoreAccel); invalidateFilter(); } bool CatalogTreeFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { int filerOptions = m_filerOptions; bool accepts = true; if (bool(filerOptions & Ready) != bool(filerOptions & NotReady)) { bool ready = sourceModel()->index(source_row, CatalogTreeModel::TranslationStatus, source_parent).data(Qt::UserRole).toBool(); accepts = (ready == bool(filerOptions & Ready) || ready != bool(filerOptions & NotReady)); } if (accepts && bool(filerOptions & NonEmpty) != bool(filerOptions & Empty)) { bool untr = sourceModel()->index(source_row, CatalogTreeModel::IsEmpty, source_parent).data(Qt::UserRole).toBool(); accepts = (untr == bool(filerOptions & Empty) || untr != bool(filerOptions & NonEmpty)); } if (accepts && bool(filerOptions & Modified) != bool(filerOptions & NonModified)) { bool modified = sourceModel()->index(source_row, CatalogTreeModel::IsModified, source_parent).data(Qt::UserRole).toBool(); accepts = (modified == bool(filerOptions & Modified) || modified != bool(filerOptions & NonModified)); } if (accepts && bool(filerOptions & Plural) != bool(filerOptions & NonPlural)) { bool modified = sourceModel()->index(source_row, CatalogTreeModel::IsPlural, source_parent).data(Qt::UserRole).toBool(); accepts = (modified == bool(filerOptions & Plural) || modified != bool(filerOptions & NonPlural)); } // These are the possible sync options of a row: // * SameInSync: The sync file contains a row with the same msgid and the same msgstr. // * DifferentInSync: The sync file contains a row with the same msgid and different msgstr. // * NotInSync: The sync file does not contain any row with the same msgid. // // The code below takes care of filtering rows when any of those options is not checked. // const int mask = (SameInSync | DifferentInSync | NotInSync); if (accepts && m_mergeCatalog && (filerOptions & mask) && (filerOptions & mask) != mask) { bool isPresent = m_mergeCatalog->isPresent(source_row); bool isDifferent = m_mergeCatalog->isDifferent(source_row); accepts = ! ((isPresent && !isDifferent && !bool(filerOptions & SameInSync)) || (isPresent && isDifferent && !bool(filerOptions & DifferentInSync)) || (!isPresent && !bool(filerOptions & NotInSync)) ); } if (accepts && (filerOptions & STATES) != STATES) { int state = sourceModel()->index(source_row, CatalogTreeModel::State, source_parent).data(Qt::UserRole).toInt(); accepts = (filerOptions & (1 << (state + FIRSTSTATEPOSITION))); } accepts = accepts && !(m_individualRejectFilterEnable && source_row < m_individualRejectFilter.size() && m_individualRejectFilter.at(source_row)); return accepts && QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } void CatalogTreeFilterModel::setMergeCatalogPointer(MergeCatalog* pointer) { m_mergeCatalog = pointer; } diff --git a/src/tm/jobs.cpp b/src/tm/jobs.cpp index bf3ddef..54761e6 100644 --- a/src/tm/jobs.cpp +++ b/src/tm/jobs.cpp @@ -1,2131 +1,2130 @@ /* **************************************************************************** This file is part of Lokalize Copyright (C) 2007-2014 by Nick Shaforostoff 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 "jobs.h" #include "lokalize_debug.h" #include "catalog.h" #include "project.h" #include "diff.h" #include "prefs_lokalize.h" #include "version.h" #include "stemming.h" #include #include #include #include #include #include #include #include #include #include #include using namespace TM; QThreadPool* TM::threadPool() { static QThreadPool* inst = new QThreadPool; return inst; } #ifdef Q_OS_WIN #define U QLatin1String #else #define U QStringLiteral #endif #define TM_DELIMITER '\v' #define TM_SEPARATOR '\b' #define TM_NOTAPPROVED 0x04 static bool stop = false; void TM::cancelAllJobs() { stop = true; } static qlonglong newTMSourceEntryCount = 0; static qlonglong reusedTMSourceEntryCount = 0; /** * splits string into words, removing any markup * * TODO segmentation by sentences... **/ static void doSplit(QString& cleanEn, QStringList& words, QRegExp& rxClean1, const QString& accel ) { static QRegExp rxSplit(QStringLiteral("\\W+|\\d+")); if (!rxClean1.pattern().isEmpty()) cleanEn.replace(rxClean1, QStringLiteral(" ")); cleanEn.remove(accel); words = cleanEn.toLower().split(rxSplit, QString::SkipEmptyParts); if (words.size() > 4) { int i = 0; for (; i < words.size(); ++i) { if (words.at(i).size() < 4) words.removeAt(i--); else if (words.at(i).startsWith('t') && words.at(i).size() == 4) { if (words.at(i) == QLatin1String("then") || words.at(i) == QLatin1String("than") || words.at(i) == QLatin1String("that") || words.at(i) == QLatin1String("this") ) words.removeAt(i--); } } } } static qlonglong getFileId(const QString& path, QSqlDatabase& db) { QSqlQuery query1(db); QString escapedPath = path; escapedPath.replace(QLatin1Char('\''), QLatin1String("''")); QString pathExpr = QStringLiteral("path='") % escapedPath % '\''; if (path.isEmpty()) pathExpr = QStringLiteral("path ISNULL"); if (Q_UNLIKELY(!query1.exec(U("SELECT id FROM files WHERE " "path='") % escapedPath % '\''))) qCWarning(LOKALIZE_LOG) << "select db error: " << query1.lastError().text(); if (Q_LIKELY(query1.next())) { //this is translation of en string that is already present in db qlonglong id = query1.value(0).toLongLong(); query1.clear(); return id; } query1.clear(); //nope, this is new file bool qpsql = (db.driverName() == QLatin1String("QPSQL")); QString sql = QStringLiteral("INSERT INTO files (path) VALUES (?)"); if (qpsql) sql += QLatin1String(" RETURNING id"); query1.prepare(sql); query1.bindValue(0, path); if (Q_LIKELY(query1.exec())) return qpsql ? (query1.next(), query1.value(0).toLongLong()) : query1.lastInsertId().toLongLong(); else qCWarning(LOKALIZE_LOG) << "insert db error: " << query1.lastError().text(); return -1; } static void addToIndex(qlonglong sourceId, QString sourceString, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QStringList words; doSplit(sourceString, words, rxClean1, accel); if (Q_UNLIKELY(words.isEmpty())) return; QSqlQuery query1(db); QByteArray sourceIdStr = QByteArray::number(sourceId, 36); bool isShort = words.size() < 20; int j = words.size(); while (--j >= 0) { // insert word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='") % words.at(j) % '\''))) qCWarning(LOKALIZE_LOG) << "select error 3: " << query1.lastError().text(); //we _have_ it bool weHaveIt = query1.next(); if (weHaveIt) { //just add new id QByteArray arr; QString field; if (isShort) { arr = query1.value(1).toByteArray(); field = QStringLiteral("ids_short"); } else { arr = query1.value(2).toByteArray(); field = QStringLiteral("ids_long"); } query1.clear(); if (arr.contains(' ' % sourceIdStr % ' ') || arr.startsWith(sourceIdStr + ' ') || arr.endsWith(' ' + sourceIdStr) || arr == sourceIdStr) return;//this string is already indexed query1.prepare(QStringLiteral("UPDATE words SET ") % field % QStringLiteral("=? WHERE word='") % words.at(j) % '\''); if (!arr.isEmpty()) arr += ' '; arr += sourceIdStr; query1.bindValue(0, arr); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "update error 4: " << query1.lastError().text(); } else { query1.clear(); query1.prepare(QStringLiteral("INSERT INTO words (word, ids_short, ids_long) VALUES (?, ?, ?)")); QByteArray idsShort; QByteArray idsLong; if (isShort) idsShort = sourceIdStr; else idsLong = sourceIdStr; query1.bindValue(0, words.at(j)); query1.bindValue(1, idsShort); query1.bindValue(2, idsLong); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "insert error 2: " << query1.lastError().text() ; } } } /** * remove source string from index if there are no other * 'good' entries using it but the entry specified with mainId */ static void removeFromIndex(qlonglong mainId, qlonglong sourceId, QString sourceString, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QStringList words; doSplit(sourceString, words, rxClean1, accel); if (Q_UNLIKELY(words.isEmpty())) return; QSqlQuery query1(db); QByteArray sourceIdStr = QByteArray::number(sourceId, 36); //BEGIN check //TM_NOTAPPROVED=4 if (Q_UNLIKELY(!query1.exec(U("SELECT count(*) FROM main, target_strings WHERE " "main.source=") % QString::number(sourceId) % U(" AND " "main.target=target_strings.id AND " "target_strings.target NOTNULL AND " "main.id!=") % QString::number(mainId) % U(" AND " "(main.bits&4)!=4")))) { qCWarning(LOKALIZE_LOG) << "select error 500: " << query1.lastError().text(); return; } bool exit = query1.next() && (query1.value(0).toLongLong() > 0); query1.clear(); if (exit) return; //END check bool isShort = words.size() < 20; int j = words.size(); while (--j >= 0) { // remove from record for the word (if we do not have it) if (Q_UNLIKELY(!query1.exec(U("SELECT word, ids_short, ids_long FROM words WHERE " "word='") % words.at(j) % '\''))) { qCWarning(LOKALIZE_LOG) << "select error 3: " << query1.lastError().text(); return; } if (!query1.next()) { qCWarning(LOKALIZE_LOG) << "exit here 1"; //we don't have record for the word, so nothing to remove query1.clear(); return; } QByteArray arr; QString field; if (isShort) { arr = query1.value(1).toByteArray(); field = QStringLiteral("ids_short"); } else { arr = query1.value(2).toByteArray(); field = QStringLiteral("ids_long"); } query1.clear(); if (arr.contains(' ' + sourceIdStr + ' ')) arr.replace(' ' + sourceIdStr + ' ', " "); else if (arr.startsWith(sourceIdStr + ' ')) arr.remove(0, sourceIdStr.size() + 1); else if (arr.endsWith(' ' + sourceIdStr)) arr.chop(sourceIdStr.size() + 1); else if (arr == sourceIdStr) arr.clear(); query1.prepare(U("UPDATE words " "SET ") % field % U("=? " "WHERE word='") % words.at(j) % '\''); query1.bindValue(0, arr); if (Q_UNLIKELY(!query1.exec())) qCWarning(LOKALIZE_LOG) << "update error 504: " << query1.lastError().text(); } } static bool doRemoveEntry(qlonglong mainId, QRegExp& rxClean1, const QString& accel, QSqlDatabase& db) { QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U("SELECT source_strings.id, source_strings.source FROM source_strings, main WHERE " "source_strings.id=main.source AND main.id=") + QString::number(mainId)))) return false; if (!query1.next()) return false; qlonglong sourceId = query1.value(0).toLongLong(); QString source_string = query1.value(1).toString(); query1.clear(); if (!query1.exec(QStringLiteral("SELECT count(*) FROM main WHERE source=") + QString::number(sourceId)) || !query1.next()) return false; bool theOnly = query1.value(0).toInt() == 1; query1.clear(); if (theOnly) { removeFromIndex(mainId, sourceId, source_string, rxClean1, accel, db); qCWarning(LOKALIZE_LOG) << "ok delete?" << query1.exec(QStringLiteral("DELETE FROM source_strings WHERE id=") + QString::number(sourceId)); } if (Q_UNLIKELY(!query1.exec(U("SELECT target FROM main WHERE " "main.id=") + QString::number(mainId)) || !query1.next())) return false; qlonglong targetId = query1.value(0).toLongLong(); query1.clear(); if (!query1.exec(QStringLiteral("SELECT count(*) FROM main WHERE target=") + QString::number(targetId)) || ! query1.next()) return false; theOnly = query1.value(0).toInt() == 1; query1.clear(); if (theOnly) query1.exec(QStringLiteral("DELETE FROM target_strings WHERE id=") + QString::number(targetId)); return query1.exec(QStringLiteral("DELETE FROM main WHERE id=") + QString::number(mainId)); } static bool doRemoveFile(const QString& filePath, QSqlDatabase& db) { qlonglong fileId = getFileId(filePath, db); QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U("SELECT id FROM files WHERE " "id=") + QString::number(fileId)))) return false; if (!query1.next()) return false; query1.clear(); query1.exec(QStringLiteral("DELETE source_strings FROM source_strings, main WHERE source_strings.id = main.source AND main.file =") + QString::number(fileId)); query1.exec(QStringLiteral("DELETE target_strings FROM target_strings, main WHERE target_strings.id = main.target AND main.file =") + QString::number(fileId)); query1.exec(QStringLiteral("DELETE FROM main WHERE file = ") + QString::number(fileId)); return query1.exec(QStringLiteral("DELETE FROM files WHERE id=") + QString::number(fileId)); } static int doRemoveMissingFiles(QSqlDatabase& db, const QString& dbName, QObject *job) { int deletedFiles = 0; QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U("SELECT files.path FROM files")))) return false; if (!query1.next()) return false; do { QString filePath = query1.value(0).toString(); if (Project::instance()->isFileMissing(filePath)) { qCWarning(LOKALIZE_LOG) << "Removing file " << filePath << " from translation memory"; RemoveFileJob* job_removefile = new RemoveFileJob(filePath, dbName, job); TM::threadPool()->start(job_removefile, REMOVEFILE); deletedFiles++; } } while (query1.next()); return deletedFiles; } static QString escape(QString str) { return str.replace(QLatin1Char('\''), QStringLiteral("''")); } static bool doInsertEntry(CatalogString source, CatalogString target, const QString& ctxt, //TODO QStringList -- after XLIFF bool approved, qlonglong fileId, QSqlDatabase& db, QRegExp& rxClean1,//cleaning regexps for word index update const QString& accel, qlonglong priorId, qlonglong& mainId ) { QTime a; a.start(); mainId = -1; if (Q_UNLIKELY(source.isEmpty())) { qCWarning(LOKALIZE_LOG) << "doInsertEntry: source empty"; return false; } bool qpsql = (db.driverName() == QLatin1String("QPSQL")); //we store non-entranslaed entries to make search over all source parts possible bool untranslated = target.isEmpty(); bool shouldBeInIndex = !untranslated && approved; //remove first occurrence of accel character so that search returns words containing accel mark int sourceAccelPos = source.string.indexOf(accel); if (sourceAccelPos != -1) source.string.remove(sourceAccelPos, accel.size()); int targetAccelPos = target.string.indexOf(accel); if (targetAccelPos != -1) target.string.remove(targetAccelPos, accel.size()); //check if we already have record with the same en string QSqlQuery query1(db); QString escapedCtxt = escape(ctxt); QByteArray sourceTags = source.tagsAsByteArray(); QByteArray targetTags = target.tagsAsByteArray(); //BEGIN get sourceId query1.prepare(QString(U("SELECT id FROM source_strings WHERE " "source=? AND (source_accel%1) AND source_markup%2")).arg (sourceAccelPos != -1 ? QStringLiteral("=?") : QStringLiteral("=-1 OR source_accel ISNULL"), sourceTags.isEmpty() ? QStringLiteral(" ISNULL") : QStringLiteral("=?"))); int paranum = 0; query1.bindValue(paranum++, source.string); if (sourceAccelPos != -1) query1.bindValue(paranum++, sourceAccelPos); if (!sourceTags.isEmpty()) query1.bindValue(paranum++, sourceTags); if (Q_UNLIKELY(!query1.exec())) { qCWarning(LOKALIZE_LOG) << "doInsertEntry: select db source_strings error: " << query1.lastError().text(); return false; } qlonglong sourceId; if (!query1.next()) { //BEGIN insert source anew //qCDebug(LOKALIZE_LOG) <<"insert source anew";; ++newTMSourceEntryCount; QString sql = QStringLiteral("INSERT INTO source_strings (source, source_markup, source_accel) VALUES (?, ?, ?)"); if (qpsql) sql += QLatin1String(" RETURNING id"); query1.clear(); query1.prepare(sql); query1.bindValue(0, source.string); query1.bindValue(1, sourceTags); query1.bindValue(2, sourceAccelPos != -1 ? QVariant(sourceAccelPos) : QVariant()); if (Q_UNLIKELY(!query1.exec())) { qCWarning(LOKALIZE_LOG) << "doInsertEntry: select db source_strings error: " << query1.lastError().text(); return false; } sourceId = qpsql ? (query1.next(), query1.value(0).toLongLong()) : query1.lastInsertId().toLongLong(); query1.clear(); //update index if (shouldBeInIndex) addToIndex(sourceId, source.string, rxClean1, accel, db); //END insert source anew } else { sourceId = query1.value(0).toLongLong(); ++reusedTMSourceEntryCount; //qCDebug(LOKALIZE_LOG)<<"SOURCE ALREADY PRESENT"< there will be new record insertion and main table update below } //qCDebug(LOKALIZE_LOG)< tmConfigCache; static void setConfig(QSqlDatabase& db, const TMConfig& c) { QSqlQuery query(db); query.prepare(QStringLiteral("INSERT INTO tm_config (key, value) VALUES (?, ?)")); query.addBindValue(0); query.addBindValue(c.markup); //qCDebug(LOKALIZE_LOG)<<"setting tm db config:"<setPriority(QThread::IdlePriority); if (m_type == TM::Local) { QSqlDatabase db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_dbName); QString dbFolder = QStandardPaths::writableLocation(QStandardPaths::DataLocation); QFileInfo fileInfo(dbFolder); if (!fileInfo.exists(dbFolder)) fileInfo.absoluteDir().mkpath(fileInfo.fileName()); db.setDatabaseName(dbFolder % QLatin1Char('/') % m_dbName % TM_DATABASE_EXTENSION); m_connectionSuccessful = db.open(); if (Q_UNLIKELY(!m_connectionSuccessful)) { qCDebug(LOKALIZE_LOG) << "failed to open db" << db.databaseName() << db.lastError().text(); QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } if (!initSqliteDb(db)) { //need to recreate db ;( QString filename = db.databaseName(); db.close(); QSqlDatabase::removeDatabase(m_dbName); QFile::remove(filename); db = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_dbName); db.setDatabaseName(filename); m_connectionSuccessful = db.open() && initSqliteDb(db); if (!m_connectionSuccessful) { QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } } } else { if (QSqlDatabase::contains(m_dbName)) { //reconnect is true QSqlDatabase::database(m_dbName).close(); QSqlDatabase::removeDatabase(m_dbName); } if (!m_connParams.isFilled()) { QFile rdb(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/') + m_dbName % REMOTETM_DATABASE_EXTENSION); if (!rdb.open(QIODevice::ReadOnly | QIODevice::Text)) { emit done(this); return; } QTextStream rdbParams(&rdb); m_connParams.driver = rdbParams.readLine(); m_connParams.host = rdbParams.readLine(); m_connParams.db = rdbParams.readLine(); m_connParams.user = rdbParams.readLine(); m_connParams.passwd = rdbParams.readLine(); } QSqlDatabase db = QSqlDatabase::addDatabase(m_connParams.driver, m_dbName); db.setHostName(m_connParams.host); db.setDatabaseName(m_connParams.db); db.setUserName(m_connParams.user); db.setPassword(m_connParams.passwd); m_connectionSuccessful = db.open(); if (Q_UNLIKELY(!m_connectionSuccessful)) { QSqlDatabase::removeDatabase(m_dbName); emit done(this); return; } m_connParams.user = db.userName(); initPgDb(db); } } QSqlDatabase db = QSqlDatabase::database(m_dbName); //if (!m_markup.isEmpty()||!m_accel.isEmpty()) if (m_setParams) setConfig(db, m_tmConfig); else m_tmConfig = getConfig(db); qCDebug(LOKALIZE_LOG) << "db" << m_dbName << "opened" << a.elapsed() << m_tmConfig.targetLangCode; getStats(db, m_stat.pairsCount, m_stat.uniqueSourcesCount, m_stat.uniqueTranslationsCount); if (m_type == TM::Local) { db.close(); db.open(); } emit done(this); } CloseDBJob::CloseDBJob(const QString& name) : QObject(), QRunnable() , m_dbName(name) { setAutoDelete(false); } CloseDBJob::~CloseDBJob() { qCDebug(LOKALIZE_LOG) << "closedb dtor" << m_dbName; } void CloseDBJob::run() { if (m_dbName.length()) QSqlDatabase::removeDatabase(m_dbName); emit done(this); } static QString makeAcceledString(QString source, const QString& accel, const QVariant& accelPos) { if (accelPos.isNull()) return source; int accelPosInt = accelPos.toInt(); if (accelPosInt != -1) source.insert(accelPosInt, accel); return source; } SelectJob* TM::initSelectJob(Catalog* catalog, DocPosition pos, QString db, int opt) { SelectJob* job = new SelectJob(catalog->sourceWithTags(pos), catalog->context(pos.entry).first(), catalog->url(), pos, db.isEmpty() ? Project::instance()->projectID() : db); if (opt & Enqueue) { //deletion should be done by receiver, e.g. slotSuggestionsCame() threadPool()->start(job, SELECT); } return job; } SelectJob::SelectJob(const CatalogString& source, const QString& ctxt, const QString& file, const DocPosition& pos, const QString& dbName) : QObject(), QRunnable() , m_source(source) , m_ctxt(ctxt) , m_file(file) , m_dequeued(false) , m_pos(pos) , m_dbName(dbName) { setAutoDelete(false); //qCDebug(LOKALIZE_LOG)<<"selectjob"< invertMap(const QMap& source) { //uses the fact that map has its keys always sorted QMap sortingMap; for (QMap::const_iterator i = source.constBegin(); i != source.constEnd(); ++i) { sortingMap.insertMulti(i.value(), i.key()); } return sortingMap; } //returns true if seen translation with >85% bool SelectJob::doSelect(QSqlDatabase& db, QStringList& words, //QList& entries, bool isShort) { bool qpsql = (db.driverName() == QLatin1String("QPSQL")); QMap occurencies; QVector idsForWord; QSqlQuery queryWords(db); //TODO ??? not sure. make another loop before to create QList< QList > then reorder it by size static const QString queryC[] = {U("SELECT ids_long FROM words WHERE word='%1'"), U("SELECT ids_short FROM words WHERE word='%1'") }; QString queryString = queryC[isShort]; //for each word... int o = words.size(); while (--o >= 0) { //if this is not the first word occurrence, just readd ids for it if (!(!idsForWord.isEmpty() && words.at(o) == words.at(o + 1))) { idsForWord.clear(); queryWords.exec(queryString.arg(words.at(o))); if (Q_UNLIKELY(!queryWords.exec(queryString.arg(words.at(o))))) qCWarning(LOKALIZE_LOG) << "select error: " << queryWords.lastError().text() << endl; if (queryWords.next()) { QByteArray arr(queryWords.value(0).toByteArray()); queryWords.clear(); QList ids(arr.split(' ')); int p = ids.size(); idsForWord.reserve(p); while (--p >= 0) idsForWord.append(ids.at(p).toLongLong(/*bool ok*/0, 36)); } else { queryWords.clear(); continue; } } //qCWarning(LOKALIZE_LOG) <<"SelectJob: idsForWord.size() "<::const_iterator i = idsForWord.constBegin(); i != idsForWord.constEnd(); i++) occurencies[*i]++; //0 is default value } //accels are removed TMConfig c = getConfig(db); QString tmp = c.markup; if (!c.markup.isEmpty()) tmp += '|'; QRegExp rxSplit(QLatin1Char('(') % tmp % QStringLiteral("\\W+|\\d+)+")); QString sourceClean(m_source.string); sourceClean.remove(c.accel); //split m_english for use in wordDiff later--all words are needed so we cant use list we already have QStringList englishList(sourceClean.toLower().split(rxSplit, QString::SkipEmptyParts)); static QRegExp delPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); static QRegExp addPart(QStringLiteral("*"), Qt::CaseSensitive, QRegExp::Wildcard); delPart.setMinimal(true); addPart.setMinimal(true); //QList concordanceLevels=sortedUniqueValues(occurencies); //we start from entries with higher word-concordance level QMap concordanceLevelToIds = invertMap(occurencies); if (concordanceLevelToIds.isEmpty()) return false; bool seen85 = false; int limit = 200; auto clit = concordanceLevelToIds.constEnd(); if (concordanceLevelToIds.size()) --clit; if (concordanceLevelToIds.size()) while (--limit >= 0) { if (Q_UNLIKELY(m_dequeued)) break; //for every concordance level qlonglong level = clit.key(); QString joined; while (level == clit.key()) { joined += QString::number(clit.value()) + ','; if (clit == concordanceLevelToIds.constBegin() || --limit < 0) break; --clit; } joined.chop(1); //get records containing current word QSqlQuery queryFetch(U( "SELECT id, source, source_accel, source_markup FROM source_strings WHERE " "source_strings.id IN (") % joined % ')', db); TMEntry e; while (queryFetch.next()) { e.id = queryFetch.value(0).toLongLong(); if (queryFetch.value(3).toByteArray().size()) qCDebug(LOKALIZE_LOG) << "BA" << queryFetch.value(3).toByteArray(); e.source = CatalogString(makeAcceledString(queryFetch.value(1).toString(), c.accel, queryFetch.value(2)), queryFetch.value(3).toByteArray()); if (e.source.string.contains(TAGRANGE_IMAGE_SYMBOL)) { if (!e.source.tags.size()) qCWarning(LOKALIZE_LOG) << "problem:" << queryFetch.value(3).toByteArray().size() << queryFetch.value(3).toByteArray(); } //e.target=queryFetch.value(2).toString(); //QStringList e_ctxt=queryFetch.value(3).toString().split('\b',QString::SkipEmptyParts); //e.date=queryFetch.value(4).toString(); e.markupExpr = c.markup; e.accelExpr = c.accel; e.dbName = db.connectionName(); //BEGIN calc score QString str = e.source.string; str.remove(c.accel); QStringList englishSuggList(str.toLower().split(rxSplit, QString::SkipEmptyParts)); if (englishSuggList.size() > 10 * englishList.size()) continue; //sugg is 'old' --translator has to adapt its translation to 'new'--current QString result = wordDiff(englishSuggList, englishList); //qCWarning(LOKALIZE_LOG) <<"SelectJob: doin "< 1 so we have decreased it, and increased result: / exp(0.014 * float(addLen) * log10(3.0f + addSubStrCount)); if (delLen) { //qCWarning(LOKALIZE_LOG) <<"SelectJob: delLen:"< 8500; if (seen85 && e.score < 6000) continue; //BEGIN fetch rest of the data QString change_author_str; QString authors_table_str; if (qpsql) { //change_author_str=", main.change_author "; change_author_str = QStringLiteral(", pg_user.usename "); authors_table_str = QStringLiteral(" JOIN pg_user ON (pg_user.usesysid=main.change_author) "); } QSqlQuery queryRest(U( "SELECT main.id, main.date, main.ctxt, main.bits, " "target_strings.target, target_strings.target_accel, target_strings.target_markup, " "files.path, main.change_date ") % change_author_str % U( "FROM main JOIN target_strings ON (target_strings.id=main.target) JOIN files ON (files.id=main.file) ") % authors_table_str % U("WHERE " "main.source=") % QString::number(e.id) % U(" AND " "(main.bits&4)!=4 AND " "target_strings.target NOTNULL") , db); //ORDER BY tm_main.id ? queryRest.exec(); //qCDebug(LOKALIZE_LOG)<<"main select error"< sortedEntryList; //to eliminate same targets from different files while (queryRest.next()) { e.id = queryRest.value(0).toLongLong(); e.date = queryRest.value(1).toDate(); e.ctxt = queryRest.value(2).toString(); e.target = CatalogString(makeAcceledString(queryRest.value(4).toString(), c.accel, queryRest.value(5)), queryRest.value(6).toByteArray()); QStringList matchData = queryRest.value(2).toString().split(TM_DELIMITER, QString::KeepEmptyParts); //context|plural e.file = queryRest.value(7).toString(); if (e.target.isEmpty()) continue; e.obsolete = queryRest.value(3).toInt() & 1; e.changeDate = queryRest.value(8).toDate(); if (qpsql) e.changeAuthor = queryRest.value(9).toString(); //BEGIN exact match score++ if (possibleExactMatch) { //"exact" match (case insensitive+w/o non-word characters!) if (m_source.string == e.source.string) e.score = 10000; else e.score = 9900; } if (!m_ctxt.isEmpty() && matchData.size() > 0) { //check not needed? if (matchData.at(0) == m_ctxt) e.score += 33; } //qCWarning(LOKALIZE_LOG)<<"m_pos"< 1) { int form = matchData.at(1).toInt(); //pluralMatches=(form&&form==m_pos.form); if (form && form == (int)m_pos.form) { //qCWarning(LOKALIZE_LOG)<<"this"< hash; int oldCount = m_entries.size(); QMap::const_iterator it = sortedEntryList.constEnd(); if (sortedEntryList.size()) while (true) { --it; const TMEntry& e = it.key(); int& hits = hash[e.target.string]; if (!hits) //0 was default value m_entries.append(e); hits++; if (it == sortedEntryList.constBegin()) break; } for (int i = oldCount; i < m_entries.size(); ++i) m_entries[i].hits = hash.value(m_entries.at(i).target.string); //END fetch rest of the data } queryFetch.clear(); if (clit == concordanceLevelToIds.constBegin()) break; if (seen85) limit = qMin(limit, 100); //be more restrictive for the next concordance levels } return seen85; } void SelectJob::run() { //qCDebug(LOKALIZE_LOG)<<"select started"<setPriority(QThread::IdlePriority); QTime a; a.start(); if (Q_UNLIKELY(!QSqlDatabase::contains(m_dbName))) { emit done(this); return; } QSqlDatabase db = QSqlDatabase::database(m_dbName); if (Q_UNLIKELY(!db.isValid() || !db.isOpen())) { emit done(this); return; } //qCDebug(LOKALIZE_LOG)<<"select started 2"<()); int limit = qMin(Settings::suggCount(), m_entries.size()); int minScore = Settings::suggScore() * 100; int i = m_entries.size(); while (--i >= limit || m_entries.last().score < minScore) m_entries.removeLast(); if (Q_UNLIKELY(m_dequeued)) { emit done(this); return; } ++i; while (--i >= 0) { m_entries[i].accelExpr = c.accel; m_entries[i].markupExpr = c.markup; m_entries[i].diff = userVisibleWordDiff(m_entries.at(i).source.string, m_source.string, m_entries.at(i).accelExpr, m_entries.at(i).markupExpr); } emit done(this); } ScanJob::ScanJob(const QString& filePath, const QString& dbName) : QRunnable() , m_filePath(filePath) , m_time(0) , m_added(0) , m_newVersions(0) , m_size(0) , m_dbName(dbName) { qCDebug(LOKALIZE_LOG) << m_dbName << m_filePath; } ScanJob::~ScanJob() { } void ScanJob::run() { if (stop || !QSqlDatabase::contains(m_dbName)) { return; } qCWarning(LOKALIZE_LOG) << "scan job started for" << m_filePath << m_dbName << stop << m_dbName; //QThread::currentThread()->setPriority(QThread::IdlePriority); QTime a; a.start(); QSqlDatabase db = QSqlDatabase::database(m_dbName); if (!db.isOpen()) return; //initSqliteDb(db); TMConfig c = getConfig(db, true); QRegExp rxClean1(c.markup); rxClean1.setMinimal(true); Catalog catalog(0); if (Q_LIKELY(catalog.loadFromUrl(m_filePath, QString(), &m_size, /*no auto save*/true) == 0)) { if (c.targetLangCode != catalog.targetLangCode()) { qCWarning(LOKALIZE_LOG) << "not indexing file because target languages don't match:" << c.targetLangCode << "in TM vs" << catalog.targetLangCode() << "in file"; return; } qlonglong priorId = -1; QSqlQuery queryBegin(QStringLiteral("BEGIN"), db); //qCWarning(LOKALIZE_LOG) <<"queryBegin error: " < #include /** @author Nick Shaforostoff */ class TmxParser : public QXmlDefaultHandler { enum State { //localstate for getting chars into right place null = 0, seg, propContext, propFile, propPluralForm, propApproved }; enum Lang { Source, Target, Null }; public: TmxParser(const QString& dbName); ~TmxParser(); private: bool startDocument(); bool startElement(const QString&, const QString&, const QString&, const QXmlAttributes&); bool endElement(const QString&, const QString&, const QString&); bool characters(const QString&); private: QSqlDatabase db; QRegExp rxClean1; QString accel; int m_hits; CatalogString m_segment[3]; //Lang enum QList m_inlineTags; QString m_context; QString m_pluralForm; QString m_filePath; QString m_approvedString; State m_state: 8; Lang m_lang: 8; ushort m_added; QMap m_fileIds; QString m_dbLangCode; }; TmxParser::TmxParser(const QString& dbName) : m_hits(0) , m_state(null) , m_lang(Null) , m_added(0) , m_dbLangCode(Project::instance()->langCode().toLower()) { db = QSqlDatabase::database(dbName); TMConfig c = getConfig(db); rxClean1.setPattern(c.markup); rxClean1.setMinimal(true); accel = c.accel; } bool TmxParser::startDocument() { //initSqliteDb(db); m_fileIds.clear(); QSqlQuery queryBegin(QLatin1String("BEGIN"), db); m_state = null; m_lang = Null; return true; } TmxParser::~TmxParser() { QSqlQuery queryEnd(QLatin1String("END"), db); } bool TmxParser::startElement(const QString&, const QString&, const QString& qName, const QXmlAttributes& attr) { if (qName == QLatin1String("tu")) { bool ok; m_hits = attr.value(QLatin1String("usagecount")).toInt(&ok); if (!ok) m_hits = -1; m_segment[Source].clear(); m_segment[Target].clear(); m_context.clear(); m_pluralForm.clear(); m_filePath.clear(); m_approvedString.clear(); } else if (qName == QLatin1String("tuv")) { QString attrLang = attr.value(QStringLiteral("xml:lang")).toLower(); if (attrLang == QLatin1String("en")) //TODO startsWith? m_lang = Source; else if (attrLang == m_dbLangCode) m_lang = Target; else { qCWarning(LOKALIZE_LOG) << "skipping lang" << attr.value("xml:lang"); m_lang = Null; } } else if (qName == QLatin1String("prop")) { QString attrType = attr.value(QStringLiteral("type")).toLower(); if (attrType == QLatin1String("x-context")) m_state = propContext; else if (attrType == QLatin1String("x-file")) m_state = propFile; else if (attrType == QLatin1String("x-pluralform")) m_state = propPluralForm; else if (attrType == QLatin1String("x-approved")) m_state = propApproved; else m_state = null; } else if (qName == QLatin1String("seg")) { m_state = seg; } else if (m_state == seg && m_lang != Null) { InlineTag::InlineElement t = InlineTag::getElementType(qName.toLatin1()); if (t != InlineTag::_unknown) { m_segment[m_lang].string += QChar(TAGRANGE_IMAGE_SYMBOL); int pos = m_segment[m_lang].string.size(); m_inlineTags.append(InlineTag(pos, pos, t, attr.value(QStringLiteral("id")))); } } return true; } bool TmxParser::endElement(const QString&, const QString&, const QString& qName) { if (qName == QLatin1String("tu")) { if (m_filePath.isEmpty()) m_filePath = QLatin1String("tmx-import"); if (!m_fileIds.contains(m_filePath)) m_fileIds.insert(m_filePath, getFileId(m_filePath, db)); qlonglong fileId = m_fileIds.value(m_filePath); if (!m_pluralForm.isEmpty()) m_context += TM_DELIMITER + m_pluralForm; qlonglong priorId = -1; bool ok = doInsertEntry(m_segment[Source], m_segment[Target], m_context, m_approvedString != QLatin1String("no"), fileId, db, rxClean1, accel, priorId, priorId); if (Q_LIKELY(ok)) ++m_added; } else if (m_state == seg && m_lang != Null) { InlineTag::InlineElement t = InlineTag::getElementType(qName.toLatin1()); if (t != InlineTag::_unknown) { InlineTag tag = m_inlineTags.takeLast(); qCWarning(LOKALIZE_LOG) << qName << tag.getElementName(); if (tag.isPaired()) { tag.end = m_segment[m_lang].string.size(); m_segment[m_lang].string += QChar(TAGRANGE_IMAGE_SYMBOL); } m_segment[m_lang].tags.append(tag); } } m_state = null; return true; } bool TmxParser::characters(const QString& ch) { if (m_state == seg && m_lang != Null) m_segment[m_lang].string += ch; else if (m_state == propFile) m_filePath += ch; else if (m_state == propContext) m_context += ch; else if (m_state == propPluralForm) m_pluralForm += ch; else if (m_state == propApproved) m_approvedString += ch; return true; } ImportTmxJob::ImportTmxJob(const QString& filename, const QString& dbName) : QRunnable() , m_filename(filename) , m_time(0) , m_dbName(dbName) { } ImportTmxJob::~ImportTmxJob() { qCWarning(LOKALIZE_LOG) << "ImportTmxJob dtor"; } void ImportTmxJob::run() { QTime a; a.start(); QFile file(m_filename); if (!file.open(QFile::ReadOnly | QFile::Text)) return; TmxParser parser(m_dbName); QXmlSimpleReader reader; reader.setContentHandler(&parser); QXmlInputSource xmlInputSource(&file); if (!reader.parse(xmlInputSource)) qCWarning(LOKALIZE_LOG) << "failed to load" << m_filename; //qCWarning(LOKALIZE_LOG) <<"Done scanning "< ExportTmxJob::ExportTmxJob(const QString& filename, const QString& dbName) : QRunnable() , m_filename(filename) , m_time(0) , m_dbName(dbName) { } ExportTmxJob::~ExportTmxJob() { qCDebug(LOKALIZE_LOG) << "ExportTmxJob dtor"; } void ExportTmxJob::run() { QTime a; a.start(); QFile out(m_filename); if (!out.open(QFile::WriteOnly | QFile::Text)) return; QXmlStreamWriter xmlOut(&out); xmlOut.setAutoFormatting(true); xmlOut.writeStartDocument(QStringLiteral("1.0")); xmlOut.writeStartElement(QStringLiteral("tmx")); xmlOut.writeAttribute(QStringLiteral("version"), QStringLiteral("2.0")); xmlOut.writeStartElement(QStringLiteral("header")); xmlOut.writeAttribute(QStringLiteral("creationtool"), QStringLiteral("lokalize")); xmlOut.writeAttribute(QStringLiteral("creationtoolversion"), QStringLiteral(LOKALIZE_VERSION)); xmlOut.writeAttribute(QStringLiteral("segtype"), QStringLiteral("paragraph")); xmlOut.writeAttribute(QStringLiteral("o-encoding"), QStringLiteral("UTF-8")); xmlOut.writeEndElement(); xmlOut.writeStartElement(QStringLiteral("body")); QString dbLangCode = Project::instance()->langCode(); QSqlDatabase db = QSqlDatabase::database(m_dbName); QSqlQuery query1(db); if (Q_UNLIKELY(!query1.exec(U( "SELECT main.id, main.ctxt, main.date, main.bits, " "source_strings.source, source_strings.source_accel, " "target_strings.target, target_strings.target_accel, " "files.path, main.change_date " "FROM main, source_strings, target_strings, files " "WHERE source_strings.id=main.source AND " "target_strings.id=main.target AND " "files.id=main.file")))) qCWarning(LOKALIZE_LOG) << "select error: " << query1.lastError().text(); TMConfig c = getConfig(db); const QString DATE_FORMAT = QStringLiteral("yyyyMMdd"); const QString PROP = QStringLiteral("prop"); const QString TYPE = QStringLiteral("type"); while (query1.next()) { QString source = makeAcceledString(query1.value(4).toString(), c.accel, query1.value(5)); QString target = makeAcceledString(query1.value(6).toString(), c.accel, query1.value(7)); xmlOut.writeStartElement(QStringLiteral("tu")); xmlOut.writeAttribute(QStringLiteral("tuid"), QString::number(query1.value(0).toLongLong())); xmlOut.writeStartElement(QStringLiteral("tuv")); xmlOut.writeAttribute(QStringLiteral("xml:lang"), QStringLiteral("en")); xmlOut.writeStartElement(QStringLiteral("seg")); xmlOut.writeCharacters(source); xmlOut.writeEndElement(); xmlOut.writeEndElement(); xmlOut.writeStartElement(QStringLiteral("tuv")); xmlOut.writeAttribute(QStringLiteral("xml:lang"), dbLangCode); xmlOut.writeAttribute(QStringLiteral("creationdate"), QDate::fromString(query1.value(2).toString(), Qt::ISODate).toString(DATE_FORMAT)); xmlOut.writeAttribute(QStringLiteral("changedate"), QDate::fromString(query1.value(9).toString(), Qt::ISODate).toString(DATE_FORMAT)); QString ctxt = query1.value(1).toString(); if (!ctxt.isEmpty()) { int pos = ctxt.indexOf(TM_DELIMITER); if (pos != -1) { QString plural = ctxt; plural.remove(0, pos + 1); ctxt.remove(pos, plural.size()); xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-pluralform"); xmlOut.writeCharacters(plural); xmlOut.writeEndElement(); } if (!ctxt.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-context"); xmlOut.writeCharacters(ctxt); xmlOut.writeEndElement(); } } QString filePath = query1.value(8).toString(); if (!filePath.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-file"); xmlOut.writeCharacters(filePath); xmlOut.writeEndElement(); } qlonglong bits = query1.value(8).toLongLong(); if (bits & TM_NOTAPPROVED) if (!filePath.isEmpty()) { xmlOut.writeStartElement(PROP); xmlOut.writeAttribute(TYPE, "x-approved"); xmlOut.writeCharacters("no"); xmlOut.writeEndElement(); } xmlOut.writeStartElement(QStringLiteral("seg")); xmlOut.writeCharacters(target); xmlOut.writeEndElement(); xmlOut.writeEndElement(); xmlOut.writeEndElement(); } query1.clear(); xmlOut.writeEndDocument(); out.close(); qCWarning(LOKALIZE_LOG) << "ExportTmxJob done exporting:" << a.elapsed(); m_time = a.elapsed(); } //END TMX ExecQueryJob::ExecQueryJob(const QString& queryString, const QString& dbName) : QObject(), QRunnable() , query(0) , m_dbName(dbName) , m_query(queryString) { setAutoDelete(false); //qCDebug(LOKALIZE_LOG)<<"ExecQueryJob"<lastError().text(); emit done(this); }