Index: compile_rules.py
==================================================================
--- compile_rules.py
+++ compile_rules.py
@@ -8,10 +8,12 @@
 
 
 DEF = {}
 FUNCTIONS = []
 
+RULESET = set()     # set of rule-ids to check if there is several rules with the same id
+
 JSREGEXES = {}
 
 WORDLIMITLEFT  = r"(?<![\w.,–-])"   # r"(?<![-.,—])\b"  seems slower
 WORDLIMITRIGHT = r"(?![\w–-])"      # r"\b(?!-—)"       seems slower
 
@@ -179,16 +181,19 @@
         cCaseMode = m.group(1)[1]
         cWordLimitRight = m.group(1)[2]
         sOption = m.group(2)[1:]  if m.group(2)  else False
         if m.group(3):
             sRuleId =  m.group(3)[1:-1]
+            if sRuleId in RULESET:
+                print("# Warning. Several rules have the same id: " + sRuleId)
+            RULESET.add(sRuleId)
         nPriority = dOptPriority.get(sOption, 4)
         if m.group(4):
             nPriority = int(m.group(4)[1:])
         s = s[m.end(0):]
     else:
-        print("Warning. No option defined at line: " + sLineId)
+        print("# Warning. No option defined at line: " + sLineId)
 
     #### REGEX TRIGGER
     i = s.find(" <<-")
     if i == -1:
         print("# Error: no condition at line " + sLineId)
@@ -220,14 +225,14 @@
 
     ## count number of groups (must be done before modifying the regex)
     nGroup = countGroupInRegex(sRegex)
     if nGroup > 0:
         if not tGroups:
-            print("# warning: groups positioning code for JavaScript should be defined at line " + sLineId)
+            print("# Warning: groups positioning code for JavaScript should be defined at line " + sLineId)
         else:
             if nGroup != len(tGroups):
-                print("# error: groups positioning code irrelevant at line " + sLineId)
+                print("# Error: groups positioning code irrelevant at line " + sLineId)
 
     ## word limit
     if cWordLimitLeft == '[' and not sRegex.startswith(("^", '’', "'", ",")):
         sRegex = WORDLIMITLEFT + sRegex
     if cWordLimitRight == ']' and not sRegex.endswith(("$", '’', "'", ",")):
@@ -318,50 +323,50 @@
         if sMsg[0:1] == "=":
             sMsg = prepareFunction(sMsg[1:])
             FUNCTIONS.append(("m"+sIdAction, sMsg))
             for x in re.finditer("group[(](\d+)[)]", sMsg):
                 if int(x.group(1)) > nGroup:
-                    print("# error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)")
+                    print("# Error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)")
             sMsg = "=m"+sIdAction
         else:
             for x in re.finditer(r"\\(\d+)", sMsg):
                 if int(x.group(1)) > nGroup:
-                    print("# error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)")
+                    print("# Error in groups in message at line " + sIdAction + " ("+str(nGroup)+" groups only)")
             if re.search("[.]\\w+[(]", sMsg):
-                print("# error in message at line " + sIdAction + ":  This message looks like code. Line should begin with =")
+                print("# Error in message at line " + sIdAction + ":  This message looks like code. Line should begin with =")
             
     if sAction[0:1] == "=" or cAction == "=":
         if "define" in sAction and not re.search(r"define\(\\\d+ *, *\[.*\] *\)", sAction):
-            print("# error in action at line " + sIdAction + ": second argument for define must be a list of strings")
+            print("# Error in action at line " + sIdAction + ": second argument for define must be a list of strings")
         sAction = prepareFunction(sAction)
         sAction = sAction.replace("m.group(i[4])", "m.group("+str(iGroup)+")")
         for x in re.finditer("group[(](\d+)[)]", sAction):
             if int(x.group(1)) > nGroup:
-                print("# error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)")
+                print("# Error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)")
     else:
         for x in re.finditer(r"\\(\d+)", sAction):
             if int(x.group(1)) > nGroup:
-                print("# error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)")
+                print("# Error in groups in replacement at line " + sIdAction + " ("+str(nGroup)+" groups only)")
         if re.search("[.]\\w+[(]", sAction):
