 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.
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_dbAz xml-t pedig mondjuk xslt-vel át tudjuk konvertálni tetszőleges más formátumra is.
Mentegessünk minden nap!
		 
		
+jegyzések