mirror of https://github.com/Gnucash/gnucash
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.
454 lines
16 KiB
454 lines
16 KiB
<article id="xacc-repdev">
|
|
<artheader>
|
|
<title>Report Development</title>
|
|
<author>
|
|
<firstname>Christopher</firstname>
|
|
<surname>Browne</surname>
|
|
</author>
|
|
</artheader>
|
|
|
|
<sect1 id="xacc-reportdev"> <title>Report Development</title>
|
|
|
|
<para> <application>GnuCash</application> reports are generated in a
|
|
manner that is quite reminiscent of web <ulink
|
|
url="http://hoohoo.ncsa.uiuc.edu/cgi/"> CGI </ulink>; it involves:
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem><para> Writing programs that generate "option dialogs."
|
|
</para>
|
|
|
|
<para> Unlike the CGI approach, <application>GnuCash</application>
|
|
options wind up directly generating GNOME widgets, and do not use HTML
|
|
FORM tagging. The similarities are nonetheless quite conspicuous as
|
|
the options systems both involve:
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem><para> Building a property list that describes options and
|
|
default values;</para></listitem>
|
|
|
|
<listitem><para> Submitting that property list for user
|
|
input;</para></listitem>
|
|
|
|
<listitem><para> Returning another property list containing the user's
|
|
input.</para></listitem>
|
|
|
|
</itemizedlist></para></listitem>
|
|
|
|
<listitem><para> Writing programs that pull data from the application,
|
|
based on the option property list, and then generate HTML
|
|
output.</para>
|
|
|
|
<para> Many web applications produce HTML of questionable integrity
|
|
since the developer generates HTML in the slap-happy manner of just
|
|
printing out strings that might contain any sort of "pseudo-tagged"
|
|
data. In contrast, <application>GnuCash</application> uses an
|
|
HTML-oriented record structure which makes it harder to build
|
|
<emphasis> bad </emphasis> HTML than it is to build valid
|
|
HTML. </para></listitem>
|
|
|
|
</itemizedlist></para>
|
|
|
|
<para> This presentation will assume that the reader is already
|
|
generally familiar with Scheme, particularly with the following
|
|
concepts:
|
|
|
|
<itemizedlist>
|
|
<listitem><para> Binding values and functions using
|
|
<function>define</function></para>
|
|
<para> As with:
|
|
<screen>
|
|
(define *value-x* 25)
|
|
(define *value-y* 30)
|
|
(define *string-a* "Here's a string!")
|
|
(define *string-b* "A string with embedded quote \" ")
|
|
(define (add-1 x)
|
|
(+ x 1))
|
|
</screen></para>
|
|
</listitem>
|
|
<listitem><para> Defining local bindings using
|
|
<function id="let">let</function> and <function>let</function>
|
|
</para>
|
|
<screen>
|
|
> (define let-to-hide-stuff
|
|
(lambda (x y z)
|
|
(let ((x 25);; Local binding of x inside the let
|
|
(w 11))
|
|
(display "Inside let, x is:") (display x) (newline)
|
|
(display "And w only exists inside the let as ") (display w)
|
|
(newline)
|
|
(display "Add x, y, z, w inside let:")
|
|
(display (+ x y z w)) (newline))
|
|
(display "Now, outside the let:") (newline)
|
|
(display "x is:") (display x) (newline)
|
|
(display "y is:") (display y) (newline)
|
|
(display "z is:") (display z) (newline)
|
|
(display "w is:") (display w) (newline)))
|
|
> (let-to-hide-stuff 10 20 30)
|
|
> (let-to-hide-stuff 10 20 30)
|
|
Inside let, x is:25
|
|
And w only exists inside the let as 11
|
|
Add x, y, z, w inside let:86
|
|
Now, outside the let:
|
|
x is:10
|
|
y is:20
|
|
z is:30
|
|
reference to undefined identifier: w
|
|
w is:>
|
|
</screen>
|
|
|
|
</listitem>
|
|
<listitem><para> Defining lists using <function>list</function></para>
|
|
<para> As with:
|
|
<screen>
|
|
> (list 1 2 3 "four" 'five)
|
|
(1 2 3 "four" five)
|
|
> '(1 2 3 "four" five)
|
|
(1 2 3 "four" five)
|
|
</screen></para>
|
|
</listitem>
|
|
<listitem><para> Representing anonymous functions using
|
|
<function>lambda</function></para>
|
|
|
|
<para> The <function>add-1</function> function shown earlier is
|
|
<emphasis>actually</emphasis> defined thus:
|
|
<screen>
|
|
(define add-1
|
|
(lambda (x) (+ x 1)))
|
|
</screen></para>
|
|
</listitem>
|
|
<listitem><para> The <function>if</function> control structure</para>
|
|
</listitem>
|
|
|
|
<listitem><para> Using <function>map</function> to apply a function to all the
|
|
members of a list, producing another list, as with:
|
|
|
|
<screen>
|
|
> (map (lambda (x) (+ x 1)) '(1 2 3 4))
|
|
(2 3 4 5)
|
|
</screen>
|
|
or
|
|
<screen>
|
|
> (map (lambda (s n) (string-append s ":" (number->string n)))
|
|
'("one" "two" "three" "four") '(1 2 3 4))
|
|
("one:1" "two:2" "three:3" "four:4")
|
|
</screen></para></listitem>
|
|
|
|
</itemizedlist></para>
|
|
|
|
<para> If the meanings of some of these are not too clear, see the
|
|
references in the section on <link linkend="xacc-schemedocs"> Scheme
|
|
Documentation, </link> and try running code such as the above using
|
|
Guile.</para>
|
|
|
|
<sect2> <title>Report Overview</title>
|
|
|
|
<para> A GnuCash report is added to the system using the function
|
|
<function>gnc:define-report</function>, to which one passes the
|
|
following parameters:
|
|
|
|
<itemizedlist>
|
|
<listitem><para> <structfield>version</structfield></para>
|
|
|
|
<para> This is a version number; not presently used to do anything
|
|
particularly clever.</para></listitem>
|
|
|
|
<listitem><para> <structfield>name</structfield></para>
|
|
|
|
<para> This is a <emphasis>not-translated</emphasis> name for the
|
|
report that is notably used to provide the name that will be added to
|
|
the <application>GnuCash</application> reporting
|
|
menu.</para></listitem>
|
|
|
|
<listitem><para> <link linkend="options-generator"> <structfield>
|
|
options-generator </structfield> </link>
|
|
</para>
|
|
|
|
<para> This argument is a function which takes no parameters, and
|
|
generates a set of options that will be used to display a dialog that
|
|
the user can use to select values for the report's parameters.</para>
|
|
|
|
<para> This will typically include things like date ranges and lists
|
|
of accounts, to determine what data should be selected for the report,
|
|
as well as other controls that determine how the data will be
|
|
rendered. </para>
|
|
</listitem>
|
|
|
|
<listitem><para> <link linkend="renderer">
|
|
<structfield>renderer</structfield>
|
|
</link>
|
|
</para>
|
|
|
|
<para> The "renderer" does the work of generating the report. It
|
|
accepts a "database" of options, as generated by the <structfield>
|
|
options-generator </structfield>, pulls the corresponding data from
|
|
the <application>GnuCash</application> engine, and generates, as
|
|
output, an object of type <sgmltag> html-document </sgmltag>, which is
|
|
what the <application>GnuCash</application> report engine knows how to
|
|
display.</para></listitem>
|
|
|
|
</itemizedlist></para>
|
|
|
|
<para> A trivial example might look like the following:
|
|
|
|
<informalexample>
|
|
<screen>
|
|
(let ()
|
|
(define (options-generator)
|
|
(let ((options (gnc:new-options))) ;;; Create new option list
|
|
options)) ;;; Return it
|
|
(define (renderer options)
|
|
(let ((document (gnc:make-html-document)))
|
|
document)) ;;; Generates an empty document
|
|
(gnc:define-report
|
|
'version 1
|
|
'name (N_ "Trivial Example")
|
|
'options-generator options-generator
|
|
'renderer renderer))
|
|
</screen>
|
|
</informalexample></para>
|
|
|
|
<para> Note that reports are typically defined inside a <link linkend="let"> <function> let </function> </link> environment; the "work
|
|
functions" will thereby all be invisible to code outside the
|
|
<function> let </function> environment, which means you won't need to
|
|
worry about coming up with unique function names. Only the report
|
|
name forcibly needs to be unique. </para>
|
|
|
|
<para> So long as each report is similarly wrapped in a <function> let
|
|
</function> environment, you could call <emphasis> all </emphasis> of
|
|
the "rendering" functions <function> rendition </function> without
|
|
causing any conflicts.</para>
|
|
</sect2>
|
|
|
|
<sect2 id="options-generator"> <title>The options-generator
|
|
function</title>
|
|
|
|
<para> The options generator introduces a number of additional
|
|
functions that are used to set up option dialogs.
|
|
|
|
<screen>
|
|
(define (options-generator)
|
|
(let ((option-set (gnc:new-options))) <co id="newopts">
|
|
(gnc:options-add-report-date! <co id="optdate">
|
|
option-set pagename-general "End Date" "a")
|
|
(gnc:options-add-date-interval! <co id="optdaterng">
|
|
option-set pagename-general "From" "To" "a")
|
|
(gnc:options-add-interval-choice! <co id="optint">
|
|
option-set pagename-general
|
|
"Step Size" "b" 'YearDelta)
|
|
(gnc:options-add-account-levels! <co id="optacl">
|
|
option-set pagename-general
|
|
"Show Accounts down to Level" "c" 2)
|
|
(gnc:options-add-account-selection! <co id="optacc">
|
|
option-set pagename-general "Account Display Depth"
|
|
"Always show subaccounts" "Accounts" "a" 3
|
|
*LIST-OF-INCOME-AND-EXPENSE-ACCOUNTS*
|
|
options))
|
|
</screen>
|
|
<calloutlist>
|
|
|
|
<callout arearefs="newopts"> <para>
|
|
<function>gnc:new-options</function> creates a new, empty set of
|
|
options. This has to be run first; the later functions need to have
|
|
an option set to refer to.</para> </callout>
|
|
|
|
<callout arearefs="optdate"> <para>
|
|
<function>gnc:options-add-report-date!</function> adds a selection
|
|
option that indicates a single date, generally used as the end
|
|
date.</para> </callout>
|
|
|
|
<callout arearefs="optdaterng"> <para>
|
|
<function>gnc:options-add-date-interval!</function> adds in a
|
|
selection option that allows specifying a range of
|
|
dates.</para></callout>
|
|
|
|
<callout arearefs="optint"> <para>
|
|
<function>gnc:options-add-interval-choice!</function> adds a selection
|
|
option that allows choosing between various time intervals, including
|
|
days, weeks, two-week periods, months, and years.</para></callout>
|
|
|
|
<callout arearefs="optacl"> <para> <function>
|
|
gnc:options-add-account-levels!</function> adds an option indicating
|
|
how deep a set of account levels should be shown.</para></callout>
|
|
|
|
<callout arearefs="optacc"> <para> <function>
|
|
gnc:options-add-account-selection!</function> allows selecting a set
|
|
of accounts.</para> <para> Note that the last argument is a list of
|
|
accounts from which to select, which means <link linkend="filtacc">
|
|
filtering a list of relevant accounts, </link> at some point.</para>
|
|
</callout> </calloutlist></para>
|
|
|
|
<para> There are also additional option functions:
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem><para> <function>gnc:options-add-currency!</function> to
|
|
select a currency;</para></listitem>
|
|
|
|
<listitem><para> <function>gnc:options-add-plot-size!</function> to
|
|
control how a graphical plot should be sized.</para></listitem>
|
|
</itemizedlist></para>
|
|
|
|
<para> Underlying these are the following base "option generator"
|
|
functions defined in <filename>options-utilities.scm</filename> that
|
|
may be used to create new kinds of options:
|
|
|
|
<itemizedlist>
|
|
<listitem><para> <function>gnc:register-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-date-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-multichoice-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-simple-boolean-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-account-list-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-currency-option</function></para></listitem>
|
|
<listitem><para> <function>gnc:make-number-range-option</function></para></listitem>
|
|
</itemizedlist></para></sect2>
|
|
|
|
<sect2 id="accessing-data"> <title>Accessing GnuCash Data</title>
|
|
|
|
<para> There are several forms of data that you may wish to access:
|
|
</para>
|
|
<itemizedlist>
|
|
<listitem><para> <link linkend="filtacc"> Accounting Info </link></para></listitem>
|
|
<listitem><para> <link linkend="stockprices"> Stock Prices </link></para></listitem>
|
|
<listitem><para> <link linkend="accoption"> Reading Option Data </link></para></listitem>
|
|
</itemizedlist>
|
|
|
|
<sect3 id="filtacc"> <title> Accessing Engine Data </title>
|
|
|
|
<para> The functions used to access the various forms of accounting
|
|
data may be found in the file <filename> src/g-wrap/gnc.html
|
|
</filename>. </para>
|
|
|
|
<note> <para> Need some examples here... </para> </note></sect3>
|
|
|
|
<sect3 id="accoption"> <title>Accessing Option Data</title>
|
|
|
|
<para> Functions <function>gnc:lookup-option</function>,
|
|
<function>gnc:report-options</function>, and
|
|
<function>gnc:option-value</function> are the crucial functions you
|
|
are likely to use from <filename> src/scm/options.scm
|
|
</filename></para>
|
|
|
|
<para> Exerpted from <filename> src/scm/report/hello-world.scm
|
|
</filename> is the following:
|
|
|
|
<screen>
|
|
;; First, build some helper functions for looking up option values.
|
|
(define (get-op section name)
|
|
(gnc:lookup-option (gnc:report-options report-obj) section name))
|
|
|
|
(define (op-value section name)
|
|
(gnc:option-value (get-op section name)))
|
|
|
|
;; The next thing we do is make local variables for all the specific
|
|
;; options in the set of options given to the function.
|
|
(let
|
|
((bool-val (op-value "Hello, World!" "Boolean Option"))
|
|
(mult-val (op-value "Hello, World!" "Multi Choice Option"))
|
|
(string-val (op-value "Hello, World!" "String Option"))
|
|
(date-val (gnc:date-option-absolute-time
|
|
(op-value "Hello, World!" "Just a Date Option")))
|
|
(date2-val (gnc:date-option-absolute-time
|
|
(op-value "Hello, World!" "Time and Date Option")))
|
|
(rel-date-val (gnc:date-option-absolute-time
|
|
(op-value "Hello, World!" "Relative Date Option")))
|
|
(combo-date-val (gnc:date-option-absolute-time
|
|
(op-value "Hello, World!" "Combo Date Option")))
|
|
(num-val (op-value "Hello, World!" "Number Option"))
|
|
(bg-color-op (get-op "Hello, World!" "Background Color"))
|
|
(txt-color-op (get-op "Hello, World!" "Text Color"))
|
|
(accounts (op-value "Hello Again" "An account list option"))
|
|
(list-val (op-value "Hello Again" "A list option"))
|
|
(crash-val (op-value "Testing" "Crash the report")))
|
|
(now-do-stuff-with-options))</screen>
|
|
</para></sect3>
|
|
|
|
<sect3 id="stockprices"> <title> Stock Prices </title>
|
|
|
|
<warning>
|
|
<para> The stock price database is under construction, so it is a bit
|
|
early to get specific about this...</para> </warning></sect3></sect2>
|
|
|
|
<sect2 id="html-generation"> <title>HTML Generation functions</title>
|
|
|
|
<para> Reports are generated as a tree of <link linkend="guile"> Guile
|
|
</link> records, rooted by an <html-document> record, which
|
|
consists of style information, a title, and a list of
|
|
<html-object> records that consist of a rendition function and a
|
|
further list of objects.</para>
|
|
|
|
<para> We might generate a simple report thus:
|
|
<screen>
|
|
(define (build-simple-document)
|
|
(let* ((document (gnc:make-html-document))
|
|
;;; Here are a couple of helper functions
|
|
(addfpara (lambda (obj)
|
|
(gnc:html-document-add-object!
|
|
document
|
|
(gnc:make-html-text
|
|
(gnc:html-markup-p obj)))))
|
|
(addpara (lambda (text)
|
|
(addfpara
|
|
(gnc:html-markup/format text)))))
|
|
;;; Set the title
|
|
(gnc:html-document-set-title! document (_ "Simple List of Values"))
|
|
;;; Add in a paragraph of text
|
|
(addpara
|
|
(_ "This is a simple report, starting with a paragraph of text"))
|
|
(addpara
|
|
(_ "Next, we calculate random values, adding them to a balance."))
|
|
(let loop
|
|
((balance 0))
|
|
(if (< balance 500)
|
|
(let ((newamt (- (random 500 200)))) ;;; Random number
|
|
(addfpara
|
|
(gnc:html-markup/format
|
|
(_ "Another random adjustment of %s yields %s")
|
|
(gnc:html-markup-tt (number->string newamt))
|
|
(gnc:html-markup-b (number->string balance))))
|
|
(loop (+ balance newamt)))))
|
|
document)) ;;; Finally, return the document
|
|
</screen>
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="renderer"> <title>The rendition function</title>
|
|
|
|
<para> The rendition function is where the functions from the
|
|
preceding sections all come together.
|
|
|
|
<itemizedlist>
|
|
|
|
<listitem><para> It takes, as input, a "database" of options generated
|
|
once the user adds their input to the dialog produced by the <link linkend="options-generator"> options-generator
|
|
</link>.</para></listitem>
|
|
|
|
<listitem><para> It uses those options to control how it <link linkend="accessing-data"> accesses GnuCash data </link> </para>
|
|
</listitem>
|
|
|
|
<listitem><para> Finally, from that data, it <link linkend="html-generation"> generates a "database" of HTML
|
|
output. </link></para></listitem> </itemizedlist></para>
|
|
|
|
<para> The rendition function provides, as its return value, the
|
|
"database of HTML," which <application>GnuCash</application> then
|
|
displays in an HTML viewer.
|
|
|
|
<informalexample>
|
|
<screen>
|
|
(define (renderer options)
|
|
(let ((document (gnc:make-html-document)))
|
|
document)) ;;; Generates an empty document
|
|
</screen>
|
|
</informalexample></para></sect2>
|
|
<sect2> <title>For More Information</title>
|
|
|
|
<para> If you need more information, or have developed a new report
|
|
that may be of use to others, please contact the GnuCash development
|
|
mailing list at <email> gnucash-devel@gnucash.org </email>. </para>
|
|
|
|
<!-- Local variables: -->
|
|
<!-- sgml-parent-document: "gnucash.sgml" -->
|
|
<!-- End: --></sect2></sect1></article>
|