add a model for XML, editing is still missing
authorKonrad Rosenbaum <konrad@silmor.de>
Wed, 8 Feb 2012 20:45:46 +0000 (21:45 +0100)
committerKonrad Rosenbaum <konrad@silmor.de>
Wed, 8 Feb 2012 20:45:46 +0000 (21:45 +0100)
src/misc/dommodel.cpp [new file with mode: 0644]
src/misc/dommodel.h [new file with mode: 0644]
src/misc/misc.pri
src/templates/odfedit.cpp

diff --git a/src/misc/dommodel.cpp b/src/misc/dommodel.cpp
new file mode 100644 (file)
index 0000000..3b287e4
--- /dev/null
@@ -0,0 +1,401 @@
+//
+// C++ Implementation: DOM model
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2012
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#include "dommodel.h"
+
+#include <QDomDocument>
+#include <QDomElement>
+#include <QMap>
+#include <QSet>
+
+#include <QDebug>
+
+//switch this to en-/dis-able debug messages
+static inline
+// QDebug mDebug(){return QDebug(QtDebugMsg);}
+QNoDebug mDebug(){return QNoDebug();}
+
+#include <DPtr>
+
+class DPTR_CLASS_NAME(MDomItemModel)
+{
+       public:
+               //nextid: counter for internal DOM node ids
+               //docid: id of the document node
+               int nextidx,docidx;
+               //cache structure that holds one node each
+               struct DomCache{
+                       QDomNode node;
+                       QList<int>children;
+                       int parent;
+                       DomCache():parent(-1){}
+                       DomCache(const DomCache&)=default;
+                       DomCache(const QDomNode&node_,int parent_=-1)
+                       :node(node_),parent(parent_){}
+               };
+               //cache of all DOM nodes, index is the internal ID
+               QMap<int,DomCache>domCache;
+               //set of node types that are shown
+               QSet<QDomNode::NodeType>showType;
+               //functors for generation of the display
+               QMap<int,MDomModelFunctor> contentFromNode;
+               //config for element display
+               int elemAttrLen,elemTextLen;
+               //header data
+               QMap<QPair<int,int>,QVariant>horizHeader,vertHeader;
+               
+               Private();
+               
+               int buildCache(const QDomNode&,int p=-1);
+};
+
+DEFINE_DPTR(MDomItemModel);
+
+MDomItemModel::MDomItemModel(QObject* parent)
+       : QAbstractItemModel(parent)
+{
+}
+
+MDomItemModel::Private::Private()
+       :nextidx(1),docidx(-1)
+{
+       showType<<QDomNode::ElementNode;
+       elemAttrLen=20;elemTextLen=30;
+}
+
+
+MDomItemModel::MDomItemModel(const QDomDocument& doc, QObject* parent)
+       : QAbstractItemModel(parent)
+{
+       setDomDocument(doc);
+}
+
+void MDomItemModel::setDomDocument(const QDomDocument& doc)
+{
+       beginResetModel();
+       d->domCache.clear();
+       d->docidx=d->buildCache(doc);
+       mDebug()<<"DOM model setting new document - found"<<d->domCache.size()<<"DOM nodes, root node is"<<d->docidx;
+       endResetModel();
+}
+
+int MDomItemModel::Private::buildCache(const QDomNode& node,int parent)
+{
+       if(node.isNull())return -1;
+       //create this node
+       int idx=nextidx++;
+       DomCache dc(node,parent);
+       //recursively add children
+       QDomNodeList nl=node.childNodes();
+       mDebug()<<"node"<<idx<<"type"<<node.nodeType()<<"name"<<node.nodeName()<<"has"<<nl.size()<<"children";
+       for(int i=0;i<nl.size();i++)
+               dc.children.append(buildCache(nl.at(i),idx));
+       //insert this node
+       domCache.insert(idx,dc);
+       //return index
+       return idx;
+}
+
+void MDomItemModel::showNodeTypes(const QSet< QDomNode::NodeType >& nt)
+{
+       if(nt==d->showType)return;
+       beginResetModel();
+       d->showType=nt;
+       if(!d->showType.contains(QDomNode::ElementNode))
+               d->showType.insert(QDomNode::ElementNode);
+       mDebug()<<"DOM Model - showing these types"<<d->showType;
+       endResetModel();
+}
+
+void MDomItemModel::addShowNodeType(QDomNode::NodeType nt)
+{
+       if(d->showType.contains(nt))return;
+       beginResetModel();
+       d->showType.insert(nt);
+       mDebug()<<"DOM Model - showing these types"<<d->showType;
+       endResetModel();
+}
+
+void MDomItemModel::addShowNodeTypes(const QSet< QDomNode::NodeType >& nt)
+{
+       if(nt.isEmpty())return;
+       beginResetModel();
+       foreach(QDomNode::NodeType n,nt)
+               if(!d->showType.contains(n))
+                       d->showType.insert(n);
+       mDebug()<<"DOM Model - showing these types"<<d->showType;
+       endResetModel();
+}
+
+void MDomItemModel::showAllNodeTypes()
+{
+       showNodeTypes(QSet<QDomNode::NodeType>()
+               <<QDomNode::ElementNode
+               <<QDomNode::AttributeNode
+               <<QDomNode::TextNode
+               <<QDomNode::CDATASectionNode
+               <<QDomNode::EntityReferenceNode
+               <<QDomNode::EntityNode
+               <<QDomNode::ProcessingInstructionNode
+               <<QDomNode::CommentNode
+               <<QDomNode::DocumentNode
+               <<QDomNode::DocumentTypeNode
+               <<QDomNode::DocumentFragmentNode
+               <<QDomNode::NotationNode
+               <<QDomNode::BaseNode
+       );
+}
+
+QSet< QDomNode::NodeType > MDomItemModel::shownNodeTypes()const
+{
+       return d->showType;
+}
+
+void MDomItemModel::showElementProperties(int ma,int mt)
+{
+       if(d->elemAttrLen == ma && d->elemTextLen == mt)return;
+       beginResetModel();
+       d->elemAttrLen=ma;
+       d->elemTextLen=mt;
+       endResetModel();
+}
+
+QDomNode MDomItemModel::node(const QModelIndex& index) const
+{
+       if(!index.isValid()){
+               if(!d->domCache.contains(d->docidx)) return QDomNode();
+               else return d->domCache[d->docidx].node.cloneNode();
+       }
+       if(!d->domCache.contains(index.internalId()))return QDomNode();
+       return d->domCache[index.internalId()].node.cloneNode();
+}
+
+void MDomItemModel::setNode(const QModelIndex& index, const QDomNode& )
+{
+#warning implement
+}
+
+int MDomItemModel::columnCount(const QModelIndex& parent) const
+{
+       return 1;
+}
+
+QDomDocument MDomItemModel::domDocument() const
+{
+       if(d->docidx>=0 && d->domCache.contains(d->docidx))
+               return d->domCache[d->docidx].node.cloneNode().toDocument();
+       else
+               return QDomDocument();
+}
+
+QVariant MDomItemModel::data(const QModelIndex& index, int role) const
+{
+       if(role==DomNodeRole)return QVariant::fromValue(node(index));
+       //root index
+       QDomNode node;
+       if(!index.isValid()){
+               if(d->domCache.contains(d->docidx))
+                       node=d->domCache[d->docidx].node;
+       }else{
+               if(d->domCache.contains(index.internalId()))
+                       node=d->domCache[index.internalId()].node;
+       }
+       //check node
+       if(node.isNull())return QVariant();
+       //try to find callback
+       if(d->contentFromNode.contains(role))return d->contentFromNode[role](node);
+       //no callback: display role
+       if(role==Qt::DisplayRole)return displayFromNode(node);
+       //fall back
+       return QVariant();
+}
+
+QVariant MDomItemModel::displayFromNode(const QDomNode& node) const
+{
+       if(node.isNull())return "NULL node";
+       switch(node.nodeType()){
+               case QDomNode::ElementNode:{
+                       QString r="tag <"+node.nodeName();
+                       QString a,t;
+                       //append attribs
+                       if(d->elemAttrLen>=0 && node.hasAttributes()){
+                               auto attrs=node.attributes();
+                               QString a;
+                               for(int i=0;i<attrs.size();i++){
+                                       if(i)a+=" ";
+                                       QDomAttr att=attrs.item(i).toAttr();
+                                       a+=att.nodeName()+"=\""+att.nodeValue()+"\"";
+                               }
+                               if(a.size()>d->elemAttrLen){
+                                       a.truncate(d->elemAttrLen);
+                                       a+="...";
+                               }
+                               r+=" "+a.trimmed();
+                       }
+                       r+=">";
+                       //append text
+                       if(d->elemTextLen>0){
+                               QString t;
+                               QDomNodeList nl=node.childNodes();
+                               for(int i=0;i<nl.size();i++){
+                                       QDomNode nd=nl.at(i);
+                                       if(nd.isCharacterData()){
+                                               if(t.size()>0)t+=" ";
+                                               t+=QString(nd.nodeValue()).replace('\n',' ');
+                                       }
+                               }
+                               if(t.size()>d->elemTextLen){
+                                       t.truncate(d->elemTextLen);
+                                       t+="...";
+                               }
+                               r+=t.trimmed();
+                       }
+                       return r;
+               }
+               case QDomNode::AttributeNode:
+                       return "attribute "+node.nodeName()+"="+node.nodeValue();
+               case QDomNode::TextNode:
+               case QDomNode::CDATASectionNode:
+                       return "text \""+QString(node.nodeValue()).replace('\n',' ')+"\"";
+               default:
+                       return "node ("+QString::number(node.nodeType())+"): "+node.nodeName()+" - \""+node.nodeValue()+"\"";
+       }
+}
+
+int MDomItemModel::rowCount(const QModelIndex& parent) const
+{
+       int pid=-1;
+       if(!parent.isValid())pid=d->docidx;
+       else pid=parent.internalId();
+       if(!d->domCache.contains(pid)){
+               mDebug()<<"DOM Model - index"<<parent.row()<<parent.column()<<parent.internalId()<<"does not exist";
+               return 0;
+       }
+       int ret=0;
+       foreach(int cid,d->domCache[pid].children)
+               if(d->domCache.contains(cid) && d->showType.contains(d->domCache[cid].node.nodeType()))
+                       ret++;
+       mDebug()<<"DOM Model - index"<<parent.row()<<parent.column()<<pid<<"has"<<ret<<"rows out of"<<d->domCache[pid].children.size()<<"items";
+       return ret;
+}
+
+QModelIndex MDomItemModel::index(int row, int column, const QModelIndex& parent) const
+{
+       //only col 0 is valid for now
+       if(column!=0){
+               mDebug()<<"DOM Model - index for wrong column requested"<<row<<column;
+               return QModelIndex();
+       }
+       //discard invalid rows
+       if(row<0){
+               mDebug()<<"DOM Model - index for negative row requested"<<row<<column;
+               return QModelIndex();
+       }
+       //validate/find parent
+       int pid=-1;
+       if(!parent.isValid())pid=d->docidx;
+       else pid=parent.internalId();
+       if(pid<=0 || !d->domCache.contains(pid)){
+               mDebug()<<"DOM Model - index for invalid node requested"<<row<<column<<pid<<"parent is"<<parent.row()<<parent.column()<<parent.internalId();
+               return QModelIndex();
+       }
+       //try to find element
+       if(row>=d->domCache[pid].children.size()){
+               mDebug()<<"DOM Model - index of empty parent requested, parent:"<<parent.row()<<parent.column()<<parent.internalId();
+               return QModelIndex();
+       }
+       int rc=0;
+       foreach(int cid,d->domCache[pid].children){
+               if(!d->showType.contains(d->domCache[cid].node.nodeType()))
+                       continue;
+               if(row==rc++){
+                       mDebug()<<"DOM Model - creating index"<<row<<0<<cid<<"of parent"<<parent.row()<<parent.column()<<parent.internalId();
+                       return createIndex(row,0,cid);
+               }
+       }
+       //failed
+       mDebug()<<"DOM Model - requested index out of range"<<row<<column<<"of parent"<<parent.row()<<parent.column()<<parent.internalId();
+       return QModelIndex();
+}
+
+QModelIndex MDomItemModel::parent(const QModelIndex& index) const
+{
+       if(!index.isValid() || !d->domCache.contains(index.internalId())){
+               mDebug()<<"DOM Model - requesting parent of"<<index.row()<<index.column()<<index.internalId()<<"- invalid item";
+               return QModelIndex();
+       }
+       //find parent
+       int pid=d->domCache[index.internalId()].parent;
+       if(pid<=0 || !d->domCache.contains(pid)){
+               mDebug()<<"DOM Model - requesting parent of"<<index.row()<<index.column()<<index.internalId()<<"- invalid parent";
+               return QModelIndex();
+       }
+       //find grand-parent
+       int gpid=d->domCache[pid].parent;
+       //is grand-parent invalid? (parent must be the root node)
+       if(gpid<=0 || !d->domCache.contains(gpid)){
+               mDebug()<<"DOM Model - requesting parent of"<<index.row()<<index.column()<<index.internalId()<<"- invalid grand-parent, cannot search";
+               return QModelIndex();
+       }
+       //find row of the parent inside grand-parent
+       int row=0;
+       foreach(int nid,d->domCache[gpid].children){
+               if(nid==pid)break;
+               if(d->showType.contains(d->domCache[nid].node.nodeType()))row++;
+       }
+       mDebug()<<"DOM Model - requesting parent of"<<index.row()<<index.column()<<index.internalId()<<"- parent is"<<row<<0<<pid;
+       return createIndex(row,0,pid);
+}
+
+void MDomItemModel::removeNode(const QModelIndex& )
+{
+#warning implement
+}
+
+bool MDomItemModel::removeRows(int row, int count, const QModelIndex& parent)
+{
+#warning implement
+       return false;
+}
+
+void MDomItemModel::reparentChildNodes(const QModelIndex& )
+{
+#warning implement
+}
+
+void MDomItemModel::reparentNode(const QModelIndex& , const QDomNode& )
+{
+#warning implement
+}
+
+void MDomItemModel::setContentFromNodeCall(int r,MDomModelFunctor f)
+{
+       if(f)
+               d->contentFromNode.insert(r,f);
+       else
+               d->contentFromNode.remove(r);
+}
+
+QVariant MDomItemModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+       if(orientation==Qt::Horizontal)
+               return d->horizHeader.value(QPair<int,int>(section,role));
+       else
+               return d->vertHeader.value(QPair<int,int>(section,role));
+}
+
+bool MDomItemModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role)
+{
+       if(orientation==Qt::Horizontal)
+               d->horizHeader.insert(QPair<int,int>(section,role),value);
+       else
+               d->vertHeader.insert(QPair<int,int>(section,role),value);
+       return true;
+}
diff --git a/src/misc/dommodel.h b/src/misc/dommodel.h
new file mode 100644 (file)
index 0000000..030a348
--- /dev/null
@@ -0,0 +1,83 @@
+//
+// C++ Interface: odtrender
+//
+// Description: 
+//
+//
+// Author: Konrad Rosenbaum <konrad@silmor.de>, (C) 2008-2011
+//
+// Copyright: See README/COPYING.GPL files that come with this distribution
+//
+//
+
+#ifndef MAGICSMOKE_DOMMODEL_H
+#define MAGICSMOKE_DOMMODEL_H
+
+#include <QAbstractItemModel>
+#include <DPtrBase>
+#include <QDomNode>
+
+#include <functional>
+
+class QDomNode;
+class QDomDocument;
+
+typedef std::function<QVariant(const QDomNode&)> MDomModelFunctor;
+
+///This is a specialized model type that shows and allows to manipulate a DOM tree.
+///The model holds a copy of the DOM tree, so the original is not changed even if the model changes.
+///You can retrieve a current copy of the model's DOM tree by calling domDocument()
+class MDomItemModel:public QAbstractItemModel
+{
+       Q_OBJECT
+       DECLARE_DPTR(d);
+       public:
+               MDomItemModel(QObject* parent = 0);
+               MDomItemModel(const QDomDocument&, QObject* parent = 0);
+               
+               virtual QDomDocument domDocument()const;
+               
+               static const int DomNodeRole=Qt::UserRole;
+               
+               virtual int columnCount(const QModelIndex&parent=QModelIndex())const;
+               virtual QVariant data(const QModelIndex&index, int role=Qt::DisplayRole)const;
+               virtual QModelIndex index(int row, int column, const QModelIndex&parent=QModelIndex())const;
+               virtual QModelIndex parent(const QModelIndex&index)const;
+               virtual int rowCount(const QModelIndex&parent=QModelIndex())const;
+               
+               virtual QSet<QDomNode::NodeType> shownNodeTypes()const;
+               
+               virtual QDomNode node(const QModelIndex&index)const;
+               
+               bool setHeaderData(int section, Qt::Orientation orientation, const QVariant & value, int role = Qt::EditRole);
+               QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole)const;
+               
+       public slots:
+               virtual void setDomDocument(const QDomDocument&);
+               
+               void showNodeTypes(const QSet<QDomNode::NodeType>&);
+               void addShowNodeType(QDomNode::NodeType);
+               void addShowNodeTypes(const QSet<QDomNode::NodeType>&);
+               void showAllNodeTypes();
+               
+               void showElementProperties(int maxattr,int maxtext);
+               
+               virtual void setNode(const QModelIndex&index,const QDomNode&);
+               virtual bool removeRows(int row, int count, const QModelIndex&parent=QModelIndex());
+               virtual void removeNode(const QModelIndex&);
+               virtual void reparentNode(const QModelIndex&,const QDomNode&);
+               virtual void reparentChildNodes(const QModelIndex&);
+               
+               void setContentFromNodeCall(int role,MDomModelFunctor callback);
+               
+               virtual void clear(){setDomDocument(QDomDocument());}
+               
+       private:
+               ///default implementation for Display-Role
+               QVariant displayFromNode(const QDomNode&)const;
+};
+
+Q_DECLARE_METATYPE(QDomNode);
+Q_DECLARE_METATYPE(MDomModelFunctor);
+
+#endif
index 0793c37..8cc486c 100644 (file)
@@ -5,7 +5,8 @@ HEADERS += \
        $$PWD/sclock.h \
        $$PWD/formula.h \
        $$PWD/lambda.h \
