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.
634 lines
17 KiB
634 lines
17 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 <tdefile.h>
|
|
#include <ktar.h>
|
|
#include <kzip.h>
|
|
#include <kfilterdev.h>
|
|
#include <tdetempfile.h>
|
|
#include <kdebug.h>
|
|
#include <tdemessagebox.h>
|
|
#include <tdelocale.h>
|
|
#include <kcommand.h>
|
|
#include <tdeaction.h>
|
|
#include <kurl.h>
|
|
#include <kurlrequester.h>
|
|
|
|
#include <tqcombobox.h>
|
|
#include <tqbuffer.h>
|
|
#include <tqregexp.h>
|
|
#include <tqtextcodec.h>
|
|
#include <tqlabel.h>
|
|
#include <tqcheckbox.h>
|
|
|
|
KXEDocument::KXEDocument(TQObject *parent, const char *name)
|
|
:TQObject (parent,name),
|
|
TQDomDocument(),
|
|
KXMLGUIClient()
|
|
{
|
|
m_bDocIsCompressed = false;
|
|
m_bIsModified = false;
|
|
m_strCompressedTarEntryName = "";
|
|
m_url = "";
|
|
|
|
//setXMLFile("kxedocument.rc");
|
|
}
|
|
|
|
|
|
KXEDocument::~KXEDocument()
|
|
{
|
|
}
|
|
|
|
|
|
bool KXEDocument::save(const TQString &strFileName)
|
|
{
|
|
if (this->documentElement().isNull() &&
|
|
KMessageBox::warningContinueCancel(0,
|
|
i18n("Your file doesn't have root element defined. \n\
|
|
Continue saving?"))==KMessageBox::Cancel )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TQString strXML;
|
|
|
|
TQTextStream streamXML(&strXML, IO_WriteOnly);
|
|
int iIndent = KXMLEditorFactory::configuration()->textview()->indentSteps();
|
|
((TQDomDocument*)this)->save(streamXML, iIndent);
|
|
|
|
TQString strEncoding;
|
|
TQTextCodec *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(TQRegExp("[\"']"), iStart)) > 0)
|
|
{
|
|
TQChar ch = strXML[iStart];
|
|
iStart++; // skip ch
|
|
if((iEnd = strXML.find(ch, iStart)) > 0)
|
|
{
|
|
strEncoding = strXML.mid(iStart, iEnd - iStart);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(strEncoding.length() <= 0)
|
|
pTextCodec = TQTextCodec::codecForLocale(); // default
|
|
else
|
|
pTextCodec = TQTextCodec::codecForName(strEncoding);
|
|
|
|
if(pTextCodec == 0)
|
|
{ if(KMessageBox::questionYesNo(0, i18n("Codec for encoding %1 not found ! Continue saving ?").arg(strEncoding)) != KMessageBox::Yes)
|
|
return false;
|
|
}
|
|
|
|
TQCString strDecoded;
|
|
if(pTextCodec)
|
|
{ strDecoded = pTextCodec->fromUnicode(strXML);
|
|
}
|
|
|
|
// save string to file
|
|
if(!m_bDocIsCompressed)
|
|
{ TQFile 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 -----------------------------------------
|
|
TQString 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 TQString &strFileName)
|
|
{
|
|
TQString strCompressedTarEntryName;
|
|
|
|
kdDebug() << "KXEDocument::open: opening file " << strFileName << endl;
|
|
|
|
// obtain file extension -----------------------------------------
|
|
TQString strExtension;
|
|
|
|
int iPos = strFileName.findRev('.');
|
|
|
|
if(iPos > 0)
|
|
{ strExtension = strFileName.mid(iPos + 1);
|
|
}
|
|
|
|
TQString 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);
|
|
TQFile &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);
|
|
}
|
|
|
|
TQIODevice *pIODevice = KFilterDev::deviceForFile(strFileName, "application/x-gzip");
|
|
|
|
if(pIODevice->open( IO_ReadOnly ))
|
|
{
|
|
TQTextStream stream(pIODevice);
|
|
TQString 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);
|
|
|
|
TQBuffer 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
|
|
TQFile 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
|
|
TQFile fileAux(m_bDocIsCompressed ? strTmpfileName : strFileName);
|
|
if(! fileAux.open(IO_ReadOnly))
|
|
{
|
|
kdDebug() << "KXEDocument::openFile: Can't open file." << endl;
|
|
return false;
|
|
}
|
|
|
|
TQTextStream txtStreamLocal( & file );
|
|
|
|
// Lookup at XML document encoding -----------------------------------------------
|
|
TQTextStream txtStreamAux( & fileAux );
|
|
TQString strFirstLine = txtStreamAux.readLine();
|
|
fileAux.close();
|
|
int iStart, iEnd;
|
|
if((iStart = strFirstLine.find("encoding", 0)) > 0)
|
|
{
|
|
TQString strEncoding;
|
|
// info about encoding found;
|
|
iStart += 8; // skip encoding
|
|
|
|
// search " or ' after encoding
|
|
if((iStart = strFirstLine.find(TQRegExp("[\"']"), iStart)) > 0)
|
|
{
|
|
TQChar ch = strFirstLine[iStart];
|
|
iStart++; // skip ch
|
|
if((iEnd = strFirstLine.find(ch, iStart)) > 0)
|
|
{
|
|
strEncoding = strFirstLine.mid(iStart, iEnd - iStart);
|
|
|
|
TQTextCodec *pTextCodec = TQTextCodec::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(TQTextStream::Locale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// XML documment dont have info about encoding, set default UTF-8
|
|
txtStreamLocal.setCodec(TQTextCodec::codecForName("UTF-8"));
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------
|
|
TQString strFileContents = txtStreamLocal.read();
|
|
file.close();
|
|
|
|
if(m_bDocIsCompressed)
|
|
{
|
|
TQDir dir;
|
|
dir.remove(strTmpfileName);
|
|
}
|
|
|
|
//-- Set string with XML to TQDomDocument ------------------------------------------
|
|
TQString strErrorMsg;
|
|
int iErrorLine, iErrorColumn;
|
|
TQDomDocument * pNewDoc = new TQDomDocument; // 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
|
|
TQDomNode e = pNewDoc->removeChild( pNewDoc->documentElement() );
|
|
TQDomDocument::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 TQDomNode & node)
|
|
{
|
|
emit sigNodeCreated(node);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::updateNodeDeleted(const TQDomNode & node)
|
|
{
|
|
emit sigNodeDeleted(node);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::updateNodeChanged( const TQDomElement & domElement )
|
|
{
|
|
emit sigNodeChanged(domElement);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::updateNodeChanged( const TQDomCharacterData & node )
|
|
{
|
|
emit sigNodeChanged(node);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::updateNodeChanged( const TQDomProcessingInstruction &domProcInstr )
|
|
{
|
|
emit sigNodeChanged(domProcInstr);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::updateNodeMoved( const TQDomNode & node )
|
|
{
|
|
emit sigNodeMoved(node);
|
|
setModified();
|
|
}
|
|
|
|
void KXEDocument::attachStylesheet(const KURL& stylesheet)
|
|
{
|
|
setSpecProcInstr("xml-stylesheet",TQString("type = 'text/xsl' href = '")+stylesheet.url()+"' ");
|
|
}
|
|
|
|
void KXEDocument::detachStylesheet()
|
|
{
|
|
removeSpecProcInstr("xml-stylesheet");
|
|
}
|
|
|
|
void KXEDocument::attachSchema(const KURL& schema)
|
|
{
|
|
TQDomElement domElement = documentElement();
|
|
if (!domElement.isNull())
|
|
{
|
|
domElement.setAttributeNS(SCHEMA_NAMESPACE,
|
|
SCHEMA_ATTRIBUTE_XSI,
|
|
schema.url());
|
|
// refresh views
|
|
updateNodeChanged(domElement);
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void KXEDocument::detachSchema()
|
|
{
|
|
TQDomElement domElement = this->documentElement();
|
|
if (!domElement.isNull())
|
|
{
|
|
domElement.removeAttributeNS(SCHEMA_NAMESPACE,SCHEMA_ATTRIBUTE);
|
|
// refresh views
|
|
updateNodeChanged(domElement);
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
void KXEDocument::setSpecProcInstr(const TQString& target, const TQString& data)
|
|
{
|
|
// removing old one
|
|
removeSpecProcInstr(target);
|
|
// create new one
|
|
if (!data.isEmpty())
|
|
{
|
|
TQDomProcessingInstruction domProcInstr = this->createProcessingInstruction(target,data);
|
|
|
|
TQDomNode 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 TQString &target)
|
|
{
|
|
TQDomNode domNode = getSpecProcInstr(target);
|
|
if (!domNode.isNull())
|
|
{
|
|
updateNodeDeleted(domNode);
|
|
((TQDomDocument*)this)->removeChild(domNode);
|
|
setModified();
|
|
}
|
|
}
|
|
|
|
TQDomNode KXEDocument::getSpecProcInstr(const TQString& target)
|
|
{
|
|
TQDomNode result;
|
|
TQDomNodeList domNodeList = this->childNodes();
|
|
for (uint i=0;i<domNodeList.count();i++)
|
|
if (domNodeList.item(i).isProcessingInstruction())
|
|
{
|
|
TQDomProcessingInstruction 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", TQString( "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()
|
|
{
|
|
TQDomNode 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())
|
|
{
|
|
TQDomNode domNode = getSpecProcInstr("xml-stylesheet");
|
|
TQString 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()
|
|
{
|
|
TQDomNode 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())
|
|
{
|
|
TQString strOldData = "";
|
|
if (!node.isNull())
|
|
strOldData = node.toProcessingInstruction().data();
|
|
KCommand *pCmd = new KXEVersionEncodingCommand(this,strOldData,dlg.getData());
|
|
return pCmd;
|
|
}
|
|
return 0L;
|
|
}
|
|
|
|
#include "kxedocument.moc"
|