CXML

CXML

GitHub repo

release contributors status

INTRODUCTION

RAPPEL DU SUJET

Pour recontextualiser, le lancement du projet a eu lieu à l'issue de la piscine de C.
Pour ce projet, nous devons réaliser une application en C permettant de valider des fichiers XML à l'aide d'une DTD.

Nous devons construire un ensemble de fonction permettant dans un premier temps de tester si les éléments d'un fichier xml sont conformes à la DTD, ainsi que de vérifier si le fichier XML est correct.

Nous devons ensuite prendre en compte la validation des attributs afin qu'ils soient conformes à la DTD. Il faut également vérifier si le fichier XML est correct, sans limites de profondeur.

Enfin, il faut réaliser un éditeur graphique de DTD afin de valider directement un fichier XML depuis une interface développée avec GTK. Cette interface devra également être capable de proposer des suggestions lors de l'écriture du fichier XML.

FOCUS SUR L'APPLICATION

FONCTIONNALITÉS

Parser un fichier XML

Avant d'être une application permettant de valider un fichier XML, elle doit le comprendre,

l'analyser. C'est pour cela qu'une des principales fonctionnalités de l'application consiste à parser un fichier XML et le stocker à l'aide de structures de données. Cela nous apporte plusieurs avantages : Avoir un document structuré dans notre code afin de mieux naviguer dedans La limite de profondeur n'est plus un problème

Nous pouvons au moment où nous parsons un document détecter les erreurs de syntaxes

Parser un fichier DTD

De la même manière que nous parsons un fichier XML, nous parsons un fichier DTD. En effet, le parsing d'un fichier DTD est plus ou moins semblable au parsing d'un fichier XML, le principe reste le même.

Au moyen de structure de données conçues spécialement pour un fichier DTD dont nous détaillerons l'utilisation plus tard dans le document, nous stockons toutes les données issues du fichier.

C'est pourquoi certains avantages constatés pour le parsing d'un fichier XML sont également applicables dans le cas d'un fichier DTD.

Vérification syntaxique du fichier XML

Lorsque nous parsons notre fichier XML pour le stocker, il faut que le fichier XML soit correct pour ne pas créer d'erreur.

Nous avons donc dû implémenter une série de règles afin de vérifier que notre document est correct.

Voici une liste non exhaustive de certaines de ces règles :

Si une seule de ces règles n'est pas respecté, cela coupe le processus de parsing, indique dans l'application ainsi que dans un fichier log.txt la ligne, la colonne ainsi que la nature de l'erreur.

Un code erreur est alors renvoyé.

Vérification syntaxique du fichier DTD

Bien que ce ne soit pas indiqué explicitement dans le sujet que nous devons vérifier la syntaxe du fichier DTD, nous avons choisi de le faire.

Ainsi notre application avant de vérifier si le fichier XML est correct comme demandé dans l'énoncé du projet, vérifie d'abord la conformité du fichier DTD.

Une analyse syntaxique est réalisée afin de desceller de potentielles erreurs.

Par conséquent, notre application gère à la fois les erreurs au niveau du fichier XML, mais également les potentielles erreurs du fichier DTD.

Voici une liste non exhaustive de certaines de ces règles :

Validation de la conformité d'un fichier XML depuis une DTD externe

C'est donc à partir d'une DTD externe que nous vérifions si le fichier XML est correct ou non.

Nous allons donc charger dans des structures de données notre document XML et DTD.

À partir de cela, nous allons itérer sur les règles DTD pour valider notre document XML, à savoir :

CHOIX D'IMPLEMENTATIONS

DTD externe

Pour une meilleure répartition des tâches et afin de prendre une décision dès le début du projet, il a été décidé que notre application serait conçue pour valider un document XML depuis une DTD externe.

Cela signifie que la DTD doit être dans un fichier à part (avec l'extension dtd) et non au début du fichier XML.

Parser le fichier XML et DTD

Nous ne savons pas s'il y a d'autres approches possibles pour valider un document XML, mais nous avons fait le choix de parser nos documents afin de pouvoir vérifier leurs conformités, mais aussi, car cela nous semblait plus simple aussi pour itérer sur chaque règle DTD et naviguer dans le document XML, peu importe la profondeur.

Cross plateforme

Parce qu'un programme qui fonctionne sur un système d'exploitation, c'est bien, un programme qui fonctionne sur plusieurs systèmes d'exploitation, c'est mieux !

Nous avons souhaité faire en sorte que tout notre programme soit compilable aussi bien sur le système Windows que Linux.

Cela impliquait d'être vigilant par rapport aux dépendances, qui peuvent varier d'un système à l'autre, surtout GTK qui fait bien transpirer sur Windows...

Projet CMAKE

CMake (Cross platform Make) est un outil open source permettant de gérer la compilation d'un projet C/C++ sur différente plateformes.

C'est beaucoup plus simple que de devoir compiler à la main avec GCC, et permet de créer des scripts avancés, qui vont beaucoup aider pour lier des librairies à notre projet et aussi faire une application cross plateforme.

L'outil est bien pris en charge dans l'IDE Clion, éditeur que nous utilisons pour développer notre application.

STRUCTURES DE DONNEES

Pour que notre application fonctionne correctement, notamment au niveau du parsing, il a fallu créer différentes structures de données afin d'ordonner notre code.

DTD Parser

typedef struct dtd_document_s dtd_document;

struct dtd_document_s {
  char *source;
  char *root_node;
  element_node *first_element_node;
  attribute_node *first_attribute_node;
};

Cette structure est notre point d'entrée vers notre document DTD.

Les champs suivants représentent :

typedef struct element_node_s element_node;

struct element_node_s {
  char *tag_name;
  char *rule_type;
  dtd_rule *rule;
  struct element_node_s *next;
};

Cette structure fonctionne comme une liste chainée contenant toutes les règles sur les éléments d'un fichier DTD.

Les champs suivants représentent :

typedef struct dtd_rule_s dtd_rule; 

struct dtd_rule_s {
  char *rule_name;
  char rule_spec;
  char rule_sep;
  struct dtd_rule_s *next; 
};

Dans le cas d'une règle pour les éléments, il peut y avoir différents éléments dans la parenthèse, séparé par différents caractères.

Les champs suivants représentent :

typedef struct attribute_node_s attribute_node; 

struct attribute_node_s {
  char *rule_type;
  char *element_name;
  char *attribute_name;
  char *attribute_type;
  char *attribute_option;
  struct attribute_node_s *next;
};

Cette structure fonctionne comme une liste chainée contenant toutes les règles sur les attributs d'un fichier DTD.

Les champs suivants représentent :

XML Parser

typedef struct xml_document_s xml_document; 

struct xml_document_s {
  char *source;
  char *version;
  char *encoding;
  xml_node *root_node;
};

Cette structure est notre point d'entrée vers notre document XML.

Les champs suivants représentent :

typedef struct xml_node_s xml_node;
 
struct xml_node_s {
  char *tag;
  char *inner_text;
  xml_node *parent;
  xml_attribute_list attribute_list; 
  xml_node_list children;
};

Cette structure représente une balise XML ainsi que son contenu.

Les champs suivants représentent :

typedef struct xml_attribute_list_s xml_attribute_list;

struct xml_attribute_list_s {
  int capacity;
  int size;
  xml_attribute *data;
};

Contient les informations sur les attributs d'une balise.

Les champs suivants représentent :

typedef struct xml_attribute_s xml_attribute;

struct xml_attribute_s {
  char *key;
  char *value;
};

Contient les informations d'un attribut.

Les champs suivants représentent :

typedef struct xml_node_list_s xml_node_list;

struct xml_node_list_s {
  int capacity;
  int size;
  xml_node **data;
};

Contient les informations sur les enfants d'une balise.

Les champs suivants représentent :

GTK

Pour GTK, il est utile de créer une structure de données regroupant tous les widgets de notre interface. De cette manière, à l'aide d'une variable statique que nous initialisons au début du programme pour connecter chacun des widgets au pointeur associé, nous pouvons interagir avec n'importe quel widget depuis n'importe où dans notre code.

typedef struct {
  GtkWidget *window;
  GtkButton *validateButton;
  GtkButton *flushButton;
  GtkFileChooserButton *xmlFileChooserButton;
  GtkFileChooserButton *dtdFileChooserButton;
  GtkLabel *statusLabel;
  GtkTextBuffer *consoleTextBuffer;
  GtkTextView *consoleTextView;
  GtkScrolledWindow *scrollableWindow;
} App_widgets;

static App_widgets *widgets;

SCHEMA XML

Ceci est la représentation graphique de la manière dont le parseur XML traite le document et créé un ensemble de structure de données représentant notre document XML.

image3.png

SCHEMA DTD

Ceci est la représentation graphique de la manière dont le parseur DTD traite le document et créé un ensemble de structure de données représentant notre document DTD.

image4.png

FONCTIONS PRINCIPALES

Menu en ligne de commande

L'application comporte un menu_cli qui est simple. Le menu se décompose en 2 parties.

L'utilisateur doit choisir d'un côté le fichier XML et de l'autre le fichier DTD.

Ensuite, l'application vérifie si les fichiers existent dans les chemins que l'utilisateur fourni ainsi que l'extension des fichiers.

Si l'un des deux fichiers n'est pas correct, l'application affiche une erreur et demande à saisir de nouveau le chemin d'accès.

Il envoie ensuite les informations à la méthode de validation.

Parse DTD

Comme son nom l'indique la fonction parse_dtd nous permet de parser le document DTD.

C'est une fonction primordiale dans notre application, car elle nous permet d'une part de parcourir le fichier DTD mais également de vérifier qu'il ne comporte pas d'erreurs de syntaxe.

Ci-joint la définition de cette méthode :

int parse_dtd(dtd_document *document);

Comme on peut le voir cette fonction prend en paramètre une variable de type dtd_document ce qui correspond à notre structure dtd_document_s et renvoie un entier.

Pour comprendre pourquoi, ci-joint la déclaration de cette méthode :

int parse_dtd(dtd_document *document) {

  size_t size = strlen(document->source);
  
  if (is_doctype(document, size)) {
    return doctype_process(&document, size);
  } else {
    return no_doctype_process(&document, size);
  }
  
  return 0;
}

Étant donné qu'un fichier DTD peut comporter ou non la déclaration DOCTYPE!, la fonction parse_dtd gèrent les deux possibilités.

C'est la méthode is_doctype(document, size) qui le détermine en recherchant la présence de la chaine DOCTYPE! dans le fichier.

Les méthodes doctype_process et no_doctype_process sont très semblables, mais quoi qu'il en soit à l'issue du parsing du fichier DTD les méthodes renvoient un entier, 1 s'il n'y a eu aucune erreur durant la compilation et 0 le cas échéant.

Parse xml

Le nom de cette fonction est également explicite.

Tout comme la méthode « parse_dtd » cette méthode renvoie l'entier '1' si l'exécution n'a rencontré aucun problème, 0 sinon et prend en paramètre une variable de type xml_document ce qui correspond à notre structure ** xml_document** et la taille du fichier XML.

Ci-joint la définition de cette méthode :

int parse_xml_file(xml_document *document, size_t size);

Validate dtd

C'est la fonction validate_dtd qui détermine si le fichier XML est conforme au fichier DTD, elle prend en paramètres le chemin vers le fichier XML ainsi que le chemin vers le fichier DTD.

Ci-joint la définition de la méthode :

int validate_dtd(const char *xml_path, const char *dtd_path);

Toute comme les méthodes de parsing, cette méthode renvoie l'entier '1' si l'exécution n'a rencontré aucun problème, 0 sinon.

Voilà comment la méthode peut être est appelée :

if (validate_dtd("xml_files/xml_example_6.xml", "dtd_files/dtd_example_6.dtd")) {
  printf("DTD Test 1 valided\n");
}

DETAILS TECHNIQUES

Fichier log

Notre application comprend un fichier log qui nous permet d'obtenir et/ou d'afficher différentes informations relatives à l'utilisation de notre application.

Ce fichier s'avère très utile lorsqu'on souhaite comprendre l'origine d'une erreur. Dans notre cas tout cela est géré à l'aide d'un ensemble de méthodes.

Ci-joint les définitions des fonctions du fichier log.h

int setLogFileName(char *filename);

void logIt(char *message, int error);

void console_writeline(const char *text);

Voici un exemple de log, on y retrouve la date et l'heure. Lorsqu'une erreur est détectée, on remarque que la ligne et la colonne de l'erreur est renseigné.

image5.png

CMake

CMake (Cross platform Make) est un outil open source permettant de gérer la compilation d'un projet C/C++ sur différente plateformes.

C'est beaucoup plus simple que de devoir compiler à la main avec GCC, et permet de créer des scripts avancer, qui vont beaucoup aider pour lier des librairies à notre projet et aussi faire une application cross-plateforme.

L'outil est bien pris en charge dans l'IDE Clion, éditeur que nous utilisons pour développer notre application.

DOSSIER D'INSTALLATION

PARTIE 1 A 3 (MENU LIGNE DE COMMANDES)

Pour installer l'application de la partie 1 à 3, rien de plus simple !

Il suffit de télécharger avec le lien suivant, selon la version que vous souhaitez :

Choisir l'exécutable correspondant au système, cela est détaillé à chaque fois :

image6.png

Il suffira alors de lancer l'exécutable (Cf : Dossier d'utilisation).

PARTIE 4 (APPLICATION INTERFACE GRAPHIQUE GTK)**

La partie 4 contient des fichiers pour faire fonctionner correctement GTK, et s'installe donc différemment.

Linux

Avant tout chose, vous devez installer sur votre poste la librairie GTK.

Pour cela, vous devez ouvrir une invite de commande avec les droits administrateurs et installer le paquet suivant :

image7.png

image8.png

Vous pourrez télécharger ensuite l'archive de l'application depuis ce lien, en faisant attention à bien prendre la version pour Linux.

image9.png

Une fois téléchargé, décompressez l'archive :

image10.png

Rendez-vous ensuite à l'intérieur du dossier et ouvrez-y un terminal :

image11.png

Enfin, exécutez la commande suivante pour rendre le lanceur d'application exécutable :

image12.png

Vous pourrez alors faire un clic droit dessus et l'ouvrir avec un terminal :

image13.png

Windows

Pour Windows (le meilleur OS), c'est beaucoup plus simple !

Vous pourrez télécharger l'installateur de l'application depuis ce lien, en faisant attention à bien prendre la version pour Windows.

image14.png

Lancez l'installateur et suivez toutes les étapes :

image15.png image16.png

DOSSIER D'UTILISATION

PARTIE 1 A 3 (MENU LIGNE DE COMMANDES)

L'application en ligne de commande peut s'exécuter de deux manières différentes.

Exécution avec arguments

La première façon d'exécuter le programme est de le lancer en ligne de commande en mettant en argument :

Si les arguments fournis à l'entrée du programme ne sont pas valides (mauvaise extension ou fichier inexistant, pas accessible en lecture), le programme va basculer automatiquement sur le menu pour renseigner des chemins valides.

Exécution avec le menu

Il est également possible de lancer le programme en double-cliquant dessus, ou bien en ligne de commande, mais sans donner d'arguments.

Dans ce cas-là, le programme vous demandera de renseigner un chemin valide vers un fichier XML, puis un chemin valide vers un fichier DTD.

Le programme continuera de demander à l'utilisateur un chemin valide tant que celui-ci ne fournira pas un fichier :

Lancement de la validation

Une fois que les paramètres du programme sont validés, celui-ci va automatiquement lancer le processus de validation.

Si tout se passe bien, on devrait avoir ce résultat :

Dans le cas où le fichier XML n'est pas conforme à la DTD, un message d'erreur indiquant quelle règle n'est pas respecté apparait :

Si le fichier XML n'est pas valide (syntaxiquement parlant) la validation DTD n'a pas lieu de se lancer et un message indiquant la ligne, la colonne et le type d'erreur est affiché :

PARTIE 4 (APPLICATION INTERFACE GRAPHIQUE GTK)

Voilà comment se présente l'application graphique. C'est ici que nous allons importer nos différents fichiers et vérifier leur exactitude.

  1. Importer un fichier XML
  2. Importer un fichier DTD
  3. Vide la console
  4. La console
  5. Affiche le statut de l'application
  6. Bouton pour lancer la validation

Importation du fichier XML

Importation d'un fichier autre que XML (Gestion d'erreur)

Si j'ouvre un fichier avec une autre extension que .xml, une erreur s'affiche.

Importation du fichier DTD

Validation du document

Une fois tous les documents importés et validés par l'application, il ne vous reste plus qu'à presser le bouton "** Validate document**" afin de vérifier la conformité des documents.

On peut voir les traitements effectués par l'application dans la console. Une fois cela fini, il est clairement indiqué si oui ou non le fichier XML est conforme à la DTD.

Pour le champ « statuts » le code couleur est relativement simple, la couleur verte signifie que le traitement lancé s'est effectué avec succès et la couleur rouge signifie qu'il y a une erreur et donc que le fichier XML n'est pas conforme à la DTD.

BILAN DU PROJET

POINTS NON RESOLUS

Auto-complétions

Il est indiqué dans la partie 4 du projet que l'application doit proposer des suggestions des prochains éléments ou attributs lors de l'écriture de fichier XML, autrement dit une auto-complétions.

Cependant, faute de temps, c'est un point que nous n'avons malheureusement pas pu aborder.

Nous avons cependant réfléchi à une solution qui consiste à exploiter notre document DTD parsé, on pourrait alors déterminer sur quelle balise l'utilisateur est en train d'éditer et lui proposer des complétions d'attributs ou d' enfants en fonction des règles associés au même nom de balise.

PROBLEMES RENCONTRES

Temps imparti

Le délai a respecté nous a posé quelques soucis, comme dit précédemment faute de temps nous n'avons pas pu finir complètement le projet. Pour organiser des points avec tous les membres de l'équipe, nous avons dû composer avec les agendas de chacun.

Connaissances sur le sujet

Pour ce projet, nous connaissions très mal le XML et pas du tout la DTD. Il a donc fallu apprendre, comprendre toutes les subtilités et s'assurer que toutes les personnes du groupe ai compris.

Sur le langage C, nous n'étions pas tous au même niveau et comme le projet était assez conséquent, il a fallu faire de la mise à niveau au fur et à mesure que l'on attaquait des fonctionnalités avancées.

Méthodes de parsing

Aucun de nous n'a jamais vraiment fait de parsing ou même approcher la notion.

Il a donc fallu partir de 0, essayer, effacer, recommencer tout depuis le début de nombreuses fois avant de trouver un moyen viable de le faire...

Finalement il y a eu 5 versions intermédiaires très différentes pour parser un document avant d'avoir la méthode et les structures de données finales.


Revision #1
Created 10 May 2022 13:59:52 by Noé Larrieu-Lacoste
Updated 10 May 2022 14:08:19 by Noé Larrieu-Lacoste