You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kxmleditor/part/kxedocument.cpp

632 lines
16 KiB

//
// C++ Implementation: kxedocument
//
// Description:
//
//
// Author: Adam Charytoniuk <achary@poczta.onet.pl>, (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 <kfile.h>
#include <ktar.h>
#include <kzip.h>
#include <kfilterdev.h>
#include <ktempfile.h>
#include <kdebug.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <kcommand.h>
#include <kaction.h>
#include <kurl.h>
#include <kurlrequester.h>
#include <qcombobox.h>
#include <qbuffer.h>
#include <qregexp.h>
#include <qtextcodec.h>
#include <qlabel.h>
#include <qcheckbox.h>
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) == "<?xml")
{ int iStart, iEnd;
if((iStart = strXML.find("encoding", 0)) > 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 <const KArchiveFile *> (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;i<domNodeList.count();i++)
if (domNodeList.item(i).isProcessingInstruction())
{
QDomProcessingInstruction domProcInstr = domNodeList.item(i).toProcessingInstruction();
if (domProcInstr.target()==target)
return domNodeList.item(i);
}
return result;
}
void KXEDocument::newFile()
{
switch ( KXMLEditorFactory::configuration()->newfile()->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 <?xml ... ?>
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;
}