From 27d18a564adbaffa734a8c71890a24002931eb6c Mon Sep 17 00:00:00 2001
From: Rachid Aliouat <rachid.aliouat@univ-lille.fr>
Date: Wed, 8 Feb 2023 17:47:36 +0100
Subject: [PATCH] ajout du programme recup-sets-alma.pl pour recuperer les
 resultats d un jeu de resultats

---
 gestion_des_sets/README             |  14 ++
 gestion_des_sets/config.exemple     |   6 +
 gestion_des_sets/recup-sets-alma.pl | 251 ++++++++++++++++++++++++++++
 3 files changed, 271 insertions(+)
 create mode 100644 gestion_des_sets/README
 create mode 100644 gestion_des_sets/config.exemple
 create mode 100644 gestion_des_sets/recup-sets-alma.pl

diff --git a/gestion_des_sets/README b/gestion_des_sets/README
new file mode 100644
index 0000000..acf15fd
--- /dev/null
+++ b/gestion_des_sets/README
@@ -0,0 +1,14 @@
+API Configuration and Administration
+
+programme recup-sets-alma.pl
+
+Programme qui va récuperer les membres d'un sets alma. C'est à dire les résultats de recherche d'une jeu de résultat Alma.
+Api utilise : Retrieve Set Members
+GET /almaws​/v1​/conf​/sets​/{set_id}​/members 
+
+fichier de conf à passer en parametre
+
+
+perl recup-sets-alma.pl config-test.conf
+
+Le fichier de conf contient la cle api et le numéro de l'ensemble : set_id
diff --git a/gestion_des_sets/config.exemple b/gestion_des_sets/config.exemple
new file mode 100644
index 0000000..07ec343
--- /dev/null
+++ b/gestion_des_sets/config.exemple
@@ -0,0 +1,6 @@
+api_user_conf	RecupConfSandBox
+api_cle_conf	l8xx403e####################bcf612
+set_id	123456891011213141516
+fic_sortie	resultats.csv
+horodatage	no
+
diff --git a/gestion_des_sets/recup-sets-alma.pl b/gestion_des_sets/recup-sets-alma.pl
new file mode 100644
index 0000000..e918e83
--- /dev/null
+++ b/gestion_des_sets/recup-sets-alma.pl
@@ -0,0 +1,251 @@
+#!/usr/bin/perl -w
+##################
+# Rachid Aliouat
+# Le 07/02/2023
+# API Configuration and Administration
+# Récupérer les résultats de recherche d'un jeu
+#################
+use strict;
+use warnings;
+use LWP::UserAgent; 	# bibliothèque pour les webservice
+use XML::Twig;			# bibliothèque pour le XML
+use URI::Encode qw(uri_encode uri_decode);; #encoder decoder les URL   - requiert la bibliotheque liburi-encode-perl
+
+my $token="";
+my @tab_resultats=();
+my @tab_ppn=();
+my $indice=0;
+my $j=0;
+my $nom_fic_conf="";
+my $nom_fic_csv="";
+my %hash_entete=(); # pour retrouver plus facilement une valeur
+my @tab_entete=(); # pour parcourir dans l'ordre les colonnes
+my $ligne_entete_csv="";
+my $nbr_colonne=0;
+my $entete_csv_deja_ecrit=0;
+my $taille_lot=100; # taille d'un lot de resultat i.e "la limite"
+#my $max_ppn_url = 100; # nombre max de bloc ppn dans l'url : attention une url est limitée en nombre d'octect : pas plus de 100 PPN car c'est encapsulé dans des balises XML verbeuses...
+#my $compteur_ppn=0;
+
+$nom_fic_conf=$ARGV[0];
+
+##### LIRE LE FICHIER DE CONF
+my %hash_conf=();
+open (CONF, $nom_fic_conf) or die "Ouverture fichier de configuration $nom_fic_conf impossible , cause : $! \n";
+print "lecture de la configuration:\n";
+print "-" x 20, "\n";
+my @tab_inter = <CONF>; 
+close(CONF);
+my $i=0;
+for ($i=0;$i<=$#tab_inter;$i++) {
+	print $tab_inter[$i];
+	my @ligne=split('\t',$tab_inter[$i]); # decoupage ligne
+	$hash_conf{$ligne[0]}=epure($ligne[1]); # alimentation de la table de hash avec la conf
+
+}
+print "-" x 20, "\n";
+##### si horodatage du nom de fichier est "yes"
+if ($hash_conf{'horodatage'}=~ /yes/){
+	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
+	#printf("%02d:%02d:%02d", $hour, $min, $sec);
+	#my $date_jour=sprintf("%02d-%02d-%04d", $mday,  ($mon+1), (1900+$year));
+	my $date_jour=sprintf("%04d-%02d-%02d", (1900+$year),($mon+1),$mday);
+	print "Horodatage : $date_jour\n";
+	$hash_conf{'fic_sortie'}=$date_jour."-".$hash_conf{'fic_sortie'};
+}
+
+########### Appel de l'API Configuration 
+
+my $twig_rapport=XML::Twig->new( # on créer un objet TWIG dans lequel on met toute la structure XML
+		
+        pretty_print  => 'indented',
+);
+	## on commence par récuperer juste le 1er resultat pour analyse : liste des noms de colonne, nombre de resultats au total
+	my $block_xml=get_alma_sets_by_id($hash_conf{set_id},1,0); # Appel de l'API configuraiton sur l'id du set present dans le fichier de conf
+	$block_xml =~/<members total_record_count=\"(\d{1,})\">/;
+	my $nbr_membres =$1;
+	$twig_rapport->parse($block_xml); # ici tout est dans la structure twig
+	print $twig_rapport->sprint; # affichage du contenu
+	print "\n###########\nIl y a $nbr_membres resultats au total à récupérer\n";
+	print "Liste des colonnes :\n";
+	aliment_tab_indices ($twig_rapport); ### Alimentation de la ligne d'entete avec les intitulé de colonne pour le CSV
+
+	### On doit boucler pour récuperer tous les résultats car il n'y a pas de notion de ResumeToken
+	### On va jouer avec les parametre limit(nombre de membre à renvoyer) offset (la notion de saut)
+	### donc on recupére les 100 premiers, puis les 100 suivant en sautant de 100 positions
+	### on commence par faire un calcul pour savoir combien d'appel on va faire
+	### $nbr_appel = $nbr_membres / 100 ==> arrondi à l'entier supérieur
+	my $nbr_appel = div_entier_sup($nbr_membres,100);
+	print "pour $nbr_membres membres, il faut $nbr_appel appel(s)\n";
+	
+	#### Appels api pour recuperer tous les résultats de recherche par paquet de 100 :limit=100 et offset=$nbr
+	#$i=1;
+	my $limit=$taille_lot;
+	my $offset=0;
+	for ($i=1;$i<=$nbr_appel;$i++) {
+		$offset=(($i-1)*$limit);
+		print "Appel API $i / $nbr_appel : limit=$limit offset=$offset\n";
+		$block_xml=get_alma_sets_by_id($hash_conf{set_id},$limit,$offset); # Appel de l'API configuraiton sur l'id du set present dans le fichier de conf
+		$twig_rapport->parse($block_xml); 
+		aliment_tab_resultats($twig_rapport);
+
+	}
+
+
+################ Fonctions
+
+########### fonction Retourne la chaine de caractère passée en paramètre en ne laissant que les caractères autorisés
+sub epure { 
+	my ($res)=@_; 
+	
+	$res=~ s/\n//g;
+	$res=~ s/\r//g;
+	#$res=~ s/\"//g;
+	
+	return $res;
+}
+
+#### Division en arrondissant à l'entier supérieur
+sub div_entier_sup {
+	my ($num_1,$num_2)=@_;
+	
+	my $res = 0;
+	
+if(($num_1 % $num_2) == 0){
+  $res = $num_1 / $num_2;
+	}
+	else{
+		$res = int($num_1 / $num_2) + 1;
+	}
+	return($res);
+}
+
+
+######## Fonction alimente tableau indice de colonne pour chaque libellé de colonne car l'ordre des colonnes peut changer si on modifie le rapport analytics
+
+sub aliment_tab_indices{
+		my ($twig_rapport)=@_;
+		my $z=0;
+		my $nom_col="";
+		my $numero_col=$z;
+		
+	my $root= $twig_rapport->get_xpath('//members',0); 
+	#print $root->sprint; # affichage du contenu
+	print "\n";
+	
+	my @les_colonnes= $root->first_child('member')->children;  ### on récuperer une table de hash de tous les "enfants" du root 
+
+	foreach my $une_colonne (@les_colonnes)      # liste chaque nouveaute de la liste des nouveautes
+		{
+			#print $une_colonne->name,"\n";
+			$nom_col=$une_colonne->name;
+			$numero_col=$z;
+			print "Colonne num $numero_col -> $nom_col\n";
+			$tab_entete[$numero_col]=$nom_col;
+			$hash_entete{$nom_col} = $numero_col;
+			$ligne_entete_csv.=$nom_col."\t";
+			#$tab_entete[$z]=$nom_col;
+			$z++;
+			$nom_col="";
+		}
+		$nbr_colonne=$z-1;
+
+}
+
+
+
+######## Fonction alimente tableau des resultats
+sub aliment_tab_resultats{
+	print "\nALIMENTE TABLE\n";
+	my ($twig_rapport)=@_;
+	#print $twig_rapport->sprint;
+	my $root= $twig_rapport->get_xpath('//members',0); 
+	#my $root= $twig_rapport->get_xpath('//',0); 
+
+	my @les_members= $root->children;#('members');  ### on récuperer une table de hash de tous les "enfants" du root = les entrées <Row> de la racine <rowset>
+	#print "#########################\n";
+	#print "####".$root->first_child('Row')->first_child('Column12')->text()."####\n";
+	my $i=0;
+	foreach my $un_member (@les_members)      # liste chaque nouveaute de la liste des nouveautes
+		{ 
+			#print $un_member->sprint,"\n#######\n";
+			for ($i=0;$i<=$#tab_entete;$i++) {
+				#print $tab_entete[$i]." : ";
+				#print $un_member->first_child($tab_entete[$i])->text();
+				#print "\n";
+				$tab_resultats[$indice][$i]=$un_member->first_child($tab_entete[$i])->text();
+			}
+			
+			$indice++;
+			#print "\n";
+		}
+	
+}
+
+	#### Ecriture du fichier CSV en mode Ajout
+print "Ecriture du fichier CSV\n";
+	open (FIC, ">".$hash_conf{fic_sortie}) or die "Ouverture fichier de liste exemplaire CSV impossible , cause : $! \n";
+	binmode(FIC, ":utf8");
+
+print FIC "$ligne_entete_csv\n";
+for ($i=0;$i<=$#tab_resultats;$i++) {
+	for ($j=0;$j<=$#tab_entete;$j++) {
+		print FIC $tab_resultats[$i][$j];
+		if ($j<$#tab_entete) {print FIC "\t"} # on met une tabulation sauf après la derniere colonne
+		}
+	print FIC "\n";
+}
+
+close(FIC);
+
+############## fonction API GET pour questionner Alma API Configuration: Recuperer les données sur la base du set_id du jeu de resultat
+sub get_alma_sets_by_id{
+	
+	my ($set_id,$limit,$offset)=@_;  # on récupére le set_id du jeu ou le offset en paramétre
+	print "Appel fonction GET\n ----\n$set_id\n$token\n";
+
+	print "$set_id\n";
+	# Creer un agent pour questionner le webservice = céer un agent c'est comme ouvrir un navigateur internet
+
+	my $ua = LWP::UserAgent->new;
+	$ua->agent("MyApp/0.1");
+	# Creer la requete
+	my $chaine_rq="";
+	
+	print "-----appel pour $limit resultats à partir de la position $offset----\n";
+	$chaine_rq="https://api-eu.hosted.exlibrisgroup.com/almaws/v1/conf/sets/$set_id/members?limit=$limit&offset=$offset&apikey=".$hash_conf{api_cle_conf};
+	print $chaine_rq,"\n";
+	
+	my $req = HTTP::Request->new(GET => $chaine_rq);
+	$req->content_type('application/xml');
+	$req->authorization_basic($hash_conf{api_user_conf}, $hash_conf{api_cle_conf}); # login  + mot de passe => user_api + cle api
+	
+	# Passer requete au user agent et récuperer la reponse
+	my $i=0;
+	my $max=3; #maximum de tentative
+	my $status="NOK";
+	while ($status =~ /^NOK$/){
+		my $res = $ua->request($req);
+		# Vérifier la reponse
+		if ($res->is_success) {
+			print "##############################","\n";
+			#print $res->content;
+			print "Existence notice OK pour $set_id\n";
+			print "##############################","\n";
+			$status=$res->content;
+		} else {
+			print $res->content;
+			print "\n############################","\n";
+			print $res->status_line, "\n";
+			print "\n############################","\n";
+			$i++;
+			if ($i==$max) {
+				$status="MAX"; # on force la sortie de la boucle meme si c'est pas ok
+		}
+	}
+}
+	
+	
+	return $status;
+}
+
-- 
GitLab