Catmandu::AlephX(3pm)

πŸ’– Catmandu::AlephX πŸ’–

okay so bestie, welcome to the Catmandu::AlephX page πŸ’… β€” it's one of those User Contributed Perl Documentation pages, filed under Catmandu::AlephX(3pm), and it is literally your low-level client for Aleph X-Services. lowkey iconic, highkey useful. let's get into it πŸ¦‹


✨ NAME ✨

Catmandu::AlephX β€” a Low level client for Aleph X-Services. that's the whole vibe. πŸŽ€


🌸 SYNOPSIS 🌸

here's the group-chat summary of how you actually use this thing:

my $aleph = Catmandu::AlephX->new(url => "http://localhost/X");
my $item_data = $aleph->item_data(base => "rug01",doc_number => "001484477");

#all public methods return a Catmandu::AlephX::Response
# 'is_success' means that the xml-response did not contain the element 'error'
# most methods return only one 'error', but some (like update_doc) multiple.
# other errors are thrown (xml parse error, no connection ..)

if($item_data->is_success){

  say "valid response from aleph x server";

}else{

  say "aleph x server returned error-response: ".join("\n",@{$item_data->errors});

}

so the tea is πŸ“: every public method hands you back a Catmandu::AlephX::Response. and is_success? it just means the xml-response did not contain the element error β€” that's it, that's the whole check. most methods only give you one error, but some (looking at you, update_doc πŸ‘€) can give you several. and then there's a whole other category of chaos that just gets thrown at you β€” xml parse errors, no connection, that kind of thing. no is_success for those, they just yeet. 😭


πŸ’… METHODS πŸ’…

πŸŽ€ item-data πŸŽ€

straight from the AlephX docs, bestie:

this service takes a document number from you, the user. then for every single item of that document it goes and grabs:

  • Item information (From Z30). 🌟
  • Loan information (from Z36). 🌟
  • An indication whether the request is on-hold 🌟

example:

my $item_data = $aleph->item_data(base => "rug01",doc_number => "001484477");
if($item_data->is_success){
  for my $item(@{ $item_data->items() }){
    print Dumper($item);
  };
}else{
  print STDERR join("\n",@{$item_data->errors})."\n";
}

remarks:

This method is equivalent to 'op' = 'item-data' β€” periodt. πŸ’…

πŸ¦‹ item-data-multi πŸ¦‹

documentation from AlephX, and it's giving "item-data but upgraded":

this service takes a document number from you, and for each of the document's items it retrieves the following:

  • Item information (from Z30) ✨
  • Loan information (from Z36) An indication of whether or not the item is on hold, has hold requests, or is expected (that is, has not arrived yet but is expected) ✨

It is similar to the item_data X-service, except for the parameter START_POINT, which enables the retrieval of information for documents with more than 1000 items. πŸ‘‘

example:

my $item_data_m = $aleph->item_data_multi(base => "rug01",doc_number => "001484477",start_point => '000000990');
if($item_data_m->is_success){
  for my $item(@{ $item_data_m->items() }){
    print Dumper($item);
  };
}else{
  print STDERR join("\n",@{$item_data_m->errors})."\n";
}

say "items retrieved, starting at ".$item_data_m->start_point() if $item_data_m->start_point();

remarks:

