URL: https://linuxfr.org/users/epeios/journaux/code-natif-et-node-js-parser-et-preprocesseur-xml
Title: Code natif et Node.js - parser et préprocesseur XML
Authors: Claude SIMON
Date: 2017年09月01日T16:31:51+02:00
License: CC By-SA
Tags: nodejs, epeios, xpp, xppq, xml, parser et préprocesseur
Score: 10
Histoire de diversifier mon activité, et aussi parce que j'aime bien me lancer des défis, j'ai décidé de me mettre à *[Node.js](http://fr.wikipedia.org/wiki/Node.js)*. Cependant, compte tenu de ma faible appétence pour *Javascript*, lui préférant de loin *C++*, j'ai bien entendu cherché un moyen de coder pour *Node.js* avec mon langage fétiche. Et c'est tout à fait possible, grâce aux *[addon](http://nodejs.org/api/addons.html)s*. Bon, ce n'était pas vraiment une surprise, n'ayant jusqu'à présent jamais rencontré d'environnement d'exécution logiciel qui ne puisse s'interfacer, à minima, avec du *C*.
Me voici donc en train de coder mon premier *addon* pour *Node.js*, et de découvrir en passant les joies de la programmation asynchrone. Ce faisant, je me suis, comme à mon habitude, crée quelques bibliothèques pour me faciliter le développement des prochains *addons*, et j'avais acquis suffisamment d'aisance dans le domaine pour proposer avec sérénité cette nouvelle compétence à mes clients .
Néanmoins, une chose me chagrinait. Par rapport à mon environnement de développement *C++* usuel, celui nécessité pour le développement d'un *addon* faisait un peu usine à gaz. Entre les fichiers `package.json` et `binding.gyp`, la commande `node(-pre)-gyp` pour générer les fichiers automatisant la génération de l'*addon* proprement dit, les dépendances aux différentes bibliothèques (*node*, *v8*, *libuv*...), je ne pouvais plus utiliser les outils que j'avais amoureusement développés pour me faciliter la gestion mes projets *C++*.
J'ai donc décidé de développer un *addon* universel qui aurait pour rôle d'officier comme *wrapper* pour des bibliothèques dynamiques qui contiendraient le véritable code des mes différents *addons*. Ces bibliothèques seront totalement indépendants de *Node.js*, le *wrapper*, commun à toutes ces bibliothèques, se chargeant de faire le lien entre ces bibliothèques et *Node.js*.
Pour me faire la main, j'ai développé un *addon* *Node.js* proposant un *parser* et un préprocesseur *XML*. Je l'ai placé sur [npm](http://npmjs.org/), où vous le trouverez à l'adresse [http://www.npmjs.com/package/xppq](http://www.npmjs.com/package/xppq). Comme tout paquet présent sur *npm*, vous pouvez l'installer en lançant `npm install xppq`. N'hésitez pas, tout ce petit monde est publié sous licence libre, et il y en a pour tout les goûts : *GNU/Linux* et autres systèmes *POSIX*, dont *macOS*, ainsi que *Windows*, que ce soit sur plateforme *x86* ou *ARM*. Oui, ça peut même être installé sur *Raspberry Pi* et consorts...
À noter que, excepté pour *Windows*, l'installation de cet *addon* nécessite un environnement de développement *C++*. Ben oui, c'est quand même du natif !
Une fois installé, vous pouvez le tester en lançant `npm explore xppq -- node test.js`. Vous obtiendrez le résultat du *parsage* (?) d'un fichier *XML* de démonstration après passage par le préprocesseur, à savoir :
```
Start tag: 'SomeTag'
Attribute: 'AnAttribute' = 'SomeAttributeValue'
Start tag: 'SomeOtherTag'
Attribute: 'AnotherAttribute' = 'AnotherAttributeValue'
Value: 'TagValue'
End tag: 'SomeOtherTag'
Start tag: 'YetAnotherTag'
Attribute: 'YetAnotherAttribute' = 'YetAnotherAttributeValue'
Value: 'Some macro content !'
End tag: 'YetAnotherTag'
End tag: 'SomeTag'
```
En lançant `npm explore xppq -- node test.js 0` (notez le `0` à la fin), le contenu du fichier de démonstration s'affichera, à savoir :
```xml
TagValue
Some macro content !
```
En remplaçant le `0` par un chiffre entre `1` et `4`, différents tests seront lancés, comme le *piping* du préprocesseur, ou l'application d'un *callback* sur la sortie du préprocesseur, le préprocesseur étant en fait implémenté sous forme de [stream](http://nodejs.org/api/stream.html) *Node.js*.
Voici encore un aperçu du contenu du fichier [XPPq.js](http://q37.info/s/s/xppq-node/xppq.js), qui charge le *wrapper*, lui-même chargeant la bibliothèques dynamique contenant le *parser* et le préprocesseur, avec l’encapsulation permettant d'y accéder à partir de *Javascript* :
```javascript
"use strict"
var affix = "xppq";
var njsq = null;
var componentPath = null;
var componentFilename = null;
var path = require("path");
njsq = require('njsq');
componentPath = __dirname;
componentFilename = path.join(componentPath, affix + "njs").replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/ /g, "\\ ");
njsq.register(componentFilename);
module.exports = njsq;
module.exports.returnArgument = (text) => { return njsq._wrapper( 0, text ) };
const stream = require('stream');
class Stream extends stream.Readable {
constructor(stream, options) {
super(options);
stream.on('readable', () => { var chunk = stream.read(); if (chunk == null) njsq._wrapper(5, stream); else njsq._wrapper(4, stream, chunk); });
njsq._wrapper( 7, stream, this );
}
_read(size) {
njsq._wrapper( 6, this );
}
}
// If modified, modify also 'parser.cpp'.
var tokens = {
ERROR: 0,
START_TAG: 1,
ATTRIBUTE: 2,
VALUE: 3,
END_TAG: 4
};
module.exports = njsq;
module.exports.Stream = Stream;
module.exports.parse = (stream, callback) => { stream.on('readable', () => { var chunk = stream.read(); if ( chunk == null ) njsq._wrapper( 2, stream ); else njsq._wrapper(1, stream, chunk); } ); njsq._wrapper(3, stream, callback) };
module.exports.tokens = tokens;
```
Et encore le fichier [test.js](https://q37.info/s/s/xppq-node/test.js), qui montre des exemples de mise en œuvre du *parser* et du préprocesseur :
```javascript
"use strict"
const fs = require('fs');
const stream = require('stream');
const xppq = require('./XPPq.js');
var indentLevel = 0;
function write(text) {
process.stdout.write(text);
}
function indent(level) {
while (level--)
write(' ');
}
function callback(token, tag, attribute, value) {
switch (token) {
case xppq.tokens.ERROR:
write(">>> ERROR: '" + value + "'\n");
break;
case xppq.tokens.START_TAG:
indent(indentLevel);
write("Start tag: '" + tag + "'\n");
indentLevel++;
break;
case xppq.tokens.ATTRIBUTE:
indent(indentLevel);
write("Attribute: '" + attribute + "' = '" + value + "'\n");
break;
case xppq.tokens.VALUE:
indent(indentLevel);
write("Value: '" + value.trim() + "'\n");
break;
case xppq.tokens.END_TAG:
indentLevel--;
indent(indentLevel);
write("End tag: '" + tag + "'\n");
break;
default:
throw ("Unknown token !!!");
break;
}
}
const file = __dirname + '/demo.xml';
var test = 4; // Default test id.
var arg = process.argv[2];
if (arg != undefined)
test = Number(arg);
console.log( xppq.componentInfo() );
console.log( xppq.wrapperInfo());
console.log( xppq.returnArgument('Basic test : this text comes from the addon (native code), and is written from Javascript.' ) );
console.log( ' ---------------' );
switch (test) {
case 0:
console.log("No treatment ; to see the original file.\n");
fs.createReadStream(file).pipe(process.stdout);
break;
case 1:
console.log("Piping the preprocessing stream.\n");
new xppq.Stream(fs.createReadStream(file)).on('error', (err) => console.error('\n>>> ERROR : ' + err + '\n')).pipe(process.stdout);
break;
case 2:
console.log("Using the preprocessing stream with a callback, which transforms to lower case.\n");
new xppq.Stream(fs.createReadStream(file)).on('data', (chunk) => write(chunk.toString().toLowerCase())).on('error', (err) => console.error('\n>>> ERROR : ' + err + '\n'));
break;
case 3:
console.log("XML parsing WITHOUT preprocessing.\n");
xppq.parse(fs.createReadStream(file), callback);
break;
case 4:
console.log("XML parsing WITH preprocessing.\n");
xppq.parse(new xppq.Stream(fs.createReadStream(file)).on('error', (err) => console.error('>>> ERROR : ' + err)), callback);
break;
default:
console.error("'" + arg + "' is not a valid test id ; must be '0' to '4'.");
break;
}
```
Compte tenu de la nature *Libre* de *Node.js*, ainsi que du paquet *npm* présenté ici, celui-ci intéressera peut-être quelques développeurs *Node.js* de passage. Par ailleurs, je suis preneur de tous commentaires concernant ce paquet, notamment touchant à sa conformité aux règles de l'art (à part le fait qu'il soit codé en *C++* au lieu de *Javascript*, évidemment).
Pour terminer, le traditionnel badge *npm* du paquet, donnant accès à toutes les informations le concernant :
[](https://nodei.co/npm/xppq/)