App-PDFLibrarian

 view release on metacpan or  search on metacpan

lib/App/PDFLibrarian/BibTeX.pm  view on Meta::CPAN

    $pdfinfo{Author} = remove_tex_markup($bibentry->get("author") // $bibentry->get("editor") . " ed.");
    $pdfinfo{Title} = remove_tex_markup($bibentry->get("title"));
    $pdfinfo{Subject} = remove_tex_markup($bibentry->get("abstract"));
    $pdf->infoMetaAttributes(keys(%pdfinfo));
    $pdf->info(%pdfinfo);
    $pdf->preferences(-displaytitle => 1);

    # write XMP metadata to PDF file
    my $xmp = "";
    eval {
      $xmp = $pdf->xmpMetadata() // "";
    };
    my $xmphead = "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n";
    my $xmpdata = encode('utf-8', $xml->documentElement()->toString(0), Encode::FB_CROAK);
    my $xmptail = "\n<?xpacket end='w'?>";
    my $xmplen = length($xmphead) + length($xmpdata) + length($xmptail);
    my $xmppadlen = length($xmp) - $xmplen;
    if ($xmppadlen <= 0) {
      $xmppadlen = max(4096, 2*length($xmp), 2*length($xmpdata)) - $xmplen;
    }
    my $xmppad = ((" " x 99) . "\n") x int(1 + $xmppadlen / 100);
    my $newxmp = $xmphead . $xmpdata . substr($xmppad, 0, $xmppadlen) . $xmptail;
    $pdf->xmpMetadata($newxmp);

    # write PDF file
    eval {
      $pdf->update();
      $pdf->end();
      1;
    } or do {
      chomp(my $error = $@);
      print STDERR "$Script: could not save PDF file '$pdffile': $error\n";
      $bibentry = undef;
    };

    return $bibentry;
  };
  @modbibentries = parallel_loop("writing BibTeX entries to %i/%i PDF files", \@modbibentries, $body);

  return @modbibentries;
}