This method is equivalent to 'op' = 'item-data-multi'. The attribute 'start_point' only supplies a value, if the document has over 990 items. (yes, 990. not 1000. we don't make the rules, bestie πŸ™ˆ)

🌈 read_item 🌈

documentation from AlephX:

this service retrieves a requested item's record from a given ADM library β€” but only in case such an item does actually exist in that ADM library. no item, no record, that's the deal. πŸ’«

example:

my $readitem = $aleph->read_item(library=>"usm50",item_barcode=>293);
if($readitem->is_success){
  for my $z30(@{ $readitem->z30 }){
    print Dumper($z30);
  }
}else{
  say STDERR join("\n",@{$readitem->errors});
}

remarks:

This method is equivalent to 'op' = 'read-item'. 🫢

⭐ find ⭐

documentation from Aleph X:

this service retrieves a set number and the number of records answering a search request that you, the user, inserted. so you ask, it tells you what set you got and how much is in it. 🎯

example:

my $find = $aleph->find(request => 'wrd=(art)',base=>'rug01');
if($find->is_success){
  say "set_number: ".$find->set_number;
  say "no_records: ".$find->no_records;
  say "no_entries: ".$find->no_entries;
}else{
  say STDERR join("\n",@{$find->errors});
}

remarks:

This method is equivalent to 'op' = 'find'.

arguments:

  • request - search request πŸ”
  • adjacent - if 'Y' then the documents should contain all the search words adjacent to each other, otherwise 'N' 🧩

πŸ’œ find_doc πŸ’œ

documentation from AlephX:

this service retrieves the OAI XML format of an expanded document as given by the user. you hand it the deets, it hands you the XML. ✨

example:

my $find = $aleph->find_doc(base=>'rug01',doc_num=>'000000444',format=>'marc');
if($find->is_success){
  say Dumper($find->record);
}else{
  say STDERR join("\n",@{$find->errors});
}

remarks:

This method is equivalent to 'op' = 'find-doc'.

πŸŽ€ present πŸŽ€

documentation from Aleph X:

this service retrieves OAI XML format of expanded documents. and plot twist β€” you can view documents according to the locations within a specific set number. πŸ‘‘

example:

my $set_number = $aleph->find(request => "wrd=(BIB.AFF)",base => "rug01")->set_number;
my $present = $aleph->present(
  set_number => $set_number,
  set_entry => "000000001-000000003"
);
if($present->is_success){
  for my $record(@{ $present->records() }){
    say "doc_number: ".$record->doc_number;
    say "\tmetadata: ".$record->metadata->type;
  }
}else{
  say STDERR join("\n",@{$present->errors});
}

remarks:

This method is equivalent to 'op' = 'present'. πŸ’…

πŸ¦‹ ill_get_doc_short πŸ¦‹

documentation from Aleph X:

this service retrieves the doc number and the XML of the short document (Z13). short and sweet, just like a good group chat reply. πŸ“

example:

my $result = $aleph->ill_get_doc_short(doc_number => "000000001",library=>"usm01");
if($result->is_success){
  for my $z30(@{ $result->z13 }){
    print Dumper($z30);
  }
}else{
  say STDERR join("\n",@{$result->errors});
}

remarks:

This method is equivalent to 'op' = 'ill-get-doc-short'.

🌟 bor_auth 🌟

documentation from Aleph X:

this service retrieves the Global record (Z303), Local record (Z305) and the Data record (Z304) for a given Patron β€” if the given ID and verification code match. πŸ’–

Otherwise, an error message is returned. no match, no data, bestie. that's just how it is. 😭

example:

my %args = (
  library => $library,
  bor_id => $bor_id,
  verification => $verification
);
my $auth = $aleph->bor_auth(%args);

if($auth->is_success){

  for my $type(qw(z303 z304 z305)){
    say "$type:";
    my $data = $auth->$type();
    for my $key(keys %$data){
      say "\t$key : $data->{$key}->[0]";
    }
  }

}else{
  say STDERR "error: ".join("\n",@{$auth->errors});
  exit 1;
}

πŸŽ€ bor_info πŸŽ€

documentation from Aleph X:

this service retrieves all information related to a given Patron: Global and Local records, Loan records, Loaned items records, Short doc record, Cash record, and so on β€” if the ID and verification code provided match. πŸ‘‘

If not, an error message is returned. and here's the thing: since the bor-info X-Service retrieves a very large amount of data, and not all of it may be relevant, you can choose to receive a part of the data, based on your needs. main character energy: you decide what you want. πŸ’…

example:

my %args = (
  library => $library,
  bor_id => $bor_id,
  verification => $verification,
  loans => 'P'
);
my $info = $aleph->bor_info(%args);

if($info->is_success){

  for my $type(qw(z303 z304 z305)){
    say "$type:";
    my $data = $info->$type();
    for my $key(keys %$data){
      say "\t$key : $data->{$key}->[0]";
    }
  }
  say "fine:";
  for my $fine(@{ $info->fine() }){
    for my $type(qw(z13 z30 z31)){
      say "\t$type:";
      my $data = $fine->{$type}->[0];
      for my $key(keys %$data){
        say "\t\t$key : $data->{$key}->[0]";
      }
    }
  }

}else{
  say STDERR "error: ".join("\n",@{$info->errors});
  exit 1;
}

πŸ¦‹ ill_bor_info πŸ¦‹

documentation from Aleph X:

this service retrieves Z303, Z304, Z305 and Z308 records for a given borrower ID / barcode. four whole record types, slay. ✨

example:

the original page leaves this one blank, bestie β€” no code here, just vibes. πŸ™ˆ

🌈 circ_status 🌈

documentation from Aleph X:

The service retrieves the circulation status for each document number entered by the user. and it's giving:

  • Item information (From Z30). 🌟
  • Loan information (from Z36). 🌟
  • Loan Status (Tab15), Due Date, Due Hour etc. 🌟

example:

again, the original page is just... empty here. no code. iconic of it honestly. πŸ’€

⭐ circ_stat_m ⭐

documentation from Aleph X:

The service retrieves the circulation status for each document number entered by the user β€” and this one is suitable for documents with more than 1000 items. πŸ‘‘

  • Item information (From Z30). ✨
  • Loan information (from Z36). ✨
  • Loan Status (Tab15), Due Date, Due Hour etc. ✨

This service is similar to circ-status X-service, except for the parameter START_POINT which enables to retrieve information for documents with more than 1000 items.

example:

yep. blank again. the original page said nothing and meant it. 😭

πŸ’œ publish_avail πŸ’œ

documentation from Aleph X:

This service supplies the current availability status of a document.

heads up: The X-Server does not change any data. it's read-only energy, bestie. 🫢

example:

my $publish = $aleph->publish_avail(doc_num =>
'000196220,001313162,001484478,001484538,001317121,000000000',library=>'rug01');
if($publish->is_success){

  #format for $publish->list() : [ [<id>,<marc-array>], .. ]

  for my $item(@{ $publish->list }){

    say "id: $item->[0]";
    if($item->[1]){
      say "marc array:";
      say Dumper($item->[1]);
    }else{
      say "nothing for $item->[0]";
    }

    say "\n---";
  }
}else{
  say STDERR join("\n",@{$publish->errors});
}

remarks:

The parameter 'doc_num' supports multiple values, separated by ','. Compare this to ill_get_doc, that does not support this. πŸ’€

πŸŽ€ ill_get_doc πŸŽ€

documentation from Aleph X:

This service takes a document number and the library where the corresponding document is located, and generates the XML of the requested document as it appears in the library given. πŸ¦‹

example:

my $illgetdoc = $aleph->ill_get_doc(doc_number =>
'001317121',library=>'rug01'); if($illgetdoc->is_success){

  if($illgetdoc->record){
    say "data: ".to_json($illgetdoc->record,{ pretty => 1 });
  }
  else{
    say "nothing found";
  }

}else{
  say STDERR join("\n",@{$illgetdoc->errors}); }

🌟 renew 🌟

documentation from Aleph X:

This service renews the loan of a given item for a given patron. πŸ’–

The X-Service renews the loan only if it can be done. If, for example, there is a delinquency on the patron, the service does not renew the loan. no exceptions, no mercy. 😭

example:

blank in the original, bestie. nothing to show here. πŸ™ˆ

πŸ¦‹ hold_req πŸ¦‹

documentation from Aleph X:

The service creates a hold-request record (Z37) for a given item after performing initial checks. πŸ‘‘

example:

also empty on the original page. just vibes, no code. πŸ’…

🌈 update_doc 🌈

documentation from Aleph X:

This service performs actions (Update / Insert / Delete) related to the update of a document.

(The service uses pc_cat_c0203 which updates a document via the GUI). πŸŽ€

notes:

When executing an update request, most of the 'errors' will be warnings instead of real errors. This happens because AlephX performs an 'UPDATE-CHK' before trying to execute an 'UPDATE', and stores all warnings during that check in the xml attribute 'error'. Therefore the method 'is_success' of the Catmandu::AlephX::Response is not very useful in this case. 🚨 Search for the last 'error', and check weather it contains 'updated successfully'. (yes, "weather". the original said weather. we are faithfully reporting the weather. 🌦️)

⚠️ bestie. sit down. this next bit is the "please do not break your library" part and i need you FOCUSED ⚠️
  • An update replaces the WHOLE record. like, the entire thing. so if you fail to supply xml_full_req (or marc, which sneaks in and builds it for you), the record will be deleted! πŸ’€ not a vibe. not a drill.
  • To be sure, use the full xml response of find-doc, change the fields inside oai_marc, and supply this xml as xml_full_req. that's the safe girl routine 🫢
  • Every update adds a CAT-field to the record. your updates can be recognized by CAT$$a == "WWW-X". so when you update a record you need to include the old CAT fields (that's the default behaviour), otherwise these fields will be deleted β€” and all the history is lost. gone. no receipts. 😭
  • straight from Aleph X, quoted exactly:
"Unlike other X-Services, the parameters can include XML up to 20,000 characters in length"
  • When you update often (and therefore add a lot of CAT fields), this can lead to the error 'Server closed connection'. this is due to the maximum of characters allowed in an XML request. it's giving ceiling energy πŸ“‰
  • Possible solution:
    1. retrieve record by find_doc
    2. get marc record:
my $marc = $res->record->metadata->data
  1. filter out your CAT fields ($a == "WWW-X") to shorten the XML:
$marc = [grep { !( $_->[0] eq "CAT" && $_->[4] eq "WWW-X" ) } @$marc];
  1. update $marc
  2. send:
$aleph->update_doc(library => 'usm01',doc_action => 'UPDATE',doc_number => $doc_number,marc => $marc);

=> your xml will now contain one CAT field with subfield 'a' equal to 'WWW-X'. slay, you trimmed it 🌿


✨ example ✨

okay here's the whole flow, copy it, live it, love it:

my $aleph = Catmandu::AlephX->new(url => "http://localhost/X");

my $doc_number = '000000444';
my $find_doc = $aleph->find_doc(
  doc_num => $doc_number,
  base => "usm01"
);
my $marc = $find_doc->record->metadata->data;
my $content_ref = $find_doc->content_ref;

my %args = (
  'library' => 'usm01',
  'doc_action' => 'UPDATE',
  'doc_number' => $doc_number,
  xml_full_req => $$content_ref
);
my $u = $aleph->update_doc(%args);
if($u->is_success){
  say "all ok";
}else{
  say STDERR join("\n",@{$u->errors});
}

✨ special support for catmandu marc records ✨

when you supply the argument 'marc', an xml document will be created for you, and stored in the argument 'xml_full_req'. 'marc' must be an array of arrays. and heads up 🫣 β€” when you already supplied 'xml_full_req', it will be overwritten. so don't be out here thinking you can have both.


🌸 update_item 🌸

documentation from Aleph X

The service updates an existing item in the required ADM library after performing all relevant initial checks prior to that action.

notes

  • AlephX stores not only errors in 'errors', but also the success message. yes really. the success is sitting in the errors bucket like it pays rent there πŸ’€
  • Therefore the method 'is_success' of the Catmandu::AlephX::Response is not very useful in this case. Search for the last 'error', and check weather it contains 'updated successfully'. (iykyk β€” this is THE trick)
  • The result of 'read_item' often contains translations, instead of the real values. But these translation cannot be used when updating items.
  • e.g. z30-item-status contains 'Regular loan' instead of '001'. so pretty, so useless for writing back πŸ™ˆ

example

my $alephx = Catmandu::AlephX->new(url => "http://localhost/X");
my $item_barcode = '32044044980076';

my %args = (
  'library' => 'usm50',
  'item_barcode' => $item_barcode,
);

my $z30 = $alephx->read_item(%args)->z30();

my $xml = XMLout($z30,,RootName=>"z30",NoAttr => 1);
$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".$xml;

$args{xml_full_req} = $xml;

my $u = alephx->update_item(%args);
if($u->is_success){
  say "all ok";
}else{
  say STDERR join("\n",@{$u->errors});
}

πŸ¦‹ create_item πŸ¦‹

documentation from Aleph X

The service creates a new item in the required ADM library after performing all relevant initial checks prior to that action. The item can be created for a bib record when no ADM record is linked to it yet, or it can be created to an ADM record with existing items.

notes

<npm>…</npm>

okay ngl the notes here are literally empty in the original. nothing to report, bestie, the notes section is just standing there being mysterious πŸ’…

example

my $alephx = Catmandu::AlephX->new(url => "http://localhost/X");
my $item_barcode = '32044044980076';

my %args = (
  'adm_library'    => 'rug50',
  'bib_library'    => 'rug01',
  'bib_doc_number' => '231843137',
);

my $xml = <<EOF;
<?xml version="1.0" encoding="UTF-8" ?>
<z30>
<z30-doc-number>15</z30-doc-number>
<z30-item-sequence>10</z30-item-sequence>
<z30-barcode>32044003924339</z30-barcode>
<z30-sub-library>WID</z30-sub-library>
<z30-material>BOOK</z30-material>
<z30-item-status>01</z30-item-status>
<z30-open-date>19980804</z30-open-date>
<z30-update-date>20020708</z30-update-date>
<z30-cataloger>EXLIBRIS</z30-cataloger>
<z30-date-last-return>20080607</z30-date-last-return>
<z30-hour-last-return>1631</z30-hour-last-return>
<z30-ip-last-return>CONV</z30-ip-last-return>
<z30-no-loans>011</z30-no-loans>
<z30-alpha>L</z30-alpha>
<z30-collection>GEN</z30-collection>
<z30-call-no-type>7</z30-call-no-type>
<z30-call-no>Heb 2106.385.5</z30-call-no>
<z30-call-no-key>7 selected</z30-call-no-key>
<z30-call-no-2-type />
<z30-call-no-2 />
<z30-call-no-2-key />
<z30-description>v.1</z30-description>
<z30-note-opac />
<z30-note-circulation />
<z30-note-internal />
<z30-order-number />
<z30-inventory-number />
<z30-inventory-number-date />
<z30-last-shelf-report-date>00000000</z30-last-shelf-report-date>
<z30-price />
<z30-shelf-report-number />
<z30-on-shelf-date>00000000</z30-on-shelf-date>
<z30-on-shelf-seq>000000</z30-on-shelf-seq>
<z30-doc-number-2>000000015</z30-doc-number-2>
<z30-schedule-sequence-2>00000</z30-schedule-sequence-2>
<z30-copy-sequence-2>00000</z30-copy-sequence-2>
<z30-vendor-code />
<z30-invoice-number />
<z30-line-number>00000</z30-line-number>
<z30-pages />
<z30-issue-date />
<z30-expected-arrival-date />
<z30-arrival-date />
<z30-item-statistic />
<z30-item-process-status>XX</z30-item-process-status>
<z30-copy-id>1</z30-copy-id>
<z30-hol-doc-number>000000046</z30-hol-doc-number>
<z30-temp-location>No</z30-temp-location>
<z30-enumeration-a />
<z30-enumeration-b />
<z30-enumeration-c />
<z30-enumeration-d />
<z30-enumeration-e />
<z30-enumeration-f />
<z30-enumeration-g />
<z30-enumeration-h />
<z30-chronological-i />
<z30-chronological-j />
<z30-chronological-k />
<z30-chronological-l />
<z30-chronological-m />
<z30-supp-index-o />
<z30-85x-type />
<z30-depository-id />
<z30-linking-number>000000000</z30-linking-number>
<z30-gap-indicator />
<z30-maintenance-count>007</z30-maintenance-count>
<z30-process-status-date>20080408</z30-process-status-date>
</z30>
EOF

$args{xml_full_req} = $xml;

my $u = alephx->create_item(%args);
if($u->is_success){
  say "all ok";
}else{
  say STDERR join("\n",@{$u->errors});
}

πŸ‘‘ AUTHOR πŸ‘‘

Nicolas Franck, "<nicolas.franck at ugent.be>" β€” ty for your service, king 🫑

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See <http://dev.perl.org/licenses/> for more information.


and that's the whole page, bestie πŸ’…βœ¨ this one shipped as perl v5.36.0, dated 2023-02-02 β€” go update some records responsibly, and for the love of everything, do not forget your xml_full_req 😭🫢🌸

πŸ“¦ where this page comes from