lucidiot's cybrecluster

Validation de Well-Known Text dans une XSD

Lucidiot Informatique 2021-12-26
Une indigestion absolue d'expressions régulières ! Et ce n'est même pas fini.


Dans le précédent article, j'ai introduit des notions sur le format XSD et sur le standard OpenSearch. Ce devait en fait être seulement l'introduction de l'article d'aujourd'hui avant que je ne me rende compte que c'était devenu incroyablement long et qu'il me restait encore d'autres concepts à introduire avant de pouvoir vraiment plonger dans ce que je voulais expliquer au départ.

Nous avions ajouté de la validation améliorée pour certains paramètres de l'extension OpenSearch Geo : geo:lat, geo:lon, geo:relation et geo:box. On va maintenant s'attaquer à un dernier paramètre sur lequel nous pouvons mettre une validation plus stricte, geo:geometry.

Le paramètre geo:geometry permet de spécifier une géométrie quelconque sur laquelle la recherche sera effectuée. L'utilisation de la géométrie est déterminée par le paramètre geo:relation qu'on a vu auparavant : on peut chercher tout ce qui est strictement contenu dans la géométrie ou inversement, ou tout ce qui croise mais n'est pas forcément entièrement inclus dans la géométrie. J'utilise le terme assez abstrait de géométrie car il peut s'agir de plusieurs types de géométries :

Les polygones

Je parle d'anneaux dans un polygone car nous travaillons ici avec les définitions des géométries telles qu'elles sont connues dans le monde des systèmes d'information géographiques : les polygones que vous connaissez sans doute en mathématique sont ici des anneaux linéaires, autrement dit des lignes brisées dont le premier et le dernier point sont les mêmes. Par exemple, un carré pourrait être représenté par un anneau linéaire à 5 points :

X Y
0 0
10 0
10 10
0 10
0 0

Le premier et le dernier point sont les mêmes, et on tourne autour du carré dans le sens des aiguilles d'une montre.

Dans les systèmes d'information géographiques, un polygone n'est pas seulement un simple anneau linéaire mais plusieurs ! Il y a un anneau extérieur et optionellement des anneaux intérieurs. Les anneaux intérieurs forment en fait des trous dans le polygone. Il devient possible avec cette méthode de représenter par exemple un pays qui entoure complètement un autre pays, ce qui est une situation géopolitique qui peut se produire. Plus simplement, on pourrait aussi dessiner un donut avec un anneau extérieur et un anneau intérieur.

Un format d'échange de géométries

Je ne sors pas ces types de géométries ou cette définition du polygone de nulle part : une organisation nommée l'Open Geospatial Consortium a défini des standards pour tous les systèmes d'information géographiques, et on retrouve aujourd'hui ces standards absolument partout. Si vous êtes développeur, vous avez peut-être déjà pu vous approcher de ce standard en exploitant des données au format GeoJSON. Si vous êtes un être humain lambda, vous avez certainement déjà utilisé ce standard sans le savoir en naviguant sur Google Maps, Google Earth, Bing Maps, Apple Maps, OpenStreetMap, et en fait quasiment tous les services de cartographie en ligne.

Le document au doux nom de OpenGIS Implementation Specification for Geographic information - Simple feature access - Part 1: Common architecture définit les structures de données représentant les géométries pour les implémenteurs de systèmes d'information géographiques. C'est là qu'on trouvera des définitions pour tous les types que j'ai mentionné.

Ce document spécifie en particulier deux formats permettant d'encoder ou de décoder des géométries. Ces formats permettent par exemple d'exporter des données d'une base de données vers une autre même si ces deux bases utilisent des formats de stockage différents. Ces deux formats sont le Well-Known Text et le Well-Known Binary, autrement dit le texte bien connu et le binaire bien connu. Ils sont plutôt hauts dans mon classement des meilleurs noms de formats informatiques que je connaisse.

On ne va s'attarder aujourd'hui qu'au texte bien connu. Voici quelques exemples sauvagement volés de Wikipédia pour chacun des types de géométries mentionnés plus haut :

Point
POINT (30 10)
Ligne brisée
LINESTRING (30 10, 10 30, 40 40)
Polygone
POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
Ensemble de points
MULTIPOINT (10 40, 40 30, 20 20, 30 10)
Ensemble de lignes brisées
MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))
Ensemble de polygones
MULTIPOLYGON (
  ((40 40, 20 45, 45 30, 40 40)),
  ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),
  (30 20, 20 15, 20 25, 30 20))
)

