Une classe PHP pour manipuler des enregistrements au format ISO2709

classe iso2709_record (public release 0.0.6)
par François Lemarchand. 2003 balno@users.sourceforge.net

En préambule, je voudrais dédier ce travail à Hervé Le Crosnier. Au début des années 90, j'ai eu la chance de suivre un stage d'initiation à HTML assuré par lui. Il est plus que probable qu'il ne s'en souvienne pas, mais depuis ce stage, quand je mesure le chemin parcouru, je ne peux que dire merci Hervé, un grand MERCI.

La classe PHP dont il est question ici se pose deux objectifs : Dans un premier temps, il s'agit de lire des enregistrements ISO2709 afin de récupérer des informations. Dans un second temps, et pour être complétement efficiente, la classe devrait idéalement nous permettre de constituer de nouveaux enregistrements respectant ce format.

Rappel sur le format ISO2709

Le format d'échange ISO 2709 fournit une norme pour l'échange de données bibliographiques. Il offre une interface unifiée permettant la description d'enregistrements organisés en champs et sous-champs.

Ce format définit pour un enregistrement 3 zones :

Comme on peut le voir, la structure d'un enregistrement au format ISO 2709 est assez simple une fois que l'on en a compris le principe. La manipulation de tels enregistrements se résume à des opérations élémentaires (décomposition de l'enregistrement, consultation de la "table des matières" pour accéder à un champ, etc.) La difficulté principale reste de maintenir la synchronisation entre les trois zones de l'enregistrement lors de l'insertion ou de la suppression de champs. La classe présentée ici à pour but d'accomplir ces accès et cette synchronisation de manière relativement transparente pour le programmeur.

Propriétés

Comme toute classe PHP, la classe iso2709_record comprend des propriétés qui peuvent être accédées par votre code. Certaines de ces propriétés sont utilisées pour la gestion interne de l'objet défini dans la classe.

  propriété type description
enregistrement ISO2709 full_record string enregistrement complet
guide chaîne portion 'guide'
directory chaîne portion 'répertoire'
data chaîne portion 'zone de données'
tableaux internes inner_guide tableau associatif guide sous forme de tableau associatif
structure:
$this->inner_guide = array(
rl => 'longueur de l'enregistrement (int)',
rs => 'état de la notice (1 car.)',
dt => 'type de document (1 car.)',
bl => 'type de la notice (1 car.)',
hl => 'niveau hiérarchique (1 car.)',
pos9 => 'indéfini. contient un espace',
il => 'longueur indicateurs (int)',
sl => 'longueur code sous-champ (int)',
ba => 'adresse de base des données (int)',
el => 'niveau de catalogage (1 car.)',
ru => 'forme du catal. descriptif (1 car.)',
pos19 => 'indéfini. contient un espace',
dm1 => 'infos pour le répertoires (toujours 4)',
dm2 => 'infos pour le répertoires (toujours 5)',
dm3 => 'infos pour le répertoires (toujours 0)',
pos23 => 'indéfini. contient un espace'
);
inner_directory tableau multi-dimensionnel tableau correspondant au répertoire de l'enregistrement
structure:
$this->inner_guide[x] = array(
label => 'label du champ de rang x (string)',
length => 'longueur du champ de rang x',
adress => 'offset du champ dans l'enregistrement'
);
inner_data tableau multi-dimensionnel tableau correspondant au répertoire de l'enregistrement
structure:
$this->inner_data[x] = array(
label => 'label du champ de rang x (string)',
content => 'contenu du champ'
);
caractères spéciaux record_end chaîne caractère de fin de notice (IS3 de l'ISO 6630)
field_end chaîne caractère de fin de champ (IS2 de l'ISO 6630)
subfield_begin chaîne caractère de début de sous-champ (IS1 de l'ISO 6630)
NSB_begin chaîne caractère débutant un NSB (Non Sorting Block)
NSB_end chaîne caractère de fin NSB (Non Sorting Block)
note Les propriétés décrivant des caractères spéciaux ont toutes dans cette classe un équivalent préfixé par rgx_ contenant les mêmes caractères sous une forme utilisable dans des expressions régulières compatibles PERL.
divers update_mode entier mode de mise à jour de l'enregistrement
errors tableau tableau contenant les messages d'erreurs liés au format de l'enregistrement (méthode valid()) ou aux modifications effectuées sur celui-ci.


Dans l'état actuel des choses, la gestion des objets de PHP ne comprend aucune notion de propriétes ou méthodes publiques ou privées. Les propriétés de la classe sont donc toutes accessibles depuis n'importe quel endroit de votre code. Cependant, je ne recommande pas de modifier les propriétés préfixées par inner_ sans bien réfléchir avant. Ces variables sont en effet utilisées de manière interne dans la classe pour la gestion de l'enregistrement. On peut donc les modifier, mais sans garantie quant à la synchronisation de l'ensemble.

Méthodes

Le constructeur : iso2709_record

prototype : iso2709_record([string record], [flag update_mode])

Le constructeur est appelé lors de la création de la classe. Il admet deux paramètres :

Obtenir le contenu d'un sous-champ ou d'un champ : get_field

prototype : get_subfield(string label [, mixed refs])

La fonction get_subfield retourne le contenu du champ spécifié par le paramètres qui lui sont passés. L'appel de la méthode peut être effectué de différentes manières. Le paramètre label est l'étiquette du champ. Ce peut être une expression régulière (le point est alors un caractère "joker". ex : 7.0 indique les champs 700 et 710).

La valeur retournée par la fonction est un tableau dont la structure est définie par les paramètres passés (ref).

  1. Si le seul paramètre fourni est label, la méthode retourne un tableau dont les éléments sont constitués par les contenus entiers des champs correspondant à label.
  2. Si seule une étiquette de sous-champ est fournie en plus du label, le tableau retourné sera comme précédemment un tableau dont les éléments sont constitués par les contenus entiers des champs correspondant à label et à l'étiquette fournie.
  3. Si plusieurs étiquettes de sous-champs sont fournies, le tableau retourné sera un tableau multi-dimensionnel dont les éléments sont des tableaux associatifs contenant les valeurs des sous-champs spécifiés.


exemples d'appels valides de get_subfield :

$isbn = $myRecord->get_subfield('010')
retourne un tableau constitué par les contenus des champs 010 de l'enregistrement. Les indices de ce tableau sont numériques.
ex.
  $isbn[0] = 'contenu du premier champ 010'
  $isbn[1] = 'contenu du second champ 010'
  ...
     
$nom_auteur = $myRecord->get_subfield('7..', a)
retourne un tableau constitué par les contenus des sous-champs $a des champs commencant par 7 de l'enregistrement. Les indices de ce tableau sont numériques.
ex.
  $nom_auteur[0] = 'contenu du sous-champ 700$a'
  $nom_auteur[1] = 'contenu du sous-champ 701$a'
  ...
  $nom_auteur[x] = 'contenu du dernier sous-champ 712$a'
     
$nom_auteur = $myRecord->get_subfield('7..', a, b)
retourne un tableau indexé dont les élements sont le contenu des sous-champs $a et $b des champs commencant par 7 de l'enregistrement. Les indices de ce tableau sont numériques et chacun de ses éléments est un tableau associatif.
ex.
  $nom_auteur[0][a] = 'contenu du sous-champ 700$a'
  $nom_auteur[0][b] = 'contenu du sous-champ 700$b'
  $nom_auteur[1][a] = 'contenu du sous-champ 701$a'
  ...
  $nom_auteur[x][a] = 'contenu du dernier sous-champ 712$a'
  $nom_auteur[x][b] = 'contenu du dernier sous-champ 712$b'
     


Avec ceci, on doit en principe pouvoir accéder n'importe quel sous-champ de l'enregistrement. Si vous avez des besoins plus complexes, rien ne vous empêche de 'parser' directement les propriétes de la classe (cf. section propriétés).

Ajout d'un champ : add_field

prototype : add_field(string label, string indicators, mixed field_content)

Cette méthode permet l'ajout d'un champ à un enregistrement ISO2709. On peut l'appeler de différentes manières : le nombre de paramètres est variable. On doit fournir en premier lieu l'étiquette du champ et les éventuels indicateurs, ceux-çi pouvant être de longueur nulle si le champ n'a pas d'indicateurs.

Le passage du contenu du champ se fait par le paramètre field_content sous la forme d'un nombre variable de chaînes comprenant alternativement l'étiquette du sous-champ et le contenu du sous-champ. Si une seule chaîne est fournie, le champ est réputé n'avoir pas d'étiquette de sous-champ et contenir uniquement la chaîne fournie.

La seconde possibilité est de passer le contenu du champ sous la forme d'un tableau multi-dimensionnel dont chaque ligne est un array constitué par l'étiquette du sous-champ et le contenu du sous-champ.

exemples d'appels valides de add_field :

$myRecord->add_field('001', '', '01-0002977')
ajoute un champ 001 ayant pour contenu 01-0002977 et ne comprenant pas d'indicateurs.
$myRecord->add_field(200, '1 ', a, 'Minable le pingouin', f, 'Texte d'Helen Lester')
ajoute un champ 200 ayant un champ $a, un champ $f et dont le premier indicateur est 1.
$monChamp[0] = array( a => 'Lester');
$monChamp[1] = array( b => 'Helen');
$myRecord->add_field(700, ' 1', $monChamp);
Passage des arguments par un tableau. On insére un champ 700 avec $a et $b avec un indicateur décrivant une entrée au nom de famille.


La méthode add_field() retourne TRUE en cas de succès et FALSE en cas d'échec (mauvais format pour les paramètres). Dans ce dernier cas, un message est ajouté au tableau des erreurs (cf. méthode show_errors).

Suppression d'un champ : delete_field

prototype : delete_field(string label)

La méthode delete_field() est à utiliser pour supprimer un champ de l'enregistrement ISO2709. Son fonctionnement est relativement simple : on passe comme paramètre l'étiquette du champ à supprimer. Il est possible de supprimer plusieurs champs en un seul appel dans la mesure où le paramètre label peut être une expression régulière. En pratique, on peut considérer que le point est un caractère "joker".

exemples d'appels valides de delete_field :

$myRecord->delete_field('001')
supprime le ou les champs 001.
$myRecord->delete_field('71.')
supprime les champs de l'enregistrement dont les labels commencent par 71 (710, 711, 712...).
$myRecord->delete_field('..2')
supprime les champs de l'enregistrement dont les labels finissent par 2 (012, 022, etc.). (c'est idiot, mais sait-on jamais...)


La méthode delete_field() retourne TRUE en cas de succès et FALSE en cas d'échec (mauvais format pour le label). Dans ce dernier cas, un message est ajouté au tableau des erreurs (cf. méthode show_errors).

Gestion du guide  : set_rs, set_dt, set_bl, set_hl, set_el, set_ru

prototypes :
set_rs(string/char value)
set_dt(string/char value)
set_bl(string/char value)
set_hl(string/char value)
set_el(string/char value)
set_ru(string/char value)

Cet ensemble de méthodes permet de positionner les valeurs non calculées du guide de l'enregistrement. Pour connaître le codage des valeurs concernées, je vous invite à vous reporter aux spécifications du format UNIMARC (ou autre).

Nomenclature des fonctions :

fonction position concernée élément description
set_rs() 5 record status Etat de la notice
set_dt() 6 document type Type de document
set_bl() 7 bibliographic level Type de la notice
set_hl() 8 hierarchical level Niveau hiérarchique
set_el() 17 encoding level Niveau de catalogage
set_ru() 18 record update Forme du catalogage descriptif


L'appel de ces méthodes est on ne peut plus simple : on passe le code souhaité comme paramètre unique. Le guide (inner_guide) est modifié. Cependant, la mise à jour de l'enregistrement est toujours gérée suivant les modes USER_UPDATE et AUTO_UPDATE. Si la classe a été déclarée avec le mode USER_UPDATE, la modification ne sera effective qu'après l'appel de la méthode update().

Mise à jour de l'enregistrement : update

prototype : update(void)

Cette méthode n'est à invoquer que si la classe a été créée avec le drapeau update_mode fixé à USER_UPDATE. Si ce n'est pas le cas, l'appel à cette méthode sera effectué à chaque opération d'insertion ou d'effacement effectuée sur l'enregistrement, ce qui rend superflue une mise à jour par le script appelant (cf. le constructeur : iso2709_record).

Le rôle de cette méthode est de synchroniser l'enregistrement courant avec sa représentation interne dans la classe. Les propriétés full_record, guide et directory sont mises à jour à partir du contenu des tableaux inner_data, inner_guide, inner_directory.

Cette méthode ne retourne rien.

Vérification du format de l'enregistrement : valid

prototype : valid(void)

Cette méthode vérifie le format ISO2709 de l'enregistrement. Elle retournera TRUE si l'enregistrement courant est valide et FALSE dans le cas contraire. Attention Cette méthode valide le format ISO2709 uniquement. Aucune erreur de sera générée si votre enregistrement contient, par exemple, 2 champs 700, ce qui est interdit par le standard UNIMARC.

Gestion des caractères accentués et autres : ISO_encode et ISO_decode

prototypes :
ISO_encode(string chaine)
ISO_decode(string chaine)

Ces deux méthodes assurent la conversion des caractères codés suivant le format ISO 5426 vers le format ISO 8859-1.

La fonction ISO_encode convertit le format ISO 8859-1 vers le format ISO 5426 et ISO_decode fait l'inverse.

Ces deux fonctions retournent la chaîne passée en paramètre mais convertie suivant le cas.

De mon point de vue, ces deux méthodes sont implémentées de manière 'partielle mais suffisante', ce qui veut dire que cette implémentation n'est pas complète dans la mesure où j'ai énormément de mal à trouver des informations suffisantes sur le format ISO 5426 et l'ISO 646. Pour l'anecdote, la fonction ISO_decode est adaptée d'un bout de code C posté sur le forum de l'application bien connue recode et la fonction ISO_encode en découle directement. Si des personnes avaient plus de documentation sur le sujet, je leur serais reconnaissant de m'en faire part. L'implémentation actuelle est a peu près satisfaisante pour les langues latines et romanes, mais incomplète pour l'allemand et les alphabets 'exotiques'.

Un cas pratique

Voici un exemple d'utilisation très simple destiné à clarifier un peu tout ça. Il illustre la création d'un enregistrement ISO 2709 minimal. Les commentaires sont dans le script.

<?
// inclusion du fichier de la classe

require('./iso2709.inc');

// on crée un nouvel objet vide

$record = new iso2709_record('', AUTO_UPDATE);

// on crée le champ 100 qui est obligatoire en UNIMARC

$champ = date('Ymd').'d||||||||||||uy0frea03||||ba';

// ce champ est inséré dans l'enregistrement

$record->add_field('100', '', a, champ);

// on ajoute un champ 200

$resp[0] = array(a, "Travailler, moi ? jamais !");
$resp[1] = array(e, "l'abolition du travail");
$resp[2] = array(f, "Bob Black");
$resp[3] = array(g, "traduit de l'anglais par Julius Van Daal");


$record->add_field('200', '1 ', $resp);

// on ajoute un champ 700

$record->add_field(700, ' 1', a, 'Black', b, 'Bob');

// champ 010 : l'ISBN

$record->add_field('010', '', a, '2-84405-000-X');

// champ 210 : adresse bibliographique

$record->add_field(210, '', a, '[s.l]', c, 'L'esprit frappeur', d, '1997');

// c'est fini, vous avez votre enregistrement.
// affichage de l'enregistrement ISO 2709 :

print $record->full_record;

?>