lib/outils/conversion/casFormat.js

/** @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
}