D'autres types existent, comme la collection de géométries qui peut contenir tout et n'importe quoi, et il existe aussi des variantes avec des coordonnées à 3 et à 4 dimensions. Ces variantes feront l'objet d'un autre article.

Dans OpenSearch Geo, le paramètre geo:geometry utilise le format Well-Known Text et s'attend à ces six types.

À cette étape, une personne relativement normale qui serait en train d'écrire un schéma XSD s'arrêterait, baisserait les bras et dirait juste que geometry est de type xs:string. Et comme d'habitude, je ne suis pas relativement normal.

Le Bescherelle géométrique

La seule façon qu'on aura de faire une validation de ce genre dans une XSD est d'utiliser une expression régulière avec <xs:pattern>. Mais pour pouvoir correctement définir ces expressions régulières, on va aller plus loin que seulement regarder les quelques exemples que j'ai montré ci-dessus ; il y manque moult spécificités que je n'ai pas précisé. On va donc ouvrir le document au nom incompréhensible et y rechercher la grammaire BNF. Celle qui nous intéresse est au paragraphe 7.2.2, BNF Productions for Two-Dimension Geometry WKT. En voici un extrait :

<sign> ::= <plus sign> | <minus sign>
<signed numeric literal> ::= {<sign>}<unsigned numeric literal>
<x> ::= <signed numeric literal>
<y> ::= <signed numeric literal>
<point> ::= <x> <y>
<point text> ::= <empty set> | <left paren> <point> <right paren>
<point tagged text> ::= point <point text>

Cet extrait est une partie de la définition du point. Il y a encore pas mal d'autres grammaires liées au point, notamment le <unsigned numeric literal> qui descend encore plus en profondeur.

