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.
tdeaccessibility/kttsd/filters/xmltransformer/xmltransformerproc.cpp

386 lines
14 KiB

/***************************************************** vim:set ts=4 sw=4 sts=4:
Generic XML Transformation Filter Processing class.
-------------------
Copyright:
(C) 2005 by Gary Cramblitt <garycramblitt@comcast.net>
-------------------
Original author: Gary Cramblitt <garycramblitt@comcast.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
******************************************************************************/
// TQt includes.
#include <tqfile.h>
#include <tqregexp.h>
// KDE includes.
#include <tdeversion.h>
#include <tdeconfig.h>
#include <tdetempfile.h>
#include <kstandarddirs.h>
#include <kprocess.h>
#include <kdebug.h>
// KTTS includes.
#include "filterproc.h"
#include "utils.h"
// XmlTransformer includes.
#include "xmltransformerproc.h"
#include "xmltransformerproc.moc"
/**
* Constructor.
*/
XmlTransformerProc::XmlTransformerProc( TQObject *parent, const char *name, const TQStringList& ) :
KttsFilterProc(parent, name)
{
m_xsltProc = 0;
}
/**
* Destructor.
*/
/*virtual*/ XmlTransformerProc::~XmlTransformerProc()
{
delete m_xsltProc;
if (!m_inFilename.isEmpty()) TQFile::remove(m_inFilename);
if (!m_outFilename.isEmpty()) TQFile::remove(m_outFilename);
}
/**
* Initialize the filter.
* @param config Settings object.
* @param configGroup Settings Group.
* @return False if filter is not ready to filter.
*
* Note: The parameters are for reading from kttsdrc file. Plugins may wish to maintain
* separate configuration files of their own.
*/
bool XmlTransformerProc::init(TDEConfig* config, const TQString& configGroup)
{
// kdDebug() << "XmlTransformerProc::init: Running." << endl;
config->setGroup( configGroup );
m_UserFilterName = config->readEntry( "UserFilterName" );
m_xsltFilePath = config->readEntry( "XsltFilePath" );
m_xsltprocPath = config->readEntry( "XsltprocPath" );
m_rootElementList = config->readListEntry( "RootElement", ',' );
m_doctypeList = config->readListEntry( "DocType", ',' );
m_appIdList = config->readListEntry( "AppID", ',' );
kdDebug() << "XmlTransformerProc::init: m_xsltprocPath = " << m_xsltprocPath << endl;
kdDebug() << "XmlTransformerProc::init: m_xsltFilePath = " << m_xsltFilePath << endl;
return ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() );
}
/**
* Returns True if the plugin supports asynchronous processing,
* i.e., supports asyncConvert method.
* @return True if this plugin supports asynchronous processing.
*
* If the plugin returns True, it must also implement @ref getState .
* It must also emit @ref filteringFinished when filtering is completed.
* If the plugin returns True, it must also implement @ref stopFiltering .
* It must also emit @ref filteringStopped when filtering has been stopped.
*/
/*virtual*/ bool XmlTransformerProc::supportsAsync() { return true; }
/**
* Convert input, returning output.
* @param inputText Input text.
* @param talkerCode TalkerCode structure for the talker that KTTSD intends to
* use for synthing the text. Useful for extracting hints about
* how to filter the text. For example, languageCode.
* @param appId The DCOP appId of the application that queued the text.
* Also useful for hints about how to do the filtering.
*/
/*virtual*/ TQString XmlTransformerProc::convert(const TQString& inputText, TalkerCode* talkerCode,
const TQCString& appId)
{
// kdDebug() << "XmlTransformerProc::convert: Running." << endl;
// If not properly configured, do nothing.
if ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() )
{
kdDebug() << "XmlTransformerProc::convert: not properly configured" << endl;
return inputText;
}
// Asynchronously convert and wait for completion.
if (asyncConvert(inputText, talkerCode, appId))
{
waitForFinished();
m_state = fsIdle;
return m_text;
} else
return inputText;
}
/**
* Convert input. Runs asynchronously.
* @param inputText Input text.
* @param talkerCode TalkerCode structure for the talker that KTTSD intends to
* use for synthing the text. Useful for extracting hints about
* how to filter the text. For example, languageCode.
* @param appId The DCOP appId of the application that queued the text.
* Also useful for hints about how to do the filtering.
* @return False if the filter cannot perform the conversion.
*
* When conversion is completed, emits signal @ref filteringFinished. Calling
* program may then call @ref getOutput to retrieve converted text. Calling
* program must call @ref ackFinished to acknowledge the conversion.
*/
/*virtual*/ bool XmlTransformerProc::asyncConvert(const TQString& inputText, TalkerCode* /*talkerCode*/,
const TQCString& appId)
{
m_wasModified = false;
// kdDebug() << "XmlTransformerProc::asyncConvert: Running." << endl;
m_text = inputText;
// If not properly configured, do nothing.
if ( m_xsltFilePath.isEmpty() || m_xsltprocPath.isEmpty() )
{
kdDebug() << "XmlTransformerProc::asyncConvert: not properly configured." << endl;
return false;
}
bool found = false;
// If not correct XML type, or DOCTYPE, do nothing.
if ( !m_rootElementList.isEmpty() )
{
// kdDebug() << "XmlTransformerProc::asyncConvert:: searching for root elements " << m_rootElementList << endl;
for ( uint ndx=0; ndx < m_rootElementList.count(); ++ndx )
{
if ( KttsUtils::hasRootElement( inputText, m_rootElementList[ndx] ) )
{
found = true;
break;
}
}
if ( !found && m_doctypeList.isEmpty() )
{
kdDebug() << "XmlTransformerProc::asyncConvert: Did not find root element(s)" << m_rootElementList << endl;
return false;
}
}
if ( !found && !m_doctypeList.isEmpty() )
{
for ( uint ndx=0; ndx < m_doctypeList.count(); ++ndx )
{
if ( KttsUtils::hasDoctype( inputText, m_doctypeList[ndx] ) )
{
found = true;
break;
}
}
if ( !found )
{
// kdDebug() << "XmlTransformerProc::asyncConvert: Did not find doctype(s)" << m_doctypeList << endl;
return false;
}
}
// If appId doesn't match, return input unmolested.
if ( !m_appIdList.isEmpty() )
{
TQString appIdStr = appId;
// kdDebug() << "XmlTransformrProc::convert: converting " << inputText << " if appId "
// << appId << " matches " << m_appIdList << endl;
found = false;
for ( uint ndx=0; ndx < m_appIdList.count(); ++ndx )
{
if ( appIdStr.contains(m_appIdList[ndx]) )
{
found = true;
break;
}
}
if ( !found )
{
// kdDebug() << "XmlTransformerProc::asyncConvert: Did not find appId(s)" << m_appIdList << endl;
return false;
}
}
/// Write @param text to a temporary file.
KTempFile inFile(locateLocal("tmp", "kttsd-"), ".xml");
m_inFilename = inFile.file()->name();
TQTextStream* wstream = inFile.textStream();
if (wstream == 0) {
/// wtf...
kdDebug() << "XmlTransformerProc::convert: Can't write to " << m_inFilename << endl;;
return false;
}
// TODO: Is encoding an issue here?
// If input does not have xml processing instruction, add it.
if (!inputText.startsWith("<?xml")) *wstream << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
// FIXME: Temporary Fix until Konqi returns properly formatted xhtml with & coded as &amp;
// This will change & inside a CDATA section, which is not good, and also within comments and
// processing instructions, which is OK because we don't speak those anyway.
TQString text = inputText;
text.replace(TQRegExp("&(?!amp;)"),"&amp;");
*wstream << text;
inFile.close();
#if TDE_VERSION >= TDE_MAKE_VERSION (3,3,0)
inFile.sync();
#endif
// Get a temporary output file name.
KTempFile outFile(locateLocal("tmp", "kttsd-"), ".output");
m_outFilename = outFile.file()->name();
outFile.close();
// outFile.unlink(); // only activate this if necessary.
/// Spawn an xsltproc process to apply our stylesheet to input file.
m_xsltProc = new TDEProcess;
*m_xsltProc << m_xsltprocPath;
*m_xsltProc << "-o" << m_outFilename << "--novalid"
<< m_xsltFilePath << m_inFilename;
// Warning: This won't compile under KDE 3.2. See FreeTTS::argsToStringList().
// kdDebug() << "SSMLConvert::transform: executing command: " <<
// m_xsltProc->args() << endl;
m_state = fsFiltering;
connect(m_xsltProc, TQT_SIGNAL(processExited(TDEProcess*)),
this, TQT_SLOT(slotProcessExited(TDEProcess*)));
connect(m_xsltProc, TQT_SIGNAL(receivedStdout(TDEProcess*, char*, int)),
this, TQT_SLOT(slotReceivedStdout(TDEProcess*, char*, int)));
connect(m_xsltProc, TQT_SIGNAL(receivedStderr(TDEProcess*, char*, int)),
this, TQT_SLOT(slotReceivedStderr(TDEProcess*, char*, int)));
if (!m_xsltProc->start(TDEProcess::NotifyOnExit,
static_cast<TDEProcess::Communication>(TDEProcess::Stdout | TDEProcess::Stderr)))
{
kdDebug() << "XmlTransformerProc::convert: Error starting xsltproc" << endl;
m_state = fsIdle;
return false;
}
return true;
}
// Process output when xsltproc exits.
void XmlTransformerProc::processOutput()
{
TQFile::remove(m_inFilename);
int exitStatus = 11;
if (m_xsltProc->normalExit())
exitStatus = m_xsltProc->exitStatus();
else
kdDebug() << "XmlTransformerProc::processOutput: xsltproc was killed." << endl;
delete m_xsltProc;
m_xsltProc = 0;
if (exitStatus != 0)
{
kdDebug() << "XmlTransformerProc::processOutput: xsltproc abnormal exit. Status = " << exitStatus << endl;
m_state = fsFinished;
TQFile::remove(m_outFilename);
emit filteringFinished();
return;
}
/// Read back the data that was written to /tmp/fileName.output.
TQFile readfile(m_outFilename);
if(!readfile.open(IO_ReadOnly)) {
/// uhh yeah... Issues writing to the output file.
kdDebug() << "XmlTransformerProc::processOutput: Could not read file " << m_outFilename << endl;
m_state = fsFinished;
emit filteringFinished();
}
TQTextStream rstream(&readfile);
m_text = rstream.read();
readfile.close();
kdDebug() << "XmlTransformerProc::processOutput: Read file at " + m_inFilename + " and created " + m_outFilename + " based on the stylesheet at " << m_xsltFilePath << endl;
// Clean up.
TQFile::remove(m_outFilename);
m_state = fsFinished;
m_wasModified = true;
emit filteringFinished();
}
/**
* Waits for a previous call to asyncConvert to finish.
*/
/*virtual*/ void XmlTransformerProc::waitForFinished()
{
if (m_xsltProc)
{
if (m_xsltProc->isRunning())
{
if ( !m_xsltProc->wait( 15 ) )
{
m_xsltProc->kill();
kdDebug() << "XmlTransformerProc::waitForFinished: After waiting 15 seconds, xsltproc process seems to hung. Killing it." << endl;
processOutput();
}
}
}
}
/**
* Returns the state of the Filter.
*/
/*virtual*/ int XmlTransformerProc::getState() { return m_state; }
/**
* Returns the filtered output.
*/
/*virtual*/ TQString XmlTransformerProc::getOutput() { return m_text; }
/**
* Acknowledges the finished filtering.
*/
/*virtual*/ void XmlTransformerProc::ackFinished()
{
m_state = fsIdle;
m_text = TQString();
}
/**
* Stops filtering. The filteringStopped signal will emit when filtering
* has in fact stopped and state returns to fsIdle;
*/
/*virtual*/ void XmlTransformerProc::stopFiltering()
{
m_state = fsStopping;
m_xsltProc->kill();
}
/**
* Did this filter do anything? If the filter returns the input as output
* unmolested, it should return False when this method is called.
*/
/*virtual*/ bool XmlTransformerProc::wasModified() { return m_wasModified; }
void XmlTransformerProc::slotProcessExited(TDEProcess*)
{
// kdDebug() << "XmlTransformerProc::slotProcessExited: xsltproc has exited." << endl;
processOutput();
}
void XmlTransformerProc::slotReceivedStdout(TDEProcess*, char* /*buffer*/, int /*buflen*/)
{
// TQString buf = TQString::fromLatin1(buffer, buflen);
// kdDebug() << "XmlTransformerProc::slotReceivedStdout: Received from xsltproc: " << buf << endl;
}
void XmlTransformerProc::slotReceivedStderr(TDEProcess*, char* buffer, int buflen)
{
TQString buf = TQString::fromLatin1(buffer, buflen);
kdDebug() << "XmlTransformerProc::slotReceivedStderr: Received error from xsltproc: " << buf << endl;
}