mercredi 21 octobre 2015

sed utile

Et oui, c'est utile (sed utile)! La preuve par la suite.
Partant d'un fichier pdf, nous construisons chaque mois un fichier texte reprenant les dépenses liées à l’utilisation  d'une carte bancaire et ce en vue d'une importation dans une base de données.
Tout était OK jusqu'à ce que brusquement le fichier texte obtenu ne convienne plus alors que apparemment au niveau du fichier pdf rien n'était changé.
Ce fichier texte inadéquat se présentait comme suit:

blabla1 15,65
EUR
blabla2 58,49
EUR
blabla3 214,29
EUR
blabla4 1.028,56
EUR

La première anomalie (le nom de la devise sur une ligne à part) peut se résoudre facilement avec la commande sed:



Explications:
sed importe dans son espace de travail la première ligne du fichier cbxy avant d'exécuter le script, à savoir le texte entre apostrophes. Ce script comprend 2 commandes:

  1. N provoque l'importation de la ligne 2 dans l'espace de travail
  2. s/\n/ / remplace par un espace le caractère de fin de ligne (\n) qui sépare les deux lignes: elles sont concaténées.
La fin du script déclenche l'impression automatique de l'espace de travail puis sed passe à la lecture de la ligne suivante, c'est-à-dire la ligne 3 (blabla2).

Le problème serait donc résolu s'il n'y avait qu'une seule anomalie. Hélas une autre anomalie est apparue: la disparition du signe moins (-) devant les montants (alors que ce signe est bel et bien présent dans le fichier pdf).
De nouveau sed va nous aider:



L'option r enjoint à sed d'utiliser les expressions régulières étendues. L'option n désactive l'impression automatique de l'espace de travail, par contre le p qui se trouve à la fin du script provoque son impression à chaque substitution. De la sorte nous recevons en output seulement les lignes qui nous intéressent.
Dans la commande de substitution

s/ ([0-9]{1,3},[0-9]{2})$/ -\1/

le motif recherché se trouve en fin de ligne (symbole $). Il est constitué d'un espace suivi de 1 à 3 chiffres, une virgule puis encore 2 chiffres. Dans la chaîne de remplacement \1 désigne la partie de la chaîne trouvée correspondant à l'expression régulière qui est entre parenthèses. L'utilisation des parenthèses permet ainsi d'éviter que le signe moins ajouté soit suivi d'un espace.

La ligne blabla4 manque à l'appel. Elle peut être traitée comme ceci:


Cette fois la commande de substitution est:

s/[0-9]{1,3}\.[0-9]{3},[0-9]{2}$/-&/

La chaîne recherchée située en fin de ligne doit correspondre au motif suivant:
1 à 3 chiffres suivi d'un point, puis 3 chiffres une virgule et encore 2 chiffres (dans l’expression régulière le point est échappé ou sinon il correspondrait à n'importe quel caractère). Dans la chaîne de remplacement, & désigne la totalité de la chaîne trouvée.

Pour transformer cbxy tout en conservant les commandes à exécuter, nous créons le fichier cbxy.sed:


#!/bin/sed -rf
s/[0-9]{1,3}\.[0-9]{3},[0-9]{2}$/-&/
s/ ([0-9]{1,3},[0-9]{2})$/ -\1/
N
s/\n/ /

Nous le rendons exécutable avant utilisation:


Tout semble OK. On termine avec la commande:

[toto@rigel cbxy]$ ./cbxy.sed cbxy > cbxyok

samedi 3 octobre 2015

Listbox

Nous sommes dans libreoffice base, connecté à une notre base de données postgresql bdtest.
Ci-après une représentation schématique des tables qui vont être concernées dans la suite de cet article:

Les relations d'intégrité référentielle sont représentées par des flèches pointant vers les clefs primaires cibles.
La colonne "service" de la table "gestion" contient le code du service auquel appartient l'employé, tandis que le nom de ce service se trouve dans la table "services".
Nous avons déjà réalisé de nombreux formulaires relatifs à la table "employés" (voir par exemple ici), mais cette fois nous allons construire un formulaire très simple sur la base duquel nous allons présenter en détail la notion de zone de liste (listbox). L'utilité d'une telle zone est de pouvoir montrer à l'utilisateur une liste déroulante d’éléments parmi lesquels celui-ci peut faire un choix. A chaque élément de la liste lui correspond un autre qui est caché. Si l'élément affiché est changé, la table du formulaire est mise à jour non pas avec l'élément choisi mais avec l'élément caché correspondant. Différentes possibilités sont offertes pour le type de contenu de liste. Nous allons considérer les quatre types de contenu de liste les plus courants, à savoir: instruction sql, requête, table, liste de valeurs.

Formulaire de base
Avant de nous attaquer à cette notion de zone de liste, utilisons l'assistant pour construire le formulaire. Dans la première étape choisissons la table "employés" comme source de données et nous en sélectionnons tous les champs. Ensuite l'assistant nous propose la création d'un sous-formulaire contenant les données de la table "gestion":


(Dans bdtest existe aussi une relation de la table "paie" vers "employés" mais elle ne nous intéresse pas ici)

Cette fois n'afficherons dans le formulaire que le seul champ "service":


Pour la position des contrôles nous choisissons deux fois l'option "colonnes, étiquettes en haut".
En ce qui concerne la saisies des données, nous cochons ce qui nous convient:


Lorsque nous en avons fini avec l'assistant, il nous reste à repositionner et redimensionner les différents champs et choisir éventuellement une image de fond pour le formulaire.
Nous n'oublions pas de mettre en "lecture seule" le champ matricule. Pour ce faire nous le sélectionnons (CTRL +clic gauche) avant d'utiliser l'outil contrôle:




Utilisons l'outil "navigateur de formulaire"


afin de renommer MainForm et SubForm en Employés et Gestion:


Pour ce faire, il suffit de cliquer droit sur MainForm et SubForm et de choisir "Renommer" dans le menu contextuel qui surgit.
Cette étape n'est pas nécessaire mais apporte un plus au niveau clarté.
Il nous reste à affiner quelque peu les propriétés de "Employés" et de "Gestion". On y accède à travers le navigateur de formulaire via le menu contextuel de ces 2 éléments.
Pour "Employés" on demande un tri sur le matricule:


Pour "Gestion" nous souhaitons une barre de navigation qui soit celle de "Employés":


Remarquons en passant que l'assistant à correctement lié le formulaire "Gestion" à son fortmulaire parent.

Comme déjà indiqué le champ "service" contient seulement un code. Pour que le formulaire affiche en clair le nom du service, 2 possibilités s'offrent à nous:

  • Un sous-formulaire avec les données de la table "services", sous-formulaire lié à "Gestion"
  • Une zone de liste (listbox)
Cette article cible la deuxième possibilité. Cependant nous demandons au lecteur de faire preuve d'un peu de patience car pour mieux mettre en évidence les avantages de cette solution 2, nous allons d'abord procéder avec la solution 1 (mais le lecteur peut évidemment passer directement à la section "zone de liste").

Sous-formulaire "Services"
Cliquant droit sur "Gestion" nous créons un nouveau formulaire:


formulaire que nous renommons aussitôt "Services" avant de fixer ses propriétés sur le panneau adéquat obtenu via le menu contextuel:


Le lien avec la table parent (gestion) a du être ajouté manuellement et toucher à la table "services" ne sera pas autorisé.
Cela étant, le formulaire "Services" restant sélectionné, nous utilisons l'outil "ajouter un champ"


pour ajouter le champ "dénomination":


Après repositionnement, redimensionnement et suppression de l'étiquette du champ, on arrive à quelque chose comme ceci:



Quand un code de service est encodé, un appui sur ENTER projette le focus dans le champ suivant (celui qui contient le nom du service). Le focus passant du formulaire "Gestion" au formulaire "Services", cette action provoque l'enregistrement du nouveau code dans la base de données. Cependant le formulaire doit encore être rafraîchit pour que le nom du service soit actualisé.
Pour automatiser cela, il suffit d'assigner la macro

'*************************************
Sub ReloadTableOne
'*************************************
    Dim Frm as object
    Frm=Thiscomponent.DrawPage.Forms.getByIndex(0)
    Frm.reload()
End Sub

à l’événement "Après l'action d’enregistrement"  figurant dans l'onglet "Événements" des propriétés du formulaire "Gestion" (voir ce billet).

En l'absence d'intégrité référentielle n'importe quel code peut-être introduit même si aucun service n'y correspond.
Heureusement ce n'est pas le cas ici car postgresql gère l'intégrité référentielle: 


Zone de liste "service"
Procédons maintenant avec la deuxième possibilité: la création d'une zone de liste ce qui nous permettra d'afficher les données de la table "services" sans utilisation du sous-formulaire "Services".
Le champ "service" étant sélectionné, nous cliquons droit afin d'accéder au menu contextuel pour pouvoir remplacer le champ par une zone de liste:


Nous redimensionnons la zone puis nous accédons aux propriétés du contrôle:


Nous constatons que le champ de données sélectionné est bien  ce qui est attendu, à savoir "service".
Si le contenu de la liste est basé sur une table, seront affichés les éléments de la première colonne de cette table. Le numéro "Champ lié" indique lui la colonne (comptée à partir de 0) dont les éléments serviront pour la mise à jour éventuelle du champ "service". Ce sont les fameux éléments cachés (ils ne seront pas affichés). Mais dans la table "services", c'est tout le contraire! La colonne "dénomination"  qui contient les éléments à afficher est la deuxième, tandis que la colonne des éléments cachés est la première. Nous contournons le problème en prenant comme contenu de la liste l'instruction SQL:

SELECT dénomination, service_id
FROM services
ORDER by service_id ASC

Une autre solution serait de créer une requête libreoffice avec l'instruction ci-après et de choisir cette requête comme contenu de la liste.

Dans l'onglet général nous indiquons que la liste doit être déroulante:


Et voilà: pour changer un employé de service, il suffit que l'encodeur choisisse un nouveau nom de service dans la liste déroulante. Ce nouveau service apparaît en clair à l'écran avant même tout enregistrement. Le code du service de l'employé, figurant dans la table gestion, sera mis à jour avec service_id et ce de manière tout à fait transparente pour l'utilisateur.
De plus, indépendamment de toute intégrité référentielle, celui-ci ne peut entrer que des services existants.

Pour illustrer l’utilisation d'une table comme source de données pour une liste, créons la table servicesn (n pour nombre) où la colonne d'indice o (colonne 1) est bien la colonne "dénomination":

CREATE TABLE servicesn (
    "dénomination" character(25),
nombre integer,
    service_id character(2) NOT NULL
)

La colonne nombre est destinée à contenir le nombre d'employés pour chaque service. Nous la remplissons avec:

update servicesn
set nombre =
(select count(*) from gestion
where service = service_id)

Dans cas, l'onglet "Données" du panneau des propriétés de la liste se présenterait comme suit:


La colonne "Champ lié" (colonne des éléments cachés) est la colonne d'indice 2 (colonne 3).
Il n'est pas possible ici d'introduire via le panneau des propriétés une notion de tri. Il faudrait donc que l'ordre naturel de la table soit celui souhaité.

Nous avons déjà évoqué 3 possibilités pour le type du contenu de liste:
sql, requête, table.
Une dernière est possibilité est "Liste de valeurs".
Dans ce cas il faut encoder les valeurs qui peuvent servir pour la mise à jour du champ "service" donc dans notre cas le code des services:


La notion de champ lié est alors inopérante et il faut encoder sous l'onglet Général les valeurs qui seront présentées à l'utilisateur, soit le nom des services:


La table "services" est inutilisée, mais ceci ne semble pas être une solution satisfaisante car les valeurs en question doivent se trouver sur tous les postes clients.