sub edit_bib_in_fh {
  my ($oldfh, @bibentries) = @_;
  die unless blessed($oldfh) eq 'File::Temp';

  # save checksums of BibTeX entries
  my %checksums;
  foreach my $bibentry (@bibentries) {
    $checksums{$bibentry->get('file')} = $bibentry->get('checksum');
  }

  # edit and re-read BibTeX entries, allowing for errors
  my @errors;
  while (1) {

    while (1) {

      # open new temporary file for editing BibTeX entries
      my $fh = File::Temp->new(SUFFIX => '.bib', EXLOCK => 0) or croak "$Script: could not create temporary file";
      binmode($fh, ":encoding(iso-8859-1)");

      # write header message
      if (@errors > 0) {
        print $fh wrap("% ", "% ", <<"EOF");
PDFLibrarian has encountered several errors in parsing the following BibTeX records. These errors are indicated with comments next to the line where the errors occurred.

All errors MUST be corrected before the BibTeX records can be written back to the PDF file given by the 'file' field in each record.

To ABORT ANY CHANGES from being written, simply delete the relevant records, or the entire contents of this file.
EOF
      } else {
        print $fh wrap("% ", "% ", <<"EOF");
PDFLibrarian has extracted the following BibTeX records for editing. Any changes to the records will be written back to the PDF file given by the 'file' field in each record.

To ABORT ANY CHANGES from being written, simply delete the relevant records, or the entire contents of this file.
EOF
      }
      if (%bibtex_macros > 0) {
        print $fh "%\n% Available BibTeX macros:\n";
        foreach my $macro (keys %bibtex_macros) {
          print $fh "% $macro: $bibtex_macros{$macro}\n";
        }
      }
      print $fh "\n";

      # build hash of errors by line number
      my %errorsbyline;
      foreach (@errors) {
        if (defined($_->{from})) {
          push @{$errorsbyline{$_->{from}}}, $_->{msg};
        } else {
          push @{$errorsbyline{0}}, $_->{msg};
        }
      }

      # write any error messages without line numbers
      if (defined($errorsbyline{0})) {
        foreach (@{$errorsbyline{0}}) {
          print $fh "% ERROR: $_\n";
        }
        delete $errorsbyline{0};
        print $fh "\n";
      }

      # write contents of old temporary file, with any error messages inline
      $oldfh->flush();
      $oldfh->seek(0, SEEK_SET);
      while (<$oldfh>) {
        chomp;
        my $line = sprintf("%i", $oldfh->input_line_number);
        foreach (@{$errorsbyline{$line}}) {
          print $fh "% ERROR: $_\n";
        }
        delete $errorsbyline{$line};
        s/\s+$//;
        next if /^%/;
        next if /^$/;
        print $fh "$_\n";
        if (/^}$/) {
          print $fh "\n";
        }
      }
      $fh->flush();

      # write any remaining error messages
      foreach (keys %errorsbyline) {
        foreach (@{$errorsbyline{$_}}) {
          print $fh "% ERROR: $_\n";
        }
      }

      # print index of all currently-defined keywords
      print $fh keyword_display_str();

      # save handle to new temporary file; old temporary file is deleted
      $oldfh = $fh;

      # edit BibTeX entries
      my $editor = $ENV{'VISUAL'} // $ENV{'EDITOR'} // 'editor';
      printf STDERR "$Script: opening %i BibTeX entries in editing program '$editor' ...\n", scalar(@bibentries);
      system($editor, $fh->filename) == 0 or croak "$Script: could not edit file '$fh->filename' with editing program '$editor'";

      # try to re-read BibTeX entries
      read_bib_from_file(\@errors, \@bibentries, $fh->filename);

      # error if duplicate BibTeX keys are found
      foreach my $dupkey (find_dup_bib_keys(@bibentries)) {
        push @errors, { msg => "duplicated key '$dupkey'" };
      }

      foreach my $bibentry (@bibentries) {

        # error if required fields are empty
        foreach my $bibfield ($structure->required_fields($bibentry->type)) {
          my $bibfieldvalue = $bibentry->get($bibfield) // "";
          $bibfieldvalue =~ s/[{}]//g;
          if ($bibfieldvalue eq "") {
            push @errors, { msg => "entry '@{[$bibentry->key]}' is missing required field '${bibfield}'" };
          }
        }

        # error if BibTeX entries contain field names which differ by 's', e.g. 'keyword' and 'keywords'
        foreach my $bibfield ($bibentry->fieldlist()) {
          if ($bibentry->exists($bibfield) && $bibentry->exists($bibfield . "s")) {
            push @errors, { msg => "entry '@{[$bibentry->key]}' contains duplicate fields '${bibfield}' and '${bibfield}s'" };
          }
        }
      }

      # BibTeX entries have been successfully read
      last if @errors == 0;

    }

    {
      # open new temporary file for editing BibTeX entries
      my $fh = File::Temp->new(SUFFIX => '.bib', EXLOCK => 0) or croak "$Script: could not create temporary file";
      binmode($fh, ":encoding(iso-8859-1)");

      # format and print BibTeX entries
      write_bib_to_fh({ fh => $fh }, format_bib({}, @bibentries));
      $fh->flush();

      # save handle to new temporary file; old temporary file is deleted
      $oldfh = $fh;

      # try to re-read BibTeX entries
      read_bib_from_file(\@errors, \@bibentries, $fh->filename);
    }

    # BibTeX entries have been successfully read
    last if @errors == 0;

  }

  # restore checksums of BibTeX entries
  foreach my $bibentry (@bibentries) {
    $bibentry->set('checksum', $checksums{$bibentry->get('file')});
  }

  return @bibentries;
}

sub find_dup_bib_keys {
  my (@bibentries) = @_;

  # find duplicate keys in BibTeX entries
  my %keycount;
  foreach my $bibentry (@bibentries) {
    ++$keycount{$bibentry->key};
  }

  return grep { $keycount{$_} > 1 } keys(%keycount);
}

sub format_bib_authors {
  my ($nameformat, $maxauthors, $etal, @authors) = @_;

  # format authors
  my $authorformat = new Text::BibTeX::NameFormat($nameformat);
  foreach my $author (@authors) {
    $author = $authorformat->apply($author);
    $author = remove_tex_markup($author);
    if ($author =~ /\sCollaboration$/i) {
      $author =~ s/\s.*$//;
    }
  }

  if (@authors > 0) {

    # limit number of authors to '$maxathors'
    if (defined($maxauthors) && $maxauthors > 0 && @authors > $maxauthors) {
      @authors = ($authors[0], $etal);
    }

    # replace 'others' with preferred form of 'et al.'
    $authors[-1] = $etal if $authors[-1] eq "others";



( run in 1.452 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )