use
5.006;
BEGIN {
use
vars
qw($Revision $VERSION)
;
my
$major
= 1;
q$Revision: 10 $
=~ /: (\d+)/;
my
(
$minor
) = ($1);
$VERSION
=
"$major."
. (
$minor
<10 ?
'0'
:
''
) .
$minor
;
}
my
$debug_sql
= 0;
sub
new {
my
$self
=
shift
;
my
$class
=
ref
(
$self
) ||
$self
;
my
%args
=
@_
;
my
$DSN
=
$ENV
{
'BIBLIO_DSN'
} ||
$args
{
'dsn'
};
my
$DBMS
=
$ENV
{
'BIBLIO_DBMS'
} ||
$args
{
'dbms'
} ||
'ODBC'
;
my
$DBHOST
=
$ENV
{
'BIBLIO_HOST'
} ||
$args
{
'host'
} ||
$args
{
'dbhost'
} ;
my
$DBNAME
=
$ENV
{
'BIBLIO_NAME'
} ||
$args
{
'name'
} ||
$args
{
'dbname'
} ||
'biblio'
;
my
$DBUSER
=
$ENV
{
'BIBLIO_USER'
} ||
$args
{
'user'
} ||
$args
{
'dbuser'
};
my
$DBPASS
=
$ENV
{
'BIBLIO_PASS'
} ||
$args
{
'pass'
} ||
$args
{
'dbpass'
};
my
$DBATTR
=
$args
{
'attr'
} ||
$args
{
'dbattr'
};
$debug_sql
= 1
if
$args
{
'debug_sql'
};
$DBMS
||
$DSN
or croak
"Missing DBMS or DSN specification, did you give one?\n"
.
"You can set \$BIBLIO_DBMS or \$BIBLIO_DSN in the environment.\n"
;
if
(
$DBMS
eq
'Informix'
) {
if
( !
$ENV
{INFORMIXSERVER} ) {
chomp
(
$ENV
{INFORMIXSERVER} =
$DBHOST
|| `hostname` );
}
if
( !
$ENV
{INFORMIXDIR} ) {
if
( -d
'/opt/informix'
) {
$ENV
{INFORMIXDIR} =
'/opt/informix'
;
}
elsif
( -d
'/usr/informix'
) {
$ENV
{INFORMIXDIR} =
'/usr/informix'
;
}
else
{
croak
"Cannot locate Informix directory, please set INFORMIXDIR"
;
}
}
if
(
$DBNAME
!~ /\@/ ) {
$DBNAME
.=
"\@$DBHOST"
;
}
}
elsif
(
$DBMS
eq
'Solid'
) {
unless
(
$ENV
{DBI_USER} ) {
$ENV
{DBI_USER} =
$ENV
{DBI_PASS} =
'solid'
;
}
}
elsif
(
$DBMS
eq
'mysql'
) {
unless
(
$DSN
) {
$DSN
=
"dbi:mysql:database=$DBNAME"
;
$DSN
.=
";host=$DBHOST"
if
$DBHOST
;
}
}
$DSN
=
"dbi:$DBMS:$DBNAME"
unless
(
$DSN
);
$DBATTR
= {
AutoCommit
=> 0,
PrintError
=> 0,
RaiseError
=> 0,
}
unless
$DBATTR
;
my
$db
= DBI->
connect
(
$DSN
,
$DBUSER
,
$DBPASS
,
$DBATTR
)
or croak
"$DBI::errstr\nCannot connect to $DBMS database $DBNAME ($DSN)"
;
$db
->{ChopBlanks} = 0;
$self
= {
'db'
=>
$db
,
%args
,
};
return
bless
$self
,
$class
;
}
sub
DESTROY ($) {
my
$self
=
shift
;
$self
->disconnect();
}
sub
db {
return
shift
->{
'db'
}; }
sub
disconnect {
my
$self
=
shift
;
my
$db
=
$self
->db();
$db
->disconnect(
@_
)
if
(
$db
);
$self
->{
'db'
} =
undef
;
}
sub
do
{
return
shift
->db()->
do
(
@_
); }
sub
commit {
return
shift
->db()->commit(
@_
); }
sub
prepare {
my
$self
=
shift
;
my
(
$stmt
,
$attr
) =
@_
;
$stmt
=~ s/;\s*$//;
$stmt
=~ s/^--.*$/\t/mg;
return
$self
->db()->prepare(
$stmt
,
$attr
);
}
sub
query {
my
$self
=
shift
;
my
(
$sql_stmt
,
$attr
,
@params
) =
@_
;
my
(
$maxrows
,
@result
) = (
undef
);
if
(
defined
(
$attr
) &&
defined
(
%$attr
) &&
exists
(
$attr
->{maxrows}) ) {
$maxrows
=
$attr
->{maxrows};
delete
(
$attr
->{maxrows});
}
my
$sth
=
$self
->prepare(
$sql_stmt
,
$attr
)
or croak
"$DBI::errstr\nCannot prepare SQL stmt \n$sql_stmt"
;
$sth
->execute(
@params
)
or croak
"$DBI::errstr\nCannot execute SQL statement"
;
my
@row
;
my
$row_count
= 0;
while
(
@row
=
$sth
->fetchrow_array() ) {
foreach
(
@row
) {
s/\s*$//
if
(
defined
(
$_
) );
}
my
@copy_row
=
@row
;
push
(
@result
, \
@copy_row
);
last
if
(
$maxrows
&& ++
$row_count
>=
$maxrows
);
}
$sth
->finish()
or carp
"$DBI::errstr\nProblems releasing SQL statement"
;
return
\
@result
;
}
sub
getCiteKeys {
my
$self
=
shift
;
my
@paperIDs
;
my
$row
;
@paperIDs
=
keys
%{
$self
->queryPapers(
undef
,
undef
, [
'CiteKey'
])};
return
@paperIDs
;
}
sub
papers {
my
$self
=
shift
;
my
$papers
=
$self
->{
'biblioPapers'
};
if
( not
defined
(
$papers
) ) {
$papers
=
$self
->queryPapers();
$self
->{
'biblioPapers'
} =
$papers
;
}
return
$papers
;
}
sub
allPaperFields {
my
$self
=
shift
;
return
[
keys
(%{
$self
->{
'column-types'
}}) ];
}
sub
biblio_table {
my
(
$self
) =
@_
;
return
$self
->{
'biblio_table'
} ||
'biblio'
;
}
my
@_citeTypes
=
qw(
article
book
booklet
inproceedings
inbook
incollection
inproceedings
journal
manual
masterthesis
misc
phdthesis
proceedings
report
unpublished
email
web
video
talk
poster
thesis
patent
)
;
my
%_types
= (
'conference'
=> 6,
'techreport'
=> 13,
);
for
(
my
$i
= 0;
$i
<
scalar
(
@_citeTypes
);
$i
++ ) {
$_types
{
$_citeTypes
[
$i
]} =
$i
;
}
sub
citeTypes {
return
[
@_citeTypes
];
}
sub
CiteTypeForType {
my
(
$self
,
$Type
) =
@_
;
return
defined
(
$Type
) ?
$_citeTypes
[
$Type
] :
undef
;
}
sub
TypeForCiteType {
my
(
$self
,
$CiteType
) =
@_
;
return
$_types
{
$CiteType
};
}
sub
queryPapers {
my
$self
=
shift
;
my
(
$pattern
,
$queryFields
,
$resultFields
,
$ignoreCase
) =
@_
;
$ignoreCase
= 1
if
not
defined
(
$ignoreCase
);
$pattern
=
lc
(
$pattern
)
if
(
$ignoreCase
);
$resultFields
=
$self
->allPaperFields()
unless
defined
(
$resultFields
);
my
$table
=
$self
->biblio_table();
my
$sql
=
'SELECT '
.
join
(
', '
,
map
(
$self
->quoteField(
$_
), @{
$resultFields
})) .
" FROM $table"
.
(
$queryFields
&&
$pattern
?
' WHERE '
.
join
(
' OR '
,
map
(
'('
.
$self
->quoteField(
$_
,
$ignoreCase
) .
" LIKE "
.
$self
->quoteValue(
$_
,
$pattern
) .
')'
, @{
$queryFields
}))
:
''
);
print
STDERR
"$sql\n"
if
(
$debug_sql
);
my
$papers
=
$self
->query(
$sql
) or
die
"$DBI::errstr\nSelect failed for $sql\n"
;
return
$self
->papersArrayToHash(
$resultFields
,
$papers
);
}
sub
queryPaperWithId ($$) {
my
(
$self
,
$id
) =
@_
;
my
$resultFields
=
$self
->allPaperFields();
my
$table
=
$self
->biblio_table();
my
$sql
=
'SELECT '
.
join
(
', '
,
map
(
$self
->quoteField(
$_
, 0), @{
$resultFields
})) .
" FROM $table WHERE "
.
$self
->quoteField(
"CiteKey"
) .
' = '
.
$self
->quoteValue(
"CiteKey"
,
$id
);
print
"$sql\n"
if
(
$debug_sql
);
my
$papers
=
$self
->query(
$sql
) or
croak
"$DBI::errstr\nSelect failed for $sql\n"
;
my
$result
=
$self
->papersArrayToHash(
$resultFields
,
$papers
);
return
$result
->{
$id
};
}
sub
quoteField($$;$) {
my
(
$self
,
$field
,
$ignoreCase
) =
@_
;
my
$mapping
=
$self
->{
'column-mapping'
} || {};
$field
=
$mapping
->{
$field
}
if
exists
(
$mapping
->{
$field
});
$field
=
"\"$field\""
if
$self
->{
'quote-column-name'
};
$field
=
"lower($field)"
if
$ignoreCase
&&
$self
->{
'supports-lower'
};
return
$field
;
}
sub
quoteValue ($$$) {
my
(
$self
,
$field
,
$value
) =
@_
;
my
$column_types
=
$self
->{
'column-types'
} || {};
my
$type
=
$column_types
->{
$field
};
if
(
$type
=~ /INT/i ) {
return
$value
; }
$type
=~ /(\d+)/;
my
$length
= $1 ||
$self
->{
'column-max-string-length'
} || 254;
if
(
length
(
$value
) >
$length
) {
print
STDERR
"WARNING: long string value in $field: "
,
length
(
$value
),
" (max $type) -> might fail ...\n"
;
}
$value
=~ s/\'/\'\'/g;
return
"'$value'"
;
}
sub
papersArrayToHash ($$$) {
my
$self
=
shift
;
my
(
$resultFields
,
$papers
) =
@_
;
my
@results
=
map
(
$self
->paperArrayToHash(
$resultFields
,
$_
), @{
$papers
});
my
%papers
=
map
( (
$_
->{
'CiteKey'
} =>
$_
),
@results
);
return
\
%papers
;
}
sub
paperArrayToHash ($$$) {
my
$self
=
shift
;
my
(
$resultFields
,
$paper
) =
@_
;
my
$r
= {};
my
$id
;
my
$v
;
my
$i
= 0;
foreach
$id
(@{
$resultFields
}) {
$v
=
$self
->replaceShortcuts(
$paper
->[
$i
++]);
$r
->{
$id
} =
$v
if
defined
(
$v
) &&
$v
ne
''
;
}
$r
->{
'CiteKey'
} =
'<<no CiteKey found>>'
unless
defined
(
$r
->{
'CiteKey'
});
my
$CiteType
=
$r
->{
'CiteType'
};
if
(
defined
(
$CiteType
) &&
$self
->{
'column-types'
}->{
'CiteType'
} =~ /INT/i &&
$CiteType
=~ /^\d+$/ ) {
$CiteType
=
$self
->CiteTypeForType(
$CiteType
);
$r
->{
'CiteType'
} =
$CiteType
if
defined
$CiteType
;
}
if
(
defined
(
$r
->{
'PBibNote'
}) ) {
my
@fields
=
split
(/\r?\n/,
$r
->{
'PBibNote'
});
my
@notes
;
foreach
my
$f
(
@fields
) {
if
(
$f
=~ /^([a-z]+)\s*=\s*(.*)\s*$/i ) {
$r
->{$1} = $2;
}
else
{
push
@notes
,
$f
;
}
}
if
(
scalar
@notes
) {
$r
->{
'PBibNote'
} =
join
(
"\n"
,
@notes
);
}
else
{
delete
$r
->{
'PBibNote'
};
}
}
return
$r
;
}
my
%aliasFields
=
qw/
DOI Source
/
;
sub
storePaper {
my
(
$self
,
$ref
,
$update
) =
@_
;
my
$id
=
$ref
->{
'CiteKey'
};
my
$old_ref
=
$self
->queryPaperWithId(
$id
);
my
%refFields
=
%$ref
;
unless
(
defined
(
$old_ref
) ) {
print
"no CiteKey"
unless
defined
(
$ref
->{
'CiteKey'
});
print
"no Category"
unless
defined
(
$ref
->{
'Category'
});
if
( !
defined
(
$refFields
{
'Identifier'
}) ) {
my
$key
=
$ref
->{
'CiteKey'
};
print
STDERR
"Generate new Identifier: $key\n"
;
$refFields
{
'Identifier'
} =
$key
;
}
}
foreach
my
$f
(
keys
%aliasFields
) {
if
(
defined
$refFields
{
$f
} &&
!
defined
$refFields
{
$aliasFields
{
$f
}} ) {
$refFields
{
$aliasFields
{
$f
}} =
$refFields
{
$f
};
}
}
my
(
$sec
,
$min
,
$hour
,
$mday
,
$mon
,
$year
,
$wday
,
$yday
,
$isdst
) =
localtime
();
$refFields
{
'BibDate'
} =
sprintf
(
"%04d-%02d-%02d %02d:%02d:%02d"
,
$year
+ 1900,
$mon
,
$mday
,
$hour
,
$min
,
$sec
);
if
(
defined
(
$refFields
{
'CiteType'
}) &&
$self
->{
'column-types'
}->{
'CiteType'
} =~ /INT/i ) {
my
$type
=
$self
->TypeForCiteType(
$refFields
{
'CiteType'
});
if
(
defined
$type
) {
$refFields
{
'CiteType'
} =
$type
;
}
}
my
%bibFields
;
foreach
my
$f
(@{
$self
->allPaperFields()}) {
if
(
exists
(
$refFields
{
$f
}) ) {
$bibFields
{
$f
} =
$self
->quoteValue(
$f
,
$refFields
{
$f
});
delete
$refFields
{
$f
};
}
}
if
(
%refFields
) {
my
$note
=
join
(
"\n"
,
map
(
"$_ = $refFields{$_}"
,
keys
%refFields
));
$note
=
"$bibFields{'PBibNote'}\n\n$note\n"
if
defined
$bibFields
{
'PBibNote'
};
$bibFields
{
'PBibNote'
} =
$self
->quoteValue(
'PBibNote'
,
$note
);
}
my
$biblio
=
$self
->biblio_table();
my
$sql
;
if
(
defined
(
$old_ref
) ) {
my
$assignments
=
join
(
', '
,
map
(
$self
->quoteField(
$_
, 0) .
" = $bibFields{$_}"
,
keys
%bibFields
));
$sql
=
"UPDATE $biblio SET $assignments WHERE "
.
$self
->quoteField(
"CiteKey"
) .
" = '$id'"
}
else
{
my
(
$fields
,
$values
) =
(
join
(
', '
,
map
(
$self
->quoteField(
$_
, 0),
keys
%bibFields
)),
join
(
', '
,
values
%bibFields
));
$sql
=
"INSERT INTO $biblio ($fields) VALUES ($values)"
;
}
print
"$sql\n"
if
(
$debug_sql
);
$self
->
do
(
$sql
) or
croak
"\nDB access failed:\n\n$DBI::errstr\n\n$sql\n"
;
}
sub
replaceShortcuts {
my
(
$self
,
$text
) =
@_
;
return
undef
unless
defined
(
$text
);
return
$text
unless
$text
=~ /\{/;
my
$shortcuts
=
$self
->shortcuts();
my
$pattern
=
join
(
"|"
,
map
( /:$/ ?
"$_.*"
:
$_
, (
keys
(%{
$shortcuts
}))));
$text
=~ s/\{(
$pattern
)\}/
$self
->expandShortcut(
$shortcuts
, $1) /ge;
return
$text
;
}
sub
expandShortcut {
my
(
$self
,
$shortcuts
,
$text
) =
@_
;
my
@pars
=
split
(/:/,
$text
);
my
$k
=
shift
@pars
;
if
(
@pars
) {
$k
=
"$k:"
; }
my
$v
=
$shortcuts
->{
$k
};
$v
=~ s/%(\d)/
$pars
[$1-1] /ge;
return
$v
;
}
sub
shortcuts_table {
my
(
$self
) =
@_
;
return
$self
->{
'shortcuts_table'
};
}
sub
shortcuts {
my
(
$self
) =
@_
;
return
$self
->{
'shortcuts'
}
if
defined
(
$self
->{
'shortcuts'
});
my
$shortcuts_table
=
$self
->shortcuts_table();
return
$self
->{
'shortcuts'
} = {}
unless
$shortcuts_table
;
my
$sql
=
"SELECT * FROM $shortcuts_table"
;
print
"$sql\n"
if
(
$debug_sql
);
my
$result
=
$self
->query(
$sql
);
unless
(
$result
) {
print
STDERR
"$DBI::errstr\nSelect failed for $sql\n"
;
return
{};
}
my
%scs
=
map
((
$_
->[0] =>
$_
->[1]), @{
$result
});
return
$self
->{
'shortcuts'
} = \
%scs
;
}
sub
updateShortcuts {
my
(
$self
) =
@_
;
delete
$self
->{
'shortcuts'
};
}
1;