/* This file is part of the KDE project Copyright (C) 2002 Norbert Andres Copyright (C) 2004 - 2005 Laurent Montel This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include "opencalcimport.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SECSPERDAY (24 * 60 * 60) using namespace KSpread; class OpenCalcImportFactory : KGenericFactory { public: OpenCalcImportFactory(void) : KGenericFactory ("kspreadopencalcimport") {} protected: virtual void setupTranslations( void ) { TDEGlobal::locale()->insertCatalogue( "kofficefilters" ); } }; K_EXPORT_COMPONENT_FACTORY( libopencalcimport, OpenCalcImportFactory() ) OpenCalcImport::OpenCalcPoint::OpenCalcPoint( TQString const & str ) : isRange( false ) { bool inQuote = false; int l = str.length(); int colonPos = -1; TQString range; // replace '.' with '!' for ( int i = 0; i < l; ++i ) { if ( str[i] == '$' ) continue; if ( str[i] == '\'' ) { inQuote = !inQuote; } else if ( str[i] == '.' ) { if ( !inQuote ) { if ( i != 0 && i != (colonPos + 1) ) // no empty table names range += '!'; } else range += '.'; } else if ( str[i] == ':' ) { if ( !inQuote ) { isRange = true; colonPos = i; } range += ':'; } else range += str[i]; } translation = range; if ( isRange ) { KSpread::Range newRange( range ); table = newRange.sheetName(); topLeft = newRange.range().topLeft(); botRight = newRange.range().bottomRight(); } else { Point newPoint( range ); table = newPoint.sheetName(); topLeft = newPoint.pos(); botRight = newPoint.pos(); } } OpenCalcImport::OpenCalcImport( KoFilter *, const char *, const TQStringList & ) : KoFilter(), m_styles( 17, true ), m_defaultStyles( 17, true ), m_formats( 17, true ) { m_styles.setAutoDelete( true ); m_defaultStyles.setAutoDelete( true ); m_formats.setAutoDelete( true ); } OpenCalcImport::~OpenCalcImport() { } double timeToNum( int h, int m, int s ) { int secs = h * 3600 + m * 60 + s; return (double) secs / (double) SECSPERDAY; } bool OpenCalcImport::readRowFormat( TQDomElement & rowNode, TQDomElement * rowStyle, Sheet * table, int & row, int & number, bool isLast ) { if ( rowNode.isNull() ) return false; TQDomNode node; if ( rowStyle ) { node = rowStyle->firstChild(); kdDebug(30518) << "RowStyle: " << rowStyle << ", " << rowStyle->tagName() << endl; } double height = -1.0; bool insertPageBreak = false; Format layout( table, table->doc()->styleManager()->defaultStyle() ); while( !node.isNull() ) { TQDomElement property = node.toElement(); kdDebug(30518) << "Row: Child exists: " << property.tagName() << endl; if ( !property.isNull() && property.localName() == "properties" && property.namespaceURI() == ooNS::style ) { if ( property.hasAttributeNS( ooNS::style, "row-height" ) ) { height = KoUnit::parseValue( property.attributeNS( ooNS::style, "row-height", TQString() ) , -1 ); } if ( property.hasAttributeNS( ooNS::fo, "break-before" ) ) { if ( property.attributeNS( ooNS::fo, "break-before", TQString() ) == "page" ) { insertPageBreak = true; } } loadStyleProperties( &layout, property ); } node = node.nextSibling(); } if ( rowNode.hasAttributeNS( ooNS::table, "number-rows-repeated" ) ) { bool ok = true; int n = rowNode.attributeNS( ooNS::table, "number-rows-repeated", TQString() ).toInt( &ok ); if ( ok ) number = n; kdDebug(30518) << "Row repeated: " << number << endl; } if ( isLast ) { if ( number > 30 ) number = 30; } else { if ( number > 256 ) number = 256; } for ( int i = 0; i < number; ++i ) { RowFormat * rowL = table->nonDefaultRowFormat( row ); rowL->copy( layout ); if ( height != -1 ) { kdDebug(30518) << "Setting row height to " << height << endl; rowL->setHeight( int( height ) ); } // if ( insertPageBreak ) TODO: // rowL->setPageBreak( true ) // kdDebug(30518) << "Added RowFormat: " << row << endl; ++row; } return true; } TQString OpenCalcImport::translatePar( TQString & par ) const { OpenCalcPoint point( par ); kdDebug(30518) << " Parameter: " << par << ", Translation: " << point.translation << endl; return point.translation; } void OpenCalcImport::checkForNamedAreas( TQString & formula ) const { int l = formula.length(); int i = 0; TQString word; int start = 0; while ( i < l ) { if ( formula[i].isLetterOrNumber() ) { word += formula[i]; ++i; continue; } if ( word.length() > 0 ) { if ( m_namedAreas.find( word ) != m_namedAreas.end() ) { formula = formula.replace( start, word.length(), "'" + word + "'" ); l = formula.length(); ++i; kdDebug(30518) << "Formula: " << formula << ", L: " << l << ", i: " << i + 1 < 0 ) { if ( m_namedAreas.find( word ) != m_namedAreas.end() ) { formula = formula.replace( start, word.length(), "'" + word + "'" ); l = formula.length(); ++i; kdDebug(30518) << "Formula: " << formula << ", L: " << l << ", i: " << i + 1 <defaultCell(); TQDomNode cellNode = KoDom::namedItemNS( rowNode, ooNS::table, "table-cell" ); while ( !cellNode.isNull() ) { spanR = 1; spanC = 1; TQDomElement e = cellNode.toElement(); if ( e.isNull() ) { ++columns; cellNode = cellNode.nextSibling(); continue; } Cell* cell = 0; kdDebug(30518) << " Cell: " << columns << ", " << row << endl; // ="3" table:number-rows-spanned="1" if ( e.hasAttributeNS( ooNS::table, "number-columns-spanned" ) ) { int span = e.attributeNS( ooNS::table, "number-columns-spanned", TQString() ).toInt( &ok ); if ( ok ) spanC = span; } if ( e.hasAttributeNS( ooNS::table, "number-rows-spanned" ) ) { int span = e.attributeNS( ooNS::table, "number-rows-spanned", TQString() ).toInt( &ok ); if ( ok ) spanR = span; } TQString text; TQDomElement textP = KoDom::namedItemNS( e, ooNS::text, "p" ); if ( !textP.isNull() ) { TQDomElement subText = textP.firstChild().toElement(); // ## wrong if ( !subText.isNull() ) { // something in , e.g. links text = subText.text(); if ( subText.hasAttributeNS( ooNS::xlink, "href" ) ) { TQString link = subText.attributeNS( ooNS::xlink, "href", TQString() ); if ( link[0]=='#' ) link=link.remove( 0, 1 ); if ( !cell ) cell = table->nonDefaultCell( columns, row ); cell->setLink( link ); } } else text = textP.text(); // our text, could contain formating for value or result of formula } TQDomElement annotation = KoDom::namedItemNS( e, ooNS::office, "annotation" ); if ( !annotation.isNull() ) { TQString comment; TQDomNode node = annotation.firstChild(); while( !node.isNull() ) { TQDomElement commentElement = node.toElement(); if( !commentElement.isNull() ) if ( commentElement.localName() == "p" && e.namespaceURI()==ooNS::text) { if( !comment.isEmpty() ) comment.append( '\n' ); comment.append( commentElement.text() ); } node = node.nextSibling(); } if( !comment.isEmpty() ) { if ( !cell ) cell = table->nonDefaultCell( columns, row ); kdDebug(30518)<<" columns :"<format()->setComment( comment ); } } kdDebug(30518) << "Contains: " << text << endl; bool isFormula = false; if ( e.hasAttributeNS( ooNS::table, "style-name" ) ) { if ( !cell ) cell = table->nonDefaultCell( columns, row ); TQString psName( "Default" ); if ( e.hasAttributeNS( ooNS::style, "parent-style-name" ) ) psName = e.attributeNS( ooNS::style, "parent-style-name", TQString() ); kdDebug(30518) << "Default style: " << psName << endl; Format * layout = m_defaultStyles[psName]; if ( layout ) cell->format()->copy( *layout ); TQDomElement * st = 0; if ( e.hasAttributeNS( ooNS::table, "style-name" ) ) { kdDebug(30518) << "Style: " << e.attributeNS( ooNS::table, "style-name", TQString() ) << endl; st = m_styles[ e.attributeNS( ooNS::table, "style-name", TQString() ) ]; } if ( st ) { kdDebug(30518) << "Style: adapting " << endl; TQDomNode node = st->firstChild(); bool foundValidation = false; while( !node.isNull() ) { TQDomElement property = node.toElement(); if ( !property.isNull() ) { kdDebug(30518)<<"property.tagName() :"<format(), property ); if ( cell->format()->getAngle( columns, row ) != 0 ) { TQFontMetrics fm( cell->format()->textFont( columns, row ) ); int tmpAngle = cell->format()->getAngle( columns, row ); int textHeight = static_cast( cos( tmpAngle * M_PI / 180 ) * ( fm.ascent() + fm.descent() ) + abs ( ( int )( fm.width( cell->strOutText() ) * sin( tmpAngle * M_PI / 180 ) ) ) ); /* int textWidth = static_cast( abs ( ( int ) ( sin( tmpAngle * M_PI / 180 ) * ( fm.ascent() + fm.descent() ) ) ) + fm.width( cell->strOutText() ) * cos( tmpAngle * M_PI / 180 ) ); */ kdDebug(30518) << "Rotation: height: " << textHeight << endl; RowFormat * l = table->rowFormat( row ); if ( l->height() < textHeight ) { if ( l->isDefault() ) l = table->nonDefaultRowFormat( row ); l->setHeight( textHeight + 2 ); } } } } node = node.nextSibling(); } } } else { if ( !cell ) cell = table->nonDefaultCell( columns, row ); TQString psName( "Default" ); kdDebug(30518) << "Default style: " << psName << endl; Format * layout = m_defaultStyles[psName]; if ( layout ) cell->format()->copy( *layout ); } if ( e.hasAttributeNS( ooNS::table, "formula" ) ) { isFormula = true; TQString formula; convertFormula( formula, e.attributeNS( ooNS::table, "formula", TQString() ) ); if ( !cell ) cell = table->nonDefaultCell( columns, row ); cell->setCellText( formula ); } if ( e.hasAttributeNS( ooNS::table, "validation-name" ) ) { kdDebug(30518)<<" Celle has a validation :"<getValidity(), e.attributeNS( ooNS::table, "validation-name", TQString() ) ); } if ( e.hasAttributeNS( ooNS::table, "value-type" ) ) { if ( !cell ) cell = table->nonDefaultCell( columns, row ); cell->setCellText( text ); TQString value = e.attributeNS( ooNS::table, "value", TQString() ); TQString type = e.attributeNS( ooNS::table, "value-type", TQString() ); kdDebug(30518) << "Value: " << value << ", type: " << type << endl; bool ok = false; double dv = 0.0; if ( ( type == "float" ) || ( type == "currency" ) ) { dv = value.toDouble( &ok ); if ( ok ) { if ( !isFormula ) cell->setValue( dv ); if ( type == "currency" ) { cell->format()->setCurrency( 1, e.attributeNS( ooNS::table, "currency", TQString() ) ); cell->format()->setFormatType( Money_format ); } } } else if ( type == "percentage" ) { dv = value.toDouble( &ok ); if ( ok ) { if ( !isFormula ) cell->setValue( dv ); //TODO fixme //cell->setFactor( 100 ); // TODO: replace with custom... cell->format()->setFormatType( Percentage_format ); } } else if ( type == "boolean" ) { if ( value.isEmpty() ) value = e.attributeNS( ooNS::table, "boolean-value", TQString() ); kdDebug(30518) << "Type: boolean" << endl; if ( value == "true" ) cell->setValue( true ); else cell->setValue( false ); ok = true; cell->format()->setFormatType( Custom_format ); } else if ( type == "date" ) { if ( value.isEmpty() ) value = e.attributeNS( ooNS::table, "date-value", TQString() ); kdDebug(30518) << "Type: date, value: " << value << endl; // "1980-10-15" int year=0, month=0, day=0; ok = false; int p1 = value.find( '-' ); if ( p1 > 0 ) year = value.left( p1 ).toInt( &ok ); kdDebug(30518) << "year: " << value.left( p1 ) << endl; int p2 = value.find( '-', ++p1 ); if ( ok ) month = value.mid( p1, p2 - p1 ).toInt( &ok ); kdDebug(30518) << "month: " << value.mid( p1, p2 - p1 ) << endl; if ( ok ) day = value.right( value.length() - p2 - 1 ).toInt( &ok ); kdDebug(30518) << "day: " << value.right( value.length() - p2 ) << endl; if ( ok ) { TQDateTime dt( TQDate( year, month, day ) ); // KSpreadValue kval( dt ); // cell->setValue( kval ); cell->setValue( TQDate( year, month, day ) ); kdDebug(30518) << "Set TQDate: " << year << " - " << month << " - " << day << endl; } } else if ( type == "time" ) { if ( value.isEmpty() ) value = e.attributeNS( ooNS::table, "time-value", TQString() ); kdDebug(30518) << "Type: time: " << value << endl; // "PT15H10M12S" int hours=0, minutes=0, seconds=0; int l = value.length(); TQString num; for ( int i = 0; i < l; ++i ) { if ( value[i].isNumber() ) { num += value[i]; continue; } else if ( value[i] == 'H' ) hours = num.toInt( &ok ); else if ( value[i] == 'M' ) minutes = num.toInt( &ok ); else if ( value[i] == 'S' ) seconds = num.toInt( &ok ); else continue; kdDebug(30518) << "Num: " << num << endl; num = ""; if ( !ok ) break; } kdDebug(30518) << "Hours: " << hours << ", " << minutes << ", " << seconds << endl; if ( ok ) { // KSpreadValue kval( timeToNum( hours, minutes, seconds ) ); // cell->setValue( kval ); cell->setValue( TQTime( hours % 24, minutes, seconds ) ); cell->format()->setFormatType( Custom_format ); } } if ( !ok ) // just in case we couldn't set the value directly cell->setCellText( text ); } else if ( !text.isEmpty() ) { if ( !cell ) cell = table->nonDefaultCell( columns, row ); cell->setCellText( text ); } if ( spanR > 1 || spanC > 1 ) { if ( !cell ) cell = table->nonDefaultCell( columns, row ); cell->mergeCells( columns, row, spanC - 1, spanR - 1 ); } cellNode = cellNode.nextSibling(); if ( e.hasAttributeNS( ooNS::table, "number-columns-repeated" ) ) { // copy cell from left bool ok = false; int number = e.attributeNS( ooNS::table, "number-columns-repeated", TQString() ).toInt( &ok ); Cell* cellDest = 0; // don't repeat more than 10 if it is the last cell and empty if ( !ok || cellNode.isNull() ) { if ( number > 10 ) number = 10; } for ( int i = 1; i < number; ++i ) { ++columns; if ( cell ) { cellDest = table->nonDefaultCell( columns, row ); cellDest->copyAll( cell ); } } } ++columns; } return true; } void OpenCalcImport::loadCondition( Cell*cell,const TQDomElement &property ) { kdDebug(30518)<<"void OpenCalcImport::loadCondition( Cell*cell,const TQDomElement &property )*******\n"; loadOasisCondition( cell, property ); } void OpenCalcImport::loadOasisCondition(Cell*cell,const TQDomElement &property ) { TQDomElement elementItem( property ); StyleManager * manager = cell->sheet()->doc()->styleManager(); TQValueList cond; while ( !elementItem.isNull() ) { kdDebug(30518)<<"elementItem.tagName() :"<style( *newCondition.styleName ); if ( !newCondition.style ) ok = false; else ok = true; } if ( ok ) cond.append( newCondition ); else kdDebug(30518) << "Error loading condition " << elementItem.nodeName()<< endl; } elementItem = elementItem.nextSibling().toElement(); } if ( !cond.isEmpty() ) cell->setConditionList( cond ); } void OpenCalcImport::loadOasisConditionValue( const TQString &styleCondition, Conditional &newCondition ) { TQString val( styleCondition ); if ( val.contains( "cell-content()" ) ) { val = val.remove( "cell-content()" ); loadOasisCondition( val,newCondition ); } //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value) //for the moment we support just int/double value, not text/date/time :( if ( val.contains( "cell-content-is-between(" ) ) { val = val.remove( "cell-content-is-between(" ); val = val.remove( ")" ); TQStringList listVal = TQStringList::split( "," , val ); loadOasisValidationValue( listVal, newCondition ); newCondition.cond = Conditional::Between; } if ( val.contains( "cell-content-is-not-between(" ) ) { val = val.remove( "cell-content-is-not-between(" ); val = val.remove( ")" ); TQStringList listVal = TQStringList::split( ",", val ); loadOasisValidationValue( listVal,newCondition ); newCondition.cond = Conditional::Different; } } void OpenCalcImport::loadOasisCondition( TQString &valExpression, Conditional &newCondition ) { TQString value; if (valExpression.find( "<=" )==0 ) { value = valExpression.remove( 0,2 ); newCondition.cond = Conditional::InferiorEqual; } else if (valExpression.find( ">=" )==0 ) { value = valExpression.remove( 0,2 ); newCondition.cond = Conditional::SuperiorEqual; } else if (valExpression.find( "!=" )==0 ) { //add Differentto attribute value = valExpression.remove( 0,2 ); newCondition.cond = Conditional::DifferentTo; } else if ( valExpression.find( "<" )==0 ) { value = valExpression.remove( 0,1 ); newCondition.cond = Conditional::Inferior; } else if(valExpression.find( ">" )==0 ) { value = valExpression.remove( 0,1 ); newCondition.cond = Conditional::Superior; } else if (valExpression.find( "=" )==0 ) { value = valExpression.remove( 0,1 ); newCondition.cond = Conditional::Equal; } else kdDebug(30518)<<" I don't know how to parse it :"<defaultCell(); TQDomNode rowNode = KoDom::namedItemNS( content, ooNS::table, "table-row" ); while ( !rowNode.isNull() ) { bool collapsed = false; int number = 1; TQDomElement r = rowNode.toElement(); if ( r.isNull() ) return false; if ( r.hasAttributeNS( ooNS::table, "style-name" ) ) { TQString style = r.attributeNS( ooNS::table, "style-name", TQString() ); rowStyle = m_styles[ style ]; kdDebug(30518) << "Row style: " << style << endl; } collapsed = ( r.attributeNS( ooNS::table, "visibility", TQString() ) == "collapse" ); backupRow = row; rowNode = rowNode.nextSibling(); if ( !readRowFormat( r, rowStyle, table, row, number, rowNode.isNull() ) ) // updates "row" return false; if ( !readCells( r, table, backupRow, columns ) ) return false; RowFormat * srcLayout = table->nonDefaultRowFormat( backupRow ); RowFormat * layout = 0; if ( collapsed ) srcLayout->setHide( true ); for ( i = 1; i < number; ++i ) { layout = table->nonDefaultRowFormat( backupRow + i ); layout->copy( *srcLayout ); /* * TODO: Test: do we need to copy the cells, too? * if so we will probably also need to copy them for repeated col layouts. for ( j = 1; j <= columns; ++j ) { Cell* cell = table->cellAt( j, backupRow ); kdDebug(30518) << "Cell: " << cell << "DefCell: " << defCell << endl; if ( cell && (cell != defCell) ) { cellDest = table->nonDefaultCell( j, backupRow + i ); cellDest->copyAll( cell ); } } */ } rowStyle = 0; columns = 1; } kdDebug(30518) << "Reading in rows done" << endl << endl; return true; } bool OpenCalcImport::readColLayouts( TQDomElement & content, Sheet * table ) { kdDebug(30518) << endl << "Reading in columns..." << endl; TQDomNode colLayout = KoDom::namedItemNS( content, ooNS::table, "table-column" ); int column = 1; while ( !colLayout.isNull() ) { if ( colLayout.nodeName() != "table:table-column" ) return true; // all cols read in. TQDomElement e = colLayout.toElement(); if ( e.isNull() ) return false; // error, that's it... kdDebug(30518) << "New column: " << column << endl; int number = 1; double width = -1.0; bool collapsed = ( e.attributeNS( ooNS::table, "visibility", TQString() ) == "collapse" ); bool insertPageBreak = false; Format styleLayout( table, table->doc()->styleManager()->defaultStyle() ); kdDebug(30518) << "Check table:number-columns-repeated" << endl; if ( e.hasAttributeNS( ooNS::table, "number-columns-repeated" ) ) { bool ok = true; number = e.attributeNS( ooNS::table, "number-columns-repeated", TQString() ).toInt( &ok ); if ( !ok ) number = 1; kdDebug(30518) << "Repeated: " << number << endl; } kdDebug(30518) << "Checking table:default-cell-style-name" << endl; if ( e.hasAttributeNS( ooNS::table, "default-cell-style-name" ) ) { TQString n( e.attributeNS( ooNS::table, "default-cell-style-name", TQString() ) ); kdDebug(30518) << "Has attribute default-cell-style-name: " << n << endl; Format * defaultStyle = m_defaultStyles[ n ]; if ( !defaultStyle ) { TQString name = e.attributeNS( ooNS::table, "default-cell-style-name", TQString() ); TQDomElement * st = m_styles[ name ]; kdDebug(30518) << "Default cell style: " << name << endl; if ( st && !st->isNull() ) { Format * layout = new Format( 0, m_doc->styleManager()->defaultStyle() ); readInStyle( layout, *st ); m_defaultStyles.insert( name, layout ); kdDebug(30518) << "Insert default cell style: " << name << endl; defaultStyle = layout; } } if ( defaultStyle ) { // kdDebug(30518) << "Copying default style, Font: " << defaultStyle->font().toString() << endl; styleLayout.copy( *defaultStyle ); } } TQDomElement * colStyle = 0; if ( e.hasAttributeNS( ooNS::table, "style-name" ) ) { TQString style = e.attributeNS( ooNS::table, "style-name", TQString() ); colStyle = m_styles[ style ]; kdDebug(30518) << "Col Style: " << style << endl; } TQDomNode node; if ( colStyle ) node = colStyle->firstChild(); while( !node.isNull() ) { TQDomElement property = node.toElement(); if ( !property.isNull() && property.localName() == "properties" && property.namespaceURI() == ooNS::style ) { if ( property.hasAttributeNS( ooNS::style, "column-width" ) ) { TQString sWidth = property.attributeNS( ooNS::style, "column-width", TQString() ); width = KoUnit::parseValue( property.attributeNS( ooNS::style, "column-width", TQString() ), width ); kdDebug(30518) << "Col Width: " << sWidth << endl; } if ( property.hasAttributeNS( ooNS::fo, "break-before" ) ) { if ( property.attributeNS( ooNS::fo, "break-before", TQString() ) == "page" ) { insertPageBreak = true; } } loadStyleProperties( &styleLayout, property ); } node = node.nextSibling(); } colLayout = colLayout.nextSibling(); if ( colLayout.isNull() && ( number > 30 ) ) number = 30; for ( int i = 0; i < number; ++i ) { kdDebug(30518) << "Inserting colLayout: " << column << endl; ColumnFormat * col = new ColumnFormat( table, column ); col->copy( styleLayout ); if ( width != -1.0 ) col->setWidth( int( width ) ); // if ( insertPageBreak ) // col->setPageBreak( true ) if ( collapsed ) col->setHide( true ); table->insertColumnFormat( col ); ++column; } } return true; } void replaceMacro( TQString & text, TQString const & old, TQString const & newS ) { int n = text.find( old ); if ( n != -1 ) text = text.replace( n, old.length(), newS ); } TQString getPart( TQDomNode const & part ) { TQString result; TQDomElement e = KoDom::namedItemNS( part, ooNS::text, "p" ); while ( !e.isNull() ) { TQString text = e.text(); kdDebug(30518) << "PART: " << text << endl; TQDomElement macro = KoDom::namedItemNS( e, ooNS::text, "time" ); if ( !macro.isNull() ) replaceMacro( text, macro.text(), "