Skip to content

Commit

Permalink
Adding annotation inheritance to XDATA as per plk/biblatex#1327
Browse files Browse the repository at this point in the history
  • Loading branch information
plk committed Jan 4, 2024
1 parent 7d55cb0 commit 1e6484f
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 8 deletions.
99 changes: 98 additions & 1 deletion lib/Biber/Annotation.pm
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,104 @@ sub set_annotation {
# For easy checking later whether or not a field is annotated
$ANN->{fields}{$key}{$field} = 1;

# Record all annotation names or a field
# Record all annotation names for a field
unless (first {fc($_) eq fc($name)} $ANN->{names}{$key}{$field}->@*) {
push $ANN->{names}{$key}{$field}->@*, $name;
}
return;
}

=head2 copy_field_annotations
Copy all annotations for a field from one entry to another
=cut

sub copy_field_annotations {
shift; # class method so don't care about class name
my ($sourcekey, $targetkey, $sourcefield, $targetfield) = @_;

$ANN->{field}{$targetkey}{$targetfield} = dclone($ANN->{field}{$sourcekey}{$sourcefield})
if exists($ANN->{field}{$sourcekey}{$sourcefield});
$ANN->{item}{$targetkey}{$targetfield} = dclone($ANN->{item}{$sourcekey}{$sourcefield})
if exists($ANN->{item}{$sourcekey}{$sourcefield});
$ANN->{part}{$targetkey}{$targetfield} = dclone($ANN->{part}{$sourcekey}{$sourcefield})
if exists($ANN->{part}{$sourcekey}{$sourcefield});

foreach my $name (keys $ANN->{item}{$targetkey}{$targetfield}->%*) {
unless (first {fc($_) eq fc($name)} $ANN->{names}{$targetkey}{$targetfield}->@*) {
push $ANN->{names}{$targetkey}{$targetfield}->@*, $name;
}
}

foreach my $type (('field', 'item', 'part')) {
foreach my $f (keys $ANN->{$type}{$targetkey}->%*) {
$ANN->{fields}{$targetkey}{$f} = 1;
}
}

return;
}

=head2 inherit_annotations
Inherit, in a granular manner, annotations for a particular entryfield from
one entry to another for all annotation names, reindxeing any item counts using
an offset since annotations might be copied due to xdata inheritance which can
change the item positions of annotations
=cut

sub inherit_annotations {
shift; # class method so don't care about class name
my ($sourcekey, $targetkey, $sourcefield, $targetfield, $sourceitem, $targetitem) = @_;

if (-not $sourceitem) {
if (exists($ANN->{field}{$sourcekey}{$sourcefield})) {
$ANN->{field}{$targetkey}{$targetfield} = dclone($ANN->{field}{$sourcekey}{$sourcefield})
}
if (exists($ANN->{item}{$sourcekey}{$sourcefield})) {
$ANN->{item}{$targetkey}{$targetfield} = dclone($ANN->{item}{$sourcekey}{$sourcefield})
}
if (exists($ANN->{part}{$sourcekey}{$sourcefield})) {
$ANN->{part}{$targetkey}{$targetfield} = dclone($ANN->{part}{$sourcekey}{$sourcefield})
}
}
else {
if (exists($ANN->{item}{$sourcekey}{$sourcefield})) {
foreach my $name (keys $ANN->{item}{$sourcekey}{$sourcefield}->%*) {
unless (first {fc($_) eq fc($name)} $ANN->{names}{$targetkey}{$targetfield}->@*) {
push $ANN->{names}{$targetkey}{$targetfield}->@*, $name;
}
if (exists($ANN->{item}{$sourcekey}{$sourcefield}{$name}{$sourceitem})) {
$ANN->{item}{$targetkey}{$targetfield}{$name}{$targetitem} =
dclone($ANN->{item}{$sourcekey}{$sourcefield}{$name}{$sourceitem});
}
}
}

if (exists($ANN->{part}{$sourcekey}{$sourcefield})) {
foreach my $name (keys $ANN->{part}{$sourcekey}{$sourcefield}->%*) {
unless (first {fc($_) eq fc($name)} $ANN->{names}{$targetkey}{$targetfield}->@*) {
push $ANN->{names}{$targetkey}{$targetfield}->@*, $name;
}
if (exists($ANN->{part}{$sourcekey}{$sourcefield}{$name}{$sourceitem})) {
$ANN->{part}{$targetkey}{$targetfield}{$name}{$targetitem} =
dclone($ANN->{part}{$sourcekey}{$sourcefield}{$name}{$sourceitem})
}
}
}
}

foreach my $type (('field', 'item', 'part')) {
foreach my $f (keys $ANN->{$type}{$targetkey}->%*) {
$ANN->{fields}{$targetkey}{$f} = 1;
}
}

return;
}

