2
Copyright 2008-2012 Nick Shaforostoff <shaforostoff@kde.ru>
4
This program is free software; you can redistribute it and/or
5
modify it under the terms of the GNU General Public License as
6
published by the Free Software Foundation; either version 2 of
7
the License or (at your option) version 3 or any later version
8
accepted by the membership of KDE e.V. (or its successor approved
9
by the membership of KDE e.V.), which shall act as a proxy
10
defined in Section 14 of version 3 of the license.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program. If not, see <http://www.gnu.org/licenses/>.
21
#include "tsstorage.h"
23
#include "gettextheader.h"
26
#include "prefs_lokalize.h"
31
#include <QDomDocument>
40
#include <kdatetime.h>
41
#include <QXmlSimpleReader>
43
static const char* const noyes[]={"no","yes"};
45
static const QString names[]={"source" ,"translation","oldsource" ,"translatorcomment","comment" ,"name" ,"numerus"};
46
enum TagNames {SourceTag,TargetTag ,OldSourceTag,NoteTag ,DevNoteTag,NameTag,PluralTag};
48
static const QString attrnames[]={"location" ,"type" ,"obsolete"};
49
enum AttrNames {LocationAttr,TypeAttr,ObsoleteAttr};
51
static const QString attrvalues[]={"obsolete"};
52
enum AttValues {ObsoleteVal};
54
TsStorage::TsStorage()
59
TsStorage::~TsStorage()
63
int TsStorage::capabilities() const
65
return 0;//MultipleNotes;
70
int TsStorage::load(QIODevice* device)
72
QTime chrono;chrono.start();
75
QXmlSimpleReader reader;
76
reader.setFeature("http://qtsoftware.com/xml/features/report-whitespace-only-CharData",true);
77
reader.setFeature("http://xml.org/sax/features/namespaces",false);
78
QXmlInputSource source(device);
81
int errorLine;//+errorColumn;
82
bool success=m_doc.setContent(&source, &reader, &errorMsg, &errorLine/*,errorColumn*/);
91
QDomElement file=m_doc.elementsByTagName("TS").at(0).toElement();
92
m_sourceLangCode=file.attribute("sourcelanguage");
93
m_targetLangCode=file.attribute("language");
94
m_numberOfPluralForms=numberOfPluralFormsForLangCode(m_targetLangCode);
96
//Create entry mapping.
97
//Along the way: for langs with more than 2 forms
98
//we create any form-entries additionally needed
100
entries=m_doc.elementsByTagName("message");
101
int size=entries.size();
103
kWarning()<<chrono.elapsed();
107
bool TsStorage::save(QIODevice* device, bool belongsToProject)
109
QTextStream stream(device);
110
m_doc.save(stream,4);
115
//BEGIN STORAGE TRANSLATION
117
int TsStorage::size() const
119
//return m_map.size();
121
return entries.size();
128
* helper structure used during XLIFF XML walk-through
130
struct ContentEditingData
132
enum ActionType{Get,DeleteText,InsertText,CheckLength};
134
QString stringToInsert;
136
int lengthOfStringToRemove;
137
ActionType actionType;
140
ContentEditingData(ActionType type=Get)
142
, lengthOfStringToRemove(-1)
147
ContentEditingData(int p, int l)
149
, lengthOfStringToRemove(l)
150
, actionType(DeleteText)
154
ContentEditingData(int p,const QString& s)
157
, lengthOfStringToRemove(-1)
158
, actionType(InsertText)
162
static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data);
165
* walks through XLIFF XML and performs actions depending on ContentEditingData:
167
* - deletes content, or
170
static QString content(QDomElement elem, ContentEditingData* data=0)
172
return doContent(elem, 0, data);
175
static QString doContent(QDomElement elem, int startingPos, ContentEditingData* data)
177
//actually startingPos is current pos
182
|| (!result.isEmpty() && ContentEditingData::CheckLength))
185
bool seenCharacterDataAfterElement=false;
187
QDomNode n = elem.firstChild();
190
if (n.isCharacterData())
192
seenCharacterDataAfterElement=true;
194
QDomCharacterData c=n.toCharacterData();
195
QString cData=c.data();
197
if (data && data->pos!=-1 &&
198
data->pos>=startingPos && data->pos<=startingPos+cData.size())
200
// time to do some action! ;)
201
int localStartPos=data->pos-startingPos;
204
if (data->actionType==ContentEditingData::DeleteText) //(data->lengthOfStringToRemove!=-1)
206
if (localStartPos+data->lengthOfStringToRemove>cData.size())
208
//text is fragmented into several QDomCharacterData
209
int localDelLen=cData.size()-localStartPos;
210
//qWarning()<<"text is fragmented into several QDomCharacterData. localDelLen:"<<localDelLen<<"cData:"<<cData;
211
c.deleteData(localStartPos,localDelLen);
212
//setup data for future iterations
213
data->lengthOfStringToRemove=data->lengthOfStringToRemove-localDelLen;
214
//data->pos=startingPos;
215
//qWarning()<<"\tsetup:"<<data->pos<<data->lengthOfStringToRemove;
219
//qWarning()<<"simple delete"<<localStartPos<<data->lengthOfStringToRemove;
220
c.deleteData(localStartPos,data->lengthOfStringToRemove);
221
data->actionType=ContentEditingData::CheckLength;
222
return QString('a');//so it exits 100%
227
else if (data->actionType==ContentEditingData::InsertText)
229
c.insertData(localStartPos,data->stringToInsert);
230
data->actionType=ContentEditingData::CheckLength;
231
return QString('a');//so it exits 100%
236
// if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/)
237
// kWarning()<<"arg!"<<startingPos<<"data->pos"<<data->pos;
240
startingPos+=cData.size();
244
if (!seenCharacterDataAfterElement)
246
//add empty charData child so that user could add some text
247
elem.appendChild( elem.ownerDocument().createTextNode(QString()) );
255
//flat-model interface (ignores XLIFF grouping)
257
CatalogString TsStorage::catalogString(QDomElement contentElement) const
259
CatalogString catalogString;
260
ContentEditingData data(ContentEditingData::Get);
261
catalogString.string=content(contentElement, &data);
262
return catalogString;
265
CatalogString TsStorage::catalogString(const DocPosition& pos) const
267
return catalogString(pos.part==DocPosition::Target?targetForPos(pos):sourceForPos(pos.entry));
270
CatalogString TsStorage::targetWithTags(DocPosition pos) const
272
return catalogString(targetForPos(pos));
274
CatalogString TsStorage::sourceWithTags(DocPosition pos) const
276
return catalogString(sourceForPos(pos.entry));
279
QString TsStorage::source(const DocPosition& pos) const
281
return content(sourceForPos(pos.entry));
283
QString TsStorage::target(const DocPosition& pos) const
285
return content(targetForPos(pos));
289
void TsStorage::targetDelete(const DocPosition& pos, int count)
291
ContentEditingData data(pos.offset,count);
292
content(targetForPos(pos),&data);
295
void TsStorage::targetInsert(const DocPosition& pos, const QString& arg)
297
kWarning()<<pos.entry<<arg;
298
QDomElement targetEl=targetForPos(pos);
299
//BEGIN add <*target>
300
if (targetEl.isNull())
302
QDomNode unitEl=unitForPos(pos.entry);
303
QDomNode refNode=unitEl.firstChildElement(names[SourceTag]);
304
targetEl = unitEl.insertAfter(m_doc.createElement(names[TargetTag]),refNode).toElement();
306
if (pos.entry<size())
308
targetEl.appendChild(m_doc.createTextNode(arg));//i bet that pos.offset is 0 ;)
313
if (arg.isEmpty()) return; //means we were called just to add <taget> tag
315
ContentEditingData data(pos.offset,arg);
316
content(targetEl,&data);
319
void TsStorage::setTarget(const DocPosition& pos, const QString& arg)
327
QVector<AltTrans> TsStorage::altTrans(const DocPosition& pos) const
329
QVector<AltTrans> result;
331
QString oldsource=content(unitForPos(pos.entry).firstChildElement(names[OldSourceTag]));
332
if (!oldsource.isEmpty())
333
result<<AltTrans(CatalogString(oldsource), i18n("Previous source value, saved by lupdate tool"));
339
QStringList TsStorage::sourceFiles(const DocPosition& pos) const
343
QDomElement elem = unitForPos(pos.entry).firstChildElement(attrnames[LocationAttr]);
344
while (!elem.isNull())
346
QString sourcefile=elem.attribute("filename");
347
QString linenumber=elem.attribute("line");
348
if (!( sourcefile.isEmpty()&&linenumber.isEmpty() ))
349
result.append(sourcefile+':'+linenumber);
351
elem=elem.nextSiblingElement(attrnames[LocationAttr]);
358
QVector<Note> TsStorage::notes(const DocPosition& pos) const
360
QVector<Note> result;
362
QDomElement elem = unitForPos(pos.entry).firstChildElement(names[NoteTag]);
363
while (!elem.isNull())
366
note.content=elem.text();
369
elem=elem.nextSiblingElement(names[NoteTag]);
374
QVector<Note> TsStorage::developerNotes(const DocPosition& pos) const
376
QVector<Note> result;
378
QDomElement elem = unitForPos(pos.entry).firstChildElement(names[DevNoteTag]);
379
while (!elem.isNull())
382
note.content=elem.text();
385
elem=elem.nextSiblingElement(names[DevNoteTag]);
390
Note TsStorage::setNote(DocPosition pos, const Note& note)
392
//kWarning()<<int(pos.form)<<note.content;
393
QDomElement unit=unitForPos(pos.entry);
396
if (pos.form==-1 && !note.content.isEmpty())
398
QDomElement ref=unit.lastChildElement(names[NoteTag]);
399
elem=unit.insertAfter( m_doc.createElement(names[NoteTag]),ref).toElement();
400
elem.appendChild(m_doc.createTextNode(QString()));
404
QDomNodeList list=unit.elementsByTagName(names[NoteTag]);
405
if (pos.form==-1) pos.form=list.size()-1;
406
if (pos.form<list.size())
408
elem = unit.elementsByTagName(names[NoteTag]).at(pos.form).toElement();
409
oldNote.content=elem.text();
413
if (elem.isNull()) return oldNote;
415
if (!elem.text().isEmpty())
417
ContentEditingData data(0,elem.text().size());
421
if (!note.content.isEmpty())
423
ContentEditingData data(0,note.content);
427
unit.removeChild(elem);
432
QStringList TsStorage::context(const DocPosition& pos) const
436
QDomElement unit=unitForPos(pos.entry);
437
QDomElement context=unit.parentNode().toElement();
438
//if (context.isNull())
441
QDomElement name=context.firstChildElement(names[NameTag]);
445
result.append(name.text());
449
QStringList TsStorage::matchData(const DocPosition& pos) const
452
return QStringList();
455
QString TsStorage::id(const DocPosition& pos) const
457
QString result=source(pos);
459
QStringList ctxt=context(pos);
461
result.prepend(ctxt.first());
465
bool TsStorage::isPlural(const DocPosition& pos) const
467
QDomElement unit=unitForPos(pos.entry);
469
return unit.hasAttribute(names[PluralTag]);
472
void TsStorage::setApproved(const DocPosition& pos, bool approved)
474
targetInsert(pos,QString()); //adds <taget> if needed
475
QDomElement target=unitForPos(pos.entry).firstChildElement(names[TargetTag]); //asking directly to bypass plural state detection
476
if (target.attribute(attrnames[TypeAttr])==attrvalues[ObsoleteVal])
479
target.removeAttribute(attrnames[TypeAttr]);
481
target.setAttribute(attrnames[TypeAttr],"unfinished");
484
bool TsStorage::isApproved(const DocPosition& pos) const
486
QDomElement target=unitForPos(pos.entry).firstChildElement(names[TargetTag]);
487
return !target.hasAttribute(attrnames[TypeAttr]);
490
bool TsStorage::isObsolete(int entry) const
492
QDomElement target=unitForPos(entry).firstChildElement(names[TargetTag]);
493
return target.attribute(attrnames[TypeAttr])==attrvalues[ObsoleteVal];
496
bool TsStorage::isEmpty(const DocPosition& pos) const
498
ContentEditingData data(ContentEditingData::CheckLength);
499
return content(targetForPos(pos),&data).isEmpty();
502
bool TsStorage::isEquivTrans(const DocPosition& pos) const
504
return true;//targetForPos(pos.entry).attribute("equiv-trans")!="no";
507
void TsStorage::setEquivTrans(const DocPosition& pos, bool equivTrans)
509
//targetForPos(pos.entry).setAttribute("equiv-trans",noyes[equivTrans]);
512
QDomElement TsStorage::unitForPos(int pos) const
514
return entries.at(pos).toElement();
517
QDomElement TsStorage::targetForPos(DocPosition pos) const
519
QDomElement unit=unitForPos(pos.entry);
520
QDomElement translation=unit.firstChildElement(names[TargetTag]);
521
if (!unit.hasAttribute(names[PluralTag]))
524
if (pos.form==-1) pos.form=0;
526
QDomNodeList forms=translation.elementsByTagName("numerusform");
527
while (pos.form>=forms.size())
528
translation.appendChild( unit.ownerDocument().createElement("numerusform") );
529
return forms.at(pos.form).toElement();
532
QDomElement TsStorage::sourceForPos(int pos) const
534
return unitForPos(pos).firstChildElement(names[SourceTag]);
537
//END STORAGE TRANSLATION