1
# /=====================================================================\ #
2
# | LaTeXML::Util::ObjectDB | #
3
# | Database of Objects for crossreferencing, etc | #
4
# |=====================================================================| #
5
# | Part of LaTeXML: | #
6
# | Public domain software, produced as part of work done by the | #
7
# | United States Government & not subject to copyright in the US. | #
8
# |---------------------------------------------------------------------| #
9
# | Bruce Miller <bruce.miller@nist.gov> #_# | #
10
# | http://dlmf.nist.gov/LaTeXML/ (o o) | #
11
# \=========================================================ooo==U==ooo=/ #
13
package LaTeXML::Util::ObjectDB;
15
use LaTeXML::Util::Pathname;
17
use Storable qw(nfreeze thaw);
21
our @ISA=qw(Storable);
23
#======================================================================
25
# (1) If we can do make-like processing, when an entry is marked as
26
# modified, any referrers to it also need processing.
27
# (and we could defer a save if nothing was dirty)
28
#======================================================================
30
# * Object: places a link will take you to. Several types
31
# * chunk: any significant document object with a reference
32
# number: sectional chunks, equations, ...
33
# * index : the target is the entry in the index itself.
34
# a back reference can take you to where the \index was invoked.
35
# * bib : the target is the entry in the bibliography.
36
# a back reference can take you to the \cite.
38
#======================================================================
42
map($_->finish, @DBS);
45
#======================================================================
46
# Creating an ObjectDB object, hooking up initial database.
48
my($class, %options)=@_;
49
my $dbfile = $options{dbfile};
50
if($dbfile && $options{clean}){
51
warn "\nWARN: Removing Object database file $dbfile!!!\n";
54
my $self = bless {dbfile=>$dbfile,
55
objects=>{}, externaldb=>{},
56
verbosity => $options{verbosity}||0,
57
read_write => $options{read_write},
60
## my $flags = ($options{read_write} ? O_RDWR|O_CREAT : O_RDONLY);
61
my $flags = O_RDWR|O_CREAT;
62
tie %{$$self{externaldb}}, 'DB_File', $dbfile,$flags
63
or die "Couldn't attach DB $dbfile for object table";
70
my $status = scalar(keys %{$$self{objects}})." objects";
71
# if($$self{dbfile}){ ...
74
#======================================================================
79
if($$self{externaldb} && $$self{dbfile}){
82
my $opened = $$self{opened_timestamp};
83
foreach my $key (keys %{$$self{objects}}){
84
my $row = $$self{objects}{$key};
85
next if $$row{timestamp} < $opened;
88
delete $item{key}; # Don't store these
89
# $$row{timestamp}=$opened;
90
##print STDERR "Saving: ".$row->show."\n";
91
$$self{externaldb}{Encode::encode('utf8',$key)} = nfreeze({%item}); }
93
print STDERR "ObjectDB Stored $n objects (".scalar(keys %{$$self{externaldb}})." total)\n"
94
if $$self{verbosity} > 0;
95
untie %{$$self{externaldb}}; }
97
$$self{externaldb}=undef;
98
$$self{objects}=undef;
104
if($$self{externaldb} && $$self{dbfile}){
107
foreach my $key (keys %{$$self{objects}}){
108
my $row = $$self{objects}{$key};
109
# Skip saving, unless there's some difference between stored value
110
if(my $stored = $$self{externaldb}{Encode::encode('utf8',$key)}){ # Get the external object
111
next if compare_hash($row,thaw($stored)); }
114
##print STDERR "Saving: ".$row->show."\n";
115
$$self{externaldb}{Encode::encode('utf8',$key)} = nfreeze({%item}); }
117
print STDERR "ObjectDB Stored $n objects (".scalar(keys %{$$self{externaldb}})." total)\n"
118
if $$self{verbosity} > 0;
119
untie %{$$self{externaldb}}; }
121
$$self{externaldb}=undef;
122
$$self{objects}=undef;
131
elsif($ra ne ref $b){ 0; }
132
elsif($ra eq 'HASH'){ compare_hash($a,$b); }
133
elsif($ra eq 'ARRAY'){ compare_array($a,$b); }
139
map($attr{$_}=1, keys %$a);
140
map($attr{$_}=1, keys %$b);
141
(grep( !( (defined $$a{$_}) && (defined $$b{$_})
142
&& compare($$a{$_}, $$b{$_}) ), keys %attr) ? 0 : 1); }
149
return 0 unless compare(shift(@a),shift(@b)); }
150
(@a || @b ? 0 : 1); }
152
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
155
# Get union of all keys in externaldb & local objects.
157
map($keys{$_}=1, keys %{$$self{objects}});
158
map($keys{Encode::decode('utf8',$_)}=1, keys %{$$self{externaldb}});
161
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162
# Lookup of various kinds of things in the DB.
163
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
165
# Lookup the Object associated with label
166
# If it is not already fetched from the external db (if any), fetch it now.
170
return undef unless defined $key;
171
my $entry = $$self{objects}{$key}; # Get the local copy.
172
return $entry if $entry;
173
$entry = $$self{externaldb}{Encode::encode('utf8',$key)}; # Get the external object
175
$entry = thaw($entry);
177
bless $entry, 'LaTeXML::Util::ObjectDB::Entry';
178
$$self{objects}{$key} = $entry; }
181
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
182
# Register various interesting document nodes.
183
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
184
# Register the labeled object $node, creating, or filling in, and
185
# returning a Chunk entry.
187
my($self,$key,%props)=@_;
188
carp("Missing key for object!") unless $key;
189
my $entry = $self->lookup($key);
191
$entry = {key=>$key};
192
bless $entry, 'LaTeXML::Util::ObjectDB::Entry';
193
$$self{objects}{$key}=$entry; }
194
$entry->setValues(%props);
198
#********************************************************************************
200
#********************************************************************************
201
package LaTeXML::Util::ObjectDB::Entry;
203
use LaTeXML::Common::XML;
205
our $XMLParser = LaTeXML::Common::XML::Parser->new();
208
my($class,$key,%data)=@_;
209
bless {key=>$key,%data},$class; }
211
sub key { $_[0]->{key}; }
213
# Get/Set a value (column) in the DBRow entry, noting whether it modifies the entry.
214
# Note that XML data is stored in it's serialized form, prefixed by "XML::".
217
my $value = $$self{$attr};
218
if($value && $value =~ /^XML::/){
219
$value = $XMLParser->parseChunk(substr($value,5)); }
223
my($self,%avpairs)=@_;
224
foreach my $attr (keys %avpairs){
225
my $value = $avpairs{$attr};
226
if(((ref $value) || '') =~ /^XML::/){
227
# The node is cloned so as to copy any inherited namespace nodes.
228
$value = "XML::".$value->cloneNode(1)->toString; }
229
if(! defined $value){
230
if(defined $$self{$attr}){
231
delete $$self{$attr}; }}
232
elsif((! defined $$self{$attr}) || ($$self{$attr} ne $value)){
233
$$self{$attr}=$value; }}}
235
# Note an association with this entry
236
# Roughly equivalent to $$entry{key1}{key2}{...}=1,
237
# but keeps track of modification timestamps. --- not any more!
238
sub noteAssociation {
242
my $key = shift(@keys);
243
if(defined $$hash{$key}){
244
$hash = $$hash{$key}; }
246
$hash = $$hash{$key} = (@keys ? {} : 1); }}}
252
my $string = "ObjectDB Entry for: $$self{key}\n";
253
foreach my $attr (grep($_ ne 'key', keys %{$self})){
254
$string .= wrap(sprintf(' %16s : ',$attr),(' 'x20), showvalue($self->getValue($attr)))."\n"; }
259
if((ref $value) =~ /^XML::/){ $value->toString; }
260
elsif(ref $value eq 'HASH'){
261
"{".join(', ',map("$_=>".showvalue($$value{$_}), keys %$value))."}"; }
262
elsif(ref $value eq 'ARRAY'){
263
"[".join(', ',map(showvalue($_),@$value))."]"; }
266
#======================================================================