// // C++ Implementation: kxedocument // // Description: // // // Author: Adam Charytoniuk , (C) 2004 // // Copyright: See COPYING file that comes with this distribution // // #include "kxedocument.h" #include "kxmleditorfactory.h" #include "kxeconfiguration.h" #include "kxenewfilesettings.h" #include "kxearchiveextssettings.h" #include "kxeprintsettings.h" #include "kxetextviewsettings.h" #include "kxechoosestringdialog.h" #include "kxeattachdialogbase.h" #include "kxespecprocinstrdialog.h" #include "kxefilenewdialog.h" #include "commands_file.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KXEDocument::KXEDocument(QObject *parent, const char *name) :QObject (parent,name), QDomDocument(), KXMLGUIClient() { m_bDocIsCompressed = false; m_bIsModified = false; m_strCompressedTarEntryName = ""; m_url = ""; //setXMLFile("kxedocument.rc"); } KXEDocument::~KXEDocument() { } bool KXEDocument::save(const QString &strFileName) { if (this->documentElement().isNull() && KMessageBox::warningContinueCancel(0, i18n("Your file doesn't have root element defined. \n\ Continue saving?"))==KMessageBox::Cancel ) { return false; } QString strXML; QTextStream streamXML(&strXML, IO_WriteOnly); int iIndent = KXMLEditorFactory::configuration()->textview()->indentSteps(); ((QDomDocument*)this)->save(streamXML, iIndent); QString strEncoding; QTextCodec *pTextCodec; // find encoding info if(strXML.left(5) == " 0) { // info about encoding found; iStart += 8; // skip encoding // search " or ' after encoding if((iStart = strXML.find(QRegExp("[\"']"), iStart)) > 0) { QChar ch = strXML[iStart]; iStart++; // skip ch if((iEnd = strXML.find(ch, iStart)) > 0) { strEncoding = strXML.mid(iStart, iEnd - iStart); } } } } if(strEncoding.length() <= 0) pTextCodec = QTextCodec::codecForLocale(); // default else pTextCodec = QTextCodec::codecForName(strEncoding); if(pTextCodec == 0) { if(KMessageBox::questionYesNo(0, i18n("Codec for encoding %1 not found ! Continue saving ?").arg(strEncoding)) != KMessageBox::Yes) return false; } QCString strDecoded; if(pTextCodec) { strDecoded = pTextCodec->fromUnicode(strXML); } // save string to file if(!m_bDocIsCompressed) { QFile file(strFileName); if(file.open(IO_WriteOnly) == true) { file.writeBlock(strDecoded, strDecoded.length()); file.flush(); file.close(); } else { KMessageBox::error(0, i18n("Can't create file %1").arg(strFileName), i18n("Write error !")); } } else { // obtain file extension ----------------------------------------- QString strExtension; int iPos = strFileName.findRev('.'); if(iPos > 0) { strExtension = strFileName.mid(iPos + 1); } if(strExtension == "svgz") { KMessageBox::sorry(0, "Saving *.svgz not implemented yet", "sory"); return false; } else { KZip tarGzFile(strFileName); // New KOffice use KZip instead of KTarGz for storing files if(tarGzFile.open(IO_WriteOnly)) { tarGzFile.writeFile(m_strCompressedTarEntryName, "user", "group", strDecoded.length(), strDecoded); tarGzFile.close(); } else { KMessageBox::error(0, i18n("Can't create archive %1").arg(strFileName), i18n("Write error !")); } } } return true; } bool KXEDocument::open(const QString &strFileName) { QString strCompressedTarEntryName; kdDebug() << "KXEDocument::open: opening file " << strFileName << endl; // obtain file extension ----------------------------------------- QString strExtension; int iPos = strFileName.findRev('.'); if(iPos > 0) { strExtension = strFileName.mid(iPos + 1); } QString strTmpfileName; if ( KXMLEditorFactory::configuration()->archexts()->extensions().contains(strExtension) ) { KTempFile tmp; if (tmp.status() != 0) { kdError() << "Couldn't open temp file" << endl; KMessageBox::sorry(0, i18n("Couldn't open temp file !")); return false; } tmp.setAutoDelete(false); QFile &fileTemporary = *(tmp.file()); if(strExtension == "svgz") { //----------------------- It is gzip compressed file ----------------------- m_strCompressedTarEntryName = strFileName.left(strFileName.length() - 5); // For SVG compressed icons strip extension, e.g. "kate.svgz" has entry "kate" etc iPos = m_strCompressedTarEntryName.findRev('/'); if(iPos > 0) { m_strCompressedTarEntryName = m_strCompressedTarEntryName.mid(iPos + 1); } QIODevice *pIODevice = KFilterDev::deviceForFile(strFileName, "application/x-gzip"); if(pIODevice->open( IO_ReadOnly )) { QTextStream stream(pIODevice); QString line; //int i = 1; while ( !stream.atEnd() ) { line = stream.readLine(); // line of text excluding '\n' //printf( "%3d: %s\n", i++, line.latin1() ); fileTemporary.writeBlock(line, line.length()); } pIODevice->close(); } } else { //----------------------- It is zip archive file --------------------------- KZip tarGzFile(strFileName); // new KOffice use KZip instead of KTarGz for storing files tarGzFile.open(IO_ReadOnly); fileTemporary.open(IO_WriteOnly); const KTarDirectory *root = tarGzFile.directory(); if(!root) { return false; } // For KOffice files let user to choose maindoc or documentinfo if(strCompressedTarEntryName.length() == 0) { KXEChooseStringDialog dlgChooseString(0, 0, i18n("Choose file"), i18n("File:")); dlgChooseString.m_pComboBox->insertItem("maindoc.xml"); dlgChooseString.m_pComboBox->insertItem("documentinfo.xml"); if(dlgChooseString.exec() != KXEChooseStringDialog::Accepted) { return false; } m_strCompressedTarEntryName = dlgChooseString.m_strChoosedText; } else { m_strCompressedTarEntryName = strCompressedTarEntryName; } const KArchiveEntry *entry = root->entry(m_strCompressedTarEntryName); if(entry && entry->isFile()) { const KArchiveFile *pTarFile = static_cast (entry); QBuffer buffer(pTarFile->data()); buffer.open(IO_ReadOnly); fileTemporary.writeBlock(buffer.buffer(), buffer.size()); } else m_strCompressedTarEntryName.truncate(0); tarGzFile.close(); } strTmpfileName = fileTemporary.name(); fileTemporary.close(); m_bDocIsCompressed = true; } else m_bDocIsCompressed = false; // ( 1.) parse the file and fill our document QFile file(m_bDocIsCompressed ? strTmpfileName : strFileName); if(! file.open(IO_ReadOnly)) { kdDebug() << "KXEDocument::openFile: Can't open file." << endl; return false; } // auxiliary file for obtaining encoding info QFile fileAux(m_bDocIsCompressed ? strTmpfileName : strFileName); if(! fileAux.open(IO_ReadOnly)) { kdDebug() << "KXEDocument::openFile: Can't open file." << endl; return false; } QTextStream txtStreamLocal( & file ); // Lookup at XML document encoding ----------------------------------------------- QTextStream txtStreamAux( & fileAux ); QString strFirstLine = txtStreamAux.readLine(); fileAux.close(); int iStart, iEnd; if((iStart = strFirstLine.find("encoding", 0)) > 0) { QString strEncoding; // info about encoding found; iStart += 8; // skip encoding // search " or ' after encoding if((iStart = strFirstLine.find(QRegExp("[\"']"), iStart)) > 0) { QChar ch = strFirstLine[iStart]; iStart++; // skip ch if((iEnd = strFirstLine.find(ch, iStart)) > 0) { strEncoding = strFirstLine.mid(iStart, iEnd - iStart); QTextCodec *pTextCodec = QTextCodec::codecForName(strEncoding); if(pTextCodec) txtStreamLocal.setCodec(pTextCodec); else { KMessageBox::sorry(0, i18n("Codec for encoding %1 not found ! Using locale encoding for load.").arg(strEncoding)); txtStreamLocal.setEncoding(QTextStream::Locale); } } } } else { // XML documment dont have info about encoding, set default UTF-8 txtStreamLocal.setCodec(QTextCodec::codecForName("UTF-8")); } //-------------------------------------------------------------------------------- QString strFileContents = txtStreamLocal.read(); file.close(); if(m_bDocIsCompressed) { QDir dir; dir.remove(strTmpfileName); } //-- Set string with XML to QDomDocument ------------------------------------------ QString strErrorMsg; int iErrorLine, iErrorColumn; QDomDocument * pNewDoc = new QDomDocument; // first try with a new document if( ! pNewDoc->setContent(strFileContents, true, &strErrorMsg, &iErrorLine, &iErrorColumn) ) { kdDebug() << "KXEDocument::openFile: Failed parsing the file." << endl; KMessageBox::error(0, i18n("%1 in line %2, column %3").arg(strErrorMsg).arg(iErrorLine).arg(iErrorColumn), i18n("Parsing error !")); delete pNewDoc; // remove the new document, because it's useless return false; } // The following commented code is performance wise buggy, because the string // gets parsed a second time. I replaced it with this code. // copy the content of the parsed document to this one QDomNode e = pNewDoc->removeChild( pNewDoc->documentElement() ); QDomDocument::operator=( *pNewDoc ); appendChild( e ); // Here comes the "buggy" code. //this->setContent(pNewDoc->toString(),true,0,0); // and take the new one //delete pNewDoc; // remove the former document // To test/see the difference in loading time, you can switch the commented // codeblocks above and compare the loading-time-differences measured in // KXMLEditorPart::openFile. // Olaf // TODO: remove the comments above later emit sigOpened(); return true; } void KXEDocument::setModified(bool value) { m_bIsModified = value; emit sigModified(value); } void KXEDocument::setURL(KURL url) { m_url = url; emit sigURLChanged(url); } void KXEDocument::updateNodeCreated(const QDomNode & node) { emit sigNodeCreated(node); setModified(); } void KXEDocument::updateNodeDeleted(const QDomNode & node) { emit sigNodeDeleted(node); setModified(); } void KXEDocument::updateNodeChanged( const QDomElement & domElement ) { emit sigNodeChanged(domElement); setModified(); } void KXEDocument::updateNodeChanged( const QDomCharacterData & node ) { emit sigNodeChanged(node); setModified(); } void KXEDocument::updateNodeChanged( const QDomProcessingInstruction &domProcInstr ) { emit sigNodeChanged(domProcInstr); setModified(); } void KXEDocument::updateNodeMoved( const QDomNode & node ) { emit sigNodeMoved(node); setModified(); } void KXEDocument::attachStylesheet(const KURL& stylesheet) { setSpecProcInstr("xml-stylesheet",QString("type = 'text/xsl' href = '")+stylesheet.url()+"' "); } void KXEDocument::detachStylesheet() { removeSpecProcInstr("xml-stylesheet"); } void KXEDocument::attachSchema(const KURL& schema) { QDomElement domElement = documentElement(); if (!domElement.isNull()) { domElement.setAttributeNS(SCHEMA_NAMESPACE, SCHEMA_ATTRIBUTE_XSI, schema.url()); // refresh views updateNodeChanged(domElement); setModified(); } } void KXEDocument::detachSchema() { QDomElement domElement = this->documentElement(); if (!domElement.isNull()) { domElement.removeAttributeNS(SCHEMA_NAMESPACE,SCHEMA_ATTRIBUTE); // refresh views updateNodeChanged(domElement); setModified(); } } void KXEDocument::setSpecProcInstr(const QString& target, const QString& data) { // removing old one removeSpecProcInstr(target); // create new one if (!data.isEmpty()) { QDomProcessingInstruction domProcInstr = this->createProcessingInstruction(target,data); QDomNode node = getSpecProcInstr("xml"); if (!node.isNull()) // if there is already xml instruction, then put that one below it this->insertAfter(domProcInstr,node); else // otherwise put it always on the top this->insertBefore(domProcInstr,this->firstChild()); updateNodeCreated(domProcInstr); } setModified(); } void KXEDocument::removeSpecProcInstr(const QString &target) { QDomNode domNode = getSpecProcInstr(target); if (!domNode.isNull()) { updateNodeDeleted(domNode); ((QDomDocument*)this)->removeChild(domNode); setModified(); } } QDomNode KXEDocument::getSpecProcInstr(const QString& target) { QDomNode result; QDomNodeList domNodeList = this->childNodes(); for (uint i=0;inewfile()->newFileCreaBehav() ) { case KXENewFileSettings::CreateEmptyFile: break; // nothing to do in this case case KXENewFileSettings::CreateWithAssistance: { KXEFileNewDialog dlg( 0L); dlg.fillDialog( KXMLEditorFactory::configuration()->newfile()->dfltVersion(), KXMLEditorFactory::configuration()->newfile()->dfltEncoding() ); if( dlg.exec() ) { // if the dialog has been accepted (OK pressed) setSpecProcInstr( "xml", dlg.getData() ); // if the dialog shouldn't be shown anymore, the settings have to be changed if ( dlg.m_pDontShowAgain->isChecked() ) KXMLEditorFactory::configuration()->newfile()->setNewFileCreaBehav( KXENewFileSettings::UseDefaults, instance()->config() ); } break; } case KXENewFileSettings::UseDefaults: setSpecProcInstr( "xml", QString( "version='%1' encoding='%2'" ).arg(KXMLEditorFactory::configuration()->newfile()->dfltVersion()).arg(KXMLEditorFactory::configuration()->newfile()->dfltEncoding()) ); break; } emit sigOpened(); setModified(); } //------------- SLOTS, called from Part -------------------------------- KCommand * KXEDocument::actDetachStylesheet() { QDomNode domNode = getSpecProcInstr("xml-stylesheet"); if (!domNode.isNull()) { KCommand *pCmd = new KXEStylesheetDetachCommand(this,domNode.toProcessingInstruction().data()); return pCmd; } return 0L; } KCommand * KXEDocument::actAttachStylesheet() { KXEAttachDialogBase dlg; dlg.Label->setText(i18n("Stylesheet URL:")); if (dlg.exec()) { QDomNode domNode = getSpecProcInstr("xml-stylesheet"); QString data = ""; if (!domNode.isNull()) data = domNode.toProcessingInstruction().data(); KCommand *pCmd = new KXEStylesheetAttachCommand(this,data,dlg.attachURI->url()); return pCmd; } return 0L; } KCommand * KXEDocument::actDetachSchema() { if (!documentElement().isNull()) // just for sure... { KCommand *pCmd = new KXESchemaDetachCommand(this, documentElement().attributeNS(SCHEMA_NAMESPACE, SCHEMA_ATTRIBUTE,"") ); return pCmd; } return 0L; } KCommand * KXEDocument::actAttachSchema() { KXEAttachDialogBase dlg; dlg.Label->setText(i18n("Schema URL:")); if (dlg.exec()) { if (!documentElement().isNull()) // just for sure... { KCommand *pCmd = new KXESchemaAttachCommand(this,dlg.attachURI->url(), documentElement().attributeNS(SCHEMA_NAMESPACE,SCHEMA_ATTRIBUTE,"")); return pCmd; } } return 0L; } // Instert or edit special processing instruction KCommand * KXEDocument::actVersionEncoding() { QDomNode node = getSpecProcInstr("xml"); KXESpecProcInstrDialog dlg; if(!node.isNull()) dlg.fillDialog(node.toProcessingInstruction().data()); else dlg.fillDialog( KXMLEditorFactory::configuration()->newfile()->dfltVersion(), KXMLEditorFactory::configuration()->newfile()->dfltEncoding() ); if(dlg.exec()) { QString strOldData = ""; if (!node.isNull()) strOldData = node.toProcessingInstruction().data(); KCommand *pCmd = new KXEVersionEncodingCommand(this,strOldData,dlg.getData()); return pCmd; } return 0L; }