dolphin/konq-plugins/domtreeviewer/domtreecommands.cpp
Raphael Kubo da Costa e5cdd821c0 domtreeviewer: Fix build with clang.
DOM::Node does not originally have operator<(DOM::Node, DOM::Node)
implemented in khtml, so domtreecommands.cpp implemented it. However, it did
so in the `domtreeviewer' namespace.

When ManipulationCommand::addChangedNode() is called, it calls
ChangedNodeSet::insert(), which is actually QMap<DOM::Node, bool>::insert(),
which calls qMapLessThanKey(), which finally calls operator<().

Since QMap is not part of the `domtreeviewer' namespace, when
qMapLessThanKey() calls operator<(), it never looks for an implementation in
the `domtreeviewer' namespace. Argument-dependend name lookup makes the
compiler look for an implementation in the namespace of the function
arguments (DOM, in this case), so we need to move our implementation there.

For some reason, the code built fine with gcc.

Thanks to Nicolás Alvarez for the initial discussion, and [1] for the ugly
details.

[1] http://www.gotw.ca/publications/mill08.htm

(cherry picked from commit c3e23ca299)
2012-01-02 01:53:29 -02:00

571 lines
13 KiB
C++

/* This file is part of the KDE project
*
* Copyright (C) 2005 Leo Savernik <l.savernik@aon.at>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "domtreecommands.h"
#include <dom/dom_doc.h>
#include <dom/dom_exception.h>
#include <klocale.h>
#include <qmap.h>
using namespace domtreeviewer;
static const char * const dom_error_msgs[] = {
I18N_NOOP("No error"),
I18N_NOOP("Index size exceeded"),
I18N_NOOP("DOMString size exceeded"),
I18N_NOOP("Hierarchy request error"),
I18N_NOOP("Wrong document"),
I18N_NOOP("Invalid character"),
I18N_NOOP("No data allowed"),
I18N_NOOP("No modification allowed"),
I18N_NOOP("Not found"),
I18N_NOOP("Not supported"),
I18N_NOOP("Attribute in use"),
I18N_NOOP("Invalid state"),
I18N_NOOP("Syntax error"),
I18N_NOOP("Invalid modification"),
I18N_NOOP("Namespace error"),
I18N_NOOP("Invalid access")
};
// == global functions ==============================================
QString domtreeviewer::domErrorMessage(int dom_err)
{
if ((unsigned)dom_err >= sizeof dom_error_msgs/sizeof dom_error_msgs[0])
return i18n("Unknown Exception %1", dom_err);
else
return i18n(dom_error_msgs[dom_err]);
}
// == ManipulationCommandSignalEmitter ==============================
static ManipulationCommandSignalEmitter *_mcse;
ManipulationCommandSignalEmitter::ManipulationCommandSignalEmitter()
{}
ManipulationCommandSignalEmitter::~ManipulationCommandSignalEmitter()
{}
namespace domtreeviewer {
ManipulationCommandSignalEmitter* ManipulationCommand::mcse()
{
if (!_mcse) _mcse = new ManipulationCommandSignalEmitter;
return _mcse;
}
}
// == ChangedNodeSet ================================================
namespace DOM {
inline static bool operator <(const DOM::Node &n1, const DOM::Node &n2)
{
return (qptrdiff)n1.handle() - (qptrdiff)n2.handle() < 0;
}
}
namespace domtreeviewer {
// collection of nodes for which to emit the nodeChanged signal
class ChangedNodeSet : public QMap<DOM::Node, bool>
{
};
}
// == ManipulationCommand ===========================================
ManipulationCommand::ManipulationCommand() : _exception(0), changedNodes(0)
, _reapplied(false) , allow_signals(true)
{
}
ManipulationCommand::~ManipulationCommand()
{
}
void ManipulationCommand::connect(const char *signal, QObject *recv, const char *slot)
{
QObject::connect(mcse(), signal, recv, slot);
}
void ManipulationCommand::handleException(DOM::DOMException &ex)
{
_exception = ex;
QString msg = text() + ": " + domErrorMessage(ex.code);
emit mcse()->error(ex.code, msg);
}
void ManipulationCommand::checkAndEmitSignals()
{
if (allow_signals) {
if (changedNodes) {
ChangedNodeSet::Iterator end = changedNodes->end();
for (ChangedNodeSet::Iterator it = changedNodes->begin(); it != end; ++it) {
emit mcse()->nodeChanged(it.key());
}
}
if (struc_changed) emit mcse()->structureChanged();
}
if (changedNodes) changedNodes->clear();
}
void ManipulationCommand::addChangedNode(const DOM::Node &node)
{
if (!changedNodes) changedNodes = new ChangedNodeSet;
changedNodes->insert(node, true);
}
void ManipulationCommand::redo()
{
if (!isValid()) return;
struc_changed = false;
try {
if (shouldReapply())
reapply();
else
apply();
checkAndEmitSignals();
} catch(DOM::DOMException &ex) {
handleException(ex);
}
_reapplied = true;
}
void ManipulationCommand::undo()
{
if (!isValid()) return;
struc_changed = false;
try {
unapply();
checkAndEmitSignals();
} catch(DOM::DOMException &ex) {
handleException(ex);
}
}
void ManipulationCommand::reapply()
{
apply();
}
// == MultiCommand ===========================================
MultiCommand::MultiCommand(const QString &desc)
: _name(desc)
{
}
MultiCommand::~MultiCommand()
{
qDeleteAll(cmds);
}
void MultiCommand::addCommand(ManipulationCommand *cmd)
{
cmd->allow_signals = false;
cmds.append(cmd);
}
void MultiCommand::apply()
{
// apply in forward order
QList<ManipulationCommand*>::ConstIterator it = cmds.constBegin(), itEnd = cmds.constEnd();
for (; it != itEnd; ++it) {
try {
if (shouldReapply()) (*it)->reapply();
else (*it)->apply();
struc_changed |= (*it)->struc_changed;
mergeChangedNodesFrom(*it);
} catch (DOM::DOMException &) {
// rollback
for (--it; *it; --it) {
try {
(*it)->unapply();
} catch(DOM::DOMException &) {
// ignore
}
}
throw;
}
}
}
void MultiCommand::unapply()
{
// unapply in reverse order
QListIterator<ManipulationCommand*> it(cmds);
it.toBack();
while (it.hasPrevious()) {
ManipulationCommand* current = it.previous();
try {
current->unapply();
struc_changed |= current->struc_changed;
mergeChangedNodesFrom(current);
} catch (DOM::DOMException &) {
// rollback
while (it.hasNext()) {
ManipulationCommand* newcurrent = it.next();
try {
newcurrent->reapply();
} catch(DOM::DOMException &) {
// ignore
}
}
throw;
}
}
}
void MultiCommand::mergeChangedNodesFrom(ManipulationCommand *cmd)
{
if (!cmd->changedNodes) return;
ChangedNodeSet::ConstIterator end = cmd->changedNodes->constEnd();
for (ChangedNodeSet::ConstIterator it = cmd->changedNodes->constBegin(); it != end; ++it) {
addChangedNode(it.key());
}
cmd->changedNodes->clear();
}
QString MultiCommand::name() const
{
return _name;
}
// == AddAttributeCommand ===========================================
AddAttributeCommand::AddAttributeCommand(const DOM::Element &element, const QString &attrName, const QString &attrValue)
: _element(element), attrName(attrName), attrValue(attrValue)
{
if (attrValue.isEmpty()) this->attrValue = "<dummy>";
}
AddAttributeCommand::~AddAttributeCommand()
{
}
void AddAttributeCommand::apply()
{
_element.setAttribute(attrName, attrValue);
addChangedNode(_element);
}
void AddAttributeCommand::unapply()
{
_element.removeAttribute(attrName);
addChangedNode(_element);
}
QString AddAttributeCommand::name() const
{
return i18n("Add attribute");
}
// == ChangeAttributeValueCommand ====================================
ChangeAttributeValueCommand::ChangeAttributeValueCommand(
const DOM::Element &element, const QString &attr, const QString &value)
: _element(element), _attr(attr), new_value(value)
{
}
ChangeAttributeValueCommand::~ChangeAttributeValueCommand()
{
}
void ChangeAttributeValueCommand::apply()
{
if (!shouldReapply()) old_value = _element.getAttribute(_attr);
_element.setAttribute(_attr, new_value);
addChangedNode(_element);
}
void ChangeAttributeValueCommand::unapply()
{
_element.setAttribute(_attr, old_value);
addChangedNode(_element);
}
QString ChangeAttributeValueCommand::name() const
{
return i18n("Change attribute value");
}
// == RemoveAttributeCommand ========================================
RemoveAttributeCommand::RemoveAttributeCommand(const DOM::Element &element, const QString &attrName)
: _element(element), attrName(attrName)
{
}
RemoveAttributeCommand::~RemoveAttributeCommand()
{
}
void RemoveAttributeCommand::apply()
{
// kDebug(90180) << _element.nodeName().string() << ": " << attrName.string();
if (!shouldReapply())
oldAttrValue = _element.getAttribute(attrName);
_element.removeAttribute(attrName);
addChangedNode(_element);
}
void RemoveAttributeCommand::unapply()
{
_element.setAttribute(attrName, oldAttrValue);
addChangedNode(_element);
}
QString RemoveAttributeCommand::name() const
{
return i18n("Remove attribute");
}
// == RenameAttributeCommand ========================================
RenameAttributeCommand::RenameAttributeCommand(const DOM::Element &element, const QString &attrOldName, const QString &attrNewName)
: _element(element), attrOldName(attrOldName), attrNewName(attrNewName)
{
}
RenameAttributeCommand::~RenameAttributeCommand()
{
}
void RenameAttributeCommand::apply()
{
if (!shouldReapply())
attrValue = _element.getAttribute(attrOldName);
_element.removeAttribute(attrOldName);
_element.setAttribute(attrNewName, attrValue);
addChangedNode(_element);
}
void RenameAttributeCommand::unapply()
{
_element.removeAttribute(attrNewName);
_element.setAttribute(attrOldName, attrValue);
addChangedNode(_element);
}
QString RenameAttributeCommand::name() const
{
return i18n("Rename attribute");
}
// == ChangeCDataCommand ========================================
ChangeCDataCommand::ChangeCDataCommand(const DOM::CharacterData &cdata, const QString &value)
: cdata(cdata), value(value), has_newlines(false)
{
}
ChangeCDataCommand::~ChangeCDataCommand()
{
}
void ChangeCDataCommand::apply()
{
if (!shouldReapply()) {
oldValue = cdata.data();
has_newlines =
QString::fromRawData(value.unicode(), value.length()).contains('\n')
|| QString::fromRawData(oldValue.unicode(), oldValue.length()).contains('\n');
}
cdata.setData(value);
addChangedNode(cdata);
struc_changed = has_newlines;
}
void ChangeCDataCommand::unapply()
{
cdata.setData(oldValue);
addChangedNode(cdata);
struc_changed = has_newlines;
}
QString ChangeCDataCommand::name() const
{
return i18n("Change textual content");
}
// == ManipulateNodeCommand ===========================================
ManipulateNodeCommand::ManipulateNodeCommand(const DOM::Node &node, const DOM::Node &parent, const DOM::Node &after)
: _node(node), _parent(parent), _after(after)
{
}
ManipulateNodeCommand::~ManipulateNodeCommand()
{
}
void ManipulateNodeCommand::insert()
{
_parent.insertBefore(_node, _after);
}
void ManipulateNodeCommand::remove()
{
DOM::DocumentFragment frag = _node;
if (frag.isNull()) { // do a normal remove
_node = _parent.removeChild(_node);
} else { // remove fragment nodes and recreate fragment
DOM::DocumentFragment newfrag = _parent.ownerDocument().createDocumentFragment();
for (DOM::Node i = frag.firstChild(); !i.isNull(); i = i.nextSibling()) {
newfrag.appendChild(_parent.removeChild(i));
}
_node = newfrag;
}
}
// == InsertNodeCommand ===========================================
InsertNodeCommand::InsertNodeCommand(const DOM::Node &node, const DOM::Node &parent, const DOM::Node &after)
: ManipulateNodeCommand(node, parent, after)
{
}
InsertNodeCommand::~InsertNodeCommand()
{
}
void InsertNodeCommand::apply()
{
insert();
struc_changed = true;
}
void InsertNodeCommand::unapply()
{
remove();
struc_changed = true;
}
QString InsertNodeCommand::name() const
{
return i18n("Insert node");
}
// == RemoveNodeCommand ===========================================
RemoveNodeCommand::RemoveNodeCommand(const DOM::Node &node, const DOM::Node &parent, const DOM::Node &after)
: ManipulateNodeCommand(node, parent, after)
{
}
RemoveNodeCommand::~RemoveNodeCommand()
{
}
void RemoveNodeCommand::apply()
{
remove();
struc_changed = true;
}
void RemoveNodeCommand::unapply()
{
insert();
struc_changed = true;
}
QString RemoveNodeCommand::name() const
{
return i18n("Remove node");
}
// == MoveNodeCommand ===========================================
MoveNodeCommand::MoveNodeCommand(const DOM::Node &node, const DOM::Node &parent, const DOM::Node &after)
: _node(node), new_parent(parent), new_after(after)
{
old_parent = node.parentNode();
old_after = node.nextSibling();
}
MoveNodeCommand::~MoveNodeCommand()
{
}
void MoveNodeCommand::apply()
{
old_parent.removeChild(_node);
try {
new_parent.insertBefore(_node, new_after);
} catch (DOM::DOMException &) {
try { // rollback
old_parent.insertBefore(_node, old_after);
} catch (DOM::DOMException &) {}
throw;
}
struc_changed = true;
}
void MoveNodeCommand::unapply()
{
new_parent.removeChild(_node);
try {
old_parent.insertBefore(_node, old_after);
} catch (DOM::DOMException &) {
try { // rollback
new_parent.insertBefore(_node, new_after);
} catch (DOM::DOMException &) {}
throw;
}
struc_changed = true;
}
QString MoveNodeCommand::name() const
{
return i18n("Move node");
}
#include "domtreecommands.moc"
#undef MCSE