/**
* This file is part of SesaJsTools.
* Copyright 2014-2015, Association Sésamath
*
* SesaJsTools is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License version 3
* as published by the Free Software Foundation.
*
* SesaJsTools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with SesaJsTools (LICENCE.txt).
* @see http://www.gnu.org/licenses/agpl.txt
*
*
* Ce fichier fait partie de l'application Sésathèque, créée par l'association Sésamath.
*
* Sésathèque est un logiciel libre ; vous pouvez le redistribuer ou le modifier suivant
* les termes de la GNU Affero General Public License version 3 telle que publiée par la
* Free Software Foundation.
* Sésathèque est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE GARANTIE,
* sans même la garantie tacite de QUALITÉ MARCHANDE ou d'ADÉQUATION à UN BUT PARTICULIER.
* Consultez la GNU Affero General Public License pour plus de détails.
* Vous devez avoir reçu une copie de la GNU General Public License en même temps que Sésathèque
* (cf LICENCE.txt et http://vvlibri.org/fr/Analyse/gnu-affero-general-public-license-v3-analyse
* pour une explication en français)
*/
'use strict'
var tools = require('../index')
var hasProp = tools.hasProp
/**
* Retourne le tableau passé en argument ou un tableau vide si l'argument n'était pas un Array
* @param {Array} arg L'array à controler
* @returns {Array}
*/
function filterArray (arg) {
return (Array.isArray(arg)) ? arg : []
}
/**
* Retourne le tableau passé en argument ou un tableau vide si l'argument n'était pas un Array
* Tous les éléments qui ne sont pas des entiers positifs (0 accepté) seront éliminés
* @param {Array} arg L'array à controler
* @param {boolean} [strict=true]
* @returns {Array}
*/
function filterArrayInt (arg, strict) {
if (typeof strict !== 'boolean') strict = true
arg = filterArray(arg)
if (strict) {
return arg.filter(function (elt) {
return typeof elt === 'number' && Math.round(elt) === elt && elt > -1
})
} else {
var retour = []
arg.forEach(function (elt) {
switch (typeof elt) {
case 'string':
elt = Number(elt)
// pas de break exprès pour passer aussi dans le suivant
case 'number': // eslint-disable-line no-fallthrough
var i = Math.round(elt)
if (i > -1) retour.push(i)
// pas besoin de default, pour le reste on fait rien
}
})
return retour
}
}
/**
* Retourne le tableau passé en argument ou un tableau vide si l'argument n'était pas un Array
* Tous les éléments qui ne sont pas des strings seront éliminés
* @param {Array} arg L'array à controler
* @param {boolean} strict passer false pour ajouter un toString sur tous les éléments qui ont la méthode
* @returns {Array}
*/
function filterArrayString (arg, strict) {
if (typeof strict === 'undefined') strict = true
arg = filterArray(arg)
if (strict) {
return arg.filter(function (elt) {
return (typeof elt === 'string')
})
} else {
var retour = []
arg.forEach(function (elt) {
if (typeof elt === 'string') {
retour.push(elt)
} else {
// on ajoute si ça renvoie autre chose que du falsy après cast
var val = filterString(elt)
if (val !== '') retour.push(val)
}
})
return retour
}
}
/**
* Retourne false pour 'false' et '0' et un cast en Boolean sinon
* @param {*} arg
* @returns {boolean}
*/
function filterBoolean (arg) {
if (arg === 'false' || arg === '0') return false
return Boolean(arg)
}
/**
* Retourne l'entier positif fourni ou 0
* @param {number|string} arg
* @returns {number}
*/
function filterInt (arg) {
var int = 0
var argNb = Number(arg)
if (typeof arg === 'string') int = Math.floor(argNb)
else if (typeof arg === 'number') int = Math.floor(arg)
if (int < 0 || int !== argNb) int = 0
return int
}
/**
* Retourne un objet Date (on tente un cast si on nous fourni une string ou un entier) ou undefined
* @param arg
* @returns {Date|undefined}
*/
function filterDate (arg) {
if (!arg) return
if (arg instanceof Date) return arg
if (typeof arg === 'number') return new Date(arg)
if (typeof arg === 'string') {
// presque un nombre… On considère ça un timestamp
if (tools.isInt(Number(arg))) return new Date(arg)
// date au format YYYY-MM-DD, ça match le format json YYYY-MM-DDThh:mm:ss.sssZ
if (/^\d{4}-\d{2}-\d{2}/.test(arg)) return new Date(arg)
// sinon ça se complique, car avec des slashes c'est interprété comme MM/JJ/YYYY :
// new Date("02/05/2017") => 2017-02-05
// on veut imposer JJ/MM, donc on réécrit avec des tirets pour éviter d'embarquer moment
var chunks = /^(\d{2})\/(\d{2})\/(\d{4})(.*)?/.exec(arg)
if (chunks && chunks.length > 3) return new Date(chunks[3] + '-' + chunks[2] + '-' + chunks[1] + (chunks[4] ? chunks[4] : ''))
}
}
/**
* Retourne la chaine passée en argument, un cast en string si ça existe pour le type ou une chaine vide sinon
* @param arg
* @returns {string}
*/
function filterString (arg) {
if (arg === null || arg === undefined) return ''
if (typeof arg === 'string') return arg
// Object a une méthode toString, on peut donc pas tester son existence,
// on cast en string, si ça donne du [object Truc] on renvoie une chaine vide,
// sinon le résultat du cast
var retour = String(arg)
if (/^\[object /.test(retour)) return ''
return retour
}
/**
* Retourne un object éventuellement filtré, vide si autre chose qu'un plain object est fourni
* (Date, Array, etc, sont transformés en objets vides, mais ces types sont préservés dans le contenu de l'objet passé)
* L'objet retourné est cloné, ses objets enfants aussi sauf ceux qui ne sont pas des objets littéraux
* pour lesquels ça reste une référence (pour des valeurs de type Date, Array & co)
* @param {*} obj
* @param {RegExp|function} [excludeFilter] si regexp elle sera testée sur les propriétés pour les filtrer,
* si function elle sera appelée avec (propriété, valeur) et devra renvoyer true pour exclure
* @returns {object} Un nouvel objet issu de obj filtré (ou obj si on a pas fourni de excludeFilter)
*/
function filterObject (obj, excludeFilter) {
// notre fct récursive pour filtrer (uniquement des objets "plain")
function filterObject (obj) {
var objCleaned = {}
var value
for (var prop in obj) {
if (hasProp(obj, prop)) {
value = obj[prop]
if (hasProp(obj, prop) && !excludeFilter(prop, value)) {
if (tools.isObjectPlain(value)) {
objCleaned[prop] = filterObject(value)
} else if (Array.isArray(value) && value.length) {
objCleaned[prop] = filterArray(value)
} else {
objCleaned[prop] = value
}
}
}
}
return objCleaned
}
// idem sur les tableaux
function filterArray (arr) {
return arr.map(function (elt) {
if (tools.isObjectPlain(elt)) return filterObject(elt)
if (Array.isArray(elt)) return filterArray(elt)
return elt
})
}
var retour = {}
if (tools.isObjectPlain(obj)) {
// on transforme une regex en fct filterCallback
if (excludeFilter instanceof RegExp) {
var re = excludeFilter
excludeFilter = function (prop) {
return re.test(prop)
}
}
// et on applique le filtre si y'en a un
if (typeof excludeFilter === 'function') {
retour = filterObject(obj)
} else {
retour = obj
}
}
return retour
}
/**
* Collection de fonctions permettant de filtrer une variable d'après le type attendu
* @service sesajstools/utils/filters
*/
module.exports = {
array: filterArray,
arrayInt: filterArrayInt,
arrayString: filterArrayString,
boolean: filterBoolean,
date: filterDate,
int: filterInt,
object: filterObject,
string: filterString
}