Commit 9e80b835 authored by Simon Depiets's avatar Simon Depiets
Browse files

TS storage shall not un-escape XML special chars

By default the QXmlSimpleReader transforms escaped XML chars into the actual chars (except < and &)
Now lokalize will re-escape them back and store them in a CData to prevent Qt from amending the string internally
On saving, the XML file is first exported to a string and the CData overhead is replaced.
This behavior can be disabled in Editor Settings

BUG: 419071
parent d4adf92f
......@@ -61,6 +61,15 @@ TsStorage::TsStorage()
{
}
QString TsStorage::protect(const QString & str) const
{
QString p = str;
p.replace(QLatin1Char('\"'), QLatin1String("&quot;"));
p.replace(QLatin1Char('>'), QLatin1String("&gt;"));
p.replace(QLatin1Char('<'), QLatin1String("&lt;"));
p.replace(QLatin1Char('\''), QLatin1String("&apos;"));
return p;
}
int TsStorage::capabilities() const
{
return 0;//MultipleNotes;
......@@ -72,7 +81,6 @@ int TsStorage::load(QIODevice* device)
{
QElapsedTimer 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);
......@@ -98,6 +106,28 @@ int TsStorage::load(QIODevice* device)
//we create any form-entries additionally needed
entries = m_doc.elementsByTagName(QStringLiteral("message"));
if (!Settings::self()->convertXMLChars()) {
for (int pos = 0; pos < entries.size(); pos++) {
QDomElement refNode = entries.at(pos).firstChildElement(names[SourceTag]).toElement();
QDomNode firstRef = refNode.firstChild();
refNode.appendChild(m_doc.createCDATASection(protect(refNode.text())));
refNode.removeChild(firstRef);
QDomElement targetEl = unitForPos(pos).firstChildElement(names[TargetTag]).toElement();
if (!targetEl.isNull()) {
QDomNode firstTarget = targetEl.firstChild();
targetEl.appendChild(m_doc.createCDATASection(protect(targetEl.text())));
targetEl.removeChild(firstTarget);
}
QDomElement noteEl = unitForPos(pos).firstChildElement(names[NoteTag]).toElement();
if (!noteEl.isNull()) {
QDomNode firstNote = noteEl.firstChild();
qCWarning(LOKALIZE_LOG) << noteEl.text() << " is note EL";
noteEl.appendChild(m_doc.createCDATASection(protect(noteEl.text())));
noteEl.removeChild(firstNote);
}
}
}
qCWarning(LOKALIZE_LOG) << chrono.elapsed() << "secs, " << entries.size() << "entries";
return 0;
......@@ -107,7 +137,20 @@ bool TsStorage::save(QIODevice* device, bool belongsToProject)
{
Q_UNUSED(belongsToProject)
QTextStream stream(device);
m_doc.save(stream, 4);
if (Settings::self()->convertXMLChars()) {
m_doc.save(stream, 4);
} else {
QString tempString;
QTextStream tempStream(&tempString);
m_doc.save(tempStream, 4);
stream << tempString.replace(QStringLiteral("<source><![CDATA["), QStringLiteral("<source>"))
.replace(QStringLiteral("<translation><![CDATA["), QStringLiteral("<translation>"))
.replace(QStringLiteral("<translatorcomment><![CDATA["), QStringLiteral("<translatorcomment>"))
.replace(QStringLiteral("]]></source>"), QStringLiteral("</source>"))
.replace(QStringLiteral("]]></translation>"), QStringLiteral("</translation>"))
.replace(QStringLiteral("]]></translatorcomment>"), QStringLiteral("</translatorcomment>"));
}
return true;
}
//END OPEN/SAVE
......@@ -116,14 +159,9 @@ bool TsStorage::save(QIODevice* device, bool belongsToProject)
int TsStorage::size() const
{
//return m_map.size();
return entries.size();
}
/**
* helper structure used during XLIFF XML walk-through
*/
......@@ -181,16 +219,15 @@ static QString doContent(QDomElement elem, int startingPos, TsContentEditingData
|| (!result.isEmpty() && data && data->actionType == TsContentEditingData::CheckLength))
return QString();
bool seenCharacterDataAfterElement = false;
bool seenCDataAfterElement = false;
QDomNode n = elem.firstChild();
while (!n.isNull()) {
if (n.isCharacterData()) {
seenCharacterDataAfterElement = true;
if (Settings::self()->convertXMLChars() ? n.isCharacterData() : n.isCDATASection()) {
seenCDataAfterElement = true;
QDomCharacterData c = n.toCharacterData();
QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection();
QString cData = c.data();
if (data && data->pos != -1 &&
data->pos >= startingPos && data->pos <= startingPos + cData.size()) {
// time to do some action! ;)
......@@ -199,9 +236,8 @@ static QString doContent(QDomElement elem, int startingPos, TsContentEditingData
//BEGIN DELETE TEXT
if (data->actionType == TsContentEditingData::DeleteText) { //(data->lengthOfStringToRemove!=-1)
if (localStartPos + data->lengthOfStringToRemove > cData.size()) {
//text is fragmented into several QDomCharacterData
//text is fragmented into several QDomCData
int localDelLen = cData.size() - localStartPos;
//qCWarning(LOKALIZE_LOG)<<"text is fragmented into several QDomCharacterData. localDelLen:"<<localDelLen<<"cData:"<<cData;
c.deleteData(localStartPos, localDelLen);
//setup data for future iterations
data->lengthOfStringToRemove = data->lengthOfStringToRemove - localDelLen;
......@@ -223,18 +259,18 @@ static QString doContent(QDomElement elem, int startingPos, TsContentEditingData
}
cData = c.data();
}
//else
// if (data&&data->pos!=-1/*&& n.nextSibling().isNull()*/)
// qCWarning(LOKALIZE_LOG)<<"arg!"<<startingPos<<"data->pos"<<data->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()));
if (!seenCDataAfterElement) {
//add empty cDATA child so that user could add some text
elem.appendChild(Settings::self()->convertXMLChars() ?
elem.ownerDocument().createTextNode(QString()) :
elem.ownerDocument().createCDATASection(QString())
);
}
return result;
......@@ -305,7 +341,6 @@ void TsStorage::targetDelete(const DocPosition& pos, int count)
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()) {
......@@ -314,12 +349,14 @@ void TsStorage::targetInsert(const DocPosition& pos, const QString& arg)
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 ;)
targetEl.appendChild(Settings::self()->convertXMLChars() ?
m_doc.createTextNode(arg) :
m_doc.createCDATASection(arg));//i bet that pos.offset is 0 ;)
return;
}
}
//END add <*target>
if (arg.isEmpty()) return; //means we were called just to add <taget> tag
if (arg.isEmpty()) return; //means we were called just to add <target> tag
TsContentEditingData data(pos.offset, arg);
content(targetEl, &data);
......@@ -369,9 +406,13 @@ QVector<Note> TsStorage::notes(const DocPosition& pos) const
QDomElement elem = unitForPos(pos.entry).firstChildElement(names[NoteTag]);
while (!elem.isNull()) {
Note note;
note.content = elem.text();
result.append(note);
QDomNode n = elem.firstChild();
if (!n.isNull()) {
QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection();
Note note;
note.content = c.data();
result.append(note);
}
elem = elem.nextSiblingElement(names[NoteTag]);
}
......@@ -395,28 +436,38 @@ QVector<Note> TsStorage::developerNotes(const DocPosition& pos) const
Note TsStorage::setNote(DocPosition pos, const Note& note)
{
//qCWarning(LOKALIZE_LOG)<<int(pos.form)<<note.content;
QDomElement unit = unitForPos(pos.entry);
QDomElement elem;
Note oldNote;
if (pos.form == -1 && !note.content.isEmpty()) {
QDomElement ref = unit.lastChildElement(names[NoteTag]);
elem = unit.insertAfter(m_doc.createElement(names[NoteTag]), ref).toElement();
elem.appendChild(m_doc.createTextNode(QString()));
elem.appendChild(Settings::self()->convertXMLChars() ?
m_doc.createTextNode(QString()) :
m_doc.createCDATASection(QString()));
} else {
QDomNodeList list = unit.elementsByTagName(names[NoteTag]);
//if (pos.form==-1) pos.form=list.size()-1;
if (pos.form < list.size()) {
elem = unit.elementsByTagName(names[NoteTag]).at(pos.form).toElement();
oldNote.content = elem.text();
QDomNode n = elem.firstChild();
if (!n.isNull()) {
QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection();
oldNote.content = c.data();
}
}
}
if (elem.isNull()) return oldNote;
if (!elem.text().isEmpty()) {
TsContentEditingData data(0, elem.text().size());
content(elem, &data);
QDomNode n = elem.firstChild();
if (!n.isNull()) {
QDomCharacterData c = Settings::self()->convertXMLChars() ? n.toCharacterData() : n.toCDATASection();
if (!c.data().isEmpty()) {
TsContentEditingData data(0, c.data().size());
content(elem, &data);
}
}
if (!note.content.isEmpty()) {
......
......@@ -111,7 +111,7 @@ private:
QDomElement targetForPos(DocPosition pos) const;
QDomElement sourceForPos(int pos) const;
CatalogString catalogString(QDomElement contentElement) const;
QString protect(const QString & str) const;
private:
mutable QDomDocument m_doc;
......
......@@ -122,6 +122,9 @@
<entry name="VisualizeSeparators" type="Bool">
<default>false</default>
</entry>
<entry name="ConvertXMLChars" type="Bool">
<default>false</default>
</entry>
<entry name="MouseWheelGo" type="Bool">
<default>false</default>
</entry>
......
......@@ -26,18 +26,18 @@
<property name="spacing">
<number>6</number>
</property>
<item row="4" column="0" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="1" column="0">
<widget class="QCheckBox" name="kcfg_MouseWheelGo">
<property name="toolTip">
<string>If set, mouse wheel goes to previous or next unit, otherwise it scrolls text</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
<property name="whatsThis">
<string>&lt;html&gt;When this option is enabled, the mouse wheel is used to go to the previous or next translation unit (without modifier keys). Modifier keys can be used to change this behavior. Use:&lt;ul&gt;&lt;li&gt;&lt;b&gt;Shift&lt;/b&gt; to scroll within the text of the current unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ctrl+Shift&lt;/b&gt; to go to previous or next non-ready unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ctrl&lt;/b&gt; to go to previous or next non-ready not empty unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Alt&lt;/b&gt; to go to previous or next untranslated unit.&lt;/li&gt;&lt;/ul&gt;When the option is disabled, the mouse wheel scrolls within the text of the current translation unit.&lt;/html&gt;</string>
</property>
</spacer>
<property name="text">
<string>Mouse wheel goes to previous or next translation unit</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QCheckBox" name="kcfg_AutoApprove">
......@@ -46,16 +46,23 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="kcfg_MouseWheelGo">
<property name="toolTip">
<string>If set, mouse wheel goes to previous or next unit, otherwise it scrolls text</string>
<item row="7" column="0" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="whatsThis">
<string>&lt;html&gt;When this option is enabled, the mouse wheel is used to go to the previous or next translation unit (without modifier keys). Modifier keys can be used to change this behavior. Use:&lt;ul&gt;&lt;li&gt;&lt;b&gt;Shift&lt;/b&gt; to scroll within the text of the current unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ctrl+Shift&lt;/b&gt; to go to previous or next non-ready unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Ctrl&lt;/b&gt; to go to previous or next non-ready not empty unit,&lt;/li&gt;&lt;li&gt;&lt;b&gt;Alt&lt;/b&gt; to go to previous or next untranslated unit.&lt;/li&gt;&lt;/ul&gt;When the option is disabled, the mouse wheel scrolls within the text of the current translation unit.&lt;/html&gt;</string>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="0">
<widget class="QCheckBox" name="kcfg_VisualizeSeparators">
<property name="text">
<string>Mouse wheel goes to previous or next translation unit</string>
<string>Visualize separators such as spaces, tabs and new lines in the editor</string>
</property>
</widget>
</item>
......@@ -92,10 +99,10 @@
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="kcfg_VisualizeSeparators">
<item row="5" column="0">
<widget class="QCheckBox" name="kcfg_ConvertXMLChars">
<property name="text">
<string>Visualize separators such as spaces, tabs and new lines in the editor</string>
<string>Automatically convert XML special characters in TS files</string>
</property>
</widget>
</item>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment