File indexing completed on 2022-10-04 13:56:33

0001 /*
0002  * Vocabulary Document for KDE Edu
0003  * SPDX-FileCopyrightText: 1999-2001 Ewald Arnold <kvoctrain@ewald-arnold.de>
0004  * SPDX-FileCopyrightText: 2005-2007 Peter Hedlund <peter.hedlund@kdemail.net>
0005  * SPDX-FileCopyrightText: 2007 Frederik Gladhorn <frederik.gladhorn@kdemail.net>
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 #include "keduvocdocument.h"
0009 
0010 #include <QCoreApplication>
0011 #include <QFileInfo>
0012 #include <QTextStream>
0013 #include <QtAlgorithms>
0014 #include <QIODevice>
0015 #include <QTemporaryFile>
0016 
0017 #include <QDebug>
0018 #include <kio/filecopyjob.h>
0019 #include <KCompressionDevice>
0020 #include <KLocalizedString>
0021 #include <KAutoSaveFile>
0022 #include <KLazyLocalizedString>
0023 
0024 #include "keduvocexpression.h"
0025 #include "keduvoclesson.h"
0026 #include "keduvocleitnerbox.h"
0027 #include "keduvocwordtype.h"
0028 #include "keduvockvtml2writer.h"
0029 #include "keduvoccsvwriter.h"
0030 #include "readerwriters/readermanager.h"
0031 #include "readerwriters/readerbase.h"
0032 
0033 #define WQL_IDENT      "WordQuiz"
0034 
0035 #define KVTML_EXT        "kvtml"
0036 #define CSV_EXT          "csv"
0037 #define TXT_EXT          "txt"
0038 #define WQL_EXT          "wql"
0039 
0040 /** @details Private Data class for KEduVocDocument */
0041 class KEduVocDocument::KEduVocDocumentPrivate
0042 {
0043 public:
0044     /** constructor */
0045     KEduVocDocumentPrivate( KEduVocDocument* qq )
0046             : q( qq )
0047     {
0048         m_lessonContainer = nullptr;
0049         m_wordTypeContainer = nullptr;
0050         m_leitnerContainer = nullptr;
0051         m_autosave = new KAutoSaveFile;
0052         init();
0053     }
0054 
0055     /** destructor */
0056     ~KEduVocDocumentPrivate();
0057 
0058     /** initializer */
0059     void init();
0060 
0061     KEduVocDocument* q;                               ///< The Front End
0062 
0063     /** autosave file used to provide locking access to the underlying file
0064      * Note: It is a pointer to allow locking a new file, saving results and
0065      * then transferring the lock to m_autosave without risking loss of lock.
0066      * See saveAs for clarification*/
0067     KAutoSaveFile            *m_autosave;
0068 
0069     bool                      m_dirty;                ///< dirty bit
0070     bool                      m_isReadOnly;       ///< FileOpenReadOnly was used for opening
0071 
0072     // save these to document
0073     QList<KEduVocIdentifier>  m_identifiers;          ///< list of identifiers
0074 
0075     QList<int>                m_extraSizeHints;       ///< unused
0076     QList<int>                m_sizeHints;            ///< unused
0077 
0078     QString                   m_generator;            ///< name of generating application
0079     QString                   m_queryorg;             ///< unused
0080     QString                   m_querytrans;           ///< unused
0081 
0082     QStringList               m_tenseDescriptions;    ///< unused. Was used in merge
0083     QSet<QString>             m_usages;               ///< unused
0084 
0085     QString                   m_title;                ///< Document title
0086     QString                   m_author;               ///< Document author
0087     QString                   m_authorContact;        ///< Author contact information
0088     QString                   m_license;              ///< Document license
0089     QString                   m_comment;              ///< Document comment
0090     QString                   m_version;              ///< Document version
0091     QString                   m_csvDelimiter;         ///< CSV delimiter
0092 
0093     /** Categories that can later be used to sork kvtml files:
0094       * language, music, children, anatomy
0095       */
0096     QString                   m_category;            ///< Document category
0097 
0098     KEduVocLesson * m_lessonContainer;               ///< Root lesson container
0099     KEduVocWordType * m_wordTypeContainer;           ///< Root word types container
0100     /** Root Leitner container
0101      * (probably unused) This is not used in Parley
0102      * */
0103     KEduVocLeitnerBox * m_leitnerContainer;
0104 
0105     /** Creates an autosave file for fpath in order to lock the file
0106      *   @param autosave a reference to an allocated KAutosave
0107      *   @param fpath File path to the file to be locked
0108      *   @param flags Describes how to deal with locked file etc.
0109      *   @return ErrorCode where NoError is success
0110      *   */
0111     KEduVocDocument::ErrorCode initializeKAutoSave(KAutoSaveFile &autosave,
0112                            QString const &fpath,
0113                            FileHandlingFlags flags) const;
0114 };
0115 
0116 KEduVocDocument::KEduVocDocumentPrivate::~KEduVocDocumentPrivate()
0117 {
0118     delete m_lessonContainer;
0119     delete m_wordTypeContainer;
0120     delete m_leitnerContainer;
0121     delete m_autosave;
0122 }
0123 
0124 void KEduVocDocument::KEduVocDocumentPrivate::init()
0125 {
0126     delete m_lessonContainer;
0127     m_lessonContainer = new KEduVocLesson(i18nc("The top level lesson which contains all other lessons of the document.",
0128                         "Document Lesson"),
0129                       q);
0130     m_lessonContainer->setContainerType(KEduVocLesson::Lesson);
0131     delete m_wordTypeContainer;
0132     m_wordTypeContainer = new KEduVocWordType(i18n( "Word types" ));
0133 
0134     delete m_leitnerContainer;
0135     m_leitnerContainer = new KEduVocLeitnerBox(i18n( "Leitner Box" ));
0136 
0137     m_tenseDescriptions.clear();
0138     m_identifiers.clear();
0139     m_extraSizeHints.clear();
0140     m_sizeHints.clear();
0141     m_dirty = false;
0142     m_isReadOnly = false;
0143     m_queryorg = QLatin1String("");
0144     m_querytrans = QLatin1String("");
0145     m_autosave->setManagedFile( QUrl() );
0146     m_author = QLatin1String("");
0147     m_title = QLatin1String("");
0148     m_comment = QLatin1String("");
0149     m_version = QLatin1String("");
0150     m_generator = QLatin1String("");
0151     m_csvDelimiter = QString( '\t' );
0152     m_usages.clear();
0153     m_license.clear();
0154     m_category.clear();
0155 }
0156 
0157 
0158 KEduVocDocument::ErrorCode KEduVocDocument::KEduVocDocumentPrivate::initializeKAutoSave(KAutoSaveFile &autosave,  QString const & fpath,  FileHandlingFlags flags) const {
0159 
0160     QList<KAutoSaveFile *> staleFiles = KAutoSaveFile::staleFiles( QUrl::fromLocalFile(fpath),
0161                                                                  QCoreApplication::instance()->applicationName());
0162     if ( !staleFiles.isEmpty()) {
0163         if ( flags & FileIgnoreLock ) {
0164             foreach( KAutoSaveFile *f,  staleFiles ) {
0165                 f->open( QIODevice::ReadWrite );
0166                 f->remove();
0167                 delete f;
0168             }
0169         } else {
0170             qWarning() << i18n( "Cannot lock file %1", fpath );
0171             return FileLocked;
0172         }
0173     }
0174 
0175     autosave.setManagedFile( QUrl::fromLocalFile(fpath) );
0176     if ( !autosave.open( QIODevice::ReadWrite ) ) {
0177         qWarning() << i18n( "Cannot lock file %1", autosave.fileName() );
0178         return FileCannotLock;
0179     }
0180 
0181     return NoError;
0182 }
0183 
0184 
0185 KEduVocDocument::KEduVocDocument( QObject *parent )
0186     : QObject(parent)
0187     , d(new KEduVocDocumentPrivate(this))
0188 {
0189 }
0190 
0191 
0192 KEduVocDocument::~KEduVocDocument()
0193 {
0194     delete d;
0195 }
0196 
0197 
0198 void KEduVocDocument::setModified( bool dirty )
0199 {
0200     d->m_dirty = dirty;
0201     Q_EMIT docModified( d->m_dirty );
0202 }
0203 
0204 
0205 KEduVocDocument::FileType KEduVocDocument::detectFileType(const QString &fileName)
0206 {
0207     KCompressionDevice f(fileName);
0208     f.open(QIODevice::ReadOnly);
0209 
0210     ReaderManager::ReaderPtr reader(ReaderManager::reader(f));
0211 
0212     f.close();
0213 
0214     return reader->fileTypeHandled();
0215 }
0216 
0217 
0218 KEduVocDocument::ErrorCode KEduVocDocument::open(const QUrl &url, FileHandlingFlags flags)
0219 {
0220     // save csv delimiter to preserve it in case this is a csv document
0221     QString csv = d->m_csvDelimiter;
0222     // clear all other properties
0223     d->init();
0224     if (!url.isEmpty()) {
0225         setUrl(url);
0226     }
0227     d->m_csvDelimiter = csv;
0228 
0229     KEduVocDocument::ErrorCode errStatus = Unknown;
0230     QString errorMessage = i18n("<qt>Cannot open file<br /><b>%1</b></qt>", url.toDisplayString());
0231 
0232     QString temporaryFile;
0233     QTemporaryFile tempFile;
0234     if (url.isLocalFile()) {
0235         temporaryFile = url.toLocalFile();
0236     } else {
0237         if (!tempFile.open()) {
0238             qWarning() << i18n("Cannot open tempfile %1",  tempFile.fileName());
0239             return Unknown;
0240         }
0241         KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tempFile.fileName()), -1, KIO::Overwrite);
0242         if (!job->exec()) {
0243             qWarning() << i18n("Cannot download %1: %2",  url.toDisplayString(), job->errorString());
0244             return FileDoesNotExist;
0245         }
0246         temporaryFile = tempFile.fileName();
0247     }
0248 
0249     if (flags & FileOpenReadOnly) {
0250         d->m_isReadOnly = true;
0251     }
0252 
0253     ErrorCode autosaveError = NoError;
0254 
0255     if (!d->m_isReadOnly) {
0256         autosaveError = d->initializeKAutoSave(*d->m_autosave,  temporaryFile, flags);
0257         if (autosaveError != NoError) {
0258             return autosaveError;
0259         }
0260     }
0261 
0262     KCompressionDevice f(temporaryFile);
0263     if (f.open(QIODevice::ReadOnly)) {
0264 
0265         ReaderManager::ReaderPtr reader(ReaderManager::reader(f));
0266         errStatus = reader->read(*this);
0267 
0268         if (errStatus != KEduVocDocument::NoError) {
0269             errorMessage = i18n("Could not open or properly read \"%1\"\n(Error reported: %2)"
0270                                 , url.toDisplayString(), reader->errorMessage());
0271         }
0272     } else {
0273         errStatus = FileCannotRead;
0274     }
0275 
0276     f.close();
0277 
0278     if (errStatus == KEduVocDocument::NoError) {
0279         setModified(false);
0280     } else {
0281         qWarning() << errorMessage;
0282     }
0283 
0284     return errStatus;
0285 }
0286 
0287 void KEduVocDocument::close()
0288 {
0289     if (!d->m_isReadOnly) {
0290     d->m_autosave->releaseLock();
0291     }
0292 }
0293 
0294 KEduVocDocument::ErrorCode KEduVocDocument::saveAs( const QUrl & url, FileType ft,
0295                             FileHandlingFlags flags)
0296 {
0297     if (d->m_isReadOnly) {
0298         return FileIsReadOnly;
0299     }
0300 
0301     QUrl tmp( url );
0302 
0303     if ( ft == Automatic ) {
0304         if ( tmp.path().right( strlen( "." KVTML_EXT ) ) == "." KVTML_EXT )
0305             ft = Kvtml;
0306         else if ( tmp.path().right( strlen( "." CSV_EXT ) ) == "." CSV_EXT )
0307             ft = Csv;
0308         else {
0309             return FileTypeUnknown;
0310         }
0311     }
0312 
0313     QString errorMessage = i18n( "Cannot write to file %1", tmp.toDisplayString() );
0314 
0315     KAutoSaveFile *autosave;
0316 
0317     //If we don't care about the lock always create a new one
0318     //If we are changing files create a new lock
0319     if ( ( flags & FileIgnoreLock )
0320         || ( d->m_autosave->managedFile() != tmp ) ) {
0321 
0322         autosave = new KAutoSaveFile;
0323         ErrorCode autosaveError = d->initializeKAutoSave( *autosave, tmp.toLocalFile(), flags );
0324         if ( autosaveError != NoError) {
0325             delete autosave;
0326             return autosaveError;
0327         }
0328     }
0329 
0330     //We care about the lock and we think we still have it.
0331     else {
0332          autosave = d->m_autosave;
0333         //Is the lock still good?
0334         if ( !autosave->exists( ) ) {
0335             return FileCannotLock;
0336         }
0337     }
0338 
0339     QFile f( tmp.toLocalFile() );
0340     if ( !f.open( QIODevice::WriteOnly ) ) {
0341         qCritical() << i18n( "Cannot write to file %1", f.fileName() );
0342         return FileCannotWrite;
0343     }
0344 
0345     bool saved = false;
0346 
0347     switch ( ft ) {
0348         case Kvtml: {
0349             // write version 2 file
0350             KEduVocKvtml2Writer kvtmlWriter( &f );
0351             saved = kvtmlWriter.writeDoc( this, d->m_generator );
0352         }
0353         break;
0354         ///@todo port me
0355 //         case Kvtml1: {
0356 //             // write old version 1 file
0357 //             KEduVocKvtmlWriter kvtmlWriter( &f );
0358 //             saved = kvtmlWriter.writeDoc( this, d->m_generator );
0359 //         }
0360 //         break;
0361         case Csv: {
0362             KEduVocCsvWriter csvWriter( &f );
0363             saved = csvWriter.writeDoc( this, d->m_generator );
0364         }
0365         break;
0366         default: {
0367             qCritical() << "kvcotrainDoc::saveAs(): unknown filetype";
0368         }
0369         break;
0370     } // switch
0371 
0372     f.close();
0373 
0374     if ( !saved ) {
0375         qCritical() << "Error Saving File" << tmp.toDisplayString();
0376 
0377         if ( autosave != d->m_autosave ) {
0378             delete autosave;
0379         }
0380         return FileWriterFailed;
0381     }
0382 
0383     if ( autosave != d->m_autosave ) {
0384         //The order is important: release old lock, delete old locker and then claim new locker.
0385         d->m_autosave->releaseLock();
0386         delete d->m_autosave;
0387         d->m_autosave = autosave;
0388     }
0389 
0390     setModified( false );
0391     return NoError;
0392 }
0393 
0394 QByteArray KEduVocDocument::toByteArray(const QString &generator)
0395 {
0396     // no file needed
0397     KEduVocKvtml2Writer kvtmlWriter(nullptr);
0398     return kvtmlWriter.toByteArray( this, generator );
0399 }
0400 
0401 void KEduVocDocument::merge( KEduVocDocument *docToMerge, bool matchIdentifiers )
0402 {
0403     Q_UNUSED(docToMerge)
0404     Q_UNUSED(matchIdentifiers)
0405     qDebug() << "Merging of docs is not implemented"; /// @todo IMPLEMENT ME
0406     // This code was really horribly broken.
0407     // Now with the new classes we could attempt to reactivate it.
0408     // A rewrite might be easier.
0409 
0410     /*
0411       if (docToMerge) {
0412 
0413         QStringList new_names = docToMerge->lessonDescriptions();
0414 
0415         QStringList new_types = docToMerge->typeDescriptions();
0416 
0417         QStringList new_tenses = docToMerge->tenseDescriptions();
0418 
0419         QList<int> old_in_query = lessonsInPractice();
0420         QList<int> new_in_query = docToMerge->lessonsInPractice();
0421 
0422         QStringList new_usages = docToMerge->usageDescriptions();
0423 
0424         int lesson_offset = d->m_lessonDescriptions.count();
0425         for (int i = 0; i < new_names.count(); i++) {
0426           d->m_lessonDescriptions.append(new_names[i]);
0427         }
0428 
0429         for (int i = 0; i < new_in_query.count(); i++)
0430           old_in_query.append(new_in_query[i] + lesson_offset);
0431         setLessonsInPractice(old_in_query);
0432 
0433         int types_offset = d->m_typeDescriptions.count();
0434         for (int i = 0; i < new_types.count(); i++) {
0435           d->m_typeDescriptions.append(new_types[i]);
0436         }
0437 
0438         int tenses_offset = d->m_tenseDescriptions.count();
0439         for (int i = 0; i < new_tenses.count(); i++) {
0440           d->m_tenseDescriptions.append(new_tenses[i]);
0441         }
0442 
0443         int usages_offset = d->m_usageDescriptions.count();
0444         for (int i = 0; i < new_usages.count(); i++) {
0445           d->m_usageDescriptions.append(new_usages[i]);
0446         }
0447 
0448         bool equal = true;
0449         if (originalIdentifier() != docToMerge->originalIdentifier())
0450           equal = false;
0451         for (int i = 1; i < identifierCount(); i++)
0452           if (identifier(i) != docToMerge->identifier(i))
0453             equal = false;
0454 
0455         if (!matchIdentifiers)
0456           equal = true; ///@todo massive cheating, problem if docToMerge has more identifiers than this
0457 
0458         if (equal) {   // easy way: same language codes, just append
0459 
0460           for (int i = 0; i < docToMerge->entryCount(); i++) {
0461             KEduVocExpression *expr = docToMerge->entry(i);
0462 
0463             expr->setLesson(expr->lesson() + lesson_offset);
0464 
0465             for (int lang = 0; lang <= expr->translationCount(); lang++) {
0466               QString t = expr->translation(lang).type();
0467               // adjust type offset
0468               if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0469                 QString t2;
0470                 t.remove(0, 1);
0471                 t2.setNum(t.toInt() + types_offset);
0472                 t2.prepend(QM_USER_TYPE);
0473                 expr->translation(lang).setType (t2);
0474               }
0475 
0476               t = expr->translation(lang).usageLabel();
0477               // adjust usage offset
0478               QString tg;
0479               if (!t.isEmpty()) {
0480                 QString t2;
0481                 while (t.left(strlen(":")) == UL_USER_USAGE) {
0482                   QString n;
0483                   t.remove(0, 1);
0484                   int next;
0485                   if ((next = t.indexOf(":")) >= 0) {
0486                     n = t.left(next);
0487                     t.remove(0, next + 1);
0488                   }
0489                   else {
0490                     n = t;
0491                     t = "";
0492                   }
0493 
0494                   t2.setNum(n.toInt() + usages_offset);
0495                   t2.prepend(UL_USER_USAGE);
0496                   if (tg.length() == 0)
0497                     tg = t2;
0498                   else
0499                     tg += ':' + t2;
0500                 }
0501 
0502                 if (tg.length() == 0)
0503                   tg = t;
0504                 else if (t.length() != 0)
0505                   tg += ':' + t;
0506 
0507                 expr->translation(lang).setUsageLabel (tg);
0508               }
0509 
0510               KEduVocConjugation conj = expr->translation(lang).conjugation();
0511               bool condirty = false;
0512               for (int ci = 0; ci < conj.entryCount(); ci++) {
0513                 t = conj.getType(ci);
0514                 if (!t.isEmpty() && t.left(1) == UL_USER_TENSE) {
0515                   t.remove(0, strlen(UL_USER_TENSE));
0516                   QString t2;
0517                   t2.setNum(t.toInt() + tenses_offset);
0518                   t2.prepend(UL_USER_TENSE);
0519                   conj.setType(ci, t2);
0520                   condirty = true;
0521                 }
0522                 if (condirty)
0523                   expr->translation(lang).setConjugation(conj);
0524               }
0525             }
0526 
0527             appendEntry(expr);
0528           }
0529           setModified();
0530         }
0531         else { // hard way: move entries around, most attributes get lost
0532           QList<int> move_matrix;
0533           QList<bool> cs_equal;
0534           QString s;
0535 
0536           for (int i = 0; i < qMax (identifierCount(), docToMerge->identifierCount()); i++)
0537             cs_equal.append(false);
0538 
0539           move_matrix.append(docToMerge->indexOfIdentifier(originalIdentifier()));
0540           for (int i = 1; i < identifierCount(); i++)
0541             move_matrix.append(docToMerge->indexOfIdentifier(identifier(i)));
0542 
0543           for (int j = 0; j < docToMerge->entryCount(); j++) {
0544             KEduVocExpression new_expr;
0545             KEduVocExpression *expr = docToMerge->entry(j);
0546             new_expr.setLesson(expr->lesson()+lesson_offset);
0547 
0548             for (int i = 0; i < move_matrix.count(); i++) {
0549               int lpos = move_matrix[i];
0550               if (lpos >= 0) {
0551 
0552                 if (lpos == 0)
0553                   s = expr->original();
0554                 else
0555                   s = expr->translation(lpos);
0556 
0557                 if (!cs_equal[lpos]) {
0558                   cs_equal[lpos] = true;
0559                   QString id = lpos == 0 ? originalIdentifier() : identifier(lpos);
0560                 }
0561 
0562                 if (i == 0)
0563                   new_expr.setOriginal(s);
0564                 else
0565                   new_expr.setTranslation(i, s);
0566                 QString r = expr->remark(lpos);
0567                 new_expr.setRemark (i, r);
0568 
0569                 QString t = expr->type(lpos);
0570                 if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0571                   QString t2;
0572                   t.remove(0, 1);
0573                   t2.setNum(t.toInt() + types_offset);
0574                   t2.prepend(QM_USER_TYPE);
0575                   new_expr.setType(i, t2);
0576                 }
0577 
0578                 t = expr->usageLabel(lpos);
0579                 if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0580                   QString t2;
0581                   t.remove(0, 1);
0582                   t2.setNum(t.toInt() + usages_offset);
0583                   t2.prepend(QM_USER_TYPE);
0584                   new_expr.setUsageLabel(i, t2);
0585                 }
0586 
0587                 KEduVocConjugation conj = expr->conjugation(lpos);
0588                 for (int ci = 0; ci < conj.entryCount(); ci++) {
0589                   t = conj.getType(ci);
0590                   if (!t.isEmpty() && t.left(1) == QM_USER_TYPE) {
0591                     t.remove (0, strlen(QM_USER_TYPE));
0592                     QString t2;
0593                     t2.setNum(t.toInt() + tenses_offset);
0594                     t2.prepend(QM_USER_TYPE);
0595                     conj.setType(ci, t2);
0596                   }
0597                 }
0598 
0599               }
0600             }
0601             // only append if entries are used
0602             bool used = !new_expr.original().isEmpty();
0603             for (int i = 1; i < identifierCount(); i++)
0604               if (!new_expr.translation(i).isEmpty())
0605                 used = true;
0606 
0607             if (used) {
0608               appendEntry(&new_expr);
0609               setModified();
0610             }
0611           }
0612         }
0613       }
0614     */
0615 }
0616 
0617 const KEduVocIdentifier& KEduVocDocument::identifier( int index ) const
0618 {
0619     if ( index < 0 || index >= d->m_identifiers.size() ) {
0620         qCritical() << " Error: Invalid identifier index: " << index;
0621     }
0622     return d->m_identifiers[index];
0623 }
0624 
0625 KEduVocIdentifier& KEduVocDocument::identifier( int index )
0626 {
0627     if ( index < 0 || index >= d->m_identifiers.size() ) {
0628         qCritical() << " Error: Invalid identifier index: " << index;
0629     }
0630     return d->m_identifiers[index];
0631 }
0632 
0633 void KEduVocDocument::setIdentifier( int idx, const KEduVocIdentifier &id )
0634 {
0635     if ( idx >= 0 && idx < d->m_identifiers.size() ) {
0636         d->m_identifiers[idx] = id;
0637     }
0638     setModified(true);
0639 }
0640 
0641 // works if const is removed
0642 int KEduVocDocument::indexOfIdentifier( const QString &name ) const
0643 {
0644     for (int i = 0; i < identifierCount(); i++)
0645         if (identifier(i).locale() == name)
0646             return i;
0647     return -1;
0648 }
0649 
0650 void KEduVocDocument::removeIdentifier( int index )
0651 {
0652     if ( index < d->m_identifiers.size() && index >= 0 ) {
0653         d->m_identifiers.removeAt( index );
0654         d->m_lessonContainer->removeTranslation( index );
0655     }
0656 }
0657 
0658 
0659 bool KEduVocDocument::isModified() const
0660 {
0661     return d->m_dirty;
0662 }
0663 
0664 
0665 int KEduVocDocument::identifierCount() const
0666 {
0667     return d->m_identifiers.count();  // number of translations
0668 }
0669 
0670 int KEduVocDocument::appendIdentifier( const KEduVocIdentifier& id )
0671 {
0672     int i = d->m_identifiers.size();
0673 //qDebug() << "appendIdentifier: " << i << id.name() << id.locale();
0674     d->m_identifiers.append( id );
0675     if ( id.name().isEmpty() ) {
0676         if ( i == 0 ) {
0677             identifier(i).setName(i18nc("The name of the first language/column of vocabulary, if we have to guess it.", "Original"));
0678         } else {
0679             identifier(i).setName(i18nc( "The name of the second, third ... language/column of vocabulary, if we have to guess it.", "Translation %1", i ) );
0680         }
0681     }
0682 
0683 
0684     return i;
0685 }
0686 
0687 KEduVocLesson * KEduVocDocument::lesson()
0688 {
0689     return d->m_lessonContainer;
0690 }
0691 
0692 KEduVocWordType * KEduVocDocument::wordTypeContainer()
0693 {
0694     return d->m_wordTypeContainer;
0695 }
0696 
0697 KEduVocLeitnerBox * KEduVocDocument::leitnerContainer()
0698 {
0699     return d->m_leitnerContainer;
0700 }
0701 
0702 QUrl KEduVocDocument::url() const
0703 {
0704     return d->m_autosave->managedFile();
0705 }
0706 
0707 void KEduVocDocument::setUrl( const QUrl& url )
0708 {
0709     d->m_autosave->setManagedFile(url);
0710 }
0711 
0712 QString KEduVocDocument::title() const
0713 {
0714     if ( d->m_title.isEmpty() )
0715         return d->m_autosave->managedFile().fileName();
0716     else
0717         return d->m_title;
0718 }
0719 
0720 void KEduVocDocument::setTitle( const QString & title )
0721 {
0722     d->m_title = title;
0723     ///@todo decouple document title and root lesson title
0724     d->m_lessonContainer->setName(title);
0725     setModified(true);
0726 }
0727 
0728 QString KEduVocDocument::author() const
0729 {
0730     return d->m_author;
0731 }
0732 
0733 void KEduVocDocument::setAuthor( const QString & s )
0734 {
0735     d->m_author = s.simplified();
0736     setModified(true);
0737 }
0738 
0739 QString KEduVocDocument::authorContact() const
0740 {
0741     return d->m_authorContact;
0742 }
0743 
0744 void KEduVocDocument::setAuthorContact( const QString & s )
0745 {
0746     d->m_authorContact = s.simplified();
0747     setModified(true);
0748 }
0749 
0750 QString KEduVocDocument::license() const
0751 {
0752     return d->m_license;
0753 }
0754 
0755 QString KEduVocDocument::documentComment() const
0756 {
0757     return d->m_comment;
0758 }
0759 
0760 void KEduVocDocument::setCategory( const QString & category )
0761 {
0762     d->m_category = category;
0763     setModified(true);
0764 }
0765 
0766 QString KEduVocDocument::category() const
0767 {
0768     return d->m_category;
0769     ///@todo make writer/reader use this
0770 }
0771 
0772 void KEduVocDocument::queryIdentifier( QString &org, QString &trans ) const
0773 {
0774     org = d->m_queryorg;
0775     trans = d->m_querytrans;
0776 }
0777 
0778 void KEduVocDocument::setQueryIdentifier( const QString &org, const QString &trans )
0779 {
0780     d->m_queryorg = org;
0781     d->m_querytrans = trans;
0782     setModified(true);
0783 }
0784 
0785 void KEduVocDocument::setLicense( const QString & s )
0786 {
0787     d->m_license = s.simplified();
0788     setModified(true);
0789 }
0790 
0791 void KEduVocDocument::setDocumentComment( const QString & s )
0792 {
0793     d->m_comment = s.trimmed();
0794     setModified(true);
0795 }
0796 
0797 void KEduVocDocument::setGenerator( const QString & generator )
0798 {
0799     d->m_generator = generator;
0800     setModified(true);
0801 }
0802 
0803 QString KEduVocDocument::generator() const
0804 {
0805     return d->m_generator;
0806 }
0807 
0808 QString KEduVocDocument::version() const
0809 {
0810     return d->m_version;
0811 }
0812 
0813 void KEduVocDocument::setVersion( const QString & vers )
0814 {
0815     d->m_version = vers;
0816     setModified(true);
0817 }
0818 
0819 QString KEduVocDocument::csvDelimiter() const
0820 {
0821     return d->m_csvDelimiter;
0822 }
0823 
0824 void KEduVocDocument::setCsvDelimiter( const QString &delimiter )
0825 {
0826     d->m_csvDelimiter = delimiter;
0827     setModified(true);
0828 }
0829 
0830 
0831 QString KEduVocDocument::pattern( FileDialogMode mode )
0832 {
0833     static const struct SupportedFilter {
0834         bool reading;
0835         bool writing;
0836         const char* extensions;
0837         const KLazyLocalizedString description;
0838     }
0839     filters[] = {
0840                     { true, true, "*.kvtml", kli18n( "KDE Vocabulary Document" ) },
0841                     { true, false, "*.wql", kli18n( "KWordQuiz Document" ) },
0842                     { true, false, "*.xml.qz *.pau.gz", kli18n( "Pauker Lesson" ) },
0843                     { true, false, "*.voc", kli18n( "Vokabeltrainer" ) },
0844                     { true, false, "*.xdxf", kli18n( "XML Dictionary Exchange Format" ) },
0845                     { true, true, "*.csv", kli18n( "Comma Separated Values (CSV)" ) },
0846                     // last is marker for the end, do not remove it
0847                     { false, false, nullptr, KLazyLocalizedString() }
0848                 };
0849     QStringList newfilters;
0850     QStringList allext;
0851     for ( int i = 0; filters[i].extensions; ++i ) {
0852         if (( mode == Reading && filters[i].reading ) ||
0853                 ( mode == Writing && filters[i].writing ) ) {
0854             newfilters.append( KLocalizedString( filters[i].description ).toString() + " (" + QLatin1String( filters[i].extensions ) + ')' );
0855             allext.append( QLatin1String( filters[i].extensions ) );
0856         }
0857     }
0858     if ( mode == Reading ) {
0859         newfilters.prepend( allext.join(QLatin1Char(' ') ) + '|' + i18n( "All supported documents" ) );
0860     }
0861     return newfilters.join(QLatin1String(";;") );
0862 }
0863 
0864 QString KEduVocDocument::errorDescription( int errorCode )
0865 {
0866     switch (errorCode) {
0867     case NoError:
0868         return i18n("No error found.");
0869 
0870     case InvalidXml:
0871         return i18n("Invalid XML in document.");
0872     case FileTypeUnknown:
0873         return i18n("Unknown file type.");
0874     case FileCannotWrite:
0875         return i18n("File is not writeable.");
0876     case FileWriterFailed:
0877         return i18n("File writer failed.");
0878     case FileCannotRead:
0879         return i18n("File is not readable.");
0880     case FileReaderFailed:
0881         return i18n("The file reader failed.");
0882     case FileDoesNotExist:
0883         return i18n("The file does not exist.");
0884     case FileLocked:
0885         return i18n("The file is locked by another process.");
0886     case FileCannotLock:
0887         return i18n("The lock file can't be created.");
0888     case Unknown:
0889     default:
0890         return i18n("Unknown error.");
0891     }
0892 }