Mémo RPG-IV

 

 V5R10

 exemple :

C
/free
chain(N) nocli clients ;
if not %found ;
*in50 = *on ;
else ;
nomcourt = %subst(raisoc : 1 : 25) ;
exfmt image2 ;
endif ;
/end-free

 V7R20

  • /free et /end-free sont facultatifs
  • En placant **FREE en début de source
  •   Vous pouvez commencer colonne 1, dépasser la colonne 80
  •   Tout le code doit être en format libre

 exemple :

**free
chain(N) nocli clients ;
if not %found ;
*in50 = *on ;
else ;
nomcourt = %subst(raisoc : 1 : 25) ;
exfmt image2 ;
endif ;

 

 les codes opération suivants NE peuvent PAS être saisie en format libre (voir nos propositions de remplacement)

ADD ADDDUR ALLOC ANDxx BITOFF BITON
CABxx CALL CALLB CAS CAT CHECK
CHECKR COMP DEFINE DIV DO DOUxx
DOWxx END EXTRCT GOTO IFxx KFLD
KLIST LOOKUP MHHZO MHLZO MLHZO MLLZO
MOVE MOVEL MULT MVR OCCUR ORxx
PARM PLIST REALLOC SCAN SETOFF SETON
SHTDN SQRT SUB SUBDR SUBST TAG
TESTB TESTN TESTZ TIME WHENxx XFOOT
XLATE Z-ADD Z-SUB      
Pour la plupart d'entre eux, vous trouverez un service équivalent sous forme d'une fonction.


les codes opération suivants peuvent être saisie en format libre

ACQ BEGSR CALLP CHAIN CLEAR CLOSE
COMMIT DEALLOC DELETE DOU DOW DSPLY
DUMP ELSE ELSEIF ENDxx ENDSR EVAL
EVALR EXCEPT EXFMT EXSR FEOD FOR
FORCE IF IN ITER LEAVE LEAVESR
MONITOR NEXT ON-ERROR OPEN OTHER OUT
POST READ READC READE READP READPE
REL RESET RETURN ROLBK SELECT SETGT
SETLL SORTA TEST UNLOCK UPDATE WHEN
WRITE XML-INTO        

 

Codes Opération à facteur 2 étendu ou en format libre V5

 

Code
Exemple
EVAL
 (evalr)
eval(H)   montant = (qte * prix) * (1 + (tva /100) );
message = 'Attention , ' + user + ' la date ' + %CHAR(date)
+ 'est invalide'; // affectation implicite en V5.
IF
if   *in30;
if (qte * prix) > 10000;
if %subst(raison : 2 : 2) <> ' ';
DOU
Dou %EOF;
DOW
Dow not *in03 and not *in12;

SELECT
/WHEN
/OTHER

SELECT ;
WHEN codfacture = 'CH';
....
WHEN codafacture = 'ES' or codfacture = 'CB';
....
OTHER;
....
ENDSL;
FOR
FOR i = 1 to 15 ;
...
ENDFOR;
CALLP *
callp  QCMDEXC('wrksplf' : 7);  // nécessite un prototype
-----------
format libre V5
ACQ
ACQ(E) device fichier_icf; // lien entre un fichier ICF et une unité
BEGSR/
ENDSR
BEGSR toto;
...

endsr;
CHAIN
CHAIN(N) nocli clientf1 ;
If not %found ;
....
CLEAR
Clear *nokey clientf1; // remise à blanc de clientf1 sauf les clés
CLOSE
Close clientp1;
COMMIT
Commit; // validation de la dernière transaction
DEALLOC
dealloc(N) ptr; //libère l'espace mémoire et affecte null à ptr
DELETE
delete clientf1;
DSPLY
dsply 'voulez-vous continuer ?' 'QSYSOPR' rep;

if rep = 'O';
EVAL-CORR
 
eval-CORR DS1 = DS2; // toutes les zones de même nom sont copiées.
ELSE
if a = B;
ELSE;
ELSEIF
if a = B;
ELSEIF a = c;
EXCEPT
Except detail; // sortie (spécif O) hors cycle.
EXFMT
Exfmt image; 
EXSR
Exsr monsouspro; /  exécute monsouspro
FEOD
FEOD entcdep1 ; // force écriture physique dans entcdep1
FORCE
FORCE entcdep1 ; // force ce fichier a être lu au prochain cycle
IN
IN *lock dataarea ; // lecture d'une data aréa (avec verrou)
ITER
iter; // (saute un tour de boucle (dans un FOR par exemple)
LEAVE
leave; // (sortie anticipée d'une boucle)
LEAVESR
leavesr; // (sortie anticipée d'un sous programme)
MONITOR
Monitor; // (démarre un groupe d'instructions sous contrôle)
NEXT
next ecran nomfichier; // indique l'unité à lire la prochaine fois
ON-ERROR
Monitor;

....

on-error 1204;

....

ENDMON;
OPEN
OPEN CLientp1;
OUT
OUT dataarea; // à jour d'une data -area

POST

Post ecran01; // met à jour l'INFDS d'écran01

READ/readp

dow not %eof;
read clientf1; // lecture séquentielle (readp = en arrière)

READE/
readpe

READe nocde detdcep1; //lecture du suivant si la clé égale nocde.

READC

READC FE; // lecture séquentielle des lignes modifiés dans FE.

REL

REL UNITE ; // le contraire de ACQ

RESET

RESET datastructure; // réattribut les valeurs initiales. [INZ( ).]

RETURN

return:        // sortie anticipée d'un programme
return result; // fin d'une fonction (result est la valeur retour)
ROLBK
rolbk; // annulation de la dernière transaction

SETLL/setgt

SETLL nocli entcdef1; // se positionne sur la première commande
if %equal ; // si positionné par == (au moins une commande).

SORTA (A | D)

Sorta tbldestva ;//(tri le tableau, avant V7, doit être déclaré ASCEND/DESCEND)

TEST(E
 D | T | Z)

test(ed) *ISO datechar;  // test DATECHAR avant conversion.
if %error ; // n'est pas une date valide

UNLOCK

if *in12 ;
unlock articlp1; // déverrouille la ligne

UPDATE

else ;

update articlf1; // mise à jour.

WRITE

write clientf1; // ajout d 'un nouveau client.

XML-INTO

xml-into DS1 %xml(data); // lecture XML et placement dans DS1.

* CALLP est apparu en V3R60 et permet un appel en format libre :


il faut pour cela déclarer le programme et ses paramètres, avant, en spécif D, cela s'appelle un PROTOTYPE.
  +-----------------------------------------------------------+
! DEXEC PR EXTPGM('QCMDEXC') !
! D 250 const !
! D 15P 5 const !
+-----------------------------------------------------------+ PR indique qu'il s'agit d'un prototype (même positions que DS)

En free-form RPG
            DCL-PR exec EXTPGM('QCMDEXC')
   cde CHAR(50) CONST;
   cdl PACKED(15 : 5) CONST;
END-PR;
•EXTPGM le nom du pgm externe (peut être qualifié)
     •CONST sur un paramètre indique que ce paramètre peut ne PAS 
       être une variable (constante, calcul, fonction intégrée, ...)
     •VALUE passage de paramètre par valeur (pas de valeur retour)
     •OPTIONS(*NOPASS) ce paramètre peut ne pas etre transmis
        [tester le nombre de paramètres avec %parms() ]
•OPTIONS(*OMIT) ce paramètre peut etre omis [tester le paramètre avec %addr(param) <> *NULL]

Utilisation :
exec('WRKSPLF': 7) ;
la réception des paramètres peut elle même etre réalisée sur le même principe ,

avec prototype(jusqu'à V6R1) ET interface de procédure ( PI )

 
       DProgrammeB              PR               EXTPGM('PROGRAMMEB')
       D                                   6  0
 
       DProgrammeB              PI
       D nocli                             6  0
Depuis la V7R10, le prototype n'est plus obligatoire pour le programme en cours
à condition de placer EXTPGM sur l'interface de procédure
DProgrammeB PI EXTPGM('PROGRAMMEB') D nocli 6 0

En free-form RPG

       DCL-PI *N                           DCL-PI programmeB EXTPGM('PROGRAMMEB');
         nocli PACKED(6:0) ;     ou          nocli PACKED(6:0) ;
       END-PI;                             END-PI;

Options pour les codes opération

Code
Exemple
(H)
réalisation d'un arrondi lors de l'opération.
(N)
lecture sans verrouillage.
(P)
complémantation à blanc avec MOVE, MOVEL, XLATE, etc...
(M)
pour un calcul, travailler avec une précision maxi (un seul arrondi).
(R)
pour un calcul, travailler avec la même précision que la variable 
résultat (arrondi à chaque opération).
(E)
Erreur gérée (pour tout les ordres avec indicateur en <<).
(D)
pour CALLB, transmission du descripteur de paramètres [ILE].
(D | T | Z)
pour TEST (date, heure, horodatage).
(A | D)
pour SORTA (V7R1), indique dynamiquement le critère de tri.

 

Fonctions intégrées
voyez aussi le classement alphabétique

Fonction(x)
Retourne
Exemple
%ADDR l'adresse d'une variable EVAL ptr= %addr(data)
%PADDR l'adresse d'une procédure (pour CALLB)  
%ELEM donne le nombre d'occurrences d'un tableau ou d'une DS
if   I < %elem(tbl_tva)
%SUBST extraction d'une chaine de caractère
if   %subst(raisoc : 1 : 1) <> ' '

eval %subst(pgm : 1 : 1) = 'Q'
%SIZE donne la longueur déclaré d'une variable
if    %SUBST(nom : %size(nom): 1) = *blank
%DECPOS donne le nombre de décimales  
%TRIM élimination des espaces d'extrémité
eval NP = %trim(nom) + ' ' + %trim(prenom)
%TRIML|%TRIMR élimination des blancs de gauche/de droite  
%PARMS indique le nombre de paramètres recus
if   %parms > 2 
%ABS valeur absolue  
%DEC
%DECH

transforme une valeur numérique (char en V5.2,date en V5.3) en numérique condensé ou packé.
(%DECH applique un arrondi)

eval DATAMJ = %dec(DATCDE : *YMD)
%INT
%INTH

transforme une valeur numérique (ou caractère en 5.20)au format binaire signé

 
%UNS
%UNSH

transforme une valeur numérique (ou caractère ) au format binaire non signé

 

%FLOAT

transforme une valeur numérique (ou caractère) au format virgule flottante  

%EDITFLT

transforme de virgule flottante vers chaîne  

%EDITC

transforme du numérique en chaine avec application
d'un edit code
eval msg = 'date de traitement' + 
%editc(date : 'Y')

eval montanchar = %editc(
qte * prix : '3')

%EDITW

idem %EDITC mais avec un mot d'édition  
%CHAR transforme date ou numérique en chaîne
ZoneChar=%CHAR(ZoneDec) // 000123,45- ==> '-123,45'
%STR transforme une chaine RPG en chaîne compatible C
(pointeur vers une chaine terminée par x'00' )
 

%LEN

retourne la longueur d'une expression
if   %len(%trim(nom)) > 2
eval L = %len(zone-variable)

%SCAN

recherche si présence d'une chaîne de caractères
eval chaine = 'ILE/RPG-IV' 
eval pos = %scan('I' : chaine) [pos=1]
eval deb = pos + 1
eval pos = %scan('I' : chaine: deb) [pos=9]
%REPLACE remplace par une chaîne l'information située à la position indiquée
                          [var1 = 'AS/400'] 
eval x = %scan('4' : var1)
eval var2 = %replace('5': var1:x)
[var2 = 'AS/500']
eval var2 = %replace('X': var1 : 1 : 3)
[je remplace 3 caractères : var2 = 'X500']

%NULLIND

test la val.nulle sur un champ base de données
IF   %NULLIND(datcmd) = *ON 

EVAL %NULLIND(prime) = *OFF
EVAL prime = 1500

%OPEN

indique si un fichier est ouvert
if   %open(clients)

%EOF

indique si l'on est fin de fichier
dow  not %eof(clients)

%ERROR

indique si on est en erreur
(à utiliser suite à un code avec (E).)
if   %error

%STATUS

indique le n° d'erreur (code status)
if   %error and %status(clients) = 1252

%EQUAL

indique
un positionnement par égalité (SETLL)
une recherche aboutie par égalité (LOOKUP)
 

%FOUND

indique
un positionnement réalisé(SETLL,SETGT)
un recherche aboutie (CHAIN)
 
---- V4R40 ----
 
 

%GRAPH

conversion en DBCS
 

%UCS2

conversion en UNICODE
 

%XFOOT

somme d'un tableau
eval total = %xfoot(tb_salaire) + prime

%DIV

résultat de la division (entiers uniquement)
 

%REM

reste de la division (entiers uniquement)
if   %rem(an : 4 )= 0 
---- V5R10 ----
 
 
%ALLOC l'adresse d'un nouvel espace mémoire EVAL ptr= %alloc(nboctets)
%CHECK la position du premier caractère invalide
Eval pos = %check('0123456789.' : variable)
%CHECKR Idem %CHECK, mais en commencant à droite.
 
%DATE convertit une variable (alpha ou dec) en date ou retourne la date du jour.
eval datcmd = %DATE(datchar : *YMD0)//(c'est un zéro)
%DAYS indique un nombre de jours
eval datliv = datcmd + %DAYS(15)
%DIFF exprime un écart entre deux dates ou heures
eval ecart = %DIFF(datliv : datcmd : *DAYS)
%SUBDT extrait une partie d'une date
eval annee = %SUBDT(datliv : *YEARS)
%HOURS exprime un nombre d'heures
eval demain = maintenant + %hours(18)

%LOOKUP
(%LOOKUPxx)

indique si un élément est présent dans un tableau
eval pos = %lookup('$' : monnaies)
%MINUTES exprime un nombre de minutes
voir %HOURS()
%MONTHS exprime un nombre de mois
voir %DAYS()
%MSECONDS

exprime un nombre de microsecondes

voir %HOURS
%OCCUR

positionne sur une occurrence de DS

%OCCUR(DS1) = 7 ou x = %occur(DS2)
%REALLOC

Réalloue un espace mémoire plus grand

voir %ALLOC

%SECONDS

exprime un nombre de secondes
voir %HOURS()

%SHTDN

indique si le système est en phase d'arrêt
if %SHTDN

%TIME

convertit une variable (alpha ou dec) en heure ou retourne l'heure en cours.
if %time(pointage : *HMS) = T'23:59:59'

%TLOOKUP[xx]

indique si un élément est présent dans une table GAP
voir %lookup
%XLATE transforme une variable apha
eval resultat = %XLATE('abcdef' : 'ABCDEF' : origine)
%YEARS exprime un nombre d'années
voir %DAYS
---- V5R20 ----
 
 
%KDS Utilisation d'une DS en tant que liste de clés sur un CHAIN.
CHAIN %KDS(dscli) clientf1;
// mais aussi CHAIN (DEPCLI : NOCLI) clientf1;
%FIELDS Mise à jour d'une liste finie de zones lors d'un UPDATE .
Update clientf1 %FIELDS(nomcli : depcli : ville) ;
%BITNOT inverse les bits
%BITNOT(x'00') = x'FF'
%BITOR applique un OU logique bit à bit
%BITOR(x'F0' : x'0F') = x'FF'
%BITAND applique un ET logique bit à bit
%BITAND(x'F1' : x'1F') = x'11'
%BITXOR applique un OU exclusif bit à bit
%BITXOR(x'F1' : x'1F') = x'EE' [1110 1110)
---- V5R30 ----
 
 
%SUBARR une partie d'un tableau
resultat = %xfoot( %subarr(tbl : i : 5) )// 5 postes
---- V5R40 ----
 
 
%XML coordonnées des données XML à lire (cf XML-INTO)
xml-into DS1 %xml(data : 'doc=string')
---- V7R10 ----
 
 
%PARMNUM retourne la position d'un paramètre
if   %parms > %parmnum(nocli) // nocli transmis ?
%SCANRPL recherche et remplace une chaîne
eval resultat = %SCANRPL('GAP' : 'RPG' : origine)
---- V7R30 ----
 
 

%SCANR

recherche (de droite à gauche) si présence d'une chaîne
eval chaine = 'ILE/RPG-IV' 
eval pos = %scanr('I' : chaine) [pos=9]

%MAX

Retourne la plus grande valeur (TR2)
maxi = %max(priha: prirv: privente);

%MIN

Retourne la plus petite valeur (TR2)
datecheance = %min(datliv : datfac) + %days(60);


Codes Opération Historiques (ne passent pas le format libre)

voyez aussi le classement alphabétique

Code
Facteur 1
Facteur 2
Zone résultat
>>
<<
==
COMP élément comparé val. de comparaison   > < =
SETON      
--
--
--
SETOFF      
--
--
--
--------------------------- --------------- ------------------- ------------ ----- --- ------
ADD (ajout) (origine) val. ajoutée Résultat + - 0
SUB (soustraction) (origine) val. soustraite Résultat + - 0
MULT (multiplication) (origine) multiplicateur Résultat + - 0
DIV (division) (origine) diviseur Résultat + - 0
MVR (obtention du reste)     Résultat + - 0
Z-ADD (zéro puis ajout)   valeur Résultat      
Z-SUB (changement signe)   valeur Résultat      
--------------------------- --------------- ------------------- ------------ ----- --- ------
BEGSR (début sous pgm) nom-spgm          
ENDSR (fin sous pgm) label          
CABxx (Comp and Branch) élément comparé val. de comparaison label      
IFxx élément comparé val. de comparaison        
ANDxx élément comparé val. de comparaison        
ORXX élément comparé val. de comparaison        
ELSE            
DOUXX (jusqu'à) élément comparé val. de comparaison        
DOWxx (tant que) élément comparé val. de comparaison        
SELECT (test multiples)            
WHENXX élément comparé val. de comparaison        
OTHER (autre cas p. SELECT)            
END,
ENDIF, ENDDO,ENDSL,ENDCS
           
LEAVE (sortie anticipée)            
ITER (saut d'un tour de boucle)            
TAG (définition d'un label) label          
CABxx (Comp and Branch) élément comparé val. de comparaison label      
GOTO   label        
CASxx (Comp and SR) élément comparé val. de comparaison sous-pgm      
----------------------------- --------------- ------------------- ------------ ----- --- ------
CALL   pgm externe        
PLIST (liste de paramètre) (*ENTRY)          
PARM (définition paramètre) val au retour val à l'envoi Paramètre      
CALLB   procédure liée        
RETURN (fin immédiate/pgm)            
--------------------------- --------------- ------------------- ------------ ----- --- ------
KLIST (définition de clé) nom-klist          
KFLD (zone d'une clé)     zone clé      
CHAIN (accès direct) clé format (fichier)   NR ER  
READ (accès séquentiel)   format (fichier)     ER EOF
READC (lecture s/fichier)   format     ER EOF
READE (séquentiel avec test) clé format (fichier)     ER EOF
READP (séquentiel arrière)   format (fichier)     ER BOF
READPE (arrière avec test) clé format (fichier)     ER BOF
SETLL (positionnement > ou =) clé format (fichier)   NR ER EQ
SETGT(positionnement > ) clé format (fichier)   NR ER  
UPDATE   format     ER  
DELETE (clé) format     ER  
WRITE   format     ER  
COMMIT (validation) (frontière          
ROLBK (invalidation)            
UNLOCK (libération)   fichier        
FEOD (force end of data)   fichier        
EXCEPT (spécif O)   nom-spécif O        
--------------------------- --------------- ------------------- ------------ ----- --- ------
ACQ
(ACQUIRE pour unité ICF)
nom-unité Fichier Workstn     ER  
REL
(RELEASE pour unité ICF)
nom-unité Fichier Workstn     ER  
OPEN   fichier        
CLOSE   fichier        
--------------------------- --------------- ------------------- ------------ ----- --- ------
MOVE (cadrage à droite)   origine Résultat      
MOVEL (cadrage à gauche)   origine Résultat      
CAT (concaténation) élément1 élément2
[:nb esp]
Résultat      
CHECK (vérification -->) car.autorisés chaine à vérifier position en erreur   ER FD
CHECKR (vérification <--) car.autorisés chaine à vérifier position en erreur   ER FD
XLATE (transformation) DE : A origine[ : début] Résultat      
SCAN (recherche) recherche[ : lg] chaine[: début] Position     FD
CLEAR (mise à blanc structure) (*NOKEY) zone/DS/format        
RESET (mise à état initial)   zone/DS/format        
TESTN (test si numérique)     Chaine num   (sp)
--------------------------- --------------- ------------------- ------------ ----- --- ------
MOVEA (zone dans tableau)   Origine Résultat      
XFOOT (somme d'un tableau)   tableau Résultat      
LOOKUP
(recherche dans tableau)
recherche tableau(,indice)       EQ
--------------------------- --------------- ------------------- ------------ ----- --- ------
DEFINE (déclaration dtaara) *DTAARA nom externe nom/pgm      
IN (lecture) (*LOCK) data area        
OUT (mise à jour) (*LOCK) data area        
UNLOCK   data area        
--------------------------- --------------- ------------------- ------------ ----- --- ------
DEFINE (définition zone) *LIKE zone modèle nouvelle zone      
DSPLY (message) file d'attente (réponse)      
OCCUR (DS à occurences) (autre DS) DS (position)      

ER =Erreur    FD =Found   NF =not found B = blanc 
NR =no record EOF =end of file BOF =begin of file
+ = résultat positif - = négatif 0 = null

 

 

 

Cas particulier des dates en V4

Toutes les calculs de date se font avec des durées, exprimées comme suit : valeur : *X
(*X indiquant un qualificatif de durée)

*D nombre de jours
*M nombre de mois
*Y nombre d'années
*H nombre d'heures
*MN nombre de minutes
*S nombre de secondes
*MS nombre de mico-secondes

 

Code Opération
Facteur 1
Facteur 2
Zone résultat
>>
<<
= =
ADDDUR date durée date      
SUBDUR date durée date      
  date date durée      
EXTRCT   date jour | mois | année : durée      
TIME     date/heure/timestamp      
TEST(D | T | Z)   chaine     ER  


Proposition de remplacement des codes invalides en format libre
Code Opération
Remplacement
Exemple
ADD   + EVAL A = A +1 ou A = A+1 ou A+=1
ADDDUR   + Datliv = datcmd + %DAYS(45)
ALLOC %alloc  
ANDxx    (test en libre) if A=B AND compteur<>0
CABxx   (à éviter)  
CALL CALLP + EXTPGM CALLP pgmA (parm1 : parm2) // ou -> pgmA(parm1 : parm2);
CALLB CALLP + EXTPROC    idem
CASxx IF + EXSR  
CAT    + Message = 'date de traitement' + DATCHAR
CHECK %check pos = %check('0123456789-+,' : qtechar)
CHECKR %checkr    idem
*LIKE DEFINE déclaration en D avec LIKE( )  
*DTAARA DEFINE déclaration en D avec DTAARA( )  
DIV    /  
DO FOR FOR i = 1 to 50
DOUxx DOU Dou %eof
DOWxx DOW Dow not %eof
END ENDxx (déja recommandé avant)  
ENDCS    cf CAS  
EXTRCT %subdt mois = %SUBDT(DATCMD : *M)
GOTO    c'est quoi ?       ;-) GOTO DEBUT -> ITER
GOTO FIN       -> LEAVE
IFxx IF if GAP = 4
KFLD %KDS ou directement (K1 :K2) CHAIN (societe: nocli) clientf1
KLIST     (cf ci-dessus)  
LOOKUP %lookup tva = %lookup(code : tbtva)
MOVE EVALR , Convertir avec les fonctions intégrées datcmd = %DATE(datchar:*ISO) ou chaine = %char(datcmd)
MOVEL EVAL, idem pour les conversions  
MOVEA
Définition d'une zone CHAR en recouvrement puis %SUBST
DCL-S IND99_p POINTER INZ(%addr(*in));
DCL-S ind99 CHAR(99) BASED(IND99_p);

MOVEA '010' *in(80) -> %SUBST(IND99 : 80 : 3) = '010';
MULT    *  
MVR %rem if %rem(AN : 4) = 0
OCCUR %occur  
ORxx  cf ANDxx  
PARM Prototype en spécif D *  
PLIST  cf PARM  
REALLAOC %realloac  
SCAN %scan QUATRE = %scan('4' : 'RPG4')
SETOFF *inxx = *off  
SETON *inxx = *on  
SUB    -  
SUBDUR    - HIER = AUJOURDHUI - %days(1)
ou SUBDUR %diff UN = %DIFF(aujourdhui : hier:*Days)
SUBST %subst  
TAG    cf GOTO  
TESTN %check if %check('0123456789' : QTECHAR) > 0
TIME %date() ou %time() aujourdhui = %date()
WHENxx WHEN  
XFOOT %xfoot TVA = %xfoot(tbmt) * TAUX
XLATE %xlate GRAND = %xlate(minuscles: majuscules: PETIT)
Z-ADD EVAL  
Z-SUB EVAL -  

(C) Volubis 2001-2017