-       $$PWD/msengine.h
+       $$PWD/msengine.h \
+       $$PWD/dommodel.h
 
 SOURCES += \
        $$PWD/code39.cpp \
@@ -14,7 +15,8 @@ SOURCES += \
        $$PWD/waitcursor.cpp \
        $$PWD/sclock.cpp \
        $$PWD/formula.cpp \
-       $$PWD/msengine.cpp
+       $$PWD/msengine.cpp \
+       $$PWD/dommodel.cpp
 
 INCLUDEPATH += $$PWD
 QMAKE_CXXFLAGS += -std=gnu++0x
\ No newline at end of file
index 13da05e..409bcf1 100644 (file)
@@ -13,7 +13,8 @@
 #include "odfedit.h"
 #include "centbox.h"
 #include "misc.h"
-#include "ticketrender.h"
+#include "odtrender.h"
+#include "dommodel.h"
 
 #include "MOTicket"
 #include "MOVoucher"
@@ -41,6 +42,7 @@
 #include <QPointF>
 #include <QPushButton>
 #include <QScrollArea>
+#include <QScrollBar>
 #include <QSettings>
 #include <QSignalMapper>
 #include <QSizeF>
@@ -51,6 +53,7 @@
 #include <QTemporaryFile>
 #include <QUnZip>
 #include <QZip>
