/* This file is part of the KDE project Copyright (C) 1998-2002 The KSpread Team www.koffice.org/kspread Copyright (C) 2005 Tomas Mecir 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. 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. */ // built-in database functions #include "functions.h" #include "valuecalc.h" #include "valueconverter.h" using namespace KSpread; // prototypes Value func_daverage (valVector args, ValueCalc *calc, FuncExtra *); Value func_dcount (valVector args, ValueCalc *calc, FuncExtra *); Value func_dcounta (valVector args, ValueCalc *calc, FuncExtra *); Value func_dget (valVector args, ValueCalc *calc, FuncExtra *); Value func_dmax (valVector args, ValueCalc *calc, FuncExtra *); Value func_dmin (valVector args, ValueCalc *calc, FuncExtra *); Value func_dproduct (valVector args, ValueCalc *calc, FuncExtra *); Value func_dstdev (valVector args, ValueCalc *calc, FuncExtra *); Value func_dstdevp (valVector args, ValueCalc *calc, FuncExtra *); Value func_dsum (valVector args, ValueCalc *calc, FuncExtra *); Value func_dvar (valVector args, ValueCalc *calc, FuncExtra *); Value func_dvarp (valVector args, ValueCalc *calc, FuncExtra *); Value func_getpivotdata (valVector args, ValueCalc *calc, FuncExtra *); // registers all database functions void RegisterDatabaseFunctions() { FunctionRepository* repo = FunctionRepository::self(); Function *f; f = new Function ("DAVERAGE", func_daverage); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DCOUNT", func_dcount); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DCOUNTA", func_dcounta); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DGET", func_dget); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DMAX", func_dmax); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DMIN", func_dmin); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DPRODUCT", func_dproduct); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DSTDEV", func_dstdev); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DSTDEVP", func_dstdevp); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DSUM", func_dsum); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DVAR", func_dvar); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("DVARP", func_dvarp); f->setParamCount (3); f->setAcceptArray (); repo->add (f); f = new Function ("GETPIVOTDATA", func_getpivotdata); // partially Excel-compatible f->setParamCount (2); f->setAcceptArray (); repo->add (f); } int getFieldIndex (ValueCalc *calc, Value fieldName, Value database) { if (fieldName.isNumber()) return fieldName.asInteger() - 1; if (!fieldName.isString ()) return -1; TQString fn = fieldName.asString(); int cols = database.columns (); for (int i = 0; i < cols; ++i) if (fn.lower() == calc->conv()->asString (database.element (i, 0)).asString().lower()) return i; return -1; } // *********************************************************** // *** DBConditions class - maintains an array of conditions *** // *********************************************************** class DBConditions { public: DBConditions (ValueCalc *vc, Value database, Value conds); ~DBConditions (); /** Does a specified row of the database match the given criteria? The row with column names is ignored - hence 0 specifies first data row. */ bool matches (unsigned row); private: void parse (Value conds); ValueCalc *calc; Condition **cond; int rows, cols; Value db; }; DBConditions::DBConditions (ValueCalc *vc, Value database, Value conds) : calc(vc), cond(0), rows(0), cols(0), db(database) { parse (conds); } DBConditions::~DBConditions () { int count = rows*cols; for (int r = 0; r < count; ++r) delete cond[r]; delete[] cond; } void DBConditions::parse (Value conds) { // initialize the array rows = conds.rows() - 1; cols = db.columns(); int count = rows*cols; cond = new Condition* [count]; for (int r = 0; r < count; ++r) cond[r] = 0; // perform the parsing itself int cc = conds.columns (); for (int c = 0; c < cc; ++c) { // first row contains column names int col = getFieldIndex (calc, conds.element (c, 0), db); if (col < 0) continue; // failed - ignore the column // fill in the conditions for a given column name for (int r = 0; r < rows; ++r) { Value cnd = conds.element (c, r+1); if (cnd.isEmpty()) continue; int idx = r * cols + col; if (cond[idx]) delete cond[idx]; cond[idx] = new Condition; calc->getCond (*cond[idx], cnd); } } } bool DBConditions::matches (unsigned row) { if (row >= db.rows() - 1) return false; // out of range // we have a match, if at least one row of criteria matches for (int r = 0; r < rows; ++r) { // within a row, all criteria must match bool match = true; for (int c = 0; c < cols; ++c) { int idx = r * cols + c; if (!cond[idx]) continue; if (!calc->matches (*cond[idx], db.element (c, row + 1))) { match = false; // didn't match break; } } if (match) // all conditions in this row matched return true; } // no row matched return false; } // ******************************************* // *** Function implementations start here *** // ******************************************* // Function: DSUM Value func_dsum (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value res( Value::Float ); for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) res = calc->add (res, val); } return res; } // Function: DAVERAGE Value func_daverage (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value res; int count = 0; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { res = calc->add (res, val); count++; } } if (count) res = calc->div (res, count); return res; } // Function: DCOUNT Value func_dcount (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names int count = 0; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if ((!val.isEmpty()) && (!val.isBoolean()) && (!val.isString())) count++; } return Value (count); } // Function: DCOUNTA Value func_dcounta (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names int count = 0; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty()) count++; } return Value (count); } // Function: DGET Value func_dget (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); bool match = false; Value result = Value::errorVALUE(); int rows = database.rows() - 1; // first row contains column names for (int r = 0; r < rows; ++r) if (conds.matches (r)) { if (match) { // error on multiple matches result = Value::errorVALUE(); break; } result = database.element (fieldIndex, r + 1); match = true; } return result; } // Function: DMAX Value func_dmax (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value res; bool got = false; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { if (!got) { res = val; got = true; } else if (calc->greater (val, res)) res = val; } } return res; } // Function: DMIN Value func_dmin (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value res; bool got = false; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { if (!got) { res = val; got = true; } else if (calc->lower (val, res)) res = val; } } return res; } // Function: DPRODUCT Value func_dproduct (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value res = 1.0; bool got = false; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { got = true; res = calc->mul (res, val); } } if (got) return res; return Value::errorVALUE (); } // Function: DSTDEV Value func_dstdev (valVector args, ValueCalc *calc, FuncExtra *) { // sqrt (dvar) return calc->sqrt (func_dvar (args, calc, 0)); } // Function: DSTDEVP Value func_dstdevp (valVector args, ValueCalc *calc, FuncExtra *) { // sqrt (dvarp) return calc->sqrt (func_dvarp (args, calc, 0)); } // Function: DVAR Value func_dvar (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value avg; int count = 0; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { avg = calc->add (avg, val); count++; } } if (count < 2) return Value::errorDIV0(); avg = calc->div (avg, count); Value res; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) res = calc->add (res, calc->sqr (calc->sub (val, avg))); } // res / (count-1) return calc->div (res, count - 1); } // Function: DVARP Value func_dvarp (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; Value conditions = args[2]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); DBConditions conds (calc, database, conditions); int rows = database.rows() - 1; // first row contains column names Value avg; int count = 0; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) { avg = calc->add (avg, val); count++; } } if (count == 0) return Value::errorDIV0(); avg = calc->div (avg, count); Value res; for (int r = 0; r < rows; ++r) if (conds.matches (r)) { Value val = database.element (fieldIndex, r + 1); // include this value in the result if (!val.isEmpty ()) res = calc->add (res, calc->sqr (calc->sub (val, avg))); } // res / count return calc->div (res, count); } // Function: GETPIVOTDATA // FIXME implement more things with this, see Excel ! Value func_getpivotdata (valVector args, ValueCalc *calc, FuncExtra *) { Value database = args[0]; int fieldIndex = getFieldIndex (calc, args[1], database); if (fieldIndex < 0) return Value::errorVALUE(); // the row at the bottom int row = database.rows() - 1; return database.element (fieldIndex, row); }