=head2 copy_annotations
Copy all annotations from one entry to another
Expand All @@ -66,6 +157,12 @@ sub copy_annotations {
$ANN->{item}{$targetkey} = dclone($ANN->{item}{$sourcekey}) if exists($ANN->{item}{$sourcekey});
$ANN->{part}{$targetkey} = dclone($ANN->{part}{$sourcekey}) if exists($ANN->{part}{$sourcekey});
$ANN->{names}{$targetkey} = dclone($ANN->{names}{$sourcekey}) if exists($ANN->{names}{$sourcekey});

foreach my $type (('field', 'item', 'part')) {
foreach my $f (keys $ANN->{$type}{$targetkey}->%*) {
$ANN->{fields}{$targetkey}{$f} = 1;
}
}
return;
}

Expand Down
61 changes: 55 additions & 6 deletions lib/Biber/Entry.pm
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,11 @@ sub resolve_xdata {
foreach my $field ($xdataentry->datafields()) { # set fields
next if $field eq 'ids'; # Never inherit aliases
$self->set_datafield($field, $xdataentry->get_field($field));
# Inherit field annotations too
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$field,
$field);

# Record graphing information if required
if (Biber::Config->getoption('output_format') eq 'dot') {
Expand Down Expand Up @@ -805,9 +810,17 @@ sub resolve_xdata {
# Name lists
if ($dm->field_is_type('list', 'name', $reffield)) {
if ($xdatum->{xdataposition} eq '*') { # insert all positions from XDATA field
my $bibentries = $section->bibentries;
my $be = $bibentries->entry($xdatum->{xdataentries}[0]);
$self->get_field($reffield)->splice($xdataentry->get_field($xdatafield), $refposition);
# Inherit annotations for the field and remap indices to new positions in target
for (my $i=1; $i<=$xdataentry->get_field($xdatafield)->count; $i++) {
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$xdatafield,
$reffield,
$i,
$refposition+($i-1));
}

if ($logger->is_debug()) { # performance tune
$logger->debug("Inserting at position $refposition in name field '$reffield' in entry '$entry_key' via XDATA");
}
Expand All @@ -818,8 +831,15 @@ sub resolve_xdata {
$xdatum->{resolved} = 0;
next;
}

$self->get_field($reffield)->replace_name($xdataentry->get_field($xdatafield)->nth_name($xdataposition), $refposition);
$self->get_field($reffield)->
replace_name($xdataentry->get_field($xdatafield)->nth_name($xdataposition), $refposition);
# Inherit annotations for the field and remap indices to new positions in target
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$xdatafield,
$reffield,
$xdataposition,
$refposition);

if ($logger->is_debug()) { # performance tune
$logger->debug("Setting position $refposition in name field '$reffield' in entry '$entry_key' via XDATA");
Expand All @@ -831,7 +851,20 @@ sub resolve_xdata {
if ($xdatum->{xdataposition} eq '*') { # insert all positions from XDATA field
my $bibentries = $section->bibentries;
my $be = $bibentries->entry($xdatum->{xdataentries}[0]);
splice($self->get_field($reffield)->@*, $refposition-1, 1, $be->get_field($xdatum->{xdatafield})->@*);
splice($self->get_field($reffield)->@*,
$refposition-1,
1,
$be->get_field($xdatafield)->@*);
# Inherit annotations for the field and remap indices to new positions in target
for (my $i=1; $i<=scalar($xdataentry->get_field($xdatafield)->@*);$i++) {
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$xdatafield,
$reffield,
$i,
$refposition+($i-1));
}

if ($logger->is_debug()) { # performance tune
$logger->debug("Inserting at position $refposition in list field '$reffield' in entry '$entry_key' via XDATA");
}
Expand All @@ -843,7 +876,16 @@ sub resolve_xdata {
next;
}
$self->get_field($reffield)->[$refposition-1] =
$xdataentry->get_field($xdatafield)->[$refposition-1];
$xdataentry->get_field($xdatafield)->[$xdataposition-1];

# Inherit annotations for the field and remap indices to new positions in target
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$xdatafield,
$reffield,
$xdataposition,
$refposition);

if ($logger->is_debug()) { # performance tune
$logger->debug("Setting position $refposition in list field '$reffield' in entry '$entry_key' via XDATA");
}
Expand All @@ -853,6 +895,13 @@ sub resolve_xdata {
else {

$self->set_datafield($reffield, $xdataentry->get_field($xdatafield));

# Inherit annotations for the field and remap indices to new positions in target
Biber::Annotation->inherit_annotations($xdataentry->get_field('citekey'),
$self->get_field('citekey'),
$xdatafield,
$reffield);

if ($logger->is_debug()) { # performance tune
$logger->debug("Setting field '$reffield' in entry '$entry_key' via XDATA");
}
Expand Down
1 change: 1 addition & 0 deletions lib/Biber/Output/bbl.pm
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ sub set_output_entry {
foreach my $listfield ($dmh->{lists}->@*) {
# Performance - as little as possible here - loop over DM fields for every entry
if (my $lf = $be->get_field($listfield)) {
use Data::Dump;dd($lf);
if ( lc($lf->[-1]) eq Biber::Config->getoption('others_string') ) {
$acc .= " \\true{more$listfield}\n";
pop $lf->@*; # remove the last element in the array
Expand Down
1 change: 1 addition & 0 deletions t/tdata/xdata.bcf
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,7 @@
<bcf:citekey order="5" intorder="1">gxd1g</bcf:citekey>
<bcf:citekey order="6" intorder="1">lxd1</bcf:citekey>
<bcf:citekey order="7" intorder="1">bltxgxd1</bcf:citekey>
<bcf:citekey order="8" intorder="1">xdann1</bcf:citekey>
</bcf:section>
<!-- SORTING TEMPLATES -->
<bcf:sortingtemplate name="nty">
Expand Down
38 changes: 38 additions & 0 deletions t/tdata/xdata.bib
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,41 @@ @XDATA{lxd3:loop
@XDATA{lxd4:loop,
EDITOR = {xdata=lxd4:loop-editor-1}
}

% annotation inheritance via xdata
@XDATA{notes,
NOTE = {A note},
NOTE+an = {=bignote}
}

@XDATA{auths,
EDITOR = {John Doe and Jane Doe and May Bee},
EDITOR+an = {1=bigishauthor;3=bigauthor}
}

@XDATA{nloc,
LOCATION = {xdata=loc-location-2}
}

@XDATA{loc,
INSTITUTION = {inst1 and inst2},
INSTITUTION+an = {2=biginst},
LOCATION = {xloc1 and xloc2},
LOCATION+an = {2=bigloc}
}

@XDATA{pub,
PUBLISHER = {MacMillan},
PUBLISHER+an = {1=bigpublisher}
}

@BOOK{xdann1,
AUTHOR = {Arthur Smith and xdata=auths-editor-3 and xdata=auths-editor-1 and Bill Blogs},
AUTHOR+an = {1=biggerauthor},
EDITOR = {Raymond Little and xdata=auths-editor and Gerald Roth},
TITLE = {Very Long Title with XDATA},
LOCATION = {loca and xdata=nloc-location-1 and xdata=loc-location-2},
NOTE = {xdata=notes-note},
INSTITUTION = {xdata=loc-institution and inst3},
XDATA = {pub}
}
97 changes: 96 additions & 1 deletion t/xdata.t
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use warnings;
use utf8;
no warnings 'utf8';

use Test::More tests => 12;
use Test::More tests => 13;
use Test::Differences;
use List::AllUtils qw( first );
unified_diff;
Expand Down Expand Up @@ -360,6 +360,100 @@ my $bltxgxd1 = q| \entry{bltxgxd1}{book}{}{}
\endentry
|;

my $xdann1 = q| \entry{xdann1}{book}{}{}
\name{author}{4}{}{%
{{hash=9c855075c7ab53ad38ec38086eda2029}{%
family={Smith},
familyi={S\bibinitperiod},
given={Arthur},
giveni={A\bibinitperiod}}}%
{{hash=0c6731af5e4274be0b0ceef16eccb8f6}{%
family={Bee},
familyi={B\bibinitperiod},
given={May},
giveni={M\bibinitperiod}}}%
{{hash=bd051a2f7a5f377e3a62581b0e0f8577}{%
family={Doe},
familyi={D\bibinitperiod},
given={John},
giveni={J\bibinitperiod}}}%
{{hash=ccc542396e5b42506590dc7132859c8c}{%
family={Blogs},
familyi={B\bibinitperiod},
given={Bill},
giveni={B\bibinitperiod}}}%
}
\name{editor}{5}{}{%
{{hash=93f025f0446f3db59decfaf17a19dbbe}{%
family={Little},
familyi={L\bibinitperiod},
given={Raymond},
giveni={R\bibinitperiod}}}%
{{hash=bd051a2f7a5f377e3a62581b0e0f8577}{%
family={Doe},
familyi={D\bibinitperiod},
given={John},
giveni={J\bibinitperiod}}}%
{{hash=d6cfb2b8c4b3f9440ec4642438129367}{%
family={Doe},
familyi={D\bibinitperiod},
given={Jane},
giveni={J\bibinitperiod}}}%
{{hash=0c6731af5e4274be0b0ceef16eccb8f6}{%
family={Bee},
familyi={B\bibinitperiod},
given={May},
giveni={M\bibinitperiod}}}%
{{hash=ead97b429847e5d377495ef9e13acb27}{%
family={Roth},
familyi={R\bibinitperiod},
given={Gerald},
giveni={G\bibinitperiod}}}%
}
\list{institution}{3}{%
{inst1}%
{inst2}%
{inst3}%
}
\list{location}{3}{%
{loca}%
{xloc2}%
{xloc2}%
}
\list{publisher}{1}{%
{MacMillan}%
}
\strng{namehash}{416c234e34c8082fb7acf86c6e7a499a}
\strng{fullhash}{7d301d11b9579ee16fad350195f2d756}
\strng{fullhashraw}{7d301d11b9579ee16fad350195f2d756}
\strng{bibnamehash}{416c234e34c8082fb7acf86c6e7a499a}
\strng{authorbibnamehash}{416c234e34c8082fb7acf86c6e7a499a}
\strng{authornamehash}{416c234e34c8082fb7acf86c6e7a499a}
\strng{authorfullhash}{7d301d11b9579ee16fad350195f2d756}
\strng{authorfullhashraw}{7d301d11b9579ee16fad350195f2d756}
\strng{editorbibnamehash}{d1f1309f75dc90b7a1846a2efbd43572}
\strng{editornamehash}{d1f1309f75dc90b7a1846a2efbd43572}
\strng{editorfullhash}{519612891addebf4b3e5e61fefc6d52d}
\strng{editorfullhashraw}{519612891addebf4b3e5e61fefc6d52d}
\field{sortinit}{S}
\field{sortinithash}{b164b07b29984b41daf1e85279fbc5ab}
\field{labelnamesource}{author}
\field{labeltitlesource}{title}
\field{note}{A note}
\field{title}{Very Long Title with XDATA}
\annotation{field}{note}{default}{}{}{0}{bignote}
\annotation{item}{author}{default}{1}{}{0}{biggerauthor}
\annotation{item}{author}{default}{2}{}{0}{bigauthor}
\annotation{item}{author}{default}{3}{}{0}{bigishauthor}
\annotation{item}{editor}{default}{2}{}{0}{bigishauthor}
\annotation{item}{editor}{default}{4}{}{0}{bigauthor}
\annotation{item}{institution}{default}{2}{}{0}{biginst}
\annotation{item}{location}{default}{2}{}{0}{bigloc}
\annotation{item}{location}{default}{3}{}{0}{bigloc}
\annotation{item}{publisher}{default}{1}{}{0}{bigpublisher}
\endentry
|;

# Test::Differences doesn't like utf8 unless it's encoded here
eq_or_diff($out->get_output_entry('xd1', $main), $xd1, 'xdata test - 1');
eq_or_diff(encode_utf8($out->get_output_entry('xd2', $main)), encode_utf8($xd2), 'xdata test - 2');
Expand All @@ -369,6 +463,7 @@ eq_or_diff($out->get_output_entry('macmillan:pub', $main), undef, 'xdata test -
eq_or_diff($out->get_output_entry('gxd1', $main), $gxd1, 'xdata granular test - 1');
eq_or_diff($out->get_output_entry('gxd1g', $main), $gxd1g, 'xdata granular test - 2');
eq_or_diff($out->get_output_entry('bltxgxd1', $main), $bltxgxd1, 'xdata granular test - 3');
eq_or_diff($out->get_output_entry('xdann1', $main), $xdann1, 'xdata annotation test - 1');
chomp $stderr;
ok((first {$_ eq "ERROR - Circular XDATA inheritance between 'lxd1:loop'<->'lxd2:loop'"} split("\n",$stderr)), 'Cyclic xdata error check - 1');
ok((first {$_ eq "ERROR - Circular XDATA inheritance between 'lxd4:loop'<->'lxd4:loop'"} split("\n",$stderr)), 'Cyclic xdata error check - 2');
Expand Down

0 comments on commit 1e6484f

Please sign in to comment.