-            print("# error in action at line " + sIdAction + ":  This action looks like code. Line should begin with =")
+            print("# Error in action at line " + sIdAction + ":  This action looks like code. Line should begin with =")
 
     if cAction == "-":
         ## error detected
         if not sAction:
-            print("# error in action at line " + sIdAction + ":  This action is empty.")
+            print("# Error in action at line " + sIdAction + ":  This action is empty.")
         if sAction[0:1] == "=":
             FUNCTIONS.append(("s"+sIdAction, sAction[1:]))
             sAction = "=s"+sIdAction
         elif sAction.startswith('"') and sAction.endswith('"'):
             sAction = sAction[1:-1]
         if not sMsg:
-            print("# error in action at line " + sIdAction + ":  the message is empty.")
+            print("# Error in action at line " + sIdAction + ":  the message is empty.")
         return [sCondition, cAction, sAction, iGroup, sMsg, sURL]
     elif cAction == "~":
         ## text preprocessor
         if not sAction:
-            print("# error in action at line " + sIdAction + ":  This action is empty.")
+            print("# Error in action at line " + sIdAction + ":  This action is empty.")
         if sAction[0:1] == "=":
             FUNCTIONS.append(("p"+sIdAction, sAction[1:]))
             sAction = "=p"+sIdAction
         elif sAction.startswith('"') and sAction.endswith('"'):
             sAction = sAction[1:-1]
@@ -369,11 +374,11 @@
     elif cAction == "=":
         ## disambiguator
         if sAction[0:1] == "=":
             sAction = sAction[1:]
         if not sAction:
-            print("# error in action at line " + sIdAction + ":  This action is empty.")
+            print("# Error in action at line " + sIdAction + ":  This action is empty.")
         FUNCTIONS.append(("d"+sIdAction, sAction))
         sAction = "d"+sIdAction
         return [sCondition, cAction, sAction]
     elif cAction == ">":
         ## no action, break loop if condition is False

Index: gc_lang/fr/rules.grx
==================================================================
--- gc_lang/fr/rules.grx
+++ gc_lang/fr/rules.grx
@@ -422,11 +422,11 @@
 # IP
 __[s]__  \d+[.:]\d+[.:]\d+[.:]\d+  <<- ~>> *
 # mètres (m)
 __[s>__  "\d+ (m) "  @@w <<- ~1>> _
 # heures
-__[s]__  (?:à |)[012]?\d[h:]\d\d(?:[m:]\d\ds?|) <<- ~>> *
+__[s]__  (?:de |à |)[012]?\d[h:]\d\d(?:[m:]\d\ds?|) <<- ~>> *
 # crochets
 __[s]__  \[…\] <<- ~>> *
 __[s]__  \[({w_1})\] @@1
     <<- \1.isdigit() ~>> *
     <<- __else__ ~>> _
@@ -1893,11 +1893,11 @@
 
 TEST: __ocr__ {{Us}} arrive demain.
 
 
 # il / i1 / if / 11
-__[i]/ocr(ocr_il_ils2)__
+__[i]/ocr(ocr_il_ils3)__
     [i1][1f]s?
     <<- not \0.endswith("s") ->> Il|il                                                              # Erreur de numérisation ?
     <<- __else__ ->> Ils|ils                                                                        # Erreur de numérisation ?
 
 TEST: __ocr__ {{i1s}} en savent beaucoup trop pour leur propre bien.
@@ -3102,11 +3102,11 @@
     (se) (?:qu[ei]?|dont|malgré|pourquoi|avec|pour|par) @@0 <<- -1>> ce     # Confusion. Exemples : ce bâtiment, se perdre.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=2440
 __[i]/conf(conf_qui_se_verbe)__
     qui (ce) ({w_2})  @@4,$
     <<- morphex(\2, ":V", ":[NAQ].*:[me]") or before(r"(?i)\b[cs]e +")
     -1>> se                                                                 # Confusion probable. Exemples : ce bâtiment, se perdre.|http://bdl.oqlf.gouv.qc.ca/bdl/gabarit_bdl.asp?id=2440
-__[i]/conf(conf_ce_être)__
+__[i]/conf(conf_ceux_ce_être)__
     (ceux) (?:ne |)(?:sont|serai(?:en|)[ts]?|f[uû](?:ren|)t|n’(?!ayant|étant)\w+) @@0
     <<- -1>> ce                                                             # Confusion.|http://www.intellego.fr/soutien-scolaire-6eme/aide-scolaire-francais/ce-ceux-ou-se/3829
 __[s]/conf(conf_ce_ne_être_doit)__
     ([sS]e) n(?:e |’)({être}|d[eouû]\w+|p[oeuû]\w+)  @@0,$
     <<- morph(\2, ">(?:être|pouvoir|devoir) .*:3s", False)
@@ -3453,14 +3453,14 @@
 
 # quand / quant / qu’en
 __[i]/conf(conf_quant_à)__
     (?<![dD]e )(quand) (?:à|aux?)  @@0
     <<- not morph(word(-1), ">(?:arriver|venir|à|revenir|partir|aller) ") -1>> quant                # Confusion probable. Quand = à quel moment. Quant à = à propos de.
-__[i]/conf(conf_quand)__    quant(?! à| aux?| est[ -]il d(?:es?|u) ) <<- ->> quand                  # Confusion. Quand = à quel moment. Quant à = à propos de.
+__[i]/conf(conf_quand1)__   quant(?! à| aux?| est[ -]il d(?:es?|u) ) <<- ->> quand                  # Confusion. Quand = à quel moment. Quant à = à propos de.
 __[i]/conf(conf_qu_en1)__   (quan[dt]) est[ -]il d(?:es?|u) @@0 <<- -1>> qu’en                      # Confusion. Ce qu’il en est de… → Qu’en est-il de… ?
 __[i]/conf(conf_qu_en2)__   (quan[dt]) ({w_2}ant) @@0,$ <<- morph(\2, ":P", False) -1>> qu’en       # Confusion probable.
-__[i]/conf(conf_quand)__
+__[i]/conf(conf_quand2)__
     (qu en) (?:je|tu|ils?) @@0
     <<- not after("^ +ne s(?:ai[st]|u[st]|urent|avai(?:[ts]|ent)) ") -1>> quand                     # Confusion probable. Pour évoquer un moment, écrivez :
 
 TEST: {{Quant}} est-il du chien ?
 TEST: {{Quand}} à ma santé, elle est défaillante.
@@ -6958,11 +6958,11 @@
 TEST: {{comme même}} il y va fort, le saligaud !
 TEST: La météo disait qu’il ferait beau temps, mais il pleut {{comme même}}…
 
 
 # quel que soit / quoi qu’il en soit
-__[i]/conf(conf_quel_que_soit)__
+__[i]/conf(conf_quel_que_soit1)__
     quelques? soi(?:ent|t|s|)
     <<- ->> quel que soit|quelle que soit|quels que soient|quelles que soient             # Confusion.|https://fr.wiktionary.org/wiki/quel_que_soit
 __[i]/conf(conf_quoi_qu_il_en_soit)__
     quoiqu il en soit <<- not morph(word(1), ":[AQ]", False) ->> quoi qu’il en soit       # Confusion.|https://fr.wiktionary.org/wiki/quoi_qu%E2%80%99il_en_soit
 
@@ -7103,11 +7103,11 @@
     (?:chez|don de|sur|avec|pour) (soit) @@$ <<- not after(" soit ") -1>> soi                       # Confusion probable.
 __[i]/conf(conf_en_soi)__
     (?<!’)en (soit)  @@3
     <<- morph(word(1), ":[GY]", True, True) and not before("(?i)quel(?:s|les?|) qu $|on $|il $") and not after(" soit ")
     -1>> soi                                                                                        # Confusion probable.
-__[i]/conf(conf_quel_que_soit)__
+__[i]/conf(conf_quel_que_soit2)__
     quel(?:le|)s? que (soi(?:es?|)) @@$ <<- -1>> soit|soient                                        # Confusion probable. 
 __[i]/conf(conf_soi_même1)__
     (soi[tes]s? mêmes?) @@$
     <<- morph(word(-1), ":[YQ]|>(?:avec|contre|par|pour|sur) ", False, True) -1>> soi-même          # Confusion probable : moi-même, toi-même, lui-même, elle-même, soi-même, elles-mêmes, eux-mêmes.
 __[i]/conf(conf_soi_même2)__
@@ -8026,11 +8026,11 @@
 TEST: Elle te laisse finir le travail.
 TEST: Je me laisse de quoi finir.
 TEST: Il te laisse trois jours de délai.
 
 
-__[i]/ppas(ppas_me_te_laisser_adj)__
+__[i]/ppas(ppas_nous_les_laisser_adj)__
     (nous|les) +(laiss\w*) +({w_3})  @@0,w,$
     <<- morph(\2, ">laisser ", False) and morphex(\3, ":[AQ].*:s", ":(?:[YG]|[AQ].*:[ip])")
     and (\1.endswith("es") or ( \1.endswith("us") and not \2.endswith("ons") )) -3>> =suggPlur(@)   # Accord avec « \1 » : « \3 » devrait être au pluriel.
 
 TEST: je les laisse {{indifférent}}.
@@ -8551,33 +8551,33 @@
     -3>> =suggFemPlur(@)                                                     # Accord avec le sujet « \1 » : « \3 » devrait être au féminin pluriel.
 
 TEST: elles se sentent {{perdu}}
 
 
-__[i]/ppas(ppas_le_verbe)__
+__[i]/ppas(ppas_le_verbe_pensée)__
     le ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[esx])  @@w,$
     <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ]:(?:[me]:p|f)", ":(?:G|Y|[AQ]:m:[is])")
     -2>> =suggMasSing(@)                                                     # Accord avec le COD “le” : « \2 » doit être au masculin singulier.
-__[i]/ppas(ppas_la_verbe)__
+__[i]/ppas(ppas_la_verbe_pensée)__
     la ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[uiéesx])  @@w,$
     <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ]:(?:[fe]:p|m)", ":(?:G|Y|[AQ]:f:[is])")
     -2>> =suggFemSing(@)                                                     # Accord avec le COD “la” : « \2 » doit être au féminin singulier.
-__[i]/ppas(ppas_les_verbe)__
+__[i]/ppas(ppas_les_verbe_pensée)__
     les ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2})  @@w,$
     <<- morph(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])")
     -2>> =suggPlur(@)                                                        # Accord avec le COD “les” : « \2 » doit être au pluriel.
-__[i]/ppas(ppas_me_te_verbe)__
+__[i]/ppas(ppas_me_te_verbe_pensée)__
     ([mt]e) ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2}[sx])  @@0,w,$
     <<- morph(\2, ">(?:trouver|considérer|croire|rendre|voilà) ", False) and morphex(\3, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])")
     -3>> =suggSing(@)                                                        # Accord avec le pronom “\1” : « \3 » doit être au singulier.
-__[i]/ppas(ppas_se_verbe)__
+__[i]/ppas(ppas_se_verbe_pensée)__
     se ((?:trouv|consid[éè]r|cr[ouû]|rend)\w*) +({w_3})  @@w,$
     <<- morph(\1, ">(?:trouver|considérer|croire|rendre) .*:3s", False) and morphex(\2, ":[AQ].*:p", ":(?:G|Y|[AQ].*:[is])")
     -2>> =suggSing(@)                                                        # Accord avec le pronom “se” (le verbe étant au singulier) : « \2 » doit être au singulier.
     <<- morph(\1, ">(?:trouver|considérer|croire|rendre) .*:3p", False) and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])")
     -2>> =suggSing(@)                                                        # Accord avec le pronom “se” (le verbe étant au pluriel) : « \2 » doit être au pluriel.
-__[i]/ppas(ppas_nous_verbe)__
+__[i]/ppas(ppas_nous_verbe_pensée)__
     nous ((?:trouv|consid[éè]r|cr[ouû]|rend|voilà)\w*) +({w_2})  @@w,$
     <<- ( morphex(\1, ">(?:trouver|considérer|croire|rendre|voilà) ", ":1p")
     or (morph(\1, ">(?:trouver|considérer|croire) .*:1p", False) and before(r"\bn(?:ous|e) +$")) )
     and morphex(\2, ":[AQ].*:s", ":(?:G|Y|[AQ].*:[ip])")
     -2>> =suggPlur(@)                                                        # Accord avec le pronom “nous” : « \2 » doit être au pluriel.
@@ -8681,11 +8681,11 @@
     # Le participe passé devrait être au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais
 
 TEST: des hommes, des femmes, des enfants qui ne leur avaient {{faits}} que du bien.
 
 
-__[i]/ppas(ppas_avoir)__
+__[i]/ppas(ppas_avoir_ppas_mas_sing)__
     avoir +({w_2})  @@$
     <<- not re.search("(?i)^(?:confiance|cours|envie|peine|prise|crainte|cure|affaire|hâte|force|recours)$", \1)
     and morphex(\1, ":Q.*:(?:f|m:p)", ":m:[si]") and before("(?i)(?:après +$|sans +$|pour +$|que? +$|quand +$|, +$|^ *$)")
     -1>> =suggMasSing(@)
     # Le participe passé devrait être au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais
@@ -8790,11 +8790,11 @@
     # Le participe passé devrait être au masculin singulier.|http://fr.wikipedia.org/wiki/Accord_du_participe_pass%C3%A9_en_fran%C3%A7ais
 
 TEST: ce que ça a {{donnée}}
 
 
-__[i]/ppas(ppas_avoir)__
+__[i]/ppas(ppas_avoir_conf_infi)__
     ({avoir}) +({w_2}e[rz])  @@0,$
     <<- not re.search("^(?:A|avions)$", \1) and morph(\1, ":V0a", False) and morph(\2, ":V.+:(?:Y|2p)", False)
     -2>> =suggVerbPpas(@, ":m:s")                                                                   # Incohérence avec « \1 » : « \2 » n’est pas un participe passé.
     <<- __also__ and \1 == "a" and \2.endswith("r") and not before(r"(?i)\b(?:[mtn]’|il +|on +|elle +)$")
     -1>> à                                                                                          # Confusion probable : “a” est une conjugaison du verbe avoir. Pour la préposition, écrivez :
@@ -10237,16 +10237,16 @@
 TEST: Je {{voudrai}} qu’il soit déjà là.
 TEST: J’aimerai ces cours-là autant que les autres.
 TEST: J’aimerai la danse et la musique, puisque vous l’exigez.
 
 
-__[i>/vmode(vmode_j_aurais_aimé_que_avoir_être)__
-    j’(aurai) +(?:aimé|souhaité|préféré|voulu) +(?:que? |> )  @@2
+__[i>/vmode(vmode_j_aurais_aimé_que_avoir_être1)__
+    j’(aurai) +(?:aimé|souhaité|préféré|voulu) +(?:que? |ne |> )  @@2
     <<- -1>> aurais|eusse                           # Pour un souhait passé, utilisez le conditionnel passé et non le futur antérieur. Exemple pour le futur antérieur : « quand j’aurai fini… »
-__[i]/vmode(vmode_j_aurais_aimé_que_avoir_être)__
-    j’(aurai) +(?:aimé|souhaité|préféré|voulu) +({infi}|ne)  @@2,$
-    <<- morph(\2, ":[YX]", False)
+__[i]/vmode(vmode_j_aurais_aimé_que_avoir_être2)__
+    j’(aurai) +(?:aimé|souhaité|préféré|voulu) +(?:[nmtsl]’|)({infi})  @@2,$
+    <<- morph(\2, ":Y", False)
     -1>> aurais|eusse                               # Pour un souhait passé, utilisez le conditionnel passé et non le futur antérieur. Exemple pour le futur antérieur : « quand j’aurai fini… »
 
 TEST: J’{{aurai}} aimé nous offrir ce magnifique cadeau.
 TEST: j’{{aurai}} voulu être un artiste.
 TEST: j’{{aurai}} préféré ne pas avoir à l’entendre.