libixion-0.15.0/ 0000755 0001750 0001750 00000000000 13523106414 010412 5 0000000 0000000 libixion-0.15.0/README 0000644 0001750 0001750 00000015244 13523106313 011216 0000000 0000000 Ixion is a general purpose formula parser & interpreter that can calculate
multiple named targets, or "cells".
## Overview
The goal of this project is to create a library for calculating the
results of formula expressions stored in multiple named targets, or
"cells". The cells can be referenced from each other, and the library
takes care of resolving their dependencies automatically upon calculation.
The caller can run the calculation routine either in a single-threaded
mode, or a multi-threaded mode. The library also supports re-calculations
where the contents of one or more cells have been modified since the last
calculation, and a partial calculation of only the affected cells need to
be calculated.
## Portability
This library is written with portability in mind; platform specific calls
are avoided as much as possible. It makes use of the [boost library](http://boost.org)
to achieve portability in some places.
## Performance
Achieving good performance is one of the goals of this project. As much
care is taken as humanly possible, to attain reasonable performance.
## Threaded calculation
Ixion can perform threaded calculation using arbitrary number of threads,
for both full and partial calculation modes.
## Supported features
* Each calculation session is defined in a plain text file, which is parsed
and interpreted by the Ixion parser.
* Fully threaded calculation.
* Name resolution using A1- and R1C1-style references.
* Support 2D cell references and named expressions.
* Support range references.
* Support table references.
* 3D cell and range references.
* Dependency tracking during both full calculation and partial re-calculation.
* Inline strings.
* Volatile functions. The framework for volatile functions is implemented. We
just need to implement more functions.
* C++ API.
* Python API.
* Matrix support via grouped formulas.
## Planned features
* More built-in functions.
* Support for custom functions defined in the caller program.
* Support for external references.
* Implicit intersection.
## Documentation
* [Official API documentation](https://ixion.readthedocs.io/en/latest/) for general users of the library.
## Installation
Please refer to the [CONTRIBUTING.md](CONTRIBUTING.md) file for build and
installation instructions.
## Download source packages
| Version | API Version | Release Date | Download | Check Sum | File Size (bytes) |
|---------|-------------|--------------|----------|-----------|-------------------|
| 0.14.1 | 0.14 | 2018-09-14 | [libixion-0.14.1.tar.xz](http://kohei.us/files/ixion/src/libixion-0.14.1.tar.xz) | sha256sum: 6ad1384fcf813083c6d981a16b2643c953f9bac4c2caf1ed1682921d9b69ed91 | 452249 |
| | | | [libixion-0.14.1.tar.gz](http://kohei.us/files/ixion/src/libixion-0.14.1.tar.gz) | sha256sum: 1b951da168cd55d22f59b28e66e0606c9a6bfe18ef637cb9ef81d146559f74e1 | 578708 |
| 0.14.0 | 0.14 | 2018-08-22 | [libixion-0.14.0.tar.xz](http://kohei.us/files/ixion/src/libixion-0.14.0.tar.xz) | sha256sum: 5805d49bb110e53eeb4224cdbcbcdba91928315dcb5672af8f90942ad34afe5e | 427100 |
| | | | [libixion-0.14.0.tar.gz](http://kohei.us/files/ixion/src/libixion-0.14.0.tar.gz) | sha256sum: 5f646dd5089700093be157ff71698e276df9512afb4806954094a936f80ca9d1 | 662646 |
| 0.13.0 | 0.13 | 2017-08-15 | [libixion-0.13.0.tar.xz](http://kohei.us/files/ixion/src/libixion-0.13.0.tar.xz) | sha256sum: 5ae360c52ba2d17c4abf5ae21fa947f75925459e085acef5972395f77333c7e5 | 413756 |
| | | | [libixion-0.13.0.tar.gz](http://kohei.us/files/ixion/src/libixion-0.13.0.tar.gz) | sha256sum: f990c18354a5aaa7e2a99a38c44f37f8169aa86a54bf285be26e21453fae3b8b | 636751 |
| 0.12.2 | 0.12 | 2016-12-14 | [libixion-0.12.2.tar.xz](http://kohei.us/files/ixion/src/libixion-0.12.2.tar.xz) | sha256sum: 8b44008836bb4e1a3dff4d3e40afec6c73037e3518e72cc85b5cc675fbc2daae | 407280 |
| | | | [libixion-0.12.2.tar.gz](http://kohei.us/files/ixion/src/libixion-0.12.2.tar.gz) | sha256sum: 00d7a44f3d8266fd7da46ceb336288f77fc57bdd95402bdc9bb95f1dcb883baf | 627147 |
| 0.12.1 | 0.12 | 2016-09-17 | [libixion-0.12.1.tar.xz](http://kohei.us/files/ixion/src/libixion-0.12.1.tar.xz) | sha256sum: 000820ba51109ec21cbdb7ea83c1fdb0acbcfeb55b4a6a80fe02b71d45c587c2 | 406300 |
| | | | [libixion-0.12.1.tar.gz](http://kohei.us/files/ixion/src/libixion-0.12.1.tar.gz) | sha256sum: 7d03679041f291456052ccff8710f591b955a8ca53bc1670df10f8741274775d | 628577 |
| 0.12.0 | 0.12 | 2016-07-15 | [libixion-0.12.0.tar.xz](http://kohei.us/files/ixion/src/libixion-0.12.0.tar.xz) | sha256sum: 055b7b9e31663f0acb7f249d68ca08e480f8f5d43ef5e4bd57b2fb04242307b0 | 393284 |
| | | | [libixion-0.12.0.tar.gz](http://kohei.us/files/ixion/src/libixion-0.12.0.tar.gz) | sha256sum: ba1d6f78fe3f302723d286ffe1ec25571b7983a360184c66a88782177fe3496e | 601525 |
| 0.11.1 | 0.11 | 2016-05-11 | [libixion-0.11.1.tar.xz](http://kohei.us/files/ixion/src/libixion-0.11.1.tar.xz) | sha256sum: c9e9f52580d618fa969fc0293f55af21a9c74bfb802e655c6bf239202f95bede | 366660 |
| | | | [libixion-0.11.1.tar.gz](http://kohei.us/files/ixion/src/libixion-0.11.1.tar.gz) | sha256sum: 9ff20f6370e6f1e86a67d4159123195a875b11f7332b0cf9373ba98c57953916 | 568876 |
| 0.11.0 | 0.11 | 2016-02-13 | [libixion-0.11.0.tar.xz](http://kohei.us/files/ixion/src/libixion-0.11.0.tar.xz) | sha256sum: 97a6e7f2b1fcbff69e76fe4e1df62f1cfcc353820472991e37de00aacb024293 | 365652 |
| | | | [libixion-0.11.0.tar.gz](http://kohei.us/files/ixion/src/libixion-0.11.0.tar.gz) | sha256sum: fd697e9ab334bb1cf0161fab25f46bbbcf517248de9bbc1f684d9854b9b287c0 | 567644 |
| 0.9.1 | | 2015-04-05 | [libixion-0.9.1.tar.xz](http://kohei.us/files/ixion/src/libixion-0.9.1.tar.xz) | md5sum: d292f6d62847f2305178459390842eac
sha1sum: 8ae071e7e89784933caadeffc16ed7b0764350a9 | - |
| 0.9.0 | | 2015-02-16 | [libixion-0.9.0.tar.xz](http://kohei.us/files/ixion/src/libixion-0.9.0.tar.xz) | md5sum: 26f293e708513dea5e6e25e9232a7400
sha1sum: 4f97682546236aee686e86293f9890d79f25cf23 | - |
| 0.7.0 | | 2013-12-13 | [libixion-0.7.0.tar.bz2](http://kohei.us/files/ixion/src/libixion-0.7.0.tar.bz2) | md5sum: 000157117801f9507f34b26ba998c4d1
sha1sum: 99b8f9f49078ef7e15280f5c73dff639a6e9472c | - |
| 0.5.0 | | 2013-03-27 | [libixion-0.5.0.tar.bz2](http://kohei.us/files/ixion/src/libixion-0.5.0.tar.bz2) | md5sum: ebaeab9ffe1e6bd68b2a20bfa430b3af
sha1sum: 99290ed5aa2ab2338ba04737210256c48885107c | - |
| 0.3.0 | | 2011-11-01 | [libixion_0.3.0.tar.bz2](http://kohei.us/files/ixion/src/libixion_0.3.0.tar.bz2) | md5sum: 96a36a0016f968a5a7c4b167eeb1643b
sha1sum: ac1fa915303ed8492ac50d6f0aa4d974e8405954 | - |
libixion-0.15.0/test/ 0000755 0001750 0001750 00000000000 13523106414 011371 5 0000000 0000000 libixion-0.15.0/test/03-expression.txt 0000644 0001750 0001750 00000000225 13302554105 014467 0000000 0000000 %% Test basic expressions.
%mode init
A1=1
A2=1+2-3*2
A3=1*2+1
A4=20*(3+4)
A5=(20*3)+4
%calc
%mode result
A1=1
A2=-3
A3=3
A4=140
A5=64
%check
%exit
libixion-0.15.0/test/thread/ 0000755 0001750 0001750 00000000000 13523106414 012640 5 0000000 0000000 libixion-0.15.0/test/thread/function-wait-simple.txt 0000644 0001750 0001750 00000000122 13302554105 017371 0000000 0000000 %mode init
A1=WAIT()+1
A2=WAIT()+20
A3=WAIT()+15
A4=(A2+A3)*2+WAIT()
%calc
%exit
libixion-0.15.0/test/thread/function-parallel.txt 0000644 0001750 0001750 00000000204 13302554105 016733 0000000 0000000 %mode init
A1=WAIT()+1
A2=WAIT()+20
A3=WAIT()+15
A4=WAIT()+5
A5=A1+A2+A3+A4+WAIT()
A6=(A2+A3)*2+WAIT()
A7=A5+A6+WAIT()
%calc
%exit
libixion-0.15.0/test/04-function-single.txt 0000644 0001750 0001750 00000000265 13302554105 015401 0000000 0000000 %% Test built-in functions.
%mode init
A1=MAX(10,20,5,36)
A2=MIN(10,20,5,36)
A3=AVERAGE(10,20,30,40)
A4=SUM(1,1,1)+SUM(4,5,6)
%calc
%mode result
A1=36
A2=5
A3=25
A4=18
%check
%exit
libixion-0.15.0/test/04-function-average.txt 0000644 0001750 0001750 00000000211 13302554105 015521 0000000 0000000 %% Test for AVERAGE function.
%mode init
A1=2
A2=4
A3=9
A4=AVERAGE(A1:A3)
A5=AVERAGE(A1,A2,A3)
%calc
%mode result
A4=5
A5=5
%check
%exit
libixion-0.15.0/test/parser-test-t7.sh 0000755 0001750 0001750 00000000227 13302554105 014451 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/04-function-logical.txt 0000644 0001750 0001750 00000000303 13302554105 015523 0000000 0000000 %% Test case for built-in logical functions.
%mode init
A1:2
A2:3
A3=if(A1=A2,"equal","not equal")
A4=if(A1<>A2,"not equal","equal")
%calc
%mode result
A3="not equal"
A4="not equal"
%check
%exit
libixion-0.15.0/test/parser-test-func.sh 0000755 0001750 0001750 00000000256 13302554105 015054 0000000 0000000 #!/usr/bin/env bash
exec_test()
{
PROGDIR=`dirname $0`
SRCDIR=$PROGDIR/../src
export PATH=$SRCDIR:$SRCDIR/.libs:$PATH
ixion-parser -t $1 $PROGDIR/*.txt
}
libixion-0.15.0/test/parser-test-t1.sh 0000755 0001750 0001750 00000000227 13302554105 014443 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/parser-test-t4.sh 0000755 0001750 0001750 00000000227 13302554105 014446 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/python/ 0000755 0001750 0001750 00000000000 13523106414 012712 5 0000000 0000000 libixion-0.15.0/test/python/document.py 0000755 0001750 0001750 00000017552 13302554105 015036 0000000 0000000 #!/usr/bin/env python3
import unittest
import ixion
class DocumentTest(unittest.TestCase):
def setUp(self):
self.doc = ixion.Document()
def test_append_sheets(self):
tests = (
"Normal", # normal name
"First Sheet", # white space
"Laura's", # single quote
'"Quoted"' # double quote
)
sheets = []
for test in tests:
sh = self.doc.append_sheet(test)
sheets.append(sh)
for test, sheet in zip(tests, sheets):
self.assertEqual(test, sheet.name)
self.assertEqual(tests, self.doc.sheet_names)
for i, test in enumerate(tests):
# get sheet by index.
sh = self.doc.get_sheet(i)
self.assertEqual(test, sh.name)
for test in tests:
# get sheet by name.
sh = self.doc.get_sheet(test)
self.assertEqual(test, sh.name)
try:
sheets[0].name = "Try to change sheet name"
self.assertTrue(False, "sheet name attribute should not be writable.")
except AttributeError:
pass # AttributeError is expected when attempting to overwrite sheet name attribute.
except:
self.assertTrue(False, "Wrong exception has been raised")
# Trying to insert a new sheet with an existing name should fail.
try:
sh = self.doc.append_sheet(tests[0])
self.assertTrue(False, "Trying to insert a new sheet with an existing sheet name should fail")
except ixion.DocumentError:
# This is expected.
pass
def test_numeric_cell_input(self):
sh1 = self.doc.append_sheet("Data")
# Empty cell should yield a value of 0.0.
check_val = sh1.get_numeric_value(0, 0)
self.assertEqual(0.0, check_val)
tests = (
# row, column, value
(3, 1, 11.2),
(4, 1, 12.0),
(6, 2, -12.0),
(6, 3, 0.0)
)
for test in tests:
sh1.set_numeric_cell(test[0], test[1], test[2]) # row, column, value
check_val = sh1.get_numeric_value(column=test[1], row=test[0]) # swap row and column
self.assertEqual(test[2], check_val)
def test_string_cell_input(self):
sh1 = self.doc.append_sheet("Data")
# Empty cell should yield an empty string.
check_val = sh1.get_string_value(0, 0)
self.assertEqual("", check_val)
tests = (
# row, column, value
(0, 0, "normal string"), # normal string
(1, 0, "A1+B1"), # string that looks like a formula expression
(2, 0, "'single quote'"), # single quote
(3, 0, "80's music"), # single quote
(4, 0, '"The" Music in the 80\'s'), # single and double quotes mixed
)
for test in tests:
sh1.set_string_cell(test[0], test[1], test[2]) # row, column, value
check_val = sh1.get_string_value(column=test[1], row=test[0]) # swap row and column
self.assertEqual(test[2], check_val)
def test_formula_cell_input(self):
sh1 = self.doc.append_sheet("Data")
sh1.set_formula_cell(0, 0, "12*3")
try:
val = sh1.get_numeric_value(0, 0)
self.assertTrue(False, "TypeError should have been raised")
except TypeError:
# TypeError is expected when trying to fetch numeric value from
# formula cell before it is calculated.
pass
self.doc.calculate()
val = sh1.get_numeric_value(0, 0)
self.assertEqual(12*3, val)
def test_formula_cell_recalc(self):
sh1 = self.doc.append_sheet("Data")
sh1.set_numeric_cell(0, 0, 1.0)
sh1.set_numeric_cell(1, 0, 2.0)
sh1.set_numeric_cell(2, 0, 4.0)
sh1.set_formula_cell(3, 0, "SUM(A1:A3)")
# initial calculation
self.doc.calculate()
val = sh1.get_numeric_value(3, 0)
self.assertEqual(7.0, val)
# recalculation
sh1.set_numeric_cell(1, 0, 8.0)
self.doc.calculate()
val = sh1.get_numeric_value(3, 0)
self.assertEqual(13.0, val)
# add another formula cell and recalc.
sh1.set_formula_cell(0, 1, "A1+15")
sh1.set_numeric_cell(0, 0, 0.0)
self.doc.calculate()
val = sh1.get_numeric_value(0, 1)
self.assertEqual(15.0, val)
val = sh1.get_numeric_value(3, 0)
self.assertEqual(12.0, val)
def test_formula_cell_recalc2(self):
sh1 = self.doc.append_sheet("Data")
sh1.set_numeric_cell(4, 1, 12.0) # B5
sh1.set_formula_cell(5, 1, "B5*2")
sh1.set_formula_cell(6, 1, "B6+10")
self.doc.calculate()
val = sh1.get_numeric_value(4, 1)
self.assertEqual(12.0, val)
val = sh1.get_numeric_value(5, 1)
self.assertEqual(24.0, val)
val = sh1.get_numeric_value(6, 1)
self.assertEqual(34.0, val)
# Delete B5 and check.
sh1.erase_cell(4, 1)
self.doc.calculate()
val = sh1.get_numeric_value(4, 1)
self.assertEqual(0.0, val)
val = sh1.get_numeric_value(5, 1)
self.assertEqual(0.0, val)
val = sh1.get_numeric_value(6, 1)
self.assertEqual(10.0, val)
def test_formula_cell_threaded_recalc(self):
sh1 = self.doc.append_sheet("Data")
sh1.set_numeric_cell(0, 0, 1.1)
sh1.set_numeric_cell(1, 0, 1.2)
sh1.set_numeric_cell(2, 0, 1.3)
sh1.set_formula_cell(0, 1, "SUM(A1:A3)")
sh1.set_formula_cell(1, 1, "B1*2")
self.doc.calculate(threads=2)
# Check the value in B1.
v = sh1.get_numeric_value(0, 1)
v = round(v, 1)
self.assertEqual(v, 3.6)
# Check the value in B2.
v = sh1.get_numeric_value(1, 1)
v = round(v, 1)
self.assertEqual(v, 7.2)
def test_formula_cell_string(self):
sh1 = self.doc.append_sheet("MyData")
sh1.set_string_cell(1, 1, "My precious string") # B2
sh1.set_formula_cell(1, 2, "B2") # C2
sh1.set_formula_cell(2, 2, "concatenate(B2, \" is here\")") # C3
self.doc.calculate()
self.assertEqual("My precious string", sh1.get_string_value(1, 1))
self.assertEqual("My precious string", sh1.get_string_value(1, 2))
self.assertEqual("My precious string is here", sh1.get_string_value(2, 2))
def test_detached_sheet(self):
# You can't set values to a detached sheet that doesn't belong to a
# Document object.
sh = ixion.Sheet()
try:
sh.set_numeric_cell(1, 1, 12)
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
sh.set_string_cell(2, 2, "String")
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
sh.set_formula_cell(2, 2, "A1")
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
sh.erase_cell(2, 1)
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
val = sh.get_numeric_value(2, 1)
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
s = sh.get_string_value(2, 1)
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
try:
expr = sh.get_formula_expression(2, 1)
self.assertTrue(False, "failed to raise a SheetError.")
except ixion.SheetError:
pass # expected
if __name__ == '__main__':
unittest.main()
libixion-0.15.0/test/python/module.py 0000755 0001750 0001750 00000002106 13302554105 014472 0000000 0000000 #!/usr/bin/env python3
import unittest
import ixion
class ModuleTest(unittest.TestCase):
def test_column_label(self):
# Get a single label.
labels = ixion.column_label(0, 1)
self.assertEqual(1, len(labels))
self.assertEqual('A', labels[0])
# Get multiple labels.
labels = ixion.column_label(2, 10)
self.assertEqual(8, len(labels))
self.assertEqual(labels, ('C','D','E','F','G','H','I','J'))
# The following start, stop combos should individually raise IndexError.
tests = (
(2, 2),
(2, 0),
(-1, 10)
)
for test in tests:
with self.assertRaises(IndexError):
labels = ixion.column_label(test[0], test[1])
# Keyword arguments should work.
labels = ixion.column_label(start=2, stop=4)
self.assertEqual(labels, ('C','D'))
# Get labels in R1C1.
labels = ixion.column_label(5, 10, 2)
self.assertEqual(labels, ('6','7','8','9','10'))
if __name__ == '__main__':
unittest.main()
libixion-0.15.0/test/parser-test-t8.sh 0000755 0001750 0001750 00000000227 13302554105 014452 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/12-inline-string-01.txt 0000644 0001750 0001750 00000000542 13302554105 015272 0000000 0000000 %% Test for inline string support.
%mode init
A1="A"
A2="n"
A3="d"
A4="y"
A5=A1
A6=LEN("test")
A7=LEN(123)
A8=LEN(123.45)
A9="Andy"
A10=A9
A11=LEN(A9)
A12=CONCATENATE(A1,A2,A3,A4)
A13=CONCATENATE(A12," is smart")
%calc
%mode result
A1="A"
A2="n"
A3="d"
A4="y"
A5="A"
A6=4
A7=3
A8=6
A9="Andy"
A10="Andy"
A11=4
A12="Andy"
A13="Andy is smart"
%check
%exit
libixion-0.15.0/test/parser-test-t2.sh 0000755 0001750 0001750 00000000227 13302554105 014444 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/parser-test-t3.sh 0000755 0001750 0001750 00000000227 13302554105 014445 0000000 0000000 #!/usr/bin/env bash
PROGDIR=`dirname $0`
source $PROGDIR/parser-test-func.sh
THREAD=`echo $0 | sed -e 's/.*t\([0-9]\)\.sh/\1/g'`
exec_test $THREAD
libixion-0.15.0/test/13-relational-operators-01.txt 0000644 0001750 0001750 00000000410 13302554105 016651 0000000 0000000 %% Test for relational operators with in-line numbers.
%mode init
A1=1=1
A2=1=2
A3=1*6=2*3
A4=1<4
A5=4<4
A6=4>2
A7=68>123
A8=1>=1
A9=1>=2
A10=2>=0
A11=45<>12
A12=33<>33
%calc
%mode result
A1=1
A2=0
A3=1
A4=1
A5=0
A6=1
A7=0
A8=1
A9=0
A10=1
A11=1
A12=0
%check
%exit
libixion-0.15.0/test/09-string-cells.txt 0000644 0001750 0001750 00000000260 13302554105 014703 0000000 0000000 %% Test for parsing string cells.
%mode init
A1@Andy
B1@Bruce
C1@Charles
D1@David
A2=A1
%calc
%mode result
A1="Andy"
B1="Bruce"
C1="Charles"
D1="David"
A2="Andy"
%check
%exit
libixion-0.15.0/test/13-relational-operators-03.txt 0000644 0001750 0001750 00000000261 13302554105 016657 0000000 0000000 %% Test for relational operators with strings.
%mode init
A1@A
A2@B
A3@C
B1=A1=A1
B2=A1>=A1
B3=A2<=A2
B4=A1>A2
B5=A1