+#include <QTreeView>
 
 
 class DPTR_CLASS_NAME(MOdfEditor)
@@ -58,6 +61,11 @@ class DPTR_CLASS_NAME(MOdfEditor)
        public:
                //template file
                QString mFileName;
+               //content.xml
+               QDomDocument mContent;
+               //widgets
+               QTreeView*mDomTree;
+               MDomItemModel*mDomModel;
                //file contents
                struct File{
                        QString name;
@@ -68,9 +76,6 @@ class DPTR_CLASS_NAME(MOdfEditor)
                        File(QString n,Type t,const QByteArray&c):name(n),type(t),content(c){}
                };
                QList<File> mFiles;
-               //content.xml
-               QDomDocument mContent;
-               //widgets
 };
 
 DEFINE_DPTR(MOdfEditor);
@@ -109,6 +114,17 @@ MOdfEditor::MOdfEditor(QWidget* parent, Qt::WindowFlags f): QMainWindow(parent,
        QHBoxLayout*hl,*hl2;
        QPushButton*p;
        
+       central->addWidget(d->mDomTree=new QTreeView);
+       d->mDomTree->setModel(d->mDomModel=new MDomItemModel(this));
+       d->mDomTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
+       d->mDomModel->setHeaderData(0,Qt::Horizontal,tr("Document XML Tree"),Qt::DisplayRole);
+       d->mDomModel->showAllNodeTypes();
+//     d->mDomModel->addShowNodeType(QDomNode::AttributeNode);
+       d->mDomTree->setSelectionMode(QAbstractItemView::SingleSelection);
+       d->mDomTree->setSelectionBehavior(QAbstractItemView::SelectRows);
+       d->mDomTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
+       d->mDomTree->header()->setResizeMode(0,QHeaderView::ResizeToContents);
+       d->mDomTree->header()->setStretchLastSection(false);
        
        //statusbar
        statusBar()->setSizeGripEnabled(true);
@@ -153,11 +169,43 @@ void MOdfEditor::loadFile(QString fn)
 
 void MOdfEditor::parseTemplate(QByteArray bytes)
 {
-       if(!d->mContent.setContent(bytes)){
-               QMessageBox::warning(this,tr("Warning"),tr("Unable to interpret template data."));
+       d->mContent.clear();
+       d->mDomModel->clear();
+       //convert V1->V2
+       QString err;int errln,errcl;
+       bool dov1=false;
+       const QString tpename=OdfTemplatePrefix+":template";
+       if(!d->mContent.setContent(bytes,false,&err,&errln,&errcl)){
+               qDebug()<<"Hmm, not XML, trying version 1 converter...";
+               qDebug()<<" Info: line ="<<errln<<"column ="<<errcl<<"error ="<<err;
+               dov1=true;
+       }else{
+               //check for template element
+               QDomElement de=d->mContent.documentElement();
+               if(de.isNull()||de.tagName()!=tpename){
+                       qDebug()<<"Template is not v2, trying to convert...";
+                       dov1=true;
+                       d->mContent.clear();
+               }
+       }
+       //conversion process
+       if(!dov1){
+               d->mDomModel->setDomDocument(d->mContent);
+               d->mDomTree->expandAll();
+               return;
+       }
+       //try again
+       if(!d->mContent.setContent(MOdtRenderer::convertV1toV2(bytes),false,&err,&errln,&errcl)){
+               qDebug()<<"Hmm, definitely not XML - even after conversion, aborting...";
+               qDebug()<<" Info: line ="<<errln<<"column ="<<errcl<<"error ="<<err;
                return;
+       }else{
+               qDebug()<<"Successfully converted the template.";
+//             qDebug()<<"document has"<<d->mContent.documentElement().childNodes().size()<<"children and"<<d->mContent.documentElement().attributes().size()<<"attribs";
        }
-       //TODO: converter V1->V2
+//     qDebug()<<"dump\n"<<d->mContent.toByteArray();
+       d->mDomModel->setDomDocument(d->mContent);
+       d->mDomTree->expandAll();
 }
 
 void MOdfEditor::openFile()