/* global module */ /* jslint node: true */ /* jslint indent: 2 */ 'use strict'; /* Module Require */ var fs = require('fs'), path = require('path'), teeft = require('rd-teeft'), request = require('request'), mkdirp = require('mkdirp'), async = require('async'); /* Libs */ var jLouvain = require('./lib/jLouvain.js'); /** * Objet regroupant les fonctionnalités liées aux tdm */ var utils = {}; /** * Classe Représentant un corpus */ class Corpus { /* * Constructeur * @param {Object} options Objet contenant les différents paramètres possible de l'objet, à savoir : * - {String} name : Nom du corpus * - {String} file : Nom du fichier contenant les Ids ISTEX * - {String} in : Répertoire d'entrée (contenant les fichiers téléchargés à partir du fichier .txt) * - {String} out : Répertoire de sortie prévu * @return {Corpus} Return un objet de type Corpus */ constructor(options) { if (!options) return; this.name = (typeof options.name === 'string') ? options.name : ""; this.file = (typeof options.file === 'string') ? options.file : ""; this.in = (typeof options.in === 'string') ? options.in : ""; this.out = (typeof options.out === 'string') ? options.out : ""; } } /** * Objet permettant de traiter un ensemble de Corpus */ utils.corpusManager = {}; /** * Initialise les chemins d'un corpus de TDM dans EzMaster * @param {String} file Chemin vers le fichier de corpus (un fichiers .corpus contenant sa description) * @param {String} outputDir Chemin vers le répertoire qui contiendra l'architecture créée * @param {function} cb Callback appelée à la fin du traitement, avec comme paramètre disponible : * - {Error} err Erreurs de Lecture/Écriture * - {Corpus} res Informations sur le corpus * @return {undefined} Return undefined * Exemple d'architecture crée pour un corpus x : * ./ * x.corpus * in/ * x/ * out/ * x/ */ utils.corpusManager.init = function(file, outputDir, cb) { var filename = path.basename(file); // [PROVISOIRE] Nom du répertoire = Nom du fichier return fs.stat(file, function(err, stats) { if (err) return cb(err); // Erreur lors du stats, on la remonte if (!stats.isFile()) return cb(); // Ce n'est pas un fichier, on l'ignore return fs.readFile(file, 'utf-8', function(err, res) { if (err) return cb(err); // Erreur lors du readFile, on la remonte var outputFile = path.join(outputDir, filename); // Erreur lors du readFile, on la remonte return fs.writeFile(outputFile, res, 'utf-8', function(err) { if (err) return cb(err); // Erreur lors du writeFile, on la remonte var corpus = new Corpus({ 'name': filename, 'file': outputFile }); return async.each(['in', 'out'], function(subDirectory, next) { // Création d'un répertoire dans in et out var dir = path.join(outputDir, subDirectory, filename); // Chemin complet du répertoire mkdirp(dir, function(err) { if (err) return next(err); // Erreur lors du mkdirp, on la remonte corpus[subDirectory] = dir; return next(); }); }, function(err) { if (err) return cb(err); // Renvois de toutes les erreurs remontée lors de la création des répertoires return cb(null, corpus); // // Corpus correctement traité }); }); }); }); } /** * Indexe tous les fichiers d'un corpus * @param {String} inputDir Répertoire d'entré contenant des fichiers .txt * @param {String} outputFile Fichier de sortie * @param {function} cb Callback appelée à la fin du traitement, avec comme paramètre disponible : * - {Error} err Erreurs lors du traitement * - {Array} res Liste des Indexations * @return {undefined} Return undefined */ utils.corpusManager.indexAll = function(inputDir, outputFile, cb) { var result = []; // List of indexations fs.readdir(inputDir, function(err, filenames) { if (err) return cb(err); // I/O Errors async.each(filenames, function(filename, callback) { var filePath = path.join(inputDir, filename); fs.readFile(filePath, 'utf-8', function(err, res) { if (err) return callback(err); // I/O Errors var docId = path.basename(filename, ('.txt')); result.push({ 'id': docId, 'keywords': teeft.index(res).keywords }); callback(); }); }, function(err) { if (err) return cb(err); // I/O Errors // write data fs.writeFile(outputFile, JSON.stringify(result), 'utf-8', function(err, res) { if (err) return cb(err); return cb(null, result); }); }); }); }; utils.graphs = {}; /** * Génère un graph de Documents (Visualisation des lien entre chaque documents) * @param {Array} indexations Liste d'indexation * @param {Object} options Données optionnelles * @param {function} cb Callback appelée à la fin du traitement, avec comme paramètre disponible : * - {Error} err Erreurs lors du traitement * - {Object} res Données affichables avec d3.js * @return {undefined} Return undefined */ utils.graphs.docToDoc = function(indexations, options, cb) { if (!options) options = {}; var terms = {}, // Each key is a term, his value is the list of documents containing it docIds = [], // List of document Ids result = { 'nodes': [], 'links': [] }, edges = [], // [{'source': '', 'target': '', 'weight': 0}, ...] nodes = [], // ['id', ...] matrix = {}, // Matrix of "doc-doc" links (sparse matrix) output = options.output || './cache/docToDoc.json', minLinkValue = options.minLinkValue || 0; // Construction of terms Object for (var i = 0; i < indexations.length; i++) { var id = indexations[i].id, keywords = indexations[i].keywords; for (var j = 0; j < keywords.length; j++) { var term = keywords[j].term; if (!terms[term]) terms[term] = []; terms[term].push(i); } } // Construction of matrix Object for (var key in terms) { // Fill it with values for (var i = 0; i < terms[key].length - 1; i++) { var idDoc1 = terms[key][i]; for (var j = i + 1; j < terms[key].length; j++) { var idDoc2 = terms[key][j], ids = [idDoc1, idDoc2], id = { 'min': Math.min(ids[0], ids[1]), 'max': Math.max(ids[0], ids[1]) }; // Only half of it will be fill! if (!matrix[id.min + ',' + id.max]) { matrix[id.min + ',' + id.max] = 0; } matrix[id.min + ',' + id.max]++; } } } // Construction of matrix of links doc-doc for (var key in matrix) { var ids = key.split(','); if (matrix[key] >= minLinkValue) { edges.push({ 'source': ids[0], 'target': ids[1], 'weight': matrix[key] }); result.links.push({ 'source': ids[0], 'target': ids[1], 'value': matrix[key] }); } } // Construction of Nodes object for (var i = 0; i < indexations.length; i++) { var id = indexations[i].id, keywords = indexations[i].keywords; var max = (keywords.length < 5) ? keywords.length : 5; nodes.push(i); result.nodes.push({ 'id': i, 'istex': id, 'value': keywords.slice(0, max).map(function(elem, i) { return elem.term }).join('; '), 'group': 0 }); } // Create the "community" var community = jLouvain().nodes(nodes).edges(edges), res = community(); // Affect community for each node for (var key in res) { result.nodes[key].group = res[key]; } // write data fs.writeFile(output, JSON.stringify(result), 'utf-8', function(err, res) { if (err) return cb(err); return cb(null, result); }); }; module.exports = utils;