/** @module lib/outils/conversion/casFormat */
/**
* Normalise une expression récupérée d’un champ mathquill (pour la passer par ex à j3pEvalue, ou bien à xcas ou webXcas)
* Utilisée par j3pCalculValeur, ou par des sections|outils qui modifient le résultat (remplacer sqrt par racine) pour le passer à Tarbre.
* (anciennement j3pMathquillXcas)
* @todo à optimiser, énormément de code qui pourrait être simplifié et fiabilisé avec des RegExp
* @param {string|number} expression
* @return {string}
*/
export function mqNormalise (expression) {
function accoladeFermante (chaine, index) {
// fonction qui donne la position de l’accolade fermante correspondant à l’accolade ouvrante positionnée en index de la chaine
// penser à une condition d’arrêt pour ne pas faire planter le truc si la saisie est mal écrite
// (ptet au debut de conversion mathquill : même nb d’accolades ouvrantes que fermantes)
// pour l’instant 1 accolade ouvrante (à l’appel de la fonction)
let indexaccolade = 1
let indexvariable = index
while (indexaccolade && indexvariable < chaine.length) {
// je peux avoir des accolades internes (par groupe de 2 necessairement)
// for (var indexvariable=index+1; indexvariable<chaine.length; indexvariable++){
indexvariable++
if (chaine.charAt(indexvariable) === '{') {
indexaccolade++
}
if (chaine.charAt(indexvariable) === '}') {
indexaccolade--
}
}
return indexvariable
}
function necessiteFois (chaine, index) {
// fonction qui renvoit "*" si à l’index courant on a un chiffre ou i - ou toute autre lettre- (renvoit une chaine vide sinon)
// fonction utile à la conversion d’une chaine mathquill (pour laquelle e^{2i\pi} est compréhensible) vers xcas (qui ne comprend pas e^(2i(PI) alors que e^i(PI) oui...)
const char = chaine.charAt(index)
return (/[0-9a-z]/.test(char)) ? '*' : ''
}
function estParentheseFermante (chaine, index) {
return chaine.charAt(index) === ')'
}
function estParentheseOuvrante (chaine, index) {
return chaine.charAt(index) === '('
}
let pos, i
// pour être sûr que c’est une chaine... (pas le cas si 0.5 par exemple)
if (typeof expression === 'number') expression = String(expression)
if (typeof expression !== 'string') {
console.error(Error(`Paramètre invalide ${typeof expression}`), expression)
// on continue comme avant…
expression = String(expression)
}
// On vire qq sous-chaînes
expression = expression
.replace(/:/g, '')
.replace(/\\cdot/g, '*') // le point multiplicateur
.replace(/\\times/g, '*') // signe multiplié ×
.replace(/,/g, '.')
// mais il ne faut pas remplacer ln x par lnx, donc on gère les parenthèses d’abord
.replace(/\\left\(/g, '(')
.replace(/\\right\)\(/g, ')')
// PB (vu après) : XCAS ne comprend pas la commande ln x, il faut absolument renseigner ln(x)...
// je vire un espace inutile parfois présent (cad qu’on trouve dans le code mathquill \cdot avec ou sans espace juste après)
.replace(/ /g, '')
// un effet de bord : les codes ln x, sin 2x+3 etc n’ont plus d’espaces et ne sont donc pas correctement interprétés par xcas d’où la rustine suivante :
.replace(/(ln|cos|sin|tan)([^(])/g, '$1 $2')
let pos2, pos3, mult, mult2
// si le mot clé \frac est présent, on l’enlève et on convertit \frac{A}{B} en A/B
// CODE QUI SUIT PEUT ETRE MIS DANS UNE FONCTION RECURSIVE (en remplaçant while par if) : si tous les if ne donnent rien, cette fonction renvoit le string passé en paramètre
while (expression.indexOf('\\frac') !== -1) {
// il y a une fraction
pos = expression.indexOf('\\frac')
// on vire le frac, pour 2+\frac{3}{4}, il reste ensuite 2+{3}{4}
expression = expression.substring(0, pos) + expression.substring(pos + 5)
// je detecte la fin du numérateur pour extraire A :
pos2 = accoladeFermante(expression, pos)
// AVEC LA FONCTION RECURSIVE : var chaineXcasA=recur(expression.substring(pos+1,pos2))
const chaineXcasA = expression.substring(pos + 1, pos2)
// pareil pour B :
pos3 = accoladeFermante(expression, pos2 + 1)
// AVEC LA FONCTION RECURSIVE : var chaineXcasB=recur(expression.substring(pos2+1,pos3))
const chaineXcasB = expression.substring(pos2 + 2, pos3)
if (necessiteFois(expression, pos3 + 1) === '*' || estParentheseOuvrante(expression, pos3 + 1)) {
mult2 = '*'
} else {
mult2 = ''
}
expression = expression.substring(0, pos) + '(' + chaineXcasA + ')/(' + chaineXcasB + ')' + mult2 + expression.substring(pos3 + 1)
}
while (expression.indexOf('\\sqrt') !== -1) {
// il y a une racine, il faut remplacer \sqrt{A} par sqrt(A)
// on teste aussi s’il faut un * avant car xcas ne comprend pas 2sqrt(3)
pos = expression.indexOf('\\sqrt')
pos2 = pos + 4
if (necessiteFois(expression, pos - 1) === '*' || estParentheseFermante(expression, pos - 1)) {
mult = '*'
pos2++
} else {
mult = ''
}
// je vire le \ en ajoutant le signe * si besoin
expression = expression.substring(0, pos) + mult + expression.substring(pos + 1)
// je remplace { par (
expression = expression.substring(0, pos2) + '(' + expression.substring(pos2 + 1)
// je detecte la fin de ce qu’il y a dans la racine'
pos3 = accoladeFermante(expression, pos2)
expression = expression.substring(0, pos3) + ')' + expression.substring(pos3 + 1)
}
while (expression.indexOf('\\mathrm{e}') !== -1) {
pos = expression.indexOf('\\mathrm{e}')
expression = expression.substring(0, pos) + 'e' + expression.substring(pos + 10)
}
// un soucis ici : si un seul terme dans l’exponentielle, MQ ne met pas d’accolade donc il faut refaire le test avec 'e^' pour savoir si necessite fois...
while (expression.indexOf('e^{') !== -1) {
// il y a un exponentielle, il faut remplacer e^{A} par e^(A) + test si signe * necessaire
pos = expression.indexOf('e^{')
pos2 = accoladeFermante(expression, pos + 2)
if (necessiteFois(expression, pos - 1) === '*' || estParentheseFermante(expression, pos - 1)) {
mult = '*'
} else {
mult = ''
}
if (necessiteFois(expression, pos2 + 1) === '*' || estParentheseOuvrante(expression, pos2 + 1)) {
mult2 = '*'
} else {
mult2 = ''
}
expression = expression.substring(0, pos) + mult + expression.substring(pos, pos + 2) + '(' + expression.substring(pos + 3, pos2) + ')' + mult2 + expression.substring(pos2 + 1)
}
// je traite le cas particulier :
for (let i = 0; i < expression.length; i++) {
if (expression.charAt(i) === 'e' && expression.charAt(i + 1) === '^' && expression.charAt(i + 2) !== '(') {
if (necessiteFois(expression, i - 1) === '*' || estParentheseFermante(expression, i - 1)) {
mult = '*'
} else {
mult = ''
}
if (necessiteFois(expression, i + 3) === '*' || estParentheseFermante(expression, i + 3)) {
mult2 = '*'
} else {
mult2 = ''
}
expression = expression.substring(0, i) + mult + expression.substring(i, i + 3) + mult2 + expression.substring(i + 3)
i++
}
}
// il y a parfois un pb avec un signe * qui s’est inséré juste avant une parenthèse fermante (cas (e^2) qui s'écrit (e^2*)
// je ne sais pas d’où vient l’erreur (et je ne veux pas créer de nouveau bug) donc je corrige ici
i = 0
while (i <= expression.length) {
if ((expression.charAt(i) === '*') && (expression.charAt(i + 1) === ')')) {
expression = expression.substring(0, i) + expression.substring(i + 1)
} else {
i++
}
}
// j’autorise la saisie en direct de exp().. du coup il faut tester s’il faut rajouter un signe fois avant :
for (let i = 0; i < expression.length; i++) {
if (expression.substring(i, i + 3) === 'exp' && (necessiteFois(expression, i - 1) === '*' || estParentheseFermante(expression, i - 1))) {
expression = expression.substring(0, i) + '*' + expression.substring(i)
i++
}
}
while (expression.indexOf('\\pi') !== -1) {
pos = expression.indexOf('\\pi')
// je remplace le \ par (
if (necessiteFois(expression, pos - 1) === '*' || estParentheseFermante(expression, pos - 1)) {
mult = '*'
} else {
mult = ''
}
expression = expression.substring(0, pos) + mult + '(PI)' + expression.substring(pos + 3)
}
// A VIRER AUSSI : \left et \right pour les parenthèses
while (expression.indexOf('\\left') !== -1) {
pos = expression.indexOf('\\left')
if (necessiteFois(expression, pos - 1) === '*' || estParentheseFermante(expression, pos - 1)) {
mult = '*'
} else {
mult = ''
}
expression = expression.substring(0, pos) + mult + expression.substring(pos + 5)
}
while (expression.indexOf('\\right') !== -1) {
pos = expression.indexOf('\\right')
if (necessiteFois(expression, pos + 7) === '*' || estParentheseOuvrante(expression, pos + 7)) {
mult = '*'
} else {
mult = ''
}
expression = expression.substring(0, pos) + ')' + mult + expression.substring(pos + 7)
}
// pour gérer un pb : (2+racine(3))i n’est pas compris par xcas, j’ajoute un fois...
for (let i = 0; i <= expression.length; i++) {
if (expression.charAt(i) === 'i' && (necessiteFois(expression, i - 1) === '*' || estParentheseFermante(expression, i - 1))) {
expression = expression.substring(0, i) + '*' + 'i' + expression.substring(i + 1)
i++
}
}
// même genre de pb : i3 n’est pas compris par xcas, j’ajoute un fois...(mais pas à "i)" d’où la modif de code pour estParentheseFermante
for (let i = 0; i <= expression.length; i++) {
if (expression.charAt(i) === 'i' && necessiteFois(expression, i + 1) === '*') {
expression = expression.substring(0, i) + 'i' + '*' + expression.substring(i + 1)
i++
}
}
// A nouveau 2ln(2x+3) n’est pas compris par xcas:
for (let i = 0; i < expression.length; i++) {
if (expression.substring(i, i + 2) === 'ln' && (necessiteFois(expression, i - 1) === '*' || estParentheseFermante(expression, i - 1))) {
expression = expression.substring(0, i) + '*' + expression.substring(i)
i++
}
}
while (expression.indexOf('{') !== -1) {
// les puissances par exemple, on remplace x^{n+1} par x^(n+1)
pos = expression.indexOf('{')
pos2 = accoladeFermante(expression, pos)
expression = expression.substring(0, pos) + '(' + expression.substring(pos + 1)
expression = expression.substring(0, pos2) + ')' + expression.substring(pos2 + 1)
// on vire le frac, pour 2+\frac{3}{4}, il reste ensuite 2+{3}{4}
}
// corrections à virer lorsque le code ci-dessus aura été rectifié
expression = expression
.replace(/s\*i\*n/g, 'sin')
return expression
}