/* This file is part of the KDE project Copyright (C) 2006 Stefan Nikolaus Copyright (C) 1998, 1999 Torben Weis 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 "formula.h" #include "kspread_doc.h" #include "kspread_locale.h" #include "kspread_map.h" #include "kspread_sheet.h" #include "kspread_style.h" #include "kspread_util.h" using namespace KSpread; //helper functions for the formatting bool KSpread::formatIsDate (FormatType fmt) { return ((fmt == ShortDate_format) || (fmt == TextDate_format) || (((int) fmt >= 200) && ((int) fmt < 300))); } bool KSpread::formatIsTime (FormatType fmt) { return (((int) fmt >= 50) && ((int) fmt < 70)); } bool KSpread::formatIsFraction (FormatType fmt) { return (((int) fmt >= 70) && ((int) fmt < 80)); } //used in Point::init, Cell::encodeFormula and // dialogs/kspread_dlg_papertqlayout.cc int KSpread::util_decodeColumnLabelText( const TQString &_col ) { int col = 0; int offset='a'-'A'; int counterColumn = 0; for ( uint i=0; i < _col.length(); i++ ) { counterColumn = (int) pow(26.0 , static_cast(_col.length() - i - 1)); if( (int)(_col[i]) >= 'A' && (int)(_col[i]) <= 'Z' ) col += counterColumn * ( _col[i].latin1() - 'A' + 1); // okay here (Werner) else if( (int)(_col[i]) >= 'a' && (int)(_col[i]) <= 'z' ) col += counterColumn * ( _col[i].latin1() - 'A' - offset + 1 ); else kdDebug(36001) << "util_decodeColumnLabelText: Wrong characters in label text for col:'" << _col << "'" << endl; } return col; } //used in dialogs/kspread_dlg_papertqlayout.cc TQString KSpread::util_rangeColumnName( const TQRect &_area) { return TQString("%1:%2") .arg( Cell::columnName( _area.left())) .arg( Cell::columnName(_area.right())); } //used in dialogs/kspread_dlg_papertqlayout.cc TQString KSpread::util_rangeRowName( const TQRect &_area) { return TQString("%1:%2") .arg( _area.top()) .arg(_area.bottom()); } TQString KSpread::util_rangeName(const TQRect &_area) { return Cell::name( _area.left(), _area.top() ) + ":" + Cell::name( _area.right(), _area.bottom() ); } TQString KSpread::util_rangeName(Sheet * _sheet, const TQRect &_area) { return _sheet->sheetName() + "!" + util_rangeName(_area); } TQDomElement KSpread::util_createElement( const TQString & tagName, const TQFont & font, TQDomDocument & doc ) { TQDomElement e( doc.createElement( tagName ) ); e.setAttribute( "family", font.family() ); e.setAttribute( "size", font.pointSize() ); e.setAttribute( "weight", font.weight() ); if ( font.bold() ) e.setAttribute( "bold", "yes" ); if ( font.italic() ) e.setAttribute( "italic", "yes" ); if ( font.underline() ) e.setAttribute( "underline", "yes" ); if ( font.strikeOut() ) e.setAttribute( "strikeout", "yes" ); //e.setAttribute( "charset", KGlobal::charsets()->name( font ) ); return e; } TQDomElement KSpread::util_createElement( const TQString & tagname, const TQPen & pen, TQDomDocument & doc ) { TQDomElement e( doc.createElement( tagname ) ); e.setAttribute( "color", pen.color().name() ); e.setAttribute( "style", (int)pen.style() ); e.setAttribute( "width", (int)pen.width() ); return e; } TQFont KSpread::util_toFont( TQDomElement & element ) { TQFont f; f.setFamily( element.attribute( "family" ) ); bool ok; f.setPointSize( element.attribute("size").toInt( &ok ) ); if ( !ok ) return TQFont(); f.setWeight( element.attribute("weight").toInt( &ok ) ); if ( !ok ) return TQFont(); if ( element.hasAttribute( "italic" ) && element.attribute("italic") == "yes" ) f.setItalic( true ); if ( element.hasAttribute( "bold" ) && element.attribute("bold") == "yes" ) f.setBold( true ); if ( element.hasAttribute( "underline" ) && element.attribute("underline") == "yes" ) f.setUnderline( true ); if ( element.hasAttribute( "strikeout" ) && element.attribute("strikeout") == "yes" ) f.setStrikeOut( true ); /* Uncomment when charset is added to kspread_dlg_layout + save a document-global charset if ( element.hasAttribute( "charset" ) ) KGlobal::charsets()->setTQFont( f, element.attribute("charset") ); else */ // ######## Not needed anymore in 3.0? //KGlobal::charsets()->setTQFont( f, KGlobal::locale()->charset() ); return f; } TQPen KSpread::util_toPen( TQDomElement & element ) { bool ok; TQPen p; p.setStyle( (Qt::PenStyle)element.attribute("style").toInt( &ok ) ); if ( !ok ) return TQPen(); p.setWidth( element.attribute("width").toInt( &ok ) ); if ( !ok ) return TQPen(); p.setColor( TQColor( element.attribute("color") ) ); return p; } Point::Point(const TQString & _str) { _sheet = 0; init(_str); } void Point::setPos(TQPoint pos) { _pos=pos; } TQPoint Point::pos() const { return _pos; } void Point::setSheet(Sheet* sheet) { _sheet=sheet; } KSpread::Sheet* Point::sheet() const { return _sheet; } void Point::setSheetName(TQString name) { _sheetName=name; } TQString Point::sheetName() const { return _sheetName; } void Point::setColumnFixed(bool colFixed) { _columnFixed=colFixed; } bool Point::columnFixed() const { return _columnFixed; } void Point::setRowFixed(bool rowFixed) { _rowFixed=rowFixed; } bool Point::rowFixed() const { return _rowFixed; } void Point::init(const TQString & _str) { _columnFixed=false; _rowFixed=false; // kdDebug(36001) <<"Point::init ("<<_str<<")"< 'Z' ) { if ( str[p] < 'a' || str[p] > 'z' ) { kdDebug(36001) << "Point::init: wrong first character in point (str: '" << str.mid( p ) << "'" << endl; return; } } //default is error int x = -1; //search for the first character != text int result = str.find( TQRegExp("[^A-Za-z]+"), p ); //get the colomn number for the character between actual position and the first non text charakter if ( result != -1 ) x = util_decodeColumnLabelText( str.mid( p, result - p ) ); // x is defined now else // If there isn't any, then this is not a point -> return { kdDebug(36001) << "Point::init: no number in string (str: '" << str.mid( p, result ) << "'" << endl; return; } p = result; //limit is KS_colMax if ( x > KS_colMax ) { kdDebug(36001) << "Point::init: column value too high (col: " << x << ")" << endl; return; } // Malformed ? if (p == len) { kdDebug(36001) << "Point::init: p==len after cols" << endl; return; } if (str[p] == '$') { _rowFixed = true; p++; // Malformed ? if ( p == len ) { kdDebug(36001) << "Point::init: p==len after $ of row" << endl; return; } } else _rowFixed = false; uint p2 = p; while ( p < len ) { if ( !isdigit( TQChar(str[p++]) ) ) { kdDebug(36001) << "Point::init: no number" << endl; return; } } bool ok; int y = str.mid( p2, p-p2 ).toInt( &ok ); if ( !ok ) { kdDebug(36001) << "Point::init: Invalid number (str: '" << str.mid( p2, p-p2 ) << "'" << endl; return; } if ( y > KS_rowMax ) { kdDebug(36001) << "Point::init: row value too high (row: " << y << ")" << endl; return; } if ( y <= 0 ) { kdDebug(36001) << "Point::init: y <= 0" << endl; return; } _pos = TQPoint( x, y ); } bool util_isPointValid( TQPoint point ) { if ( point.x() >= 1 && point.y() >= 1 && point.x() <= KS_colMax && point.y() <= KS_rowMax ) return true; else return false; } bool util_isRectValid( TQRect rect ) { if ( util_isPointValid( rect.topLeft() ) && util_isPointValid( rect.bottomRight() ) ) return true; else return false; } Point::Point( const TQString & str, Map * map, Sheet * sheet ) { uint p = 0; int p2 = str.find( '!' ); if ( p2 != -1 ) { _sheetName = str.left( p2++ ); while ( true ) { _sheet = map->findSheet( _sheetName ); if ( !sheet && _sheetName[0] == ' ' ) { _sheetName = _sheetName.right( _sheetName.length() - 1 ); continue; } break; } p = p2; //If the loop didn't return a sheet, better keep a string for isValid if ( _sheetName.isEmpty() ) { kdDebug(36001) << "Point: tableName is unknown" << endl; _sheetName = "unknown"; } } else { if ( sheet != 0 ) { _sheet = sheet; _sheetName = sheet->sheetName(); } else _sheet = 0; } init( str.mid( p ) ); } Cell *Point::cell() const { return _sheet->cellAt(_pos); } bool Point::operator== (const Point &cell) const { //sheet info ignored return (_pos == cell.pos()); } bool Point::operator< (const Point &cell) const { //sheet info ignored return (pos().y() < cell.pos().y()) ? true : ((pos().y() == cell.pos().y()) && (pos().x() < cell.pos().x())); } bool Range::operator ==(const Range& otherRange) const { if ( _range == otherRange._range && _leftFixed == otherRange._leftFixed && _rightFixed == otherRange._rightFixed && _bottomFixed == otherRange._bottomFixed && _topFixed == otherRange._topFixed && _sheet == otherRange._sheet ) return true; else return false; } Range::Range() { _sheet = 0; _range.setLeft( -1 ); _leftFixed=false; _rightFixed=false; _topFixed=false; _bottomFixed=false; } Range::Range(const TQString & _str) { _range.setLeft(-1); _sheet = 0; int p = _str.find(':'); // if (p == -1) // return; Point ul; Point lr; ; if ( p != -1) { ul = Point(_str.left(p)); lr = Point(_str.mid(p + 1)); } else { ul = Point(_str); lr = ul; } _range = TQRect(ul.pos(), lr.pos()); _sheetName = ul.sheetName(); _leftFixed = ul.columnFixed(); _rightFixed = lr.columnFixed(); _topFixed = ul.rowFixed(); _bottomFixed = lr.rowFixed(); } Range::Range( const Range& r ) { _sheet = r.sheet(); _sheetName = r.sheetName(); _range = r.range(); _namedArea = r.namedArea(); _leftFixed=r._leftFixed; _rightFixed=r._rightFixed; _topFixed=r._topFixed; _bottomFixed=r._bottomFixed; } Range::Range( const Point& ul, const Point& lr ) { _range = TQRect( ul.pos(), lr.pos() ); if ( ul.sheetName() != lr.sheetName() ) { _range.setLeft( -1 ); return; } _sheetName = ul.sheetName(); _sheet = ul.sheet(); _leftFixed = ul.columnFixed(); _rightFixed = lr.columnFixed(); _topFixed = ul.rowFixed(); _bottomFixed = lr.rowFixed(); } Range::Range(const TQString & str, Map * map, Sheet * sheet) { _range.setLeft(-1); _sheet = 0; //try to parse as named area bool gotNamed = false; TQString tmp = str.lower(); TQValueList < Reference >::Iterator it; TQValueList < Reference > area = map->doc()->listArea(); for (it = area.begin(); it != area.end(); ++it) { if ((*it).ref_name.lower() == tmp) { // success - such named area exists _range = (*it).rect; _sheet = map->findSheet((*it).sheet_name); gotNamed = true; _namedArea = tmp; break; } } if (gotNamed) { // we have a named area - no need to proceed further _leftFixed = false; _rightFixed = false; _topFixed = false; _bottomFixed = false; return; } _range.setLeft(-1); _sheet = 0; int p = 0; int p2 = str.find('!'); if (p2 != -1) { _sheetName = str.left(p2++); while ( true ) { _sheet = map->findSheet(_sheetName); if ( !_sheet && _sheetName[0] == ' ' ) { _sheetName = _sheetName.right( _sheetName.length() - 1 ); continue; } break; } p = p2; } else _sheet = sheet; int p3 = str.find(':', p); if (p3 == -1) return; Point ul(str.mid(p, p3 - p)); Point lr(str.mid(p3 + 1)); _range = TQRect(ul.pos(), lr.pos()); _leftFixed = ul.columnFixed(); _rightFixed = lr.columnFixed(); _topFixed = ul.rowFixed(); _bottomFixed = lr.rowFixed(); } TQString Range::toString() const { TQString result; if (_sheet) { result=util_rangeName(_sheet,_range); } else { result=util_rangeName(_range); } //Insert $ characters to show fixed parts of range int pos=result.find("!")+1; Q_ASSERT(pos != -1); if (_leftFixed) { result.insert(pos,'$'); pos++; //Takes account of extra character added in } if (_topFixed) { result.insert(pos+Cell::columnName(_range.left()).length(),'$'); } pos=result.find(":")+1; Q_ASSERT(pos != -1); if (_rightFixed) { result.insert(pos,'$'); pos++; //Takes account of extra character added in } if (_bottomFixed) { result.insert(pos+Cell::columnName(_range.right()).length(),'$'); } return result; } void Range::getStartPoint(Point* pt) { if (!isValid()) return; pt->setRow(startRow()); pt->setColumn(startCol()); pt->setColumnFixed(_leftFixed); pt->setRowFixed(_topFixed); pt->setSheet(_sheet); pt->setSheetName(_sheetName); } void Range::getEndPoint(Point* pt) { if (!isValid()) return; pt->setRow(endRow()); pt->setColumn(endCol()); pt->setColumnFixed(_rightFixed); pt->setRowFixed(_bottomFixed); pt->setSheet(_sheet); pt->setSheetName(_sheetName); } bool Range::contains (const Point &cell) const { return _range.contains (cell.pos()); } bool Range::intersects (const Range &r) const { return _range.intersects (r.range()); } bool Range::isValid() const { return ( _range.left() >= 0 ) && ( _range.right() >= 0 ) && ( _sheet != 0 || _sheetName.isEmpty() ) && ( _range.isValid() ) ; } TQRect Range::range() const { return _range; } void Range::setLeftFixed(bool fixed) { _leftFixed=fixed; } bool Range::leftFixed() const { return _leftFixed; } void Range::setRightFixed(bool fixed) { _rightFixed=fixed; } bool Range::rightFixed() const { return _rightFixed; } void Range::setTopFixed(bool fixed) { _topFixed=fixed; } bool Range::topFixed() const { return _topFixed; } void Range::setBottomFixed(bool fixed) { _bottomFixed=fixed; } bool Range::bottomFixed() const { return _bottomFixed; } void Range::setSheet(Sheet* sheet) { _sheet=sheet; } KSpread::Sheet* Range::sheet() const { return _sheet; } void Range::setSheetName(TQString sheetName) { _sheetName=sheetName; } TQString Range::sheetName() const { return _sheetName; } TQString Range::namedArea() const { return _namedArea; } bool KSpread::util_isAllSelected(const TQRect &selection) { return ( selection.top() == 1 && selection.bottom() == KS_rowMax && selection.left() == 1 && selection.right() == KS_colMax); } bool KSpread::util_isColumnSelected(const TQRect &selection) { return ( (selection.top() == 1) && (selection.bottom() == KS_rowMax) ); } bool KSpread::util_isRowSelected(const TQRect &selection) { return ( (selection.left() == 1) && (selection.right() == KS_colMax) ); } bool KSpread::util_isRowOrColumnSelected(const TQRect &selection) { return ( (selection.left() == 1) && (selection.right() == KS_colMax) || (selection.top() == 1) && (selection.bottom() == KS_rowMax) ); } //used in View::slotRename bool KSpread::util_validateSheetName(const TQString &name) { if (name[0] == ' ') { return false; } for (unsigned int i = 0; i < name.length(); i++) { if ( !(name[i].isLetterOrNumber() || name[i] == ' ' || name[i] == '.' || name[i] == '_')) { return false; } } return true; } RangeIterator::RangeIterator(TQRect _range, Sheet* _sheet) { range = _range; sheet = _sheet; current = TQPoint(0,0); } RangeIterator::~RangeIterator() { } Cell* RangeIterator::first() { current.setY(range.top()); /* OK, because even if this equals zero, the 'getNextCellRight' won't try to access it*/ current.setX(range.left() - 1); return next(); } Cell* RangeIterator::next() { if (current.x() == 0 && current.y() == 0) { return first(); } Cell* cell = NULL; bool done = false; while (cell == NULL && !done) { cell = sheet->getNextCellRight(current.x(), current.y()); if (cell != NULL && cell->column() > range.right()) { cell = NULL; } if (cell == NULL) { current.setX(range.left() - 1); current.setY(current.y() + 1); done = (current.y() > range.bottom()); } } return cell; } //not used anywhere int KSpread::util_penCompare( TQPen const & pen1, TQPen const & pen2 ) { if ( pen1.style() == TQt::NoPen && pen2.style() == TQt::NoPen ) return 0; if ( pen1.style() == TQt::NoPen ) return -1; if ( pen2.style() == TQt::NoPen ) return 1; if ( pen1.width() < pen2.width() ) return -1; if ( pen1.width() > pen2.width() ) return 1; if ( pen1.style() < pen2.style() ) return -1; if ( pen1.style() > pen2.style() ) return 1; if ( pen1.color().name() < pen2.color().name() ) return -1; if ( pen1.color().name() > pen2.color().name() ) return 1; return 0; } TQString KSpread::convertRefToBase( const TQString & sheet, const TQRect & rect ) { TQPoint bottomRight( rect.bottomRight() ); TQString s( "$" ); s += sheet; s += ".$"; s += Cell::columnName( bottomRight.x() ); s += '$'; s += TQString::number( bottomRight.y() ); return s; } TQString KSpread::convertRefToRange( const TQString & sheet, const TQRect & rect ) { TQPoint topLeft( rect.topLeft() ); TQPoint bottomRight( rect.bottomRight() ); if ( topLeft == bottomRight ) return convertRefToBase( sheet, rect ); TQString s( "$" ); s += sheet; s += ".$"; s += /*util_encodeColumnLabelText*/Cell::columnName( topLeft.x() ); s += '$'; s += TQString::number( topLeft.y() ); s += ":.$"; s += /*util_encodeColumnLabelText*/Cell::columnName( bottomRight.x() ); s += '$'; s += TQString::number( bottomRight.y() ); return s; } //used in Cell::convertFormulaToOasisFormat void KSpread::insertBracket( TQString & s ) { TQChar c; int i = (int) s.length() - 1; while ( i >= 0 ) { c = s[i]; if ( c == ' ' ) s[i] = '_'; if ( !(c.isLetterOrNumber() || c == ' ' || c == '.' || c == '_') ) { s.insert( i + 1, '[' ); return; } --i; } } // e.g.: Sheet4.A1:Sheet4.E28 //used in Sheet::saveOasis TQString KSpread::convertRangeToRef( const TQString & sheetName, const TQRect & _area ) { return sheetName + "." + Cell::name( _area.left(), _area.top() ) + ":" + sheetName + "."+ Cell::name( _area.right(), _area.bottom() ); } TQString KSpread::convertOasisPenToString( const TQPen & pen ) { // kdDebug()<<"convertOasisPenToString( const TQPen & pen ) :"<decimalSymbol() : "."; // initialize variables state = Start; unsigned int i = 0; const TQString ex = expr; TQString result; if (ex[0] == '=') { result="="; ++i; } // main loop while( i < ex.length() ) { TQChar ch = ex[i]; switch( state ) { case Start: { // check for number if( ch.isDigit() ) { state = InNumber; } // a string? else if ( ch == '"' ) { state = InString; result.append( ex[i++] ); } // beginning with alphanumeric ? // could be identifier, cell, range, or function... else if( isIdentifier( ch ) ) { state = InIdentifier; } // [ marks sheet name for 3-d cell, e.g ['Sales Q3'.A4] else if ( ch.tqunicode() == '[' ) { ++i; state = InReference; // NOTE Stefan: As long as KSpread does not support fixed sheets eat the dollar sign. if ( ex[i] == '$' ) ++i; } // decimal dot ? else if ( ch == '.' ) { if ( ex[i+1].isDigit() ) state = InNumber; else state = InReference; } // look for operator match else { int op; TQString s; // check for two-chars operator, such as '<=', '>=', etc s.append( ch ).append( ex[i+1] ); op = matchOperator( s ); // check for one-char operator, such as '+', ';', etc if( op == Token::InvalidOp ) { s = TQString( ch ); op = matchOperator( s ); } // any matched operator ? if ( op == Token::Equal ) { result.append( "==" ); } else { result.append( s ); } if( op != Token::InvalidOp ) { int len = s.length(); i += len; } else { ++i; state = Start; } } break; } case InReference: { // consume as long as alpha, dollar sign, underscore, or digit, or colon if( isIdentifier( ch ) || ch.isDigit() || ch == ':' ) result.append( ex[i] ); else if ( ch == '.' && i > 0 && ex[i-1] != '[' && ex[i-1] != ':' ) result.append( '!' ); else if( ch == ']' ) state = Start; else if ( ch == '\'' ) { result.append( ex[i] ); state = InSheetName; // NOTE Stefan: As long as KSpread does not support fixed sheets eat the dollar sign. if ( ex[i] == '$' ) ++i; } else if ( ch != '.' ) { state = Start; break; } ++i; break; } case InSheetName: { if ( ch == '\'' ) state = InReference; result.append( ex[i] ); ++i; break; } case InNumber: { // consume as long as it's digit if( ch.isDigit() ) result.append( ex[i++] ); // convert '.' to decimal separator else if ( ch == '.' ) { result.append( decimal ); ++i; } // exponent ? else if( ch.upper() == 'E' ) { result.append( 'E' ); ++i; } // we're done with integer number else state = Start; break; } case InString: { // consume until " if( ch != '"' ) { result.append( ex[i++] ); } // we're done else { result.append( ch ); ++i; state = Start; } break; } case InIdentifier: { // consume as long as alpha, dollar sign, underscore, or digit if( isIdentifier( ch ) || ch.isDigit() ) result.append( ex[i++] ); // we're done else state = Start; break; } default: break; } } return result; } /*TQString KSpread::Oasis::encodeFormula(const TQString& expr, const KLocale* locale) { // TODO move Cell::convertFormulaToOasisFormat to this point //expr = "not here yet"; //Q_UNUSED(locale); kdDebug() << k_funcinfo << " not implemented" qFatal(0); }*/