package XML::QOFQSF;
use warnings;
use strict;
use XML::Simple;
use XML::Writer;
use IO::File;
use Class::Struct;
use Date::Parse;
use Date::Format;
use Math::BigInt;
use Data::Random qw(:all);
require Exporter;
use vars qw (@ISA @EXPORT_OK);
@ISA = qw(Exporter);
@EXPORT_OK = qw(QSFParse QSFWrite);
struct (Account => {
"desc" => '$',
"account_type" => '$',
"code" => '$',
"notes" => '$',
"name" => '$',
"guid" => '$',
"parent_account" => 'Account',
"tax_related_p" => '$',
"non_standard_scu" => '$',
"smallest_commodity_unit" => '$',
"balance" => '$',
"rec_bal" => '$',
"p_acc" => '$',
});
struct (Trans => {
"desc" => '$',
"notes" => '$',
"num" => '$',
"guid" => '$',
"date_posted" => '$',
"date_entered" => '$',
"type" => '$',
"kvp_path" => '$',
"kvp_value" => '$',
"kvp_content" => '$',
});
struct (Split => {
"action" => '$',
"memo" => '$',
"guid" => '$',
"account" => 'Account',
"trans" => 'Trans',
"share_price" => '$',
"amount" => '$',
"date_reconciled" => '$',
"reconcile_flag" => '$',
"s_acc" => '$',
"s_trans" => '$',
});
struct (gncEntry => {
"discount_method" => '$',
"desc" => '$',
"action" => '$',
"notes" => '$',
"discount_type" => '$',
"guid" => '$',
"invoice_account" => 'Account',
"bill_to" => 'Account',
"invoice_taxable" => '$',
"billable" => '$',
"bill_tax_included" => '$',
"invoice_tax_included" => '$',
"bill_taxable" => '$',
"qty" => '$',
"bprice" => '$',
"iprice" => '$',
"date" => '$',
"date_entered" => '$',
"i_acc" => '$',
"b_acc" => '$',
});
struct (gncAddress => {
"city" => '$',
"street" => '$',
"fax" => '$',
"number" => '$',
"name" => '$',
"email" => '$',
"locality" => '$',
"phone" => '$',
"guid" => '$',
"a_owner" => '$',
"owner" => 'gncCustomer',
});
# problem: owner could be more than one type.
struct (gncCustomer => {
"id" => '$',
"notes" => '$',
"name" => '$',
"guid" => '$',
"addr" => 'gncAddress',
"shipaddr" => 'gncAddress',
"active" => '$',
"tax_table_override" => '$',
"amount_of_discount" => '$',
"amount_of_credit" => '$',
"c_addr" => '$',
"c_shipaddr" => '$',
});
struct (gncBillTerm => {
"description" => '$',
"name" => '$',
"bill_type" => '$',
"guid" => '$',
"amount_of_discount" => '$',
"cut_off" => '$',
"number_of_days_due" => '$',
"number_of_discounted_days" => '$',
});
struct (gncInvoice => {
"id" => '$',
"billing_id" => '$',
"notes" => '$',
"guid" => '$',
"terms" => 'gncBillTerm',
"account" => 'Account',
"posted_txn" => 'Trans',
"list_of_entries" => 'gncEntry',
"active" => '$',
"date_posted" => '$',
"date_opened" => '$',
"i_terms" => '$',
"i_acc" => '$',
"i_posted" => '$',
"i_entries" => '@',
});
struct (gncJob => {
"id" => '$',
"reference" => '$',
"name" => '$',
"guid" => '$',
"active" => '$',
});
struct (Expense => {
"form_of_payment" => '$',
"distance_unit" => '$',
"expense_vendor" => '$',
"expense_city" => '$',
"expense_attendees" => '$',
"category" => '$',
"expense_note" => '$',
"type_of_expense" => '$',
"guid" => '$',
"expense_amount" => '$',
"expense_date" => '$',
"currency_code" => '$',
"kvp_mnemonic" => '$',
"kvp_string" => '$',
"kvp_fraction" => '$',
# end of external values.
# numeric handlers
"amt_numerator" => '$',
"amt_denominator" => '$',
# kvp handlers
"kvp_key" => '$',
"kvp_prefix" => '$',
});
struct (Contact => {
"entryCity" => '$',
"entryCustom4" => '$',
"entryPhone1" => '$',
"entryZip" => '$',
"entryLastname" => '$',
"entryPhone2" => '$',
"entryNote" => '$',
"category" => '$',
"entryFirstname" => '$',
"entryPhone3" => '$',
"entryTitle" => '$',
"entryPhone4" => '$',
"entryCompany" => '$',
"entryPhone5" => '$',
"entryState" => '$',
"entryCustom1" => '$',
"entryAddress" => '$',
"entryCustom2" => '$',
"entryCountry" => '$',
"entryCustom3" => '$',
"guid" => '$',
});
struct (Appointment => {
"category" => '$',
"note" => '$',
"repeat_type" => '$',
"description" => '$',
"advance_unit" => '$',
"repeat_day" => '$',
"repeat_week_start" => '$',
"guid" => '$',
"use_alarm" => '$',
"repeat_forever" => '$',
"transient_repeat" => '$',
"untimed_event" => '$',
"start_time" => '$',
"end_time" => '$',
"repeat_end" => '$',
"repeat_frequency" => '$',
"exception_count" => '$',
"alarm_advance" => '$',
});
struct (ToDo => {
"todo_note" => '$',
"todo_description" => '$',
"category" => '$',
"guid" => '$',
"date_due" => '$',
"todo_priority" => '$',
"todo_complete" => '$',
"todo_length" => '$',
});
# %objects is the meta-data: the sequence and type of data.
my %objects = ();
# @foo_seq is the sequence of each field in the XML or database.
my @todo_seq = (
[ { 'todo_note' => 'string' } ],
[ { 'category' => 'string' } ],
[ { 'todo_description' => 'string' } ],
[ { 'guid' => 'guid' } ],
[ { 'date_due' => 'time' } ],
[ { 'todo_priority' => 'gint32' } ],
[ { 'todo_complete' => 'gint32' } ],
[ { 'todo_length' => 'gint32' } ],
);
my @exp_seq = (
[ { 'form_of_payment' => 'string' } ],
[ { 'distance_unit' => 'string' } ],
[ { 'expense_vendor' => 'string' } ],
[ { 'expense_city' => 'string' } ],
[ { 'expense_attendees' => 'string' } ],
[ { 'category' => 'string' } ],
[ { 'expense_note' => 'string' } ],
[ { 'type_of_expense' => 'string' } ],
[ { 'guid' => 'guid' } ],
[ { 'expense_amount' => 'numeric' } ],
[ { 'expense_date' => 'time' } ],
[ { 'currency_code' => 'gint32' } ],
[ { 'kvp_mnemonic' => 'string' } ],
[ { 'kvp_string' => 'string' } ],
[ { 'kvp_fraction' => 'gint64' } ],
);
my @app_seq = (
[ { 'category' => 'string' } ],
[ { 'note' => 'string' } ],
[ { 'repeat_type' => 'string' } ],
[ { 'description' => 'string' } ],
[ { 'advance_unit' => 'string' } ],
[ { 'repeat_day' => 'string' } ],
[ { 'repeat_week_start' => 'string' } ],
[ { 'guid' => 'guid' } ],
[ { 'use_alarm' => 'boolean' } ],
[ { 'repeat_forever' => 'boolean' } ],
[ { 'transient_repeat' => 'boolean' } ],
[ { 'untimed_event' => 'boolean' } ],
[ { 'start_time' => 'time' } ],
[ { 'end_time'=> 'time' } ],
[ { 'repeat_end' => 'time' } ],
[ { 'repeat_frequency' => 'gint32' } ],
[ { 'exception_count' => 'gint32' } ],
[ { 'alarm_advance' => 'gint32' } ],
);
my @addr_seq = (
[ { 'entryCity' => 'string' } ],
[ { 'entryCustom4' => 'string' } ],
[ { 'entryPhone1' => 'string' } ],
[ { 'entryZip' => 'string' } ],
[ { 'entryLastname' => 'string' } ],
[ { 'entryPhone2' => 'string' } ],
[ { 'entryNote' => 'string' } ],
[ { 'category' => 'string' } ],
[ { 'entryFirstname' => 'string' } ],
[ { 'entryPhone3' => 'string' } ],
[ { 'entryTitle' => 'string' } ],
[ { 'entryPhone4' => 'string' } ],
[ { 'entryCompany' => 'string' } ],
[ { 'entryPhone5' => 'string' } ],
[ { 'entryState' => 'string' } ],
[ { 'entryCustom1' => 'string' } ],
[ { 'entryAddress' => 'string' } ],
[ { 'entryCustom2' => 'string' } ],
[ { 'entryCountry' => 'string' } ],
[ { 'entryCustom3' => 'string' } ],
[ { 'guid' => 'guid' } ],
);
# todo : add the rest of the objects.
$objects{'pilot_todo'} = \@todo_seq;
$objects{'pilot_address'} = \@addr_seq;
$objects{'pilot_datebook'} = \@app_seq;
$objects{'pilot_expenses'} = \@exp_seq;
$objects{'gpe_expenses'} = \@exp_seq;
# %object_list is the instance data
my %object_list;
my (@expenses, @contacts, @appointments, @splits, @accounts,
@transactions, @gncinvoices, @gnccustomers, @gncbillterms,
@gncaddresses, @gncentries, @gncjobs, @todos );
my $build = sub
{
my $doc = shift;
@expenses = @contacts = @appointments = @splits = @accounts = ();
@transactions = @gncinvoices = @gnccustomers = @gncbillterms = ();
@gncaddresses = @gncentries = @gncjobs = @todos = ();
foreach my $key (keys (%{$doc->{book}})){
next if ($key ne "object");
my @object = (@{$doc->{book}->{object}});
foreach my $g (@object)
{
if (($g->{type} eq 'pilot_expenses') or ($g->{type} eq 'gpe_expenses'))
{
my $e = new Expense;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$e->form_of_payment ($s->{'content'})
if ($s->{'type'} eq 'form_of_payment');
$e->distance_unit ($s->{'content'})
if ($s->{'type'} eq 'distance_unit');
$e->expense_vendor ($s->{'content'})
if ($s->{'type'} eq 'expense_vendor');
$e->expense_city ($s->{'content'})
if ($s->{'type'} eq 'expense_city');
$e->expense_attendees ($s->{'content'})
if ($s->{'type'} eq 'expense_attendees');
$e->category ($s->{'content'})
if ($s->{'type'} eq 'category');
$e->expense_note ($s->{'content'})
if ($s->{'type'} eq 'expense_note');
$e->type_of_expense ($s->{'content'})
if ($s->{'type'} eq 'type_of_expense');
}
my $guids = $g->{'guid'};
foreach my $s (@$guids)
{
$e->guid ($s->{'content'})
if ($s->{'type'} eq 'guid');
}
$e->expense_amount(eval($g->{'numeric'}->{content}));
$e->expense_date(str2time($g->{'time'}->{content}));
$e->currency_code($g->{'gint32'}->{content});
my $kvps = $g->{'kvp'};
foreach my $s (@$kvps)
{
$e->kvp_mnemonic($s->{'content'})
if ($s->{'path'} eq 'expense/currency/mnemonic');
$e->kvp_string($s->{'content'})
if ($s->{'path'} eq 'expense/currency/symbol');
$e->kvp_fraction($s->{'content'})
if ($s->{'path'} eq 'expense/currency/fraction');
}
push @expenses, $e;
}
if ($g->{type} eq 'pilot_datebook')
{
my $d = new Appointment;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$d->category ($s->{'content'})
if ($s->{'type'} eq 'category');
$d->note ($s->{'content'})
if ($s->{'type'} eq 'note');
$d->repeat_type ($s->{'content'})
if ($s->{'type'} eq 'repeat_type');
$d->description ($s->{'content'})
if ($s->{'type'} eq 'description');
$d->advance_unit ($s->{'content'})
if ($s->{'type'} eq 'advance_unit');
$d->repeat_day ($s->{'content'})
if ($s->{'type'} eq 'repeat_day');
$d->repeat_week_start ($s->{'content'})
if ($s->{'type'} eq 'repeat_week_start');
}
my $guids = $g->{'guid'};
foreach my $s (@$guids)
{
$d->guid ($s->{'content'})
if ($s->{'type'} eq 'guid');
}
my $booleans = $g->{'boolean'};
foreach my $s (@$booleans)
{
$d->use_alarm ($s->{'content'})
if ($s->{'type'} eq 'use_alarm');
$d->repeat_forever ($s->{'content'})
if ($s->{'type'} eq 'repeat_forever');
$d->transient_repeat ($s->{'content'})
if ($s->{'type'} eq 'transient_repeat');
$d->untimed_event ($s->{'content'})
if ($s->{'type'} eq 'untimed_event');
}
my $times = $g->{'time'};
foreach my $s (@$times)
{
$d->start_time (str2time($s->{content}))
if ($s->{'type'} eq 'start_time');
$d->end_time (str2time($s->{content}))
if ($s->{'type'} eq 'end_time');
$d->repeat_end (str2time($s->{content}))
if ($s->{'type'} eq 'repeat_end');
}
my $ints = $g->{'gint32'};
foreach my $s (@$ints)
{
$d->repeat_frequency ($s->{content})
if ($s->{'type'} eq 'repeat_frequency');
$d->exception_count ($s->{content})
if ($s->{'type'} eq 'exception_count');
$d->alarm_advance($s->{content})
if ($s->{'type'} eq 'alarm_advance');
}
push @appointments, $d;
}
if ($g->{type} eq 'pilot_address')
{
my $c = new Contact;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$c->entryCity($s->{content})
if ($s->{'type'} eq 'entryCity');
$c->entryCustom4($s->{content})
if ($s->{'type'} eq 'entryCustom4');
$c->entryPhone1($s->{content})
if ($s->{'type'} eq 'entryPhone1');
$c->entryZip($s->{content})
if ($s->{'type'} eq 'entryZip');
$c->entryLastname($s->{content})
if ($s->{'type'} eq 'entryLastname');
$c->entryPhone2($s->{content})
if ($s->{'type'} eq 'entryPhone2');
$c->entryNote($s->{content})
if ($s->{'type'} eq 'entryNote');
$c->category($s->{content})
if ($s->{'type'} eq 'category');
$c->entryFirstname($s->{content})
if ($s->{'type'} eq 'entryFirstname');
$c->entryPhone3($s->{content})
if ($s->{'type'} eq 'entryPhone3');
$c->entryTitle($s->{content})
if ($s->{'type'} eq 'entryTitle');
$c->entryPhone4($s->{content})
if ($s->{'type'} eq 'entryPhone4');
$c->entryCompany($s->{content})
if ($s->{'type'} eq 'entryCompany');
$c->entryPhone5($s->{content})
if ($s->{'type'} eq 'entryPhone5');
$c->entryState($s->{content})
if ($s->{'type'} eq 'entryState');
$c->entryCustom1($s->{content})
if ($s->{'type'} eq 'entryCustom1');
$c->entryAddress($s->{content})
if ($s->{'type'} eq 'entryAddress');
$c->entryCustom2($s->{content})
if ($s->{'type'} eq 'entryCustom2');
$c->entryCountry($s->{content})
if ($s->{'type'} eq 'entryCountry');
$c->entryCustom3($s->{content})
if ($s->{'type'} eq 'entryCustom3');
}
my $guids = $g->{'guid'};
foreach my $s (@$guids)
{
$c->guid ($s->{'content'})
if ($s->{'type'} eq 'guid');
}
push @contacts, $c;
}
if ($g->{'type'} eq 'pilot_todo')
{
my $t = new ToDo;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$t->todo_note($s->{content})
if ($s->{'type'} eq 'todo_note');
$t->todo_description($s->{content})
if ($s->{'type'} eq 'todo_description');
$t->category($s->{content})
if ($s->{'type'} eq 'category');
}
my $guids = $g->{'guid'};
foreach my $s (@$guids)
{
$t->guid ($s->{'content'})
if ($s->{'type'} eq 'guid');
}
$t->date_due(str2time($g->{'time'}->{content}));
my $ints = $g->{'gint32'};
foreach my $s (@$ints)
{
$t->todo_priority($s->{content})
if ($s->{'type'} eq 'todo_priority');
$t->todo_complete($s->{content})
if ($s->{'type'} eq 'todo_complete');
$t->todo_length($s->{content})
if ($s->{'type'} eq 'todo_length');
}
push @todos, $t;
}
if ($g->{type} eq 'Trans')
{
my $t = new Trans;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$t->desc($s->{content})
if ($s->{'type'} eq 'desc');
$t->notes($s->{content})
if ($s->{'type'} eq 'notes');
$t->num($s->{content})
if ($s->{'type'} eq 'num');
}
my $guids = $g->{'guid'};
foreach my $s (@$guids)
{
$t->guid ($s->{'content'})
if ($s->{'type'} eq 'guid');
}
my $times = $g->{'time'};
foreach my $s (@$times)
{
$t->date_posted(str2time($s->{content}))
if ($s->{'type'} eq 'date_posted');
$t->date_entered(str2time($s->{content}))
if ($s->{'type'} eq 'date_entered');
}
$t->type($g->{'character'}->{content});
$t->kvp_path($g->{'kvp'}->{path});
$t->kvp_value($g->{'kvp'}->{value});
$t->kvp_content($g->{'kvp'}->{content});
push @transactions, $t;
}
if ($g->{type} eq 'Account')
{
my $a = new Account;
my $strings = $g->{'string'};
foreach my $s (@$strings)
{
$a->desc($s->{content})
if ($s->{'type'} eq 'desc');
$a->account_type($s->{content})
if ($s->{'type'} eq 'account_type');
$a->code($s->{content})
if ($s->{'type'} eq 'code');
$a->notes($s->{content})
if ($s->{'type'} eq 'notes');
$a->name($s->{content})
if ($s->{'type'} eq 'name');
}
my $check = @{$g->{'guid'}};
if ($check == 1)
{
$a->guid($g->{'guid'}->[0]->{content});
}
else
{
$a->guid($g->{'guid'}->[0]->{content});
$a->p_acc($g->{'guid'}->[1]->{content});
}
$a->tax_related_p($g->{'boolean'}->[0]->{content});
$a->non_standard_scu($g->{'boolean'}->[1]->{content});
$a->smallest_commodity_unit($g->{'gint32'}->{content});
push @accounts, $a;
}
if ($g->{type} eq 'Split')
{
my $s = new Split;
if ($g->{'string'}->[0]->{type} eq 'action') {
$s->action($g->{'string'}->[0]->{content});
$s->memo($g->{'string'}->[1]->{content});
}
else {
$s->action($g->{'string'}->[1]->{content});
$s->memo($g->{'string'}->[0]->{content});
}
if ($g->{'numeric'}->[0]->{type} eq 'share-price') {
# TODO: recreate the numeric for QSFWrite?
$s->share_price(eval($g->{'numeric'}->[0]->{content}));
$s->amount(eval($g->{'numeric'}->[1]->{content}));
}
else {
$s->share_price(eval($g->{'numeric'}->[1]->{content}));
$s->memo(eval($g->{'numeric'}->[0]->{content}));
}
$s->date_reconciled(str2time($g->{'date'}->{content}));
$s->reconcile_flag($g->{'character'}->{content});
if ($g->{'guid'}->[0]->{type} eq 'guid') {
$s->guid($g->{'guid'}->[0]->{content});
$s->s_acc($g->{'guid'}->[1]->{content});
$s->s_trans($g->{'guid'}->[2]->{content});
}
if ($g->{'guid'}->[1]->{type} eq 'guid') {
$s->guid($g->{'guid'}->[1]->{content});
$s->s_acc($g->{'guid'}->[0]->{content});
$s->s_trans($g->{'guid'}->[2]->{content});
}
if ($g->{'guid'}->[2]->{type} eq 'guid') {
$s->guid($g->{'guid'}->[2]->{content});
$s->s_acc($g->{'guid'}->[1]->{content});
$s->s_trans($g->{'guid'}->[0]->{content});
}
if ($g->{'guid'}->[0]->{type} eq 'account') {
$s->guid($g->{'guid'}->[2]->{content});
$s->s_acc($g->{'guid'}->[0]->{content});
$s->s_trans($g->{'guid'}->[1]->{content});
}
push @splits, $s;
}
if ($g->{type} eq 'gncEntry')
{
my $ge = new gncEntry;
$ge->discount_method($g->{'string'}->[0]->{content});
$ge->desc($g->{'string'}->[1]->{content});
$ge->action($g->{'string'}->[2]->{content});
$ge->notes($g->{'string'}->[3]->{content});
$ge->discount_type($g->{'string'}->[4]->{content});
$ge->guid($g->{'guid'}->[0]->{content});
$ge->i_acc($g->{'guid'}->[1]->{content});
$ge->b_acc($g->{'guid'}->[2]->{content});
$ge->invoice_taxable($g->{'boolean'}->[0]->{content});
$ge->billable($g->{'boolean'}->[1]->{content});
$ge->bill_tax_included($g->{'boolean'}->[2]->{content});
$ge->invoice_tax_included($g->{'boolean'}->[3]->{content});
$ge->bill_taxable($g->{'boolean'}->[4]->{content});
$ge->qty(eval($g->{'numeric'}->[0]->{content}));
$ge->bprice(eval($g->{'numeric'}->[1]->{content}));
$ge->iprice(eval($g->{'numeric'}->[2]->{content}));
$ge->date(str2time($g->{'time'}->[0]->{content}));
$ge->date_entered(str2time($g->{'time'}->[1]->{content}));
push @gncentries, $ge;
}
if ($g->{type} eq 'gncInvoice')
{
my $gi = new gncInvoice;
$gi->id($g->{'string'}->[0]->{content});
$gi->billing_id($g->{'string'}->[1]->{content});
$gi->notes($g->{'string'}->[2]->{content});
$gi->guid($g->{'guid'}->[0]->{content});
$gi->i_terms($g->{'guid'}->[1]->{content});
$gi->i_acc($g->{'guid'}->[2]->{content});
$gi->i_posted($g->{'guid'}->[3]->{content});
# list of entries is incomplete in the upstream QOF code.
# $gi->i_entries($g->{'guid'}->[4]->{content});
$gi->active($g->{'boolean'}->{content});
$gi->date_posted(str2time($g->{'date'}->[0]->{content}));
$gi->date_opened(str2time($g->{'date'}->[1]->{content}));
push @gncinvoices, $gi;
}
if ($g->{type} eq 'gncBillTerm')
{
my $gbt = new gncBillTerm;
$gbt->description($g->{'string'}->[0]->{content});
$gbt->name($g->{'string'}->[1]->{content});
$gbt->bill_type($g->{'string'}->[2]->{content});
$gbt->guid($g->{'guid'}->[0]->{content});
$gbt->amount_of_discount(eval($g->{'numeric'}->{content}));
$gbt->cut_off($g->{'gint32'}->[0]->{content});
$gbt->number_of_days_due($g->{'gint32'}->[1]->{content});
$gbt->number_of_discounted_days($g->{'gint32'}->[2]->{content});
push @gncbillterms, $gbt;
}
if ($g->{type} eq 'gncJob')
{
my $gj = new gncJob;
$gj->id($g->{'string'}->[0]->{content});
$gj->reference($g->{'string'}->[1]->{content});
$gj->name($g->{'string'}->[2]->{content});
$gj->guid($g->{'guid'}->[0]->{content});
$gj->active($g->{'boolean'}->{content});
# need an owner record
push @gncjobs, $gj;
}
if ($g->{type} eq 'gncCustomer')
{
my $gc = new gncCustomer;
$gc->id($g->{'string'}->[0]->{content});
$gc->notes($g->{'string'}->[1]->{content});
$gc->name($g->{'string'}->[2]->{content});
$gc->guid($g->{'guid'}->[0]->{content});
$gc->c_addr($g->{'guid'}->[1]->{content});
$gc->c_shipaddr($g->{'guid'}->[2]->{content});
$gc->active($g->{'boolean'}->[0]->{content});
$gc->tax_table_override($g->{'boolean'}->[1]->{content});
$gc->amount_of_discount(eval($g->{'numeric'}->[0]->{content}));
$gc->amount_of_credit(eval($g->{'numeric'}->[1]->{content}));
push @gnccustomers, $gc;
}
if ($g->{type} eq 'gncAddress')
{
my $ga = new gncAddress;
$ga->city($g->{'string'}->[0]->{content});
$ga->street($g->{'string'}->[1]->{content});
$ga->fax($g->{'string'}->[2]->{content});
$ga->number($g->{'string'}->[3]->{content});
$ga->name($g->{'string'}->[4]->{content});
$ga->email($g->{'string'}->[5]->{content});
$ga->locality($g->{'string'}->[6]->{content});
$ga->phone($g->{'string'}->[7]->{content});
$ga->guid($g->{'guid'}->[0]->{content});
$ga->a_owner($g->{'guid'}->[1]->{content});
push @gncaddresses, $ga;
}
}
}
# now cross-reference the guids
foreach my $splt (@splits)
{
foreach my $t (@transactions)
{
$splt->trans($t) if ($t->guid eq $splt->s_trans);
}
foreach my $a (@accounts)
{
$splt->account($a) if ($a->guid eq $splt->s_acc);
}
}
foreach my $t (@accounts)
{
foreach my $a (@accounts)
{
next if (!$t->p_acc);
$t->parent_account($a) if ($a->guid eq $t->p_acc);
}
}
foreach my $i (@gncinvoices)
{
foreach my $a (@accounts)
{
next if (!$i->i_acc);
$i->account($a) if ($a->guid eq $i->i_acc);
}
foreach my $t (@transactions)
{
$i->posted_txn($t) if ($t->guid eq $i->i_posted);
}
foreach my $b (@gncbillterms)
{
next if (!$i->i_terms);
$i->terms($b) if ($b->guid eq $i->i_terms);
}
# handle list of entries
}
foreach my $e (@gncentries)
{
foreach my $a (@accounts)
{
# $e->invoice_account($a) if ($a->guid eq $e->i_acc);
# $e->bill_to($a) if ($a->guid eq $e->b_acc);
}
}
foreach my $c (@gnccustomers)
{
foreach my $a (@gncaddresses)
{
$c->addr($a) if ($a->guid eq $c->c_addr);
$c->shipaddr($a) if ($a->guid eq $c->c_shipaddr);
# $a->owner($c) if ($c->guid eq $a->a_owner);
}
}
};
my $guid = sub
{
my @random_chars = rand_chars( set => 'numeric', min => 8, max => 8, shuffle => 1 );
my $r = join("", @random_chars);
@random_chars = rand_chars( set => 'numeric', min => 8, max => 8, shuffle => 1 );
$r .= join("", @random_chars);
@random_chars = rand_chars( set => 'numeric', min => 8, max => 8, shuffle => 1 );
$r .= join("", @random_chars);
@random_chars = rand_chars( set => 'numeric', min => 8, max => 8, shuffle => 1 );
$r .= join("", @random_chars);
@random_chars = rand_chars( set => 'numeric', min => 8, max => 8, shuffle => 1 );
$r .= join("", @random_chars);
my $x = Math::BigInt->new("$r");
my $g = $x->as_hex();
$g =~ s/^0x//;
$g =~ /([0-9a-f]{32})/;
return $1;
};
=head1 NAME
XML::QOFQSF - convert personal data to and from QSF XML files
Support for the QOF SQLite backend will be added in a separate module in due course.
=head1 VERSION
Version 0.05
=cut
our $VERSION = '0.05';
=head1 SYNOPSIS
Provides a single home for all QOF objects expressed as QSF XML. A similar
module for the SQLite backend is also planned. To have your QOF object included,
simply send me a sample QSF XML file. A script to create the content is also planned.
A little code snippet.
use XML::QOFQSF qw(QSFParse QSFWrite);
use Date::Parse;
use Date::Format;
my $file = "qsf-mileage.xml";
my %obj = QSFParse("$file");
my $expenses = $obj{'pilot_expenses'};
my $exp_count = @$expenses;
print "Status: $exp_count expenses\n";
my $template = "%A, %o %B %Y";
my $total_miles = 0;
foreach my $a (@$expenses)
{
if ($a->type_of_expense eq "Mileage")
{
$total_miles += $a->expense_amount;
print $a->expense_amount . " " . $a->distance_unit . " : ";
print $a->expense_vendor . " " . $a->expense_city;
print " on " . time2str($template, $a->expense_date) . "\n";
}
}
print "Total: $total_miles\n";
my $c = new Appointment;
$c->description("short summary");
$c->guid($guid_str); # QOF identifier like 5429307bcae611b59b4e2bedc77b2d68
$c->note("long description");
$c->repeat_type("repeatNone"); # dictated by pilot-qof source
$c->repeat_forever("false"); # boolean
$c->use_alarm("false"); # boolean
$c->untimed_event("false"); #boolean
$c->transient_repeat("false");
$c->start_time("2003-08-08T09:00:00Z");
$c->end_time("2003-08-08T17:30:00Z");
$c->repeat_end("2003-08-08T17:30:00Z");
$c->repeat_frequency(0);
$c->exception_count(0);
$c->alarm_advance(0);
my %obj;
my @datebook=();
push @datebook, $c;
$obj{'pilot_datebook'} = \@datebook;
QSFWrite(\%obj);
=head1 EXPORT
XML::QOFQSF exports two functions, QSFParse to parse a QSF XML file and
QSFWrite to write data to a new QSF XML file. QSFParse reads data from
the file into an array of objects of each supported type and references
to each array are added to the object_list hash using the object name
as the key. A similar hash can be passed to QSFWrite to generate a new
QSF XML file.
=head1 Query Object Framework (QOF)
QOF, the Query Object Framework, provides a set of C Language utilities
for performing generic structured complex queries on a set of data held
by a set of C/C++ objects. This framework is unique in that it does NOT
require SQL or any database at all to perform the query. Thus, it allows
programmers to add query support to their applications without having to
hook into an SQL Database.
Typically, if you have an app, and you want to add the ability to show
a set of reports, you will need the ability to perform queries in order
to get the data you need to show a report. Of course, you can always
write a set of ad-hoc subroutines to return the data that you need. But
this kind of a programming style is not extensible: just wait till you
get a user who wants a slightly different report.
The idea behind QOF is to provide a generic framework so that any query
can be executed, including queries designed by the end-user. Normally,
this is possible only if you use a database that supports SQL, and then
only if you deeply embed the database into your application. QOF provides
simpler, more natural way to work with objects.
XML::QOFQSF extends this functionality to provide a simple, scriptable,
interface to QOF data. When combined with the SQL-type queries supported
by the QOF application, this provides a flexible method for handling,
organising, converting and synchronising all kinds of compatible data.
Currently, QOF applications are based around PIM data (Personal Information
Management) like contacts, calendar, expenses and todo lists. QOF is also
an integral part of GnuCash and support for financial objects is pending
(when the cashutil application is stable). In theory, QOF can be used with
any kind of data that can be expressed in the variables available.
=head1 OBJECTS
pilot-qof objects (pilot_address, pilot_expenses, pilot_datebook
and pilot_todo) are supported. gpe-expenses is also supported. Outline
support is included for cashutil objects but as cashutil is currently
unreleased, full support is pending.
XML::QOFQSF objects are identical to the Query Object Framework (QOF)
Objects used by applications like pilot-qof. The module is not intended to
be a perl binding of any kind, merely a data conduit that understands the
various elements of a QOF Object.
L
L
L
L
The Perl structs follow the QSF XML tag names and XML::QOFQSF expects values
that match the underlying QOF object - as output by QSF XML. Variable names
must therefore comply with XML rules for attribute values and with Perl syntax
rules - e.g. foo-bar is invalid, use foo_bar instead. The same variable names
are also used as field names in SQLite.
Detailed information on QSF variables:
L
=over 2
=item *
B : any valid XML/Perl string.
B : Original QOF GUID, if preserved, otherwise blank for new.
B : valid XML boolean, true or false.
B : Not fully supported in XML::QOFQSF yet, these are precise
numerical variables expressed as a numerator and denominator: 599/100
= 5.99 or 5456321/148547 = 36.73127697 - see QOF for more information.
B