AGSScriptModuleKitaiA try to implement regular expressions in AGSRegExp0.3ºž// Variables locales, utilisées pour construire l'automate int State; // Le prochain emplacement d'état disponible bool Backslash; // Indique si on a eu un caractère d'échappement (\) avant bool Ungreedy; // Indique si l'état dont on vient est ungreedy String Optional_states; // Indique la liste des états optionnels en cours String List; // La liste d'états [entre crochets] bool Brackets; // Indique si on est [entre crochets] String State_parenthesis; // Indique l'état débutant le n-ème groupe de parenthèses char Parenthesis_level; // Indique le niveau de profondeur de parenthèses actuel RegExp RegExp_Last; // L'instance utilisée pour RegExp.Test et String.RegExp_Match // Reste à faire : // // OK?- Gestion de plusieurs parenthèses ouvrantes : ((ab)c) > 'a' doit être premier état des deux parenthèses à la fois // > piste : en plus de opening_parenthesis, utiliser CHAR disant combien de parenthèses on vient d'ouvrir // OK?- Gestion de plusieurs parenthèses fermantes : (a(bc)) > 'c' doit être dernier état des deux parenthèses à la fois // OK?- Sauver groupes parenthèses // OK?- Gestion de ^debut...fin$ // PAS POSSIBLE - Dynamiser les états (avec import attribute String geti_states(int n); et gestion à la StringArray) < difficile // OK?- Gérer la répétition pour les groupes de disjoints (liste de débuts de disjoint) // OK?- Ne matche pas avec (ab|(cd)) // OK - Localiser les variables qui peuvent l'être // OK- Refaire match[0] // OK- Ajouter match_pos (servira à l'utilisateur et aussi pour Replace) // OK?- Gérer les choses comme (ab|cd?)+ et même (ab|c(de)?)+ // OK?- BUG : ^a(b?c|(de)f)g > abcg NON > abcfg OUI // OK- Revoir a(bc|de)+f : ne matche pas abcbcf (c n'a pas accès à b) // OK- Réparer capture ? (ab)+ ne doit pas capturer "abab" // ??- Bug avec plusieurs parenthèses NON IMBRIQUÉES // Problèmes de parenthèses résolus par refonte du système // OK- Bug : (.+). ne marche pas mais (.)+. marche, (ab?)b ne marche pas pour "ab" mais marche pour "abb" // OK?- Gérer (...)+ : greedy (ajouter la parenthèse ouvrante en début de "to") // OK?- Gérer le ungreedy (+?, *?, ??) DE FAÇON GÉNÉRALE : en cas de greedy, il faut placer le propre état au début de history // OK- Gérer la non-capture (?:) // OK- Gérer le case-sensitive ? // OK- Gérer les accolades {N,M} < mémoriser la liste des lettres qui passent sur l'état depuis ce même état ou depuis la parenthèse fermante // MAIS ab{0}c matche ac (pas trop grave, car normalement, ab{0}c s'écrit ac) // - Implémenter Replace ! // Debug tool : displays the path as a series of its states' indices String AsInts(String path) { String tmp = ""; if (String.IsNullOrEmpty(path)) return tmp; int d = 0; while (d < path.Length) { tmp = tmp.Append(String.Format("%d,",path.Chars[d])); d++; } return tmp.Truncate(tmp.Length-1); } /// Removes the char at POSITION from the string String RemoveCharAt(this String*, int position) { String tmp; if (String.IsNullOrEmpty(this)) tmp = ""; else tmp = this; if ((position < 0) || (position > tmp.Length)) return tmp; if (position+1 == tmp.Length) return tmp.Truncate(tmp.Length-1); return String.Format("%s%s",tmp.Truncate(position), tmp.Substring(position+1, tmp.Length-(position+1))); } /// Places the char LETTER at POSITION in the string String PutCharAt(this String*, int position, char letter) { if (position < 0) position = 0; String tmp; if (String.IsNullOrEmpty(this)) tmp = ""; else tmp = this; while (tmp.Length <= position) tmp = tmp.AppendChar(1); tmp = tmp.ReplaceCharAt(position, letter); return tmp; } /// Inserts the char LETTER at POSITION in the string String InsertCharAt(this String*, int position, char letter) { String tmp = ""; if (!String.IsNullOrEmpty(this)) tmp = this; if (position < 0) position = 0; if (position >= tmp.Length) tmp = tmp.AppendChar(letter); else tmp = String.Format("%s%c%s", tmp.Substring(0, position), letter, tmp.Substring(position, tmp.Length-position)); return tmp; } // COEUR // Le numéro de parenthèse ouverte int Parenthesis_N; // Ajoute un état TO accessible à l'état STATE int RegExp::AddTo(int state, int to, int position) { if ((state < 0) || (state >= REGEXP_MAX_STATES)) return -1; if ((to < 0) || (to >= REGEXP_MAX_STATES)) return -1; // Display("Ajoute %d (%s) a position %d de %d (%s)", to, this.states[to], position, state, this.states[state]); // Si l'état est déjà dans la liste, on ne fait rien if (this.to[state].IndexOf(String.Format("%c",to)) >= 0) return to; // Ajout de TO comme destination éventuelle de STATE if (this.to[state] == null) this.to[state] = ""; // Si POSITION = -1, on ajoute à la fin if (position < 0) position = this.to[state].Length; this.to[state] = this.to[state].InsertCharAt(position, to); // Ajout de STATE comme origine de TO if (this.from[to] == null) this.from[to] = ""; this.from[to] = this.from[to].AppendChar(state); return to; } // Ajoute un état TO accessible à tous les états sources possibles de STATE int RegExp::AddSourcesTo(int state, int to, int position) { if ((state < 0) || (state >= REGEXP_MAX_STATES)) return -1; if ((to < 0) || (to >= REGEXP_MAX_STATES)) return -1; if (String.IsNullOrEmpty(this.from[state])) return to; int i = 0; while (i < this.from[state].Length) { // Si on est en ungreedy, on ajoute le nouvel état (TO) *avant* l'état optionnel (STATE) if (Ungreedy) position = this.to[this.from[state].Chars[i]].IndexOf(String.Format("%c",state)); // Ajout de TO comme destination éventuelle de la i-ème source de STATE this.AddTo(this.from[state].Chars[i], to, position); // Ajout de la i-ème source de STATE comme origine de TO if (this.from[to] == null) this.from[to] = ""; this.from[to] = this.from[to].AppendChar(this.from[state].Chars[i]); i++; } return to; } // Création (potentielle) d'un nouvel état et retour de cet état int RegExp::NewState(String content) { if (State >= REGEXP_MAX_STATES) return State; this.from[State] = ""; this.to[State] = ""; this.states[State] = content; State++; // On retourne l'index de l'état return State-1; } // Traitement (et ajout potentiel) du contenu et retour du nouvel état int RegExp::TreatContent(int state, String content) { // Si le contenu est nul, on ne change pas d'état if ((content == null) || (content.Length < 1)) return state; int new_state; // Le numéro de l'état à créer/retourner // Si on ajoute une parenthèse fermante, on récupère l'état de fin du groupe if (content.StartsWith("\\)")) new_state = State_parenthesis.Chars[1+2*(Parenthesis_level+1)]; // Si on est à une fin de disjoint, c'est comme si on était à la fin du groupe else if (content == "\\|") new_state = State_parenthesis.Chars[1+2*Parenthesis_level]; else if (content == "\\$") new_state = 2; // Si on est à la fin, on récupère l'état final (2) else new_state = this.NewState(content); // sinon on crée un nouvel état // S'il y a des états OPTIONNELS... if (!String.IsNullOrEmpty(Optional_states)) { int n = 0; while (n < Optional_states.Length) { char optional_state = Optional_states.Chars[n]; // Display("Optionnel %d (%s)", optional_state, this.states[optional_state]); // On ajoute le nouvel état comme accessible aux sources du n-ième état optionnel this.AddSourcesTo(optional_state, new_state); n ++; // On passe à l'état optionnel suivant } } // On ajoute NEW_STATE au début si STATE est ungreedy (1-1=0), à la fin sinon (0-1=-1) this.AddTo(state, new_state, Ungreedy-1); // Si on vient d'ouvrir une parenthèse if (content.StartsWith("\\(")) { // On ajoute l'état comme début du groupe State_parenthesis = State_parenthesis.PutCharAt(2*Parenthesis_level, new_state); // Et on crée un état final State_parenthesis = State_parenthesis.PutCharAt(1+2*Parenthesis_level, this.NewState(String.Format("\\)%d",Parenthesis_N))); } Backslash = false; // On n'est plus en échappement Optional_states = ""; // On remet à zéro Ungreedy = false; // On réinitialise la greediness // Si on était à un disjoint, on revient au début du groupe if (content == "\\|") return State_parenthesis.Chars[2*Parenthesis_level]; return new_state; // On retourne le nouvel état } // Traitement des tirets dans une liste String hyphen(this String*) { int n = 0; String tmp_list = ""; while (n < this.Length) { // Si on a un tiret en milieu de chaîne if ((this.Chars[n] == '-') && (n > 0) && (n < this.Length-1)) { // Si on a un caractère d'échappement devant, on l'ajoute simplement à la liste if (this.Chars[n-1] == 92) tmp_list = tmp_list.AppendChar('-'); // Sinon, on vérifie qu'il n'est ni précédé ni suivi d'un tiret else if ((this.Chars[n-1] != '-') && (this.Chars[n+1] != '-')) { // Si le caractère suivant est plus loin dans l'ASCII que le caractère précédent if (this.Chars[n+1] > this.Chars[n-1]) { int c = this.Chars[n-1]+1; while (c < this.Chars[n+1]) { tmp_list = tmp_list.AppendChar(c); c++; } } // Si c'est l'inverse (plus tôt) else if (this.Chars[n+1] < this.Chars[n]) { int c = this.Chars[n-1]-1; while (c > this.Chars[n+1]) { tmp_list = tmp_list.AppendChar(c); c--; } } } } // Si on n'a pas de tiret, on ajoute simplement le caractère à la liste else tmp_list = tmp_list.AppendChar(this.Chars[n]); n++; } // On retourne la nouvelle liste // Display("liste : %s", tmp_list); return tmp_list; } // Retourne la position du prochain état dont le contenu n'est pas testé dans PATH int RegExp::IndexOfVoidState(String path) { if (String.IsNullOrEmpty(path)) return -1; int i = 0; while (i < path.Length) { String state = this.states[path.Chars[i]]; if ((state.StartsWith("\\(")) || (state.StartsWith("\\)")) || (state.StartsWith("\\^"))) return i; i++; } return -1; } // Lorsque l'utilisateur définit le modèle, on le convertit en automate à états finis void noloopcheck RegExp::set_Pattern(String pattern) { // Si le modèle est vide, retour if (String.IsNullOrEmpty(pattern)) return; // Les meta-caractères String Meta = "?*+{}"; State = 1; // On est à l'état initial this.pattern = pattern; // On copie le modèle Optional_states = ""; // L'état initial n'est pas optionnel List = ""; // On n'est pas [entre crochets] au départ Brackets = false; Parenthesis_level = 0; // On n'est pas (entre parenthèses) au départ // Le groupe 0 correspond à tout le modèle : le premier état du groupe est l'état initial, // le dernier état du groupe est l'état final State_parenthesis = String.Format("%c%c",1,2); Parenthesis_N = 0; // On initialise le numéro du groupe ouvert //this.in_parenthesis = ""; // On initialise les appartenance aux groupes this.not_matched = ""; // Les groupes à ne pas capturer this.case_insensitive = ""; // Les groupes insensibles à la casse this.min_states = ""; // Le nombre de répétitions minimum d'un état this.max_states = ""; // Le nombre de répétitions maximum d'un état char letter; // Le caractère scruté String content = ""; // Le contenu de l'état en cours // i = 0, STATE = l'état initial int i = 0, state = this.NewState(""); this.NewState("\\$"); // On crée l'état final // On parcourt tous les caractères du pattern while (i <= pattern.Length) { // Si i = longueur, on est à la fin if (i == pattern.Length) { // On ajoute l'état final state = this.TreatContent(state, "\\$"); // Calcul du plus court chemin // PATH = les chemins en cours String path = String.Format("%c",1); bool running = true; // Tant que RUNNING, on cherche les états accessibles this.min_length = 0; // Au début, la longueur minimum vaut 0 while (running) { int s = 0; // On parcrourt tous les états en cours String newpath = ""; while (s < path.Length) { char state_n = path.Chars[s]; int n = 0; // On va ajouter les états accessibles à chaque état à NEWPATH while (n < this.to[state_n].Length) { char to = this.to[state_n].Chars[n]; // Display("%s acces a %s", this.states[state_n], this.states[to]); // Si l'état final est accessible, on cesse le RUNNING if ((this.states[to] == "\\$") || (this.states[to] == "\\f")) running = false; // Si l'état accessible est différent de l'état actuel, on l'ajoute à NEWPATH else if (to != state_n) newpath = newpath.AppendChar(to); n++; } s++; } // Remplacement des états "vides" par leurs destinations String oldpath = newpath; int v = this.IndexOfVoidState(newpath); while ((v > -1) && (running)) { char to = newpath.Chars[v]; // Display("%d (%s)", to, this.states[to]); newpath = newpath.RemoveCharAt(v); int d = 0; while (d < this.to[to].Length) { char newto = this.to[to].Chars[d]; if ((newto == 2) || (this.states[newto] == "\\f")) running = false; if (oldpath.IndexOf(String.Format("%c",this.to[to].Chars[d])) == -1) newpath = newpath.AppendChar(this.to[to].Chars[d]); d++; } v = this.IndexOfVoidState(newpath); } // Tous les états de NEWPATH sont accessibles à l'état prochain path = newpath; if (running) this.min_length++; } //Display("Chemin le plus court : %d > %s", this.min_length, path); return; // On retourne } // Le caractère scruté letter = pattern.Chars[i]; // Si on a un backslah et qu'on n'est pas en échappement, on le devient if ((letter == 92) && (!Backslash)) Backslash = true; // Si on a un crochet ouvrant hors échappement, hors crochets else if ((letter == '[') && (!Backslash) && (!Brackets)) Brackets = true; // Si on vient d'une parenthèse ouvrante, on regarde pour la non capture else if ((!Backslash) && (!Brackets) && (this.states[state].StartsWith("\\(")) && (letter == '?')) { if (i < pattern.Length-1) { if (pattern.Chars[i+1] == ':') this.not_matched = this.not_matched.AppendChar(state); else if (pattern.Chars[i+1] == 'i') this.case_insensitive = this.case_insensitive.AppendChar(state); i++; // On ne considérera pas le ':' suivant } } // META CARACTERES (si non échappement et non crochets) else if ((!Backslash) && (!Brackets) && (Meta.IndexOf(String.Format("%c",letter)) >= 0)) { // Accolades int min = -1, max = -1; // Si on n'a pas d'accolades, nombres négatifs if (letter == 123) { // Si on ouvre une accolade String next = pattern.Substring(i+1,pattern.Length-1); // On regarde ce qui suit if (next.Length) { // S'il y a bien du texte à suivre int v = next.IndexOf(String.Format("%c",125)); // On regarde s'il y a une accolade fermante if (v > -1) { // S'il y en a une (note : le caractère d'échappement n'a pas d'effet ici) String rep = next.Truncate(v); // On récupère le contenu des accolades int w = rep.IndexOf(String.Format("%c",',')); // On regarde s'il y a une virgule if (w > -1) { // S'il y en a une, on récupère min et max String x = rep.Substring(0, w), y = rep.Substring(w+1, rep.Length-w-1); min = x.AsInt; max = y.AsInt; if (!y.Length) max = -1; } // S'il n'y a pas de virgule, on définit min et max à la même valeur else { min = rep.AsInt; max = rep.AsInt; } // Si max est < à min, on peut répéter autant qu'on veut if (max < min) max = -1; this.min_states = this.min_states.PutCharAt(state, min+2); this.max_states = this.max_states.PutCharAt(state, max+2); i += rep.Length+1; // On passe après les accolades } } } // Si un ? suit > UNGREEDY (-1 = "ajoute à la fin") if ((i < pattern.Length-1) && (pattern.Chars[i+1] == '?')) { Ungreedy = true; i++; } // Si le meta-caractère est un ? ou un * ou si on a un minimum de 0, l'état est optionnel if ((letter == '?') || (letter == '*') || (min == 0)) { // Si on vient de fermer une parenthèse, c'est le premier état du groupe qui est optionnel if (this.states[state].StartsWith("\\)")) Optional_states = Optional_states.AppendChar(State_parenthesis.Chars[2*(Parenthesis_level+1)]); // Display("Ajoute %d (%s) a la liste des etats optionnels", State_parenthesis.Chars[Parenthesis_level], this.states[State_parenthesis.Chars[Parenthesis_level]]); else // Sinon, c'est l'état en cours qui est optionnel Optional_states = Optional_states.AppendChar(state); } // Si le meta-caractère est un * ou un + ou si le minimum ou maximum est supérieur à 1, on répète l'état if ((letter == '*') || (letter == '+') || ((min > -1) && ((max < 0) || (max > 1)))) { // Si on vient de fermer un groupe if (this.states[state].StartsWith("\\)")) // On ajoute le premier état du groupe comme destination possible this.AddTo(state, State_parenthesis.Chars[2*(Parenthesis_level+1)]); else // Sinon, on ajoute l'état à lui-même this.AddTo(state, state); } } // CARACTERES REGULIERS OU ECHAPPEMENT OU CROCHETS else { if (Brackets) { // Si on est entre [CROCHETS]... if (Backslash) { // Si on est en échappement if (letter == 'd') List = List.Append("0123456789"); // Ajout des chiffres else if (letter == 's') List = List.AppendChar(' '); // Ajout d'un espace else if (letter == 'w') List = List.Append("A-Za-z0-9_"); // Ajout d'un caractère de mot else if (letter == '-') List = List.Append("\\-"); // Ajout de \- else List = List.AppendChar(letter); // Ajout simple du caractère } else { // Si on n'est pas en échappement if (letter == ']') { // FERMETURE DES [CROCHETS] Brackets = false; // On ferme les crochets content = String.Format("[%s]", List.hyphen()); // Le contenu devient la liste traitée List = ""; // On vide la liste state = this.TreatContent(state, content); // On traite le contenu } else List = List.AppendChar(letter); // Ajout simple du caractère } } else { // Si on n'est PAS entre [CROCHETS] String digits = "0123456789"; // Si on est en échappement if (Backslash) { if (letter == '.') content = "."; // Le contenu est le caractère "point" else if (letter == 'd') content = "[0123456789]"; // Le contenu est la liste des chiffres else if (letter == 's') content = " "; // Le contenu est un espace else if (letter == 'w') content = "[A-Za-z0-9_]"; // Le contenu est un caractère de mot else if (letter == 'D') content = "[^0123456789]"; // Le contenu est l'anti-liste des chiffres else if (letter == 'W') content = "[^A-Za-z0-9]"; // Le contenu est un caractère de non-mot else if (letter == 'S') content = "[^ ]"; // Le contenu est un caractère non espace else if (digits.IndexOf(String.Format("%c",letter)) > -1) { // Si on échape un nombre, on cherche un groupe content = "\\"; // On va ajouter tous les chiffres suivant \ while ((i < pattern.Length) && (digits.IndexOf(String.Format("%c",pattern.Chars[i])) > -1)) { content = content.AppendChar(pattern.Chars[i]); i++; } i--; // On revient sur le caractère correspondant au dernier chiffre } else content = String.Format("%c",letter); // Le contenu est simplement le caractère if (content.Length > 1) content = content.hyphen(); // Si on a une liste, on traite les tirets } // Si on a . ^ $ ( ) ou | en non échappement else if (letter == '.') content = "\\."; // Le contenu est le contenu spécial \. else if (letter == '^') content = "\\^"; // Le contenu est le contenu spécial \^ else if (letter == '$') content = "\\f"; // Le contenu est le contenu spécial \$ // Si on a une (parenthèse ouvrante else if (letter == '(') { Parenthesis_N++; // On ouvre la Nième parenthèse Parenthesis_level++; // On augmente le niveau de parenthèses content = String.Format("\\(%d", Parenthesis_N); // Le contenu est le contenu spécial \( } else if ((letter == ')') && (Parenthesis_level)) { content = String.Format("\\)%d", Parenthesis_N); Parenthesis_level--; // On descend le niveau de profondeur des parenthèses } else if (letter == '|') content = "\\|"; // Le contenu est le contenu spécial \| else content = String.Format("%c", letter); // Le contenu est simplement le caractère state = this.TreatContent(state, content); // On traite le contenu } } i++; } } String RegExp::geti_match(int index) { if (String.IsNullOrEmpty(this.text)) return ""; if (index < 0) index = 0; // Gestion des groupes non capturants int i = 0; while ((i < this.not_matched.Length) && (index > 0)) { String n = this.states[this.not_matched.Chars[i]].Substring(2, this.states[this.not_matched.Chars[i]].Length-2); if (n.AsInt > index) i = this.not_matched.Length; else if (n.AsInt) index++; i++; } if (index > this.match_parenthesis.Length/2) return ""; int len = this.match_parenthesis.Chars[2*index+1]-this.match_parenthesis.Chars[2*index]; if (len < 1) return ""; return this.text.Substring(this.match_parenthesis.Chars[2*index]-2, len); } int RegExp::geti_match_start(int index) { if (String.IsNullOrEmpty(this.text)) return -1; if (index < 0) index = 0; // Gestion des groupes non capturants int i = 0; while ((i < this.not_matched.Length) && (index > 0)) { String n = this.states[this.not_matched.Chars[i]].Substring(2, this.states[this.not_matched.Chars[i]].Length-2); if (n.AsInt > index) i = this.not_matched.Length; else if (n.AsInt) index++; i++; } if (index > this.match_parenthesis.Length/2) return -1; return this.match_parenthesis.Chars[2*index]-2; } bool RegExp::Match(String text) { this.text = text; // On mémorise le texte int debute = 0; // Le caractère où on va débuter this.match_parenthesis = ""; // Les états ouvrants et fermants if (String.IsNullOrEmpty(text)) { // Si on a passé une chaîne vide // FAUX si on a besoin d'au moins un caractère pour correspondre au modèle if (this.min_length) return false; // VRAI si on peut satisfaire le modèle sans aucun caractère else return true; } bool caseSensitive = true; // La sensibilité à la casse String RepeatedStates = ""; // Les répétitions de chaque état if (text.Length < this.min_length) return false; if (String.IsNullOrEmpty(this.pattern)) return true; String history[]; // Les états alternatifs pour chaque caractère String start_parenthesis = ""; // Les états commençant les groupes history = new String[text.Length+1]; // Il y aura potentiellement autant d'alternatives que de caractères int i = 0, s = 1; // S = l'état en cours (ici, l'état initial) while ((i <= text.Length) || (this.states[s].StartsWith("\\(")) || (this.states[s].StartsWith("\\)"))) { String preexistent = ""; // Les alternatives préexistantes pour le caractère // Si on a atteint la fin du modèle : TRUE if (this.states[s] == "\\$") { this.match_parenthesis = this.match_parenthesis.PutCharAt(0, debute+2); this.match_parenthesis = this.match_parenthesis.PutCharAt(1, i+1); // i+2-1 car on a i++ une fois de trop return true; } if (i <= text.Length) history[i] = ""; // Pour le moment, aucune alternative // Si on exige le début, on revient au début if (this.states[s] == "\\^") { preexistent = history[0]; history[0] = ""; i = 0; } // Si on ouvre ou ferme une parenthèse else if ((this.states[s].StartsWith("\\(")) || (this.states[s].StartsWith("\\)"))) { i--; // On revient à la lettre précédente preexistent = history[i]; // Et on mémorise les états accessibles pour les mettre après history[i] = ""; caseSensitive = true; // Gestion de la sensibilité à la casse if (this.case_insensitive.IndexOf(String.Format("%c",s)) > -1) caseSensitive = false; } // Si on a affaire à un groupe else if (this.states[s].StartsWith("\\") && (this.states[s].Length > 1) && (this.states[s] != "\\.")) { // On avance jusqu'à la fin de la correspondance String num = this.states[s].Substring(1, this.states[s].Length-1), match = this.geti_match(num.AsInt); i += match.Length-1; } // On scrute les différents états accessibles int letter = -1; // Le caractère scruté if (i < text.Length) letter = text.Chars[i]; // Si on est sur une parenthèse, on reste sur la lettre dont on vient int n = 0; // Le n-ième état accessible char to; // L'id du n-ième état accessible while (n < this.to[s].Length) { to = this.to[s].Chars[n]; // L'id du n-ième état accessible // Gestion du minimum et du maximum int min = 1, max = 1, rep = 1; // On récupète le minimum et le maximum de répétitions if (this.min_states.Length > s) { min = this.min_states.Chars[s]; max = this.max_states.Chars[s]; } // On récupère le nombre de fois que l'état a éventuellement déjà été répété if (RepeatedStates.Length > s) rep = RepeatedStates.Chars[s]; // S'il y a un minimum > 1 et si ce minimum est supérieur au nombre de répétitions if ((min > 3) && (min > rep)) { // Si on a affaire à une parenthèse fermante et si on se dirige vers ailleurs // que la parenthèse ouvrante correspondante : TO = 1 if (this.states[s].StartsWith("\\)") && (to != s-1)) to = 1; // Sinon, si on se dirige vers un autre état que celui en cours : TO = -1 else if (!this.states[s].StartsWith("\\)") && (to != s)) to = 1; } // S'il y a un maximum et que ce maximum est égal au nombre de répétitions if ((max > 2) && (rep == max)) { // Si on a affaire à une parenthèse fermante et si on se dirige vers sa contrepartie ouvrante, TO = 1 if (this.states[s].StartsWith("\\)") && (to == s-1)) to = 1; // Sinon, si on se dirige vers le même état : TO = 1 else if (!this.states[s].StartsWith("\\)") && (to == s)) to = 1; } if ((this.states[to] == "\\.") && (letter > -1)) // N'importe quel caractère history[i] = history[i].AppendChar(to); else if (this.states[to] == "\\$") // Accès à la fin history[i] = history[i].AppendChar(to); else if ((this.states[to] == "\\f") && (letter == -1)) // Fin obligatoire history[i] = history[i].AppendChar(to); else if ((this.states[to] == "\\^") && (i == 0) && (this.states[s] != "\\^")) // Début history[i] = history[i].AppendChar(to); //else if ((this.states[to] == "\\(") && (letter > -1)) { // Parenthèse ouvrante else if (this.states[to].StartsWith("\\(")) { // Parenthèse ouvrante String p = this.states[to].Substring(2, this.states[to].Length-2); // On ne retourne pas à la parenthèse ouvrante si on en vient juste (évite récursivité infinie) if ((start_parenthesis.Length <= p.AsInt) || (start_parenthesis.Chars[p.AsInt] != i+2)) history[i] = history[i].AppendChar(to); } else if (this.states[to].StartsWith("\\)")) // Parenthèse fermante history[i] = history[i].AppendChar(to); else if (this.states[to].StartsWith("\\") && (this.states[to].Length > 1)) { // Référence à un groupe // On récupère le nombre du groupe String after = this.states[to].Substring(1, this.states[to].Length-1); if (after.AsInt) { // Si à I le texte commence par le groupe, on ajoute l'état String match = this.geti_match(after.AsInt), sub = text.Substring(i, text.Length-i); if (!String.IsNullOrEmpty(match) && sub.StartsWith(match, caseSensitive)) history[i] = history[i].AppendChar(to); } } else if (this.states[to].Length == 1) { // Caractère lui-même String sletter = String.Format("%c",letter); // On ajoute si les chaînes (d'un seul caractère chacune) correspondent (selon la modalité de la casse) if (!sletter.CompareTo(this.states[to], caseSensitive)) history[i] = history[i].AppendChar(to); } else if ((this.states[to].Length >= 3) && (letter >= 0)) { // Liste String list = this.states[to].Substring(1, this.states[to].Length-1); // On supprime [ list = list.Truncate(list.Length-1); // On supprime ] int found = list.IndexOf(String.Format("%c",letter)); // Anti-liste OK if ((list.Chars[0] == '^') && (list.Length > 1) && (found < 1)) history[i] = history[i].AppendChar(to); // Liste OK else if (((list.Chars[0] != '^') || (list.Length < 2)) && (found >= 0)) history[i] = history[i].AppendChar(to); } n++; // On passe à l'état accessible suivant } history[i] = history[i].Append(preexistent); // Display("Depuis %d (%s), scrute %c, trouve %d", s, this.states[s], letter, history[i].Length); // Si on a trouvé au moins un état prochain if (!String.IsNullOrEmpty(history[i])) { // Si on va de la parenthèse fermante à la parenthèse ouvrante ou si on reste sur le même état if ((this.states[s].StartsWith("\\)") && (history[i].Chars[0] == s-1)) || (s == history[i].Chars[0])) { int r = 3; // 3-2 = 1, donc 1 fois sur l'état if (RepeatedStates.Length > s) r = RepeatedStates.Chars[s]; // Ou bien R fois RepeatedStates = RepeatedStates.PutCharAt(s, r+1); // Et on y retourne : r+1 } s = history[i].Chars[0]; // On passe à cet état //Display("%s",this.states[s]); history[i] = history[i].RemoveCharAt(0); // Ouverture de parenthèse (si on est bien dans une capturante) if ((this.states[s].StartsWith("\\(")) && (this.not_matched.IndexOf(String.Format("%c",s)) == -1)) { String p = this.states[s].Substring(2, this.states[s].Length-2); start_parenthesis = start_parenthesis.PutCharAt(p.AsInt, i+2); } // Fermeture de parenthèse (si on est bien dans une capturante) else if ((this.states[s].StartsWith("\\)")) && (this.not_matched.IndexOf(String.Format("%c",s-1)) == -1)) { String p = this.states[s].Substring(2, this.states[s].Length-2); // On ajoute seulement si on a au moins un caractère entre les deux ou si on n'avait pas déjà ajouté if ((start_parenthesis.Chars[p.AsInt] != i+2) || (this.match_parenthesis.Length < 1+2*p.AsInt)) { // On ajoute le début du groupe this.match_parenthesis = this.match_parenthesis.PutCharAt(2*p.AsInt, start_parenthesis.Chars[p.AsInt]); // Et la fin du groupe this.match_parenthesis = this.match_parenthesis.PutCharAt(1+2*p.AsInt, i+2); } } // Display("Trouve etat %d (%s)", s, this.states[s]); } else { // Si on n'a trouvé aucun état prochain // Display("Revision"); i--; // On remonte l'historique jusqu'à la dernière alternative while ((i >= 0) && String.IsNullOrEmpty(history[i])) i--; // Si on n'a pas trouvé d'alternative if (i < 0) { debute++; // On va relancer la recherche plus loin // S'il n'y a plus la place pour le modèle : FALSE if ((text.Length - debute) < this.min_length) return false; i = debute-1; // On recommence à chercher le modèle s = 1; // depuis un caractère plus loin } else { // Si on a trouvé une alternative // Si on va de la parenthèse fermante à la parenthèse ouvrante ou si on reste sur le même état if ((this.states[s].StartsWith("\\)") && (history[i].Chars[0] == s-1)) || (s == history[i].Chars[0])) { int r = 3; // 3-2 = 1, donc 1 fois sur l'état if (RepeatedStates.Length > s) r = RepeatedStates.Chars[s]; // Ou bien R fois RepeatedStates = RepeatedStates.PutCharAt(s, r+1); // Et on y retourne : r+1 } s = history[i].Chars[0]; // On passe à cet état // Ouverture de parenthèse (si on est bien dans une capturante) if ((this.states[s].StartsWith("\\(")) && (this.not_matched.IndexOf(String.Format("%c",s)) == -1)) { String p = this.states[s].Substring(2, this.states[s].Length-2); start_parenthesis = start_parenthesis.PutCharAt(p.AsInt, i+2); } // Fermeture de parenthèse (si on est bien dans une capturante) else if ((this.states[s].StartsWith("\\)")) && (this.not_matched.IndexOf(String.Format("%c",s-1)) == -1)) { String p = this.states[s].Substring(2, this.states[s].Length-2); // On ajoute le début du groupe this.match_parenthesis = this.match_parenthesis.PutCharAt(2*p.AsInt, start_parenthesis.Chars[p.AsInt]); // Et la fin du groupe this.match_parenthesis = this.match_parenthesis.PutCharAt(1+2*p.AsInt, i+2); } // Display("Rien trouve, remonte a %c", text.Chars[i]); // S'il y avait au moins 2 possibilités, on supprime celle qu'on vient de considérer if (history[i].Length > 1) history[i] = history[i].Substring(1, history[i].Length-1); // Sinon, il n'y aura plus d'autres possibilités : on ne garde que l'état lui-même else history[i] = ""; } } i++; // On passe au caractère suivant } // Si on a scruté tous les caractères de la chaîne et qu'on a atteint la fin du modèle, TRUE if ((this.states[s] == "\\$") || (this.states[s] == "\\f")) { this.match_parenthesis = this.match_parenthesis.PutCharAt(0, debute+2); this.match_parenthesis = this.match_parenthesis.PutCharAt(1, i+2); return true; } // Si on n'a pas atteint la fin du modèle, FALSE return false; } String RegExp::get_Pattern() { return this.pattern; } // Retourne le nombre de correspondances du modèle avec le texte int RegExp::Count(String text) { if (text == null) return 0; int i = 0, n = 0; // On va appliquer le modèle autant de fois que possible while ((i < text.Length) && this.Match(text.Substring(i, text.Length-i))) { String match = this.geti_match(0); // On reprend le match juste après la correspondance i += this.geti_match_start(0)+match.Length; // On ajoute 1 à n n++; } // On ajoute le texte suivant la dernière correspondance return n; } // GROUPS est l'expression régulière qui permettra de remplacer RegExp groups; // les occurrences de \N dans le texte de substitution par le groupe correspondant function game_start() { groups.set_Pattern("\\\\(\\d+)"); } // GROUP_MATCHES mémorise les correspondances de la regexp en cours dans Replace String group_matches[]; // Remplace les correspondances du modèle dans TEXT par REPLACEWITHTEXT String RegExp::Replace(String text, String replaceWithText, RepeatStyle repeat) { if (replaceWithText == null) replaceWithText = ""; if (repeat == eOnce) { // On vérifie qu'il y a au moins une correspondance if (this.Match(text)) { // On récupère la correspondance String match = this.geti_match(0); // On traite les éventuels \N de REPLACEWITHTEXT if ((this != groups) && (this.match_parenthesis.Length>2)) { // On mémorise les correspondances en cours group_matches = new String[this.match_parenthesis.Length/2]; int n = 0; while (n < this.match_parenthesis.Length/2) { group_matches[n] = this.geti_match(n); n++; } // On informe du nombre de correspondances dans group_matches[0] group_matches[0] = String.Format("%d",this.match_parenthesis.Length/2); // On applique REPLACE sur REPLACEWITHTEXT avec la regexp GROUPS replaceWithText = groups.Replace(replaceWithText, "", eRepeat); } // On récupère le texte suivant la correspondance int start_after = this.geti_match_start(0)+match.Length; // On retourne le texte précédant, REPLACEWITHTEXT puis le texte suivant String before = text.Substring(0, this.geti_match_start(0)), after = text.Substring(start_after, text.Length-start_after); return String.Format("%s%s%s", before, replaceWithText, after); } } else if (repeat == eRepeat) { int i = 0; String ret = "", new_rep = replaceWithText; // On va appliquer l'opération autant de fois qu'il y a de correspondances du modèle while ((i < text.Length) && this.Match(text.Substring(i, text.Length-i))) { String match = this.geti_match(0); // Remplacement des éventuels \N de REPLACEWITHTEXT if ((this != groups) && (this.match_parenthesis.Length>2)) { group_matches = new String[this.match_parenthesis.Length/2]; int n = 0; while (n < this.match_parenthesis.Length/2) { group_matches[n] = this.geti_match(n); n++; } group_matches[0] = String.Format("%d",this.match_parenthesis.Length/2); new_rep = groups.Replace(replaceWithText, "", eRepeat); } // Si on est dans GROUPS, on remplace \N par les correspondances correspondantes (!) else if (this == groups) { // de la regexp dont on vient String p = groups.geti_match(1), num = group_matches[0]; if ((p.AsInt > 0) && (p.AsInt < num.AsInt)) new_rep = group_matches[p.AsInt]; } // On récupère le texte précédant la correspondance int start_after = this.geti_match_start(0)+match.Length; String before = text.Substring(i, this.geti_match_start(0)); ret = ret.Append(before); // On ajoute le texte précédant ret = ret.Append(new_rep); // et REPLACEWITHTEXT i += start_after; // On reprend le match juste après la correspondance } // On ajoute le texte suivant la dernière correspondance if (i < text.Length) ret = ret.Append(text.Substring(i, text.Length-i)); return ret; } } // Méthode statique pour appliquer une regexp sur un texte static bool RegExp::MatchText(String text, String pattern) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Match(text); } // Méthode statique pour appliquer une regexp sur un texte static String RegExp::ReplaceText(String text, String pattern, String replaceWithText, RepeatStyle repeat) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Replace(text, replaceWithText, repeat); } // Méthode statique pour appliquer une regexp sur un texte static int RegExp::CountText(String text, String pattern) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Count(text); } // Version String.Match de RegExp.Match bool RegExp_Match(this String*, String pattern) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Match(this); } // Version String.Replace de RegExp.Replace String RegExp_Replace(this String*, String pattern, String replaceWithText, RepeatStyle repeat) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Replace(this, replaceWithText, repeat); } // Version String.Count de RegExp.Count int RegExp_Count(this String*, String pattern) { RegExp_Last.set_Pattern(pattern); return RegExp_Last.Count(this); } export Regexp_Last;m #define REGEXP_MAX_STATES 256 struct RegExp { protected String text; protected String pattern; import attribute String Pattern; import String get_Pattern(); // $AUTOCOMPLETEIGNORE$ import void set_Pattern(String pattern); // $AUTOCOMPLETEIGNORE$ protected String from[REGEXP_MAX_STATES]; // Les états sources de chaque état protected String to[REGEXP_MAX_STATES]; // Les états accessibles depuis chaque état protected String states[REGEXP_MAX_STATES]; // Le contenu de chaque état protected String match_parenthesis; // Les correspondances protected String not_matched; // Les groupes à ne pas capturer protected String case_insensitive; // Les groupes non sensibles à la casse protected String min_states; // Les groupes qui doivent être répétés un minimum de fois protected String max_states; // Les groupes qui ne doivent pas être répétés plus d'un maximum de fois protected int min_length; // Le nombre minimum de caractères requis pour que le modèle détecte import int AddTo(int state, int to, int position = -1); // $AUTOCOMPLETEIGNORE$ Ajoute un état accessible à STATE import int AddSourcesTo(int state, int to, int position = -1); // $AUTOCOMPLETEIGNORE$ Ajoute un état accessible aux états sources de STATE import int TreatContent(int state, String content); // $AUTOCOMPLETEIGNORE$ Traite le contenu import int NewState(String content); // $AUTOCOMPLETEIGNORE$ Crée un nouvel état import int IndexOfVoidState(String path); // $AUTOCOMPLETEIGNORE$ Retourne le premier caractère de PATH qui représente un état "vide" /// Tests whether TEXT matches the pattern import bool Match(String text); // Teste si le texte répond au modèle /// Returns the number of times TEXT matches the pattern import int Count(String text); // Retourne le nombre de correspondances du modèle avec le texte /// Replaces the first or all occurrences of the pattern in TEXT by REPLACEWITHTEXT import String Replace(String text, String replaceWithText, RepeatStyle repeat = eRepeat); readonly import attribute String match[]; // Les correspondances trouvées (0 = tout, 1 = premier groupe, etc.) import String geti_match(int index); // $AUTOCOMPLETEIGNORE$ readonly import attribute int match_start[]; // La position où commence la N-ième correspondance import int geti_match_start(int index); // $AUTOCOMPLETEIGNORE$ /// Tests whether TEXT matches PATTERN import static bool MatchText(String text, String pattern); // Teste si le texte répond au modèle /// Replaces the first or all occurrences of PATTERN in TEXT by REPLACEWITHTEXT import static String ReplaceText(String text, String pattern, String replaceWithText, RepeatStyle repeat = eRepeat); /// Returns the number of times TEXT matches PATTERN import static int CountText(String text, String pattern); }; /// Tests whether the text matches PATTERN import bool RegExp_Match(this String*, String pattern); /// Replaces the first or all occurrences of PATTERN in the text by REPLACEWITHTEXT import String RegExp_Replace(this String*, String pattern, String replaceWithText, RepeatStyle repeat = eRepeat); /// Returns the number of times the text matches PATTERN import int RegExp_Count(this String*, String pattern); // L'instance utilisée pour RegExp.Match, String.RegExp_Match, RegExp.Replace et String.RegExp_Replace import RegExp RegExp_Last;‚êˆ+ej÷´