On retrouve souvent les grammaires BNF dans les standards car elles sont extrêmement verbeuses et donc obligent à beaucoup d'exactitude. Cela peut rendre les standards plus durs à lire mais cela évite l'imprécision : toute imprécision dans un standard conduit automatiquement à de la confusion, ou à un mésusage par certaines implémentations qui rendent un format ou un protocole incroyablement complexes à utiliser. Par exemple, si nous ne définissons pas ici ce qu'est un nombre, chaque implémentation aura une façon différente de les interpréter ; certains n'accepteraient pas +0, alors que c'est un nombre tout à fait valide, ou d'autres pourraient accepter NaN ("pas un nombre") ou inf (l'infini).

On peut simplifier un peu toutes cette grammaire et s'en servir pour tout traduire en des expressions régulières. On pourra ensuite progressivement imbriquer toutes ces expressions régulières les unes dans les autres jusqu'à arriver à des expressions qui valident chacun des types de géométries qu'on attend.

Traduction en expressions régulières

Pour éviter de couvrir cet article de grammaire BNF pas très digeste, je vais découper la grammaire originale et la simplifier en avance pour pouvoir plus facilement expliquer la construction progressive de chaque expression régulière.

Nombre exact

WKT permet de gérer deux types de nombres. Les nombres que nous connaissons et utilisons tout le temps sont les nombres exacts, ceux qui sont tout simplement... des nombres, potentiellement à virgules.

<digit> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
<unsigned integer> ::= (<digit>)*
<sign> ::= + | -
<signed integer> ::= {<sign>}<unsigned integer>
<decimal point> ::= <period> | <comma>
<exact numeric literal> ::=
   <unsigned integer>{<decimal point>{<unsigned integer>}}
   | <decimal point><unsigned integer>

En BNF, | signifie « ou ». On définit donc d'abord un chiffre comme étant un des dix chiffres arabes. Les chiffres en cunéiforme ou d'autres alphabets étranges supportés par Unicode ne sont pas pertinent. Dans une expression régulière on peut donc le représenter par [0-9], ou le raccourci \d.

Ensuite, un nombre entier sans signe (unsigned integer) est défini comme étant un ou plusieurs chiffres. Les parenthèses suivies de l'étoile signifient « un ou plusieurs ». On a donc maintenant \d+.

Pour le nombre entier avec signe, on rajoute un plus ou un moins ([+-]). Cela dit, on utilise ici des accolades autour du signe, ce qui signifie « optionnel ». Donc, un nombre entier avec un signe a le droit de ne pas avoir de signe ; le positif sera choisi par défaut. On a donc [+-]?\d+.

Enfin, le nombre exact est défini comme étant deux possibilités :

Autrement dit, on peut représenter le nombre 1 en tant que 1, 1,, 1., 1,0, ou 1.0. On peut représenter le nombre 0.5 en tant que 0.5, 0,5, .5 ou ,5.

On peut représenter la première possibilité avec \d+(?:[.,]\d*)?. On a toujours obligatoirement un nombre sans signe. Ensuite, le séparateur des décimales (la virgule ou le point) peut être suivi de 0 ou plusieurs nombres, ce qui veut dire qu'on permet aussi le cas où le séparateur est présent seul. On englobe le séparateur et les décimales dans un groupe non-capturant optionnel (?: ... )? pour rendre obligatoire la virgule si on veut inclure des décimales. Cela éviterait que 1234 soit interprété comme étant 12.34 ou 1.234 par exemple. C'est idiot, mais rien n'empêche vraiment qu'un bug se produire ou qu'un moteur d'expression régulière particulier ne traite pas un \d+\d* correctement.

Pour la seconde possibilité, c'est un peu plus simple : [.,]\d+. On peut assembler ces deux possibilités avec une alternative | dans un groupe non-capturant : (?:\d+(?:[.,]\d*)?|[.,]\d+).

Nombre approximatif

La représentation comme nombre approximatif, aussi appellée notation scientifique, est généralement utilisé pour représenter des nombres très petits ou très grands, mais elle est en réalité utilisée en permanence par l'ordinateur. D'autres parties de la spécification de géométries indiquent que les nombres sont censés être stockés en tant que nombres à virgule flottante à 64 bits selon la norme IEEE 754. Cette norme fait qu'un ordinateur utilisera deux nombres pour en représenter un à l'aide de puissance de deux. Cela permet de potentiellement choisir de perdre en précision sur un nombre mais de pouvoir quand même représenter des nombres très grands ou très petits.

Dans les représentations textuelles, on utilise plutôt des puissances de 10. Vous avez peut-être déjà rencontré des nombres comme 6.02214076×1023 : c'est un exemple de notation scientifique. On multiplie 6.02214076 par un 1 suivi de 23 zéros. En mathématiques, les notations sont cependant très inconsistantes et n'ont aucun standard réel, donc la notation plus souvent vue dans les calculatrices et les ordinateurs utilise E comme raccourci : on écrirait alors le même nombre comme 6.02214076E23.

<approximate numeric literal> ::= <mantissa>E<exponent>
<mantissa> ::= <exact numeric literal>
<exponent> ::= <signed integer>

Un nombre approximatif est donc défini comme un nombre décimal sans signe, suivi d'un E, suivi d'un nombre entier signé. On peut reprendre nos expressions précédentes et les combiner :(?:\d+(?:[.,]\d*)?|[.,]\d+)E[+-]?\d+.

On se heurte déjà à une des limitations de XSD. J'avais mentionné dans le précédent article l'impossibilité de définir des expressions réutilisables comme en Perl, donc les expressions vont devenir de plus en plus longues. Mais en plus de ça, il me manque une propriété de la spécification : rien n'est sensible à la casse. Autrement dit, 1E9 et 1e9 sont équivalents.

Dans la plupart des langages, je pourrais ajouter à l'expression régulière un indicateur signalant que l'expression n'est pas sensible à la casse. Par exemple, en JavaScript, /E/i pourrait aussi trouver e. En Python, on utiliserait re.IGNORE_CASE. Mais avec xs:pattern, on ne peut pas spécifier ce genre de paramètres, donc on se retrouve à devoir écrire dans l'expression régulière les deux possibilités : [Ee] au lieu de seulement E. Juste pour un caractère, ce n'est pas bien grave, mais lorsqu'il s'agit de valider un mot entier par exemple, ça peut vite devenir amusant. On verra ça assez vite.

L'expression finale pour un nombre approximatif est donc (?:\d+(?:[.,]\d*)?|[.,]\d+)[Ee][+-]?\d+.

Nombre

<unsigned numeric literal> ::= <exact numeric literal> | <approximate numeric literal>
<signed numeric literal> ::= {<sign>}<unsigned numeric literal>

On va maintenant pouvoir procéder à l'assemblage de nos deux nombres. Si vous avez vraiment fait très attention, vous avez peut-être remarqués que bien que nous ayions défini des nombres signés, on n'a pas ajouté de signes à nos deux représentations de nombres. Cela vient du fait que le signe est combiné ici ! Puisque la version signée des deux représentations ajoute à chaque fois un signe optionnel, on l'ajoute juste au nombre lui-même.

On peut donc combiner les deux expressions des deux nombres en une seule, et ajouter un signe pour obtenir un nombre signé :

[+-]?(?:
   (?:\d+(?:[.,]\d*)?|[.,]\d+)
  |(?:\d+(?:[.,]\d*)?|[.,]\d+)[Ee][+-]?\d+
)

En temps normal, des sauts de ligne et des indentations comme ça dans une expression peuvent selon le moteur d'expressions régulières utilisé être interprétés comme de véritables sauts de ligne à rechercher, donc on évite d'en mettre. Mais je veux montrer ici un phénomène intéressant : les deux représentations ici peuvent être factorisées en rendant simplement l'exposant optionnel. Une expression plus simple pour un nombre signé sera donc :

[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?

En voyant ça, on peut commencer à comprendre pourquoi la spécification avait tant besoin d'aller jusqu'à définir ce qu'est un nombre. Ce n'est pas si trivial.

Point

Le point est la géométrie la plus simple. Il a cependant une définition bien découpée, car il est aussi la brique de base de toutes les autres géométries.

<left paren> ::= (
<right paren> ::= )
<x> ::= <signed numeric literal>
<y> ::= <signed numeric literal>
<point> ::= <x> <y>
<empty set> ::= EMPTY
<point text> ::= <empty set> | <left paren> <point> <right paren>
<point tagged text> ::= point <point text>

On définit ici trois types importants :

Une notion qu'on n'avait pas vu auparavant est que toutes les géométries peuvent être vides : c'est-à-dire que bien qu'elles n'ont un type, elles n'existent pas. C'est un équivalent du null dans les langages de programmation. C'est là qu'intervient le EMPTY.

On va avoir besoin de ces subdivisions du point par la suite. On va commencer maintenant à essayer de réutiliser des expressions, c'est-à-dire que je vais me mettre à placer des (?P>nombre) dans les expressions. Cette syntaxe n'existe pas dans les XSD, mais existe avec PCRE. Cela nous permettra d'éviter de nous répéter, et à la fin de l'article, je recompilerai toutes ces expressions ensemble pour former des spaghettis monstrueux.

Point
(?P>nombre)\s+(?P>nombre)

Notez que je définis ici des espaces entre les nombres, sinon ça n'aurait pas de sens, mais la spécification ne précise pas directement que ces espaces existent.

Texte de point
(?:EMPTY|\(\s*(?P>point)\s*\))

On autorise aussi des espaces entre les parenthèses. J'utilise là aussi mon expérience avec les systèmes d'information géographiques et plus la spécification ; tous les types d'espaces (y compris des sauts de ligne) sont autorisés un peu partout.

Point (géométrie)
[Pp][Oo][Ii][Nn][Tt]\s*(?:\s+EMPTY|\(\s*(?P>point)\s*\))

C'est la même expression, mais avec en plus le préfixe POINT. Notez que je ne réutilise pas l'expression précédente, car il faut pouvoir permettre POINT(4 2) et POINT (4 2), mais pas POINTEMPTY ! Je rends donc les espaces optionnels par défaut, et j'en demande au moins un si on est dans l'alternative du EMPTY.

LineString

<linestring text> ::= <empty set> | <left paren> <point> {<comma> <point>}* <right paren>
<linestring tagged text> ::= linestring <linestring text>

La définition d'une ligne brisée se base directement sur les points. On peut avoir une ligne vide (LINESTRING EMPTY) ou indiquer une liste de coordonnées X et Y (LINESTRING (1 2, 3 4)). On peut représenter la série de un à plusieurs points par un premier point toujours obligatoire, et une infinité de points optionnels, dans une logique assez proche de celle de cette grammaire : (?P>point)(?:\s*,\s*(?P>point))*. Il n'y a plus qu'à tout emboîter :

Texte de ligne brisée
(?:EMPTY|\(\s*(?P>point)(?:\s*,\s*(?P>point))*\s*\))
Ligne brisée
[Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg]\s*(?:\s+EMPTY|\(\s*(?P>point)(?:\s*,\s*(?P>point))*\s*\))

Polygon

<polygon text> ::= <empty set> | <left paren> <linestring text> {<comma> <linestring text>}* <right paren>
<polygon tagged text> ::= polygon <polygon text>

J'ai expliqué plus tôt qu'un polygone dans les GIS est composé d'un anneau linéaire extérieur et de 0 ou plusieurs anneaux intérieurs. Puisqu'un anneau linéaire est seulement un type particulier de ligne brisée, on peut utiliser directement les lignes brisées dans la définition. On va utiliser le même format que tout à l'heure avec

Texte de polygone
(?:EMPTY|\(\s*(?P>linestring_text)(?:\s*,\s*(?P>linestring_text))*\s*\))

On notera que cela implique qu'il est autorisé d'avoir des anneaux vides dans un polygone : POLYGON (EMPTY, EMPTY, EMPTY). On ne pouvait pas faire ça avec des points dans des lignes brisées.

Polygone
[Pp][Oo][Ll][Yy][Gg][Oo][Nn]\s*(?:\s+EMPTY|\(\s*(?P>linestring_text)(?:\s*,\s*(?P>linestring_text))*\s*\))

MultiPoint

On s'attaque maintenant aux types de géométries moins courants. On a ici un ensemble arbitraire de points. Ils ne sont pas reliés entre eux comme ils le seraient dans une ligne brisée ; c'est juste un petit paquet de points sans défenses.

<multipoint text> ::= <empty set> | <left paren> <point text> {<comma> <point text>}* <right paren>
<multipoint tagged text> ::= multipoint <multipoint text>

Toujours le même format : des points séparés par des virgules. Mais dans les lignes brisées, on utilisait directement <point>, qui est défini seulement comme les deux coordonnées. Ici, on utilise <point text>, qui oblige à avoir des parenthèses et autorise les points vides : on peut donc avoir un MULTIPOINT ((1 2), EMPTY, (3 4)).

Texte d'ensemble de points
(?:EMPTY|\(\s*(?P>point_text)(?:\s*,\s*(?P>point_text))*\s*\))
Ensemble de points
[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ii][Nn][Tt]\s*(?:\s+EMPTY|\(\s*(?P>point_text)(?:\s*,\s*(?P>point_text))*\s*\))

MultiLineString

<multilinestring text> ::= <empty set> | <left paren> <linestring text> {<comma> <linestring text>}* <right paren>
<multilinestring tagged text> ::= multilinestring <multilinestring text>
Texte d'ensemble de lignes brisées
(?:EMPTY|\(\s*(?P>linestring_text)(?:\s*,\s*(?P>linestring_text))*\s*\))
Ensemble de lignes brisées
[Mm][Uu][Ll][Tt][Ii][Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg]\s*(?:\s+EMPTY|\(\s*(?P>linestring_text)(?:\s*,\s*(?P>linestring_text))*\s*\))

En écrivant ces expressions-là, je me suis rendu compte de quelque chose : elles ressemblent beaucoup aux polygones. Quand j'avais commencé à faire l'ensemble de points, j'avais déjà regardé si la ligne brisée y correspondait, mais ce n'était pas le cas. Ici, par contre, l'ensemble de lignes brisées a exactement la même syntaxe que pour les polygones.

On peut donc fusionner notre expression précédente pour les polygones avec celle pour l'ensemble de lignes brisées, afin de nous retrouver au bout du compte avec 5 expressions et pas 6. On va juste ajouter une alternative entre POLYGON et MULTILINESTRING.

(?:[Pp][Oo][Ll][Yy][Gg][Oo][Nn]|[Mm][Uu][Ll][Tt][Ii][Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg])\s*(?:\s+EMPTY|\(\s*(?P>linestring_text)(?:\s*,\s*(?P>linestring_text))*\s*\))

MultiPolygon

<multipolygon text> ::= <empty set> | <left paren> <polygon text> {<comma> <polygon text>}* <right paren>
<multipolygon tagged text> ::= multipolygon <multipolygon text>

Le tout dernier type : l'ensemble de polygones. Rien de très original, on peut encore avoir une série de polygones qui peuvent être vides.

Texte d'ensemble de polygones
(?:EMPTY|\(\s*(?P>polygon_text)(?:\s*,\s*(?P>polygon_text))*\s*\))
Ensemble de polygones
[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ll][Yy][Gg][Oo][Nn]\s*(?:\s+EMPTY|\(\s*(?P>polygon_text)(?:\s*,\s*(?P>polygon_text))*\s*\))

Compilation

En définissant le point, j'ai précisé un point : les expressions régulières dans les XSD n'ont point de capacité à réutiliser des groupes déjà définis comme je l'ai fait avec (?P>point). Par conséquent, pour pouvoir terminer le travail, on va devoir réassembler petit à petit nos cinq expressions régulières finales en remplaçant chaque nom de sous-expression par l'expression. On pourra ensuite tout mettre dans des <xs:pattern>.

Je vais faire une démonstration en faisant l'expansion de la géométrie de type point et je donnerai ensuite les 4 autres directement.

  1. On commence par l'expression qu'on a trouvé tout à l'heure :

    [Pp][Oo][Ii][Nn][Tt]\s*(?:\s+EMPTY|\(\s*(?P>point)\s*\))
    
  2. On a défini le point comme étant (?P>nombre)\s+(?P>nombre), donc on remplace :

    [Pp][Oo][Ii][Nn][Tt]\s*(?:\s+EMPTY|\(\s*(?P>nombre)\s+(?P>nombre)\s*\))
    
  3. On a défini le nombre comme étant [+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)? donc on remplace deux fois :

    [Pp][Oo][Ii][Nn][Tt]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\))
    

