Bonjour,
Je travaille sur un projet d'interfaçage entre un logiciel et d'autres applications.
Ce logiciel dispose en interne d'un langage propriétaire "UQL" qui permet d'interroger ses données, ce qui rend ainsi les utilisateurs autonomes pour créer des exports de données par exemple.
Malheureusement, le module qui permet de s'interfacer avec le logiciel ne supporte pas ce langage UQL et en lieu et place utilise des requêtes construites en XML.
C'est là que mon besoin intervient : je souhaite permettre aux utilisateurs d'utiliser leurs requêtes UQL dans les interfaces, et pour se faire, j'ai besoin de les traduire en requêtes XML.
Voici un exemple de requête UQL :
(je vous rassure, personne n'écrit ça, y'a un éditeur wysiwyg pour générer ça)
Code sql : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 select (CoGrp, CoNo, 'E-mail1', Company, MA.Date, MB.CoGrp, MB.CoNo, MB.PeGrp, MB.PeNo, MB.PartType, FI1.Company, KP.Sex, KP.FirstName, KP.LastName, KP1.Sex, KP1.FirstName, KP1.LastName, AU.SeqNo, AU1.SeqNo, UP.PosNo, FI2.CoGrp, FI2.CoNo, FI2.Company) from (FI) where (Company='DEVELOPPEZ.NET') with (MA) with (MA.MB) with (MB.FI as FI1) plus (FI1.KP using link 26000 as KP1) plus (FI1.AU) where (OrderDate>='$curDay-2m' AND Status='#0') plus (AU.UP) plus (KP) plus (AU as AU1) plus (FI using link 26000 as FI2)
=> L'avantage de l'éditeur, c'est que du coup ça simplifie le parseur : la mise en forme est toujours la même.
Ca se traduit en XML comme ça :
Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 <?xml version="1.0" encoding="utf-16" standalone="yes"?> <request user="******" pwd="******"> <query dateformatout="ymd" dateformatin="ymd" catexkeys="true" internalfields="false" xsdt="true" labels="false"> <tables> <table table="FI"> <table table="MA" flags="00000100"> <table table="MB" flags="00000100"> <table table="FI" alias="FI1" flags="00000100"> <table table="KP" alias="KP1" linkId="26000" flags="00000800" /> <table table="AU" flags="00000800"> <table table="UP" flags="00000800" /> </table> </table> </table> </table> <table table="KP" flags="00000800" /> <table table="AU" alias="AU1" flags="00000800" /> <table table="FI" alias="FI2" linkId="26000" flags="00000800" /> </table> </tables> <fields table="FI" fields="0,1,18,2" /> <fields table="MA" fields="2" /> <fields table="MB" fields="0,1,2,3,6" /> <fields table="FI" fields="2" alias="FI1" /> <fields table="KP" fields="0,3,2" alias="KP1" /> <fields table="AU" fields="1" /> <fields table="UP" fields="2" /> <fields table="KP" fields="0,3,2" /> <fields table="AU" fields="1" alias="AU1" /> <fields table="FI" fields="0,1,2" alias="FI2" /> <condition> <cond table="FI" fid="2" op="=" value="DEVELOPPEZ.NET" /> </condition> <condition> <cond table="AU" fid="10" op=">=" value="$curDay-2m" /> <cond table="AU" fid="218" op="=" value="#0" /> </condition> </query> </request>
La bonne nouvelle, c'est que ça c'est ce que mon programme sait déjà faire : il ne reste qu'à améliorer le parsing des clauses "where" car actuellement je sais gérer ni les parenthèses ni les OR (je fais un bête split sur "AND" pour identifier les différents <cond> a créer... Je sais, c'est moche.
Voici quelques exemples de syntaxe que je peux avoir dans les clauses where.
Exemple 1 :
CoGrp = 'company_StatNo' AND CoNo = 'company_No'
Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5 <condition> <cond tablename="Company" fieldname="CoGrp" op="=" value="company_StatNo"/> <cond tablename="Company" fieldname="CoNo" op="=" value="company_No"/> </condition>
Exemple 2 :
Country = 'France' OR Synonym = 'test'
Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 <condition resolve="true"> <lop value="or"> <cond tablename="Company" fieldname="Country" op="=" value="France"/> <cond tablename="Company" fieldname="Synonym" op="=" value="test"/> </lop> </condition>
Exemple 3 :
LeadStatus = 'Customer' AND Revenue > 1000000 and Employees > 250 AND (Country = 'Austria' OR (Country = 'Germany' AND Rep = '$curRep'))
Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 <condition> <cond tablename="Company" fieldname="LeadStatus" op="=" value="Customer"/> <cond tablename="Company" fieldname="Revenue" op=">" value="1000000"/> <cond tablename="Company" fieldname="Employees" op="=" value="250"/> <lop value="or"> <cond tablename="Company" fieldname="Country" op="=" value="Austria"/> <lop value="and"> <cond tablename="Company" fieldname="Country" op="=" value="Germany"/> <cond tablename="Company" fieldname="Rep" op="=" value="$curRep"/> </lop> </lop> </condition>
Quelques règles :
- Une clause "where" porte toujours sur une et une seule table
- Les types de jointures et clauses de jointures entre tables sont gérés sous forme de "link" directement dans les clauses "having", "plus", "with", etc.
- On peut avoir autant d'imbrications de AND/OR/parenthèses qu'on veut
- Les conditions sont toujours de type "<nom colonne parfois entre quottes> <operateur> <valeur littérale entre simples quottes>"
- Le membre de gauche d'un opérateur est toujours une colonne
- Le membre de droite d'un opérateur est toujours une valeur littérale
- Les opérateurs sont =, !=, <, <=, >, >=
- Contrairement au SQL, il n'y a pas d'opérateur "complexe" (IN, BETWEEN, etc.)
Déjà, dans un premier temps, je n'arrive même pas conceptualiser la structure des objets qui vont parser l'expression.
J'ai déjà ma classe "Cond" : (qui correspond à la balise <cond>)
Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 public class Cond : IConditionsElement { public string Fid { get; set; } public string Op { get; set; } public string Value { get; set; } }
Ma classe "Condition" : (qui correspond à la balise "<condition>")
Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 public class Condition { private readonly List<IConditionsElement> Conditions = new(); private readonly Table ConditionTable; }
Ainsi que la classe "Lop" : (qui correspond à la balise "<lop>")
Code csharp : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6 public class Lop : IConditionsElement { private readonly LopValue Value; // AND ou OR private readonly List<IConditionsElement> Conditions = new(); }
On voit que j'ai déporté l'attribut "table" sur Condition plutôt que dans Cond car il y a une et une seule valeur "table" possible pour toutes les <cond> imbriquées dans une <condition> : du coup j'ai préféré le gérer dans Condition.
Règles :
- toutes les IConditionsElement à l'intérieur d'un Condition ou Lop sont combinés avec AND sauf si l'attribut Value du Lop parent est "OR" (dans ce cas, tous les IConditionsElement qu'il contient sont combinés avec des OR).
- on peut avoir plusieurs Lop imbriqués les uns dans les autres
Le but ici est de ne parser que ce qui se trouve entre parenthèses juste après un "where" : donc une seule table à la fois, donc un seul Condition
Tout conseil ou coup de main est le bienvenu
En fait, une de mes plus grosses difficultés, c'est savoir interpréter correctement une succession (probablement incohérente) de AND et OR sans parenthèses :
Company='toto' OR Company='titi' AND Country='France' OR Country='Germany' AND Revenue > 1000000
Qui se traduirait :
Code xml : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 <condition> <lop value="or"> <cond tablename="Company" fieldname="Company" op="=" value="toto"/> <lop value="AND"> <cond tablename="Company" fieldname="Company" op="=" value="titi"/> <cond tablename="Company" fieldname="Country" op="=" value="France"/> </lop> <lop value="AND"> <cond tablename="Company" fieldname="Country" op="=" value="Germany"/> <cond tablename="Company" fieldname="Revenue" op=">" value="1000000"/> </lop> </lop> </condition>
Bon, la bonne nouvelle, c'est que les "<lop value="AND">" implicites peuvent être ajoutés, ce qui devrait implifier un peu... Mais je reste bloqué quand même
Partager