Az első cikkben leírtam, hogyan tudjuk olvashatóvá konvertálni a belső adatbázisokat. Most a Pilot-DB adatbázisok konvertálását fogom összefoglalni.
Nekem a 1.2.0m verzió van feltéve. A most elérhető legfrissebb az 1.1.3. Vajon mi történet a későbbi verziókkal? Azok miért tűntek el a valahol, az internet sűrű bugyrában? Van windows-os verzió. A pdb2csv program a Pilot-DB PDB file-jait csv formátumban írja ki (típus: DBOS, DB00). Pluszban egy másik leíró file-t is létrehoz, amit a fejlesztők szerint ifo kiterjesztéssel illik létrehozni (ifo file formátum leírása). Próbálkozzunk a konverzióval.
wine pdb2csv.exe Backup/file.PDB
Elvileg a csv2pdb-vel létre is hozhatunk ilyen file-okat. A gond az, hogy a belsőleg létrehozottakat nem lehet így konvertálni. Hibás rekordformátumra hivatkozik (file.PDB: record is corrupt).
Letöltöttem hát a konvertáló program forrását. A g++ 4.0-ás változatához egy kis foltozás szükséges (ezt innen tölthetjük le). Ez után ki-tar, "./configure", make, "make install" (ehhez megfelelő jogok kellenek). A program futtatásához a lefordított könyvtárak elérését is be kell állítani. Annak a path-szát adjuk meg:
LD_LIBRARY_PATH=/usr/local/lib/palm-db-tools/flatfile pdb2csv ...
De ez is ugyanazt a hibaüzenetet dobja a Palm-on létrehozott adatbázisokra. Valami jobb megoldást kell keresni hát... A CPAN-on találtam egy perl csomagot. Feltettem hát:
cpan
install Palm::PDB
Ezzel szépen meg lehet nyitni a file-okat, de sajna a teljes header-t és az adat rekordokat nem fordítja le, hanem úgy hagyja, rusnyán, binárisan. A fordítást tehát nekem kellett megírni. Ebben nagy segítségemre volt a a pdb2csv forrása. Íme hát. Némi kis kiegészítéssel körítve...
A szokásos fejléc, plusz a PDB -hez kapcsolatos fejlécek
#!/usr/bin/perl
#
# Open PDB files
#
# ...
use strict;
use warnings;
use POSIX;
use Palm::PDB;
use Palm::Raw;
Az egyes mezőtípusok konverzióját segítő hash:
sub dump_pdb($); # Dump Pilot-DB
Palm::PDB::RegisterPDBHandlers("Palm::Raw", "");
# Filed types: String(0), Note(5), Boolean(1), Integer(2), Float(8),
# Calculated(9), Date(3), Time(4), List(6), Link(7) = Z*?, Linked(10) = nn?
my %fld_type = (
0 => ["StrZ", -1, "Z*"], # ASCIZ
1 => ["Bool", 1, "C"], # Boolean
2 => ["Int", 4, "N"], # N big-endian, V little-endian,
3 => ["Date", 4, "nCC"], # YYYY, M, D
4 => ["Time", 2, "CC"], # HH:MM
5 => ["Note", -2, "Z*nZ*"], # ASCIZ. some num???, ASCIZ
6 => ["List", 1, "C"], # C unsigned byte
7 => ["Link", -1, "Z*"], # ??? Not tested
8 => ["Float", 8, "d>"], # big-endian double
9 => ["Calc", 9, "C9"], # How to decode?
10 => ["Linked", 4, "nn"], # ??? Not tested
);
A parancssor feldolgozása és az eredmény xml formátumban való kiírása következik. Használata: pdb2xml.pl [--header|--test] file.PDB [file1.PDB ...]. A --header csak a fejlécet írja ki, --test ellenőrzi, hogy Pilot DB-ről van-e szó (aláírás DBOS és DB00) és annak megfelelően kilép a programból.
for (@ARGV) {
if ($_ =~ /^--(.*)(?:=(.*))?$/) {
my ($arg, $val) = ($1, $2);
if ($arg eq "header") { $fHdr = 1;
} elsif ($arg eq "test") { $fHdr = 2;
} else { die "Bad arg ($arg)";
}
} else {
if (-r $_) { push @files, $_;
} else { die "File cannot read";
}
}
}
if ($#files == -1) { die "No file given"; }
for (@files) { dump_pdb($_); }
exit;
Pár segédfüggvény. Az első konvertál, a második rekurzívan kiír egy hash referenciát, aminek az értéke lehet hash, array, vagy egy string érték.
sub enc($) {
my $x = shift;
$x =~ s{&}{&};
$x =~ s{<}{<};
$x =~ s{>}{>};
return $x;
}
sub show(%;$);
sub show(%;$) {
my ($h, $n) = @_;
if (!defined $n) { $n = 0; }
my $sp = " " x ($n * 2);
for my $k (keys %$h) {
my $v = $h->{$k};
if (!defined $v) { print $sp, "<$k/>\n";
} elsif (ref $v eq "") {
print $sp, ($v ne "" ? "<$k>".enc($v)."</$k>" : "<$k/>"), "\n";
} elsif (ref $v eq "HASH") {
print $sp, "<$k>\n";
show($v, $n + 1);
print $sp, "</$k>\n";
} elsif (ref $v eq "ARRAY") {
my $r = ref $v->[0];
if ($r eq "") {
for my $l (@$v) {
print $sp, ($l ne "" ? "<$k>".enc($l)."</$k>" : "<$k/>"),
"\n";
}
} elsif ($r eq "HASH") {
print $sp, "<$k>\n";
for my $l (@$v) { show $l, $n + 1; }
print $sp, "</$k>\n";
} elsif ($r eq "ARRAY") { die "Bad ARRAY of ARRAY";
} else { die "Bad ref ($r) of ARRAY";
}
} else {
die "Bad reference (", ref($v), ")";
}
}
} # End of show
És végül a konvertáló függvény. Először a fejléc információt gyűjti ki. A definiált "view"-kkal nem foglalkoztam. A "calc" mezőket is csak felsorolom és az adattartalmat nem konvertáltam. Aki ezeket is értelmezni szeretné, írja meg és küldje el! Előre is kösz!
sub dump_pdb($) {
my $file = shift;
my $pdb = new Palm::PDB();
$pdb->Load($file);
if ($pdb->{creator} ne "DBOS" || $pdb->{type} ne "DB00") {
if ($fHdr == 2) { exit 1; }
die "$file is not a Pilot-DB file";
}
if ($fHdr == 2) { exit; }
my ($name, $major, $minor) =
($pdb->{"name"}, $pdb->{"version"}, $pdb->{"modnum"});
# Sometimes the name contains more bytes
if ($name =~ /\000/) { $name = unpack "Z*", $name; }
#
# Read Header
#
my $hdr = $pdb->{appinfo};
# 2, 64 can occure multiple times
my %hdr_type = (0 => "Field name", 1 => "Field type", 2 => "Field data",
64 => "List View Definitions", 65 => "List View Options",
128 => "LFind Options", 254 => "About", 1024 => "???");
my %hdr;
my $hdr1 = $hdr;
my ($wmode, $fld_no) = unpack "n2", $hdr1;
# $wmode == 32768 (read only)
# First byte: other settings: backup, copy-prevention
if ($wmode != 0 && $wmode != 32768) { die "Bad mode ($wmode)"; }
#
# Collect header chunks
#
$hdr1 = substr($hdr1,4);
while (length($hdr1)) {
my ($t, $l) = unpack "n2", $hdr1;
$hdr1 = substr($hdr1, 4);
if (length($hdr1) < $l) { die "Length problem"; }
if (!exists $hdr_type{$t}) { die "Bad header chunk type ($t)"; }
my $chunk = substr($hdr1, 0, $l);
if ($t == 2 || $t == 64) { push @{$hdr{$t}}, $chunk;
} else {
if (exists $hdr{$t}) { die "Multiple definitions ($t)"; }
$hdr{$t} = $chunk;
}
$hdr1 = substr($hdr1, $l);
}
#
# Process header chunks
#
# Get column titles
my @fld_names = unpack("Z*" x $fld_no, $hdr{0});
if ($#fld_names != $fld_no - 1) { die "Field count not match"; }
# Get header bytes
my $hdr_bytes = $fld_no * 2;
if (length($hdr{1}) != $hdr_bytes) { die "Field type size not match"; }
# Read field types
# Header record (contains column headers, enum values, views)
my @fld_types = unpack "n" x $fld_no, $hdr{1};
my @fld_type_names = map {
if (!exists $fld_type{$_}) { die "Not defined field type ($_)"; }
$fld_type{$_}[0]
} @fld_types;
# Read List values
my @list;
my @enum;
my @calc;
for my $x (@{$hdr{2}}) {
my $seq = unpack "n", $x;
$x = substr($x, 2);
my $fld_type_name = $fld_type{$fld_types[$seq]}[0];
if ($fld_type_name eq "List") {
my ($pcs, $w) = unpack "nn", $x;
$x = substr($x, 4);
$list[$seq] = [ unpack("Z*" x $pcs, $x) ];
push @enum, {list =>
[{field_name => $fld_names[$seq]}, {db => $pcs},
{name => [@{$list[$seq]}]}, {seq => $seq}, {_what => $w}]
};
if (scalar(@{$list[$seq]}) != $pcs) {
die "Bad number of list elements ($seq)";
}
} elsif ($fld_type_name eq "Calc") {
push @calc, {calc => [{seq => $seq}, {length => length($x)}]};
} else { die "Bad field type for DATA ($fld_type_name)";
}
}
#
# Create header tree
#
my @wfld;
for (my $i = 0; $i <= $#fld_names; ++$i) {
push @wfld, {field => [{name => $fld_names[$i]},
{type_name => $fld_type_names[$i]}, {seq => $i}]};
}
my $hhdr = [ {name => $name}, {version => $major}, {modnum => $minor},
{field_no => $fld_no},
{field_names => join("|", @fld_names)},
{field_type => join("|", @fld_type_names)},
{fields => [@wfld]},
{lists => [@enum]},
{mode => $wmode}
];
if ($#calc > -1) { push @$hhdr, {calculations => [@calc]}; }
#
# Read records
#
# {records} - reference to one record (hash)
# {id}, {category} (0), {attributes} (hash dirty), {offset}
# {data} -> raw record
my $res = $pdb->{records};
my $hbody = [{record_total_no => scalar(@$res)}];
if ($fHdr) { exit 0; }
my $n = 0;
my $ni = 0;
my $N = scalar @$res;
my @records;
for my $e (@$res) {
++$n;
# Dirty => 1, dirty => 1, if deleted: Delete => 1, expunged => 1
if (exists $e->{attributes}{Delete}) {
warn "---- Deleted $n --------\n";
next;
}
my $record = [{record_no => ++$ni}, {record_ord => $n}, {fields => []}];
for my $k (keys %$e) {
next if $k eq "data";
push @$record, {$k => $e->{$k}};
}
my $dat = $e->{data};
if (length($dat) == 0) { die "Zero length"; }
# get data positions
my @pos = unpack "n" x $fld_no, $dat;
$dat = substr($dat, $hdr_bytes);
my $empty = 0;
my @fields;
for (my $i = 0; $i <= $#fld_types; ++$i) {
my $type = $fld_types[$i];
if (!exists $fld_type{$type}) { die "$type not defined"; }
my $tnam = $fld_type{$type}[0];
my $size = $fld_type{$type}[1];
my $utyp = $fld_type{$type}[2];
my @val = unpack $utyp, $dat;
if ($size == -1) { $size = length($val[0]) + 1; # String
} elsif ($size == -2) {
if (length($val[0]) == 0) {
$size = 3;
pop @val; # Empty Note
++$empty;
} else { $size = length($val[0]) + length($val[2]) + 4 ; # Note
}
}
$dat = substr($dat, $size);
my $info = "";
if ($tnam eq "List") {
if (!defined $list[$i]) { die "List ($n/$i) not defined"; }
my $val = $val[0];
$info = ($val == 255 ? "N/A" : $list[$i][$val]);
if (!defined $info) {
die "Not defined list item ($n/$i)";
}
$val[0] = $info;
$info = "";
}
for (@val) { s/\x95/*/g; }
push @fields, {field => [{name => $fld_names[$i]}, {type => $tnam},
{value => [@val]}, {info => $info}]};
}
$record->[2] = {fields => [@fields]};
push @records, {record => $record};
# Dirty hack. Some byte remains if a Note is empty
if (length($dat) != $empty) {
print " dt (", length($dat), "): $dat\n";
warn "Some data remainig at record#$n, length: ", length($dat);
}
}
push @$hbody, {records => [@records]};
print '<?xml version="1.0" encoding="ISO-8859-2"?>', "\n";
#print '<?xml-stylesheet type="text/css" href="x.css"?>', "\n";
show {pilot_db => [{header => $hhdr}, {body => $hbody}]};
} # End of dump_db
Az xml-t pedig mondjuk xslt-vel át tudjuk konvertálni tetszőleges más formátumra is.
Mentegessünk minden nap!
+jegyzések