Et voilà le résultat pour les autres expressions :

Ligne brisée
[Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))
Polygone et ensemble de lignes brisées
(?:[Pp][Oo][Ll][Yy][Gg][Oo][Nn]|[Mm][Uu][Ll][Tt][Ii][Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg])\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\))
Ensemble de points
[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ii][Nn][Tt]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\)))*\s*\))
Ensemble de polygones
[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ll][Yy][Gg][Oo][Nn]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\)))*\s*\))

On peut donc maintenant placer tout ça dans un joli type dans notre XSD et avoir la pire validation de tous les temps pour l'attribut de géométrie :

<xs:simpleType name="Geometry">
  <xs:restriction base="xs:string">
    <xs:pattern value="[Pp][Oo][Ii][Nn][Tt]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\))" ></xs:pattern>
    <xs:pattern value="[Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))" ></xs:pattern>
    <xs:pattern value="(?:[Pp][Oo][Ll][Yy][Gg][Oo][Nn]|[Mm][Uu][Ll][Tt][Ii][Ll][Ii][Nn][Ee][Ss][Tt][Rr][Ii][Nn][Gg])\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\))" ></xs:pattern>
    <xs:pattern value="[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ii][Nn][Tt]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s*\)))*\s*\))" ></xs:pattern>
    <xs:pattern value="[Mm][Uu][Ll][Tt][Ii][Pp][Oo][Ll][Yy][Gg][Oo][Nn]\s*(?:\s+[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\))(?:\s*,\s*(?:[Ee][Mm][Pp][Tt][Yy]|\(\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?(?:\s*,\s*[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?\s+[+-]?(?:\d+(?:[.,]\d*)?|[.,]\d+)(?:[Ee][+-]?\d+)?)*\s*\)))*\s*\)))*\s*\))" ></xs:pattern>
  </xs:restriction>
</xs:simpleType>

<xs:attribute name="geometry" type="Geometry" use="optional" ></xs:attribute>

Conclusion

Je ne comprends pas pourquoi on m'autorise encore à utiliser un ordinateur, encore moins à avoir un blog.

Durant l'expansion de toutes ces expressions pour arriver aux expressions finales, je me suis toujours dit qu'il y avait un moyen d'automatiser ça. J'ai d'autres plans pour rallonger encore plus ces expressions, donc on verra prochainement comment on peut essayer d'automatiser un peu les choses pour pouvoir un petit peu plus facilement générer des expressions régulières terriblement complexes. Bien entendu, on utilisera un système étrange pour faire ça, pourquoi faire les choses bien ?


Commentaires

Il n'y a pour l'instant aucun commentaire. Soyez le premier !