legacy/outils/calculatrice/ValidationZones.js

import $ from 'jquery'
import { j3pBarre, j3pElement, j3pEnsureHtmlElement, j3pFocus, j3pFreezeElt, j3pMathquillXcas, j3pNombre, j3pPGCD, j3pSupprimeEspaces, j3pValeurde } from 'src/legacy/core/functions'
import { legacyStyles } from 'src/legacy/core/StylesJ3p'
import Tarbre from 'src/legacy/outils/calculatrice/Tarbre'
import { j3pDesactive } from 'src/lib/mathquill/functions'

/**
 * Constructeur des zones de saisie, qui retourne leur validateur
 * Il permet de créer les fonctions de validation que l’on pourra ensuite appeler pour valider les réponses données.
 * Cf le tutorial {@tutorial ValidationZones}
 * @param {Object} zonesOptions
 * @param {HTMLInputElement[]|string[]} zonesOptions.zones La liste des zones (ou de leurs ids)
 * @param {HTMLInputElement[]|string[]} [zonesOptions.validePerso] La liste des zones dont la validation est gérée dans la section (et pas par ValidationZones)
 * @param {Parcours} zonesOptions.parcours Le parcours courant (pour accéder à donneesSection.debug et
 * @constructor
 */
function ValidationZones (zonesOptions) {
  if (!this || typeof this !== 'object') throw Error('Constructeur qui doit être appelé avec new')
  if (typeof zonesOptions !== 'object') throw Error('paramètre invalide')
  if (!Array.isArray(zonesOptions.zones)) throw Error('Paramètre zones invalide (pas un tableau)')
  /**
   * @private
   * @type {HTMLInputElement[]}
   */
  const inputs = []
  for (const zone of zonesOptions.zones) {
    try {
      const zoneSaisie = j3pEnsureHtmlElement(zone)
      // Pour connaître la position de la zone de saisie dans le tableau zone (utile pour valider ou colorer une zone unique)
      zoneSaisie.indexZone = inputs.length
      inputs.push(zoneSaisie)
    } catch (error) {
      console.error(`création de la zone ${zone} impossible, pas un id ni un élément html`, error)
    }
  }
  if (!Array.isArray(zonesOptions.validePerso)) {
    zonesOptions.validePerso = []
  }
  /**
   * @typedef ZonesProps
   * @property {HTMLInputElement[]} inputs Liste des inputs (ou zones mathquill)
   * @property {boolean[]} validePerso Liste de booléen
   * @property {boolean[]} aRepondu
   * @property {string[]} reponseSaisie Liste des réponses saisies par l’élève (en cas de sélection dans une liste ça contient l’index)
   * @property {boolean[]} bonneReponse
   * @property {boolean[]} reponseSimplifiee (chaque élément peut être un tableau de booléen)
   */

  /**
   * La liste des propriétés pour nos zones à valider
   * Il devrait rester privé (interne à cette classe), mais bcp de sections le manipule et affectent des valeurs
   * @type {ZonesProps}
   */
  this.zones = {
    // array d’elts html
    inputs,
    // liste de booléen, true si on a filé l’id (ou la zone de saisie) dans validePerso, false sinon
    // zonesOptions.validePerso peut contenir l’id ou l’élément html
    validePerso: inputs.map(input => zonesOptions.validePerso.includes(input) || zonesOptions.validePerso.includes(input.id)),
    aRepondu: [], // propriété aRepondu de chaque zone
    reponseSaisie: [], // on récupère la réponse saisie
    bonneReponse: inputs.map(() => false), // pour chaque zone (sans validation perso) on sait si la réponse est bonne
    reponseSimplifiee: inputs.map(() => [true, true]), // par défaut, on considère toutes les réponses simplifiées
    isActive: inputs.map(() => true) // par défaut, toutes les zones sont actives
  }
  // pour la compatibilité ascendante (inputs s’appelait id)
  Object.defineProperty(this.zones, 'id', {
    enumerable: true,
    get: () => {
      console.error(Error('Il ne faut plus utiliser la propriété id, elle est remplacée par la propriété inputs'))
      return this.zones.inputs
    },
    set: () => {
      throw Error('Il ne faut plus utiliser la propriété id, elle est remplacée par la propriété inputs, et elle ne doit pas être modifiée')
    }
  })
  /**
   * Le parcours courant
   * @type {Parcours|undefined}
   */
  this.parcours = zonesOptions.parcours
  /**
   * @type {boolean}
   * @private
   */
  this._isDebug = this.parcours?.isDebug
}

/**
 * Valide toutes les zones (et colorie)
 * @param {boolean} [lastChance] Si le parcours n’a pas été fourni au constructeur, il faut passer un booléen (pour savoir si c’est le dernier essai et geler les réponses élève)
 * @return {{detailsreponses: {}, aRepondu: boolean, bonneReponse: boolean}}
 */
ValidationZones.prototype.validationGlobale = function (lastChance) {
  if (typeof lastChance !== 'boolean') {
    if (this.parcours) {
      lastChance = this.parcours.essaiCourant >= this.parcours.donneesSection.nbchances
    } else {
      console.warn('Sans avoir fourni de parcours au constructeur il faut passer l’argument lastChance à validationGlobale, pas de freeze possible sans lui')
    }
  }
  // On vérifie tout d’abord si l’élève a répondu à chaque zone
  const aRepondu = this.valideReponses()
  let bonneReponse = true
  const detailsreponses = {}
  if (aRepondu) {
    for (let i = this.zones.inputs.length - 1; i >= 0; i--) {
      // pour les zones dont la validation est perso, on ne les prend pas en compte (idem pour celle qui ne sont plus actives car déjà considérées comme bonnes)
      if (!this.zones.validePerso[i] && this.zones.isActive[i]) {
        const zoneSaisie = this.zones.inputs[i]
        const bilanReponseZone = this.valideUneZone(zoneSaisie, zoneSaisie.reponse)
        this.zones.bonneReponse[i] = bilanReponseZone.bonneReponse
        bonneReponse = (bonneReponse && this.zones.bonneReponse[i])
        detailsreponses[this.zones.inputs[i]] = { correct: this.zones.bonneReponse[i], valeur: this.zones.reponseSaisie[i], zonesimplifiee: this.zones.reponseSimplifiee[i], detailPolynome: bilanReponseZone.detailPolynome }
      } else {
        // ce qui suit évite d’avoir à le préciser dans la section dans le cas d’une bonne réponse'
        this.zones.bonneReponse[i] = true
      }
    }
    this.coloreLesZones()
    if (lastChance) this._freezeAll()
  } else {
    bonneReponse = false
  }
  // console.log('validationGlobale avec lastChance', lastChance, 'va retourner', { aRepondu, bonneReponse, detailsreponses })
  return { aRepondu, bonneReponse, detailsreponses }
} // fin validationGlobale

/**
 * Vérifie que toutes les zones ont bien été remplies (appelée par validationGlobale)
 * @return {boolean} True si toutes les zones ont une réponse
 */
ValidationZones.prototype.valideReponses = function () {
  let repEleve// cette variable récupère le contenu de la zone de saisie
  let aRepondu = true
  // On part de la dernière zone car si elle n’est pas complétée, alors on lui donne le focus.
  // Ainsi, la zone avec le focus est la première zone vide
  for (let i = this.zones.inputs.length - 1; i >= 0; i--) {
    this.zones.aRepondu[i] = true
    const elt = this.zones.inputs[i]
    if (elt) {
      if (!Array.isArray(elt.solutionSimplifiee)) {
        elt.solutionSimplifiee = ['valide et on accepte', 'valide et on accepte']
      }
      for (let k = 0; k <= 1; k++) {
        if (
          (elt.solutionSimplifiee[k] === 'reponse fausse') ||
          (elt.solutionSimplifiee[k] === 'reponsefausse') ||
          (elt.solutionSimplifiee[k] === 'réponse fausse') ||
          (elt.solutionSimplifiee[k] === 'reponseFausse') ||
          (elt.solutionSimplifiee[k] === 'fausse')
        ) {
          elt.solutionSimplifiee[k] = 'reponse fausse'
        } else if (
          (elt.solutionSimplifiee[k] === 'non valide') ||
          (elt.solutionSimplifiee[k] === 'nonValide') ||
          (elt.solutionSimplifiee[k] === 'reponse non valide') ||
          (elt.solutionSimplifiee[k] === 'réponse non valide') ||
          (elt.solutionSimplifiee[k] === 'nonvalide') ||
          (elt.solutionSimplifiee[k] === 'reponsenonvalide')
        ) {
          elt.solutionSimplifiee[k] = 'non valide'
        } else {
          elt.solutionSimplifiee[k] = 'valide et on accepte'
        }
      }
    }
    if (elt?.id.includes('input')) {
      // c’est une zone de saisie
      repEleve = j3pValeurde(elt)
    } else {
      // c’est une liste'
      repEleve = elt.selectedIndex
    }
    // on récupère la réponse de chaque zone de saisie
    this.zones.reponseSaisie[i] = repEleve
    if (elt?.id.includes('input')) {
      // la zone de saisie est-elle complétée
      // console.log('elt.id:', elt.id, repEleve, 'et elt:', elt, $(elt).mathquill('latex'), elt.innerHTML)
      if (repEleve === '') {
        this.zones.aRepondu[i] = false
        // à chaque fois, on redonne le focus à la zone concernée.
        // A la fin, c’est la première zone vide qui l’aura (d’où l’ordre dans lequel le for est construit)
        j3pFocus(elt)
      } else {
        const input = elt
        if (input.typeReponse === undefined) {
          input.typeReponse = ['texte']
        }
        if (input.typeReponse[0] === 'nombre') {
          this.zones.reponseSimplifiee[i] = this.reponseSimplifiee(elt)
        } else {
          this.zones.reponseSimplifiee[i] = true
        }
        for (let k = 0; k <= 1; k++) {
          if ((elt.solutionSimplifiee[k] === 'non valide') && !this.zones.reponseSimplifiee[i][k]) {
            // la réponse n’est pas simplifiée (somme ou racine restante pour k=0, fraction réductible pour k=1),
            // et on demande dans la section à ce que cela soit considéré comme une réponse non valide (l’élève doit proposer autre chose)
            this.zones.aRepondu[i] = false
          }
        }
      }
      if (!this.zones.aRepondu[i]) {
        // on redonne le focus à la zone si l’élève n’a pas répondu (ou avec une réponse non simplifiée si c’est pris en compte)
        j3pFocus(elt)
      }
    } else {
      if (elt.typeReponse === undefined) {
        elt.typeReponse = [true]
      }
      if (elt.typeReponse[0]) {
        // on veut dans ce cas que l’élève modifie la valeur par défaut de la liste
        // la liste est-elle complétée
        if (repEleve === 0) {
          this.zones.aRepondu[i] = false
          j3pFocus(elt)
        }
      } else {
        this.zones.aRepondu[i] = true
      }
    }
    aRepondu = (aRepondu && this.zones.aRepondu[i])
    // console.log('aRepondu ? zone', i, this.zones.aRepondu[i])
  }
  // console.log('valideReponses va retourner', aRepondu, 'pour', this.zones)
  return aRepondu
}

/**
 * Met en couleur les zones de saisie (suivant qu’elles soient bonne ou fausses)
 * Elle permet aussi de barrer la réponse à la fin et de désactiver la zone
 * Elle ne sera appelée que si toutes les réponses ont été données et n’agit pas sur les zones où la validaton est perso.
 */
ValidationZones.prototype.coloreLesZones = function coloreLesZones () {
  for (let i = this.zones.inputs.length - 1; i >= 0; i--) {
    // si la réponse est bonne on la remet dans la couleur de l’énoncé
    // sinon, on la met en rouge
    if (!this.zones.validePerso[i] && this.zones.isActive[i]) this.coloreUneZone(this.zones.inputs[i])
  }
}

/**
 * Met en couleur une seule zone de saisie (suivant qu’elle soit bonne ou fausse)
 * Permet aussi de barrer la réponse à la fin et de désactiver la zone.
 * Elle est appelée par coloreLesZones, mais peut l'être de manière autonome (pour une validation perso)
 * Si la réponse est bonne on la remet dans la couleur de l’énoncé, sinon, on la met en rouge
 * @param {string|HTMLElement} zone zone de saisie qui peut être identifiée par son id
 */
ValidationZones.prototype.coloreUneZone = function coloreUneZone (zone) {
  // nos deux listeners, this est l’élément html
  function onInput () {
    this.style.color = legacyStyles.petit.enonce.color
    this.removeEventListener('input', onInput)
    this.removeEventListener('keyup', onKeyup)
  }
  function onKeyup (e) {
    const keynum = (window.event) ? e.keyCode : e.which
    if (keynum === 46 || keynum === 8) {
      this.style.color = legacyStyles.petit.enonce.color
      this.removeEventListener('input', onInput)
      this.removeEventListener('keyup', onKeyup)
    }
  }

  let elt
  try {
    elt = j3pEnsureHtmlElement(zone)
  } catch (error) {
    console.error(error)
    return
  }
  if (!this.zones.isActive[elt.indexZone]) return // La zone est déjà inactive, donc on ne fait rien
  if (this.zones.bonneReponse[elt.indexZone]) {
    elt.style.color = legacyStyles.cbien
    j3pDesactive(elt)
    // On gèle cette zone
    j3pFreezeElt(elt)
    this.zones.isActive[elt.indexZone] = false
  } else {
    elt.style.color = legacyStyles.cfaux
    // on désactive la zone si l’élève n’a plus de chance pour répondre
    // sinon on redonne le focus
    if (!this.parcours) console.error(Error('Il faut passer le parcours au constructeur ValidationZones pour pouvoir appeler coloreUneZone'))
    const parcours = this.parcours || window.j3p
    if (parcours.essaiCourant < parcours.donneesSection.nbchances) {
      j3pFocus(elt)
      // Je fais en sorte que lorsqu’on va de nouveau compléter la zone, le texte va revenir en noir
      // On pourrait ajouter l’objet {once:true} comme argument pour détruire l’écouteur dès sa première utilisation, mais ça ne fonctionne pas sur tous les navigateurs
      elt.addEventListener('input', onInput)
      elt.addEventListener('keyup', onKeyup)
    } else {
      j3pDesactive(elt)
      this.zones.isActive[elt.indexZone] = false
      if ((elt.barrer === undefined) || elt.barrer) j3pBarre(elt)
      if (elt.couleur !== undefined) elt.style.color = elt.couleur
    }
  }
} // fin coloreUneZone

/**
 * Valide une zone (et la colorie)
 * Vérifie juste si une seule zone est correctement remplie
 * zone est l’élément (input) ou l’id de la zone testée, que l’on suppose non vide (valideReponses doit avoir été appelée avant).
 * reponse est un tableau de réponse si zone.typeReponse[0] vaut "texte" ou undefined, sinon, c’est un nombre ou un polynôme
 * @param {HTMLElement|string} zone
 * @param {string[]|string|number} reponse
 * @return {{detailPolynome: {}, bonneReponse: boolean}}
 */
ValidationZones.prototype.valideUneZone = function valideUneZone (zone, reponse) {
  /**
   * @type {string}
   * @private
   */
  let idZone
  if (typeof zone === 'string') {
    idZone = zone
    zone = j3pElement(zone)
  } else {
    idZone = zone.id || '' // on assure d’avoir une string pour la suite du code
  }
  let puisPrecision
  let bonneReponse = false
  let repEleve = this.zones.reponseSaisie[zone.indexZone]
  let detailPolynome = {}// cela servira pour récupérer les informations sur la validation d’un polynôme (pour mieux cerner les erreurs)'
  if (idZone.includes('input')) {
    // on a une zone de saisie
    if (zone.typeReponse === undefined) {
      zone.typeReponse = ['texte']
    }
    if (zone.typeReponse[0] === 'texte') {
      // on veut un tableau de string pour simplifier la suite
      if (!Array.isArray(reponse)) {
        if (typeof reponse === 'number') {
          reponse = [String(reponse)]
        } else if (typeof reponse === 'string') {
          reponse = [reponse]
        } else {
          console.error(Error(`reponse attendue invalide : ${reponse} (${typeof reponse})`))
          reponse = ['']
        }
      }
      bonneReponse = reponse.includes(repEleve)
      if (bonneReponse) {
        // dans ce cas, on remplace la réponse par la première acceptée par le correcteur
        if (idZone.includes('inputmq')) {
          // pour une zone mathquill
          $(zone).mathquill('latex', reponse[0])
        } else {
          // pour un autre type de zone de saisie
          zone.value = reponse[0]

          // cette zone peut être dynamique donc il faut bricoler pour que la taille s’adapte au textes éventuellement modifiés
          let fontZone = zone.style.fontFamily
          let elParent = zone
          while (!fontZone) {
            elParent = elParent.parentNode
            if (elParent) {
              fontZone = elParent.style.fontFamily
            } else {
              // il n’y a plus d’élément parent (on est arrivé sur le document)
              fontZone = legacyStyles.petit.enonce.style.fontFamily
              break
            }
          }
          const tailleFontZone = zone.style.fontSize
          const texteZone = zone.value
          const longueur = $(zone).textWidth(texteZone, fontZone, tailleFontZone)
          zone.style.width = (longueur + 16) + 'px'
        }
      }
    } else if (zone.typeReponse[0] === 'polynome') {
      // la zone est nécessairement une zone mathquill dans ce cas
      const varPoly = zone.typeReponse[1]
      const unarbre1 = new Tarbre(reponse, [varPoly])// pour le polynôme attendu

      // on interprète repEleve pour qu’il soit compris par la suite'

      const tabFrac = repEleve.split('frac')
      let irreductible = true// si des fractions sont présentes, sont-elles simplifiées ?
      let vraiPolynome = true// savoir si c’est bien un polynome (pas une fonction rationnelle par ex)
      let autreVariable = false// existe-t-il une autre variable que varPoly ?
      let signeDouble = false// pour contrôler si l’élève a écrit deux signes consécutifs'
      let egalitePolynome = true// cette variable pourra évoluer si vraiPolynome==true et autreVariable==false
      let estDeveloppe = true// savoir si on retrouve bien une forme développée (sans parenthèses)
      let estSimplifie = true// si le polynôme est développé, on vérifie qu’il est simplifié'
      let monomesDegresDifferents = true// si on a un polynôme développé et qu’il est égal à celui demandé, on vérifie si une même puissance de x 'apparaît pas deux fois.'
      let degreNonSimplifie = -1// si le polynôme n’est pas simplifié, cette variable reçoit le degré du premier monôme non réduit (par puissance décroissantes)'
      let degreErrone = -1// on cherche quel coefficient est inexacte (on renvoie le degré le plus élevé)
      const numFrac = []
      const denFrac = []
      const numFracInit = []
      const denFracInit = []
      const pgcdFrac = []
      const xFrac = []// servira pour récupérer x s’il se trouve au numérateur ou au dénominateur'
      const puisFrac = []
      const varXFrac = []// pour récupérer les puissances de x présentes dans les fractions
      // repEleveSimp sera le polynôme où les fractions auront été remplacée par leur valeur décimale approchée
      let repEleveSimp = repEleve
      const regX = new RegExp('(\\\\cdot)*(' + varPoly + ')(\\^)*(\\{?\\(?([0-9]+)\\)?)*', 'g')
      const regX2 = new RegExp('(\\\\cdot)*(' + varPoly + ')(\\^)*\\{?\\(?', 'g')
      // je regarde ici si toutes les éventuelles fractions sont bien réduites

      if (tabFrac.length > 1) {
        for (let j = 1; j < tabFrac.length; j++) {
          xFrac[j] = []
          numFrac[j] = tabFrac[j].split('}')[0].substring(1)
          numFracInit[j] = numFrac[j]
          xFrac[j][0] = numFrac[j].match(regX)
          numFrac[j] = numFrac[j].replace(regX, '')
          if (!numFrac[j]) {
            numFrac[j] = '1'
          }
          // j’ai récupéré le coef du numérateur et la puissance de x
          denFrac[j] = tabFrac[j].split('}')[1].split('}')[0].substring(1)
          denFracInit[j] = denFrac[j]
          xFrac[j][1] = denFrac[j].match(regX)
          denFrac[j] = denFrac[j].replace(regX, '')
          if (!denFrac[j]) {
            denFrac[j] = '1'
          }
          // j’ai récupéré le coef du dénominateur et la puissance de x
          pgcdFrac[j] = this.pgcd(Math.abs(numFrac[j]), Math.abs(denFrac[j]))
          if ((pgcdFrac[j] > 1) || (pgcdFrac[j] === 'pb')) {
            irreductible = false
          }
          puisFrac[j] = []

          // pour la suite, je simplifie les puissances de x (s’il y en a) et gère l’affichage de la puissance de x simplifiée'
          if (xFrac[j][0] != null) {
            if (xFrac[j][1] != null) {
              // x est présent au numérateur et au dénominateur
              puisFrac[j][0] = xFrac[j][0][0].replace(regX2, '')
              puisFrac[j][1] = xFrac[j][1][0].replace(regX2, '')
              if (!puisFrac[j][0]) {
                if (!puisFrac[j][1]) {
                  varXFrac[j] = ''
                } else {
                  if (Number(puisFrac[j][1]) === 2) {
                    varXFrac[j] = '(1)/(' + varPoly + ')'
                  } else {
                    varXFrac[j] = '(1)/(' + varPoly + '^' + String(Number(puisFrac[j][1] - 1)) + ')'
                  }
                }
              } else {
                if (!puisFrac[j][1]) {
                  if (Number(puisFrac[j][0]) === 2) {
                    varXFrac[j] = varPoly
                  } else {
                    varXFrac[j] = varPoly + '^' + String(Number(puisFrac[j][0] - 1))
                  }
                } else {
                  if (Number(puisFrac[j][0]) > Number(puisFrac[j][1])) {
                    if (Number(puisFrac[j][0]) === Number(puisFrac[j][1]) + 1) {
                      varXFrac[j] = varPoly
                    } else {
                      varXFrac[j] = varPoly + '^' + String(Math.round(Number(puisFrac[j][0] - puisFrac[j][1])))
                    }
                  } else {
                    if (Number(puisFrac[j][1]) === Number(puisFrac[j][0]) + 1) {
                      varXFrac[j] = '(1)/(' + varPoly + ')'
                    } else {
                      varXFrac[j] = '(1)/(' + varPoly + '^' + String(Math.round(Number(puisFrac[j][1] - puisFrac[j][0]))) + ')'
                    }
                  }
                }
              }
              irreductible = false
            } else {
              // on a x au numérateur et pas au dénominateur
              puisFrac[j][0] = xFrac[j][0][0].replace(regX2, '')
              if (!puisFrac[j][0]) {
                varXFrac[j] = varPoly
              } else {
                varXFrac[j] = varPoly + '^' + puisFrac[j][0]
              }
            }
          } else {
            if (xFrac[j][1] != null) {
              // on a x au dénominateur et pas au numérateur
              puisFrac[j][1] = xFrac[j][1][0].replace(regX2, '')
              if (!puisFrac[j][1]) {
                varXFrac[j] = '(1)/(' + varPoly + ')'
              } else {
                varXFrac[j] = '(1)/(' + varPoly + '^' + puisFrac[j][1] + ')'
              }
            } else {
              // sinon x n’est ni au numérateur, ni au dénominateur, on n’a donc pas à gérer ces "x"'
              varXFrac[j] = ''
            }
          }
          // console.log("varXFrac:"+varXFrac);
          // console.log("!!!!avant repEleveSimp:"+repEleveSimp)
          // je remplace alors chaque fraction par la valeur décimale et je mets aussi la puissance de x pour reconstruire le monome
          repEleveSimp = repEleveSimp.replace('\\frac{' + numFracInit[j] + '}}{' + denFracInit[j] + '}', String(numFrac[j] / denFrac[j]) + varXFrac[j])
          repEleveSimp = repEleveSimp.replace('\\frac{' + numFracInit[j] + '}}{' + denFracInit[j] + '}}', String(numFrac[j] / denFrac[j]) + varXFrac[j])
          repEleveSimp = repEleveSimp.replace('\\frac{' + numFracInit[j] + '}{' + denFracInit[j] + '}}', String(numFrac[j] / denFrac[j]) + varXFrac[j])
          repEleveSimp = repEleveSimp.replace('\\frac{' + numFracInit[j] + '}{' + denFracInit[j] + '}', String(numFrac[j] / denFrac[j]) + varXFrac[j])
          if (this._isDebug) console.debug('repEleveSimp:' + repEleveSimp)
        }
      }
      // on vérifie tout d’abord que 2 signes ne sont pas consécutifs
      // je dois prendre en compte les signes doubles
      const tabSigneDouble = ['++', '+-', '-+', '--', '*+', '*-', '**']
      const tabSigneEquiv = ['+', '-', '-', '+', '*', '*(-1)', '*']
      if (this._isDebug) console.debug('repEleveSimp avant signes :' + repEleveSimp)
      for (let j = 0; j < tabSigneDouble.length; j++) {
        while (repEleveSimp.includes(tabSigneDouble[j])) {
          // le signe double est présent
          signeDouble = true
          repEleveSimp = repEleveSimp.replace(tabSigneDouble[j], tabSigneEquiv[j])
        }
      }
      if (this._isDebug) console.debug('repEleveSimp:' + repEleveSimp)
      // ici je ne dois plus avoir de double signe
      let repEleveSuite = j3pMathquillXcas(repEleve)
      // on doit remplacer sqrt par racine pour l’évaluation'
      while (repEleveSuite.includes('sqrt')) {
        repEleveSuite = repEleveSuite.replace('sqrt', 'racine')
      }
      while (repEleveSimp.includes('sqrt')) {
        repEleveSimp = repEleveSimp.replace('sqrt', 'racine')
      }
      const regVarpol = new RegExp('(' + varPoly + ')', 'g')
      let repEleveSansFracetsqrt = repEleveSuite.replace(/(racine)/g, '')// racineTxt,"");
      repEleveSansFracetsqrt = repEleveSansFracetsqrt.replace(regVarpol, '')
      if (/[a-z]/g.test(repEleveSansFracetsqrt)) {
        autreVariable = true
      }
      let ordrePuissance // en lien avec puisPrecision;
      if (!autreVariable) {
        const unarbre2 = new Tarbre(repEleveSuite, [varPoly])// pour le polynôme entré par l’élève'
        for (let j = 0; j <= 14; j++) {
          if (unarbre1.evalue([j]) === 0) {
            puisPrecision = 12
          } else {
            ordrePuissance = String(Math.abs(unarbre1.evalue([j]))).indexOf('.')
            // ordrePuissance = Math.floor(Math.log(Math.abs(unarbre1.evalue([j])))/Math.log(10))+1;
            puisPrecision = (ordrePuissance === '-1') ? 12 - String(Math.abs(reponse)).length : 12 - ordrePuissance
          }
          egalitePolynome = (egalitePolynome && (Math.abs(unarbre1.evalue([j]) - unarbre2.evalue([j])) < Math.pow(10, -puisPrecision)))
        }
        if (zone.typeReponse[2] === 'developpe') {
          // s’il reste des parenthèses, c’est que ce n’est pas vraiment développé'
          if ((repEleve.includes('(')) || (repEleve.includes(')'))) {
            estDeveloppe = false
          } else if (repEleveSimp.includes('/')) {
            vraiPolynome = false
            // ce n’est pas un polynôme (il reste après simplification 1/x^...)
          } else {
            const repSansSommeTab = []
            const decomp1 = repEleveSimp.split('+')
            const decomp2 = []
            let j2
            for (let j = 0; j < decomp1.length; j++) {
              if (decomp1[j] !== '') {
                decomp2[j] = decomp1[j].split('-')
                for (j2 = 0; j2 < decomp2[j].length; j2++) {
                  if (decomp2[j][j2] !== '') {
                    if (j2 > 0) {
                      repSansSommeTab.push('-' + decomp2[j][j2])
                    } else {
                      repSansSommeTab.push(decomp2[j][j2])
                    }
                  }
                }
              }
            }
            if (this._isDebug) console.debug('repSansSommeTab:' + repSansSommeTab)
            // à cet instant repSansSommeTab est un tableau contenant les monomes
            // je reprends chaque élément de ce tableau et en extrait le coef qui est écrit aiinsi que le degré
            const degreRepTab = []
            const monomeReduit = []
            const passageDansMonome = []// si on trouve plusieurs puissances de x dans un monome, c’est qu’il n’est pas réduit'
            let degMax = 0
            let degMaxTab
            const regDeg = /\^\{?\(?\d{1,3}\)?}?/g // new RegExp('(\\^\\{?\\(?[0-9]{1,3}\\)?\\}?)', 'g')
            const regDegNb = /\d/g // new RegExp('[0-9]', 'g')
            for (let j = 0; j < repSansSommeTab.length; j++) {
              degreRepTab[j] = 0
              monomeReduit[j] = true
              passageDansMonome[j] = 0
              // recherche du degré le plus élevé du monome
              degMaxTab = 1
              if (regDeg.test(repSansSommeTab[j])) {
                degMaxTab = repSansSommeTab[j].match(regDeg)
              }
              for (let k = 0; k < degMaxTab.length; k++) {
                degMax = Math.max(degMax, degMaxTab[k].match(regDegNb)[0])
                if (this._isDebug) console.debug('j=' + j + '  degMax' + degMax)
              }
              // au max, on va dire que le degré peut être de 20
              j2 = degMax
              while (j2 >= 1) {
                if (repSansSommeTab[j].includes(varPoly + '^' + j2)) {
                  // x^j2 est dans ce monome (x étant varPoly)
                  passageDansMonome[j]++
                  degreRepTab[j] += j2
                  repSansSommeTab[j] = repSansSommeTab[j].replace(varPoly + '^' + String(j2), '')// cela vire x^j2
                } else {
                  j2--
                }
              }
              while (repSansSommeTab[j].includes(varPoly)) {
                // x est présent dans ce monome (x étant varPoly)
                degreRepTab[j] += 1
                passageDansMonome[j]++
                repSansSommeTab[j] = repSansSommeTab[j].replace(varPoly, '')// cela vire x
              }
              if (passageDansMonome[j] > 1) {
                monomeReduit[j] = false
                degreNonSimplifie = Math.max(degreNonSimplifie, degreRepTab[j])
              }
              if (this._isDebug) {
                console.debug('degreRepTab[' + j + ']:' + degreRepTab[j])
                console.debug('degreNonSimplifie:' + degreNonSimplifie)
              }
              // console.log("repSansSommeTab[j]="+repSansSommeTab[j]);
              // s’il reste un signe * qui soit au début ou à la fin, on le vire.
              while (repSansSommeTab[j].includes('**')) {
                repSansSommeTab[j] = repSansSommeTab[j].replace('**', '*')
              }
              // console.log("repSansSommeTab[j] sans ** ="+repSansSommeTab[j]);
              const lgCoefMonome = repSansSommeTab[j].length
              // console.log("index:"+repSansSommeTab[j].lastIndexOf("*")+"  lgCoefMonome="+lgCoefMonome)
              if (repSansSommeTab[j].lastIndexOf('*') === lgCoefMonome - 1) {
                repSansSommeTab[j] = repSansSommeTab[j].substring(0, lgCoefMonome - 1)// on enlève les signes de multiplication
              }
              if (repSansSommeTab[j].lastIndexOf('\\cdot') === lgCoefMonome - 6) {
                repSansSommeTab[j] = repSansSommeTab[j].substring(0, lgCoefMonome - 6)// on enlève les signes de multiplication
              }
              if (repSansSommeTab[j].indexOf('*') === 0) {
                repSansSommeTab[j] = repSansSommeTab[j].slice(1)// on enlève les signes de multiplication
              }
              if (repSansSommeTab[j].indexOf('\\cdot') === 0) {
                repSansSommeTab[j] = repSansSommeTab[j].slice(5)// on enlève les signes de multiplication
              }
              // j’ai un pb avec les valeur décimale'
              if (!repSansSommeTab[j]) {
                repSansSommeTab[j] = '1'
              } else if (repSansSommeTab[j] === '-') {
                repSansSommeTab[j] = '-1'
              }
              if (this._isDebug) console.debug('repSansSommeTab[' + j + ']=' + repSansSommeTab[j])
              if (Number.isNaN(Number(repSansSommeTab[j]))) {
                if (this._isDebug) console.debug(repSansSommeTab[j] + ' n’est pas un nombre')
                monomeReduit[j] = false
              } else if (Math.abs(repSansSommeTab[j]) < Math.pow(10, -12)) {
                // dans ce cas est présent le monome 0x^... ce qui n’est pas réduit'
                monomeReduit[j] = false
              }
              if (!monomeReduit[j]) {
                estSimplifie = false
              }
            }

            // j’ai vérifié que le polynôme ne contenait plus de parenthèses
            // et si c’est le cas que tous les coefficients sont bien des nombres (pas 3*5 par ex)
            // pour que le polynôme soit bien développé, il suffit de vérifier que je n’ai pas 2 monomes de même degré'
            const tabDegreOrdonne = []
            for (let j = 0; j < degreRepTab.length; j++) {
              tabDegreOrdonne.push(degreRepTab[j])
            }
            tabDegreOrdonne.sort()
            if (this._isDebug) console.debug('degreRepTab:' + degreRepTab + '  tabDegreOrdonne:' + tabDegreOrdonne)
            // si un élément de ce tableau est égal à son suivant, alors on a deux monomes de même degré
            for (let j = 0; j < tabDegreOrdonne.length - 1; j++) {
              if (tabDegreOrdonne[j] === tabDegreOrdonne[j + 1]) {
                monomesDegresDifferents = false
              }
            }
            // je contrôle enfin le coefficient de chaque monome, voir s’il est bon'
            // d’abord, si une puissance de x apparaît plusieurs fois'
            for (let j = tabDegreOrdonne.length - 1; j >= 1; j--) {
              if (tabDegreOrdonne[j] === tabDegreOrdonne[j - 1]) {
                degreNonSimplifie = Math.max(degreNonSimplifie, tabDegreOrdonne[j])
              }
            }
            // je reprends la liste des monomes non réduits
            for (let j = 0; j < degreRepTab.length; j++) {
              if (!monomeReduit[j]) {
                degreNonSimplifie = Math.max(degreNonSimplifie, degreRepTab[j])
              }
            }
            if (this._isDebug) console.debug('degreNonSimplifie 2 :' + degreNonSimplifie)
            // il faut aussi que je réordonne les coefficients dans l’ordre du degré du monôme'
            // le coefficient repSansSommeTab[j] du monome de degré degreRepTab[j] doit être égal à celui du tableau des réponses zone.typeReponse[3]
            const tabMonomePrisEnCompte = []// ceci sert à vérifier que tous les monômes de la réponses sont comparés
            for (let j = 0; j < zone.typeReponse[3].length; j++) {
              tabMonomePrisEnCompte[j] = false
            }
            for (let j = 0; j < degreRepTab.length; j++) {
              tabMonomePrisEnCompte[degreRepTab[j]] = true// le monôme de degré degreRepTab[j] de la réponse attendu est vérifié
              if (Math.abs(repSansSommeTab[j] - zone.typeReponse[3][degreRepTab[j]]) > Math.pow(10, -10)) {
                // Il y a un pb de coef dans le monôme de degré degreRepTab[j]
                if (this._isDebug) console.debug(repSansSommeTab[j] + '  et ' + zone.typeReponse[3][degreRepTab[j]] + '  avec ' + degreRepTab[j])
                degreErrone = Math.max(degreErrone, degreRepTab[j])
                if (this._isDebug) console.debug('degreErrone:' + degreErrone)
              }
            }
            for (let j = 0; j < zone.typeReponse[3].length; j++) {
              if (!tabMonomePrisEnCompte[j] && (Math.abs(zone.typeReponse[3][j]) > Math.pow(10, -12))) {
                degreErrone = Math.max(degreErrone, j)
              }
            }
            if (degreNonSimplifie > -1) {
              estSimplifie = false
            }
          }
          if (this._isDebug) console.debug('vraiPolynome :' + vraiPolynome + '   autreVariable : ' + autreVariable + '  egalitePolynome:' + egalitePolynome + '  estDeveloppe:' + estDeveloppe + '   estSimplifie:' + estSimplifie + '  monomesDegresDifferents:' + monomesDegresDifferents + '  signeDouble:' + signeDouble + '   irreductible:' + irreductible + '   degreNonSimplifie=' + degreNonSimplifie + '  degreErrone:' + degreErrone)
          bonneReponse = (estDeveloppe && estSimplifie && monomesDegresDifferents && !signeDouble && irreductible)
          detailPolynome = { autreVariable, vraiPolynome, signeDouble, irreductible, estDeveloppe, estSimplifie, degreNonSimplifie, degreErrone, monomesDegresDifferents }
          // fin de la partie sur la forme développée
        }
        bonneReponse = (bonneReponse && egalitePolynome)
      } else {
        bonneReponse = false// car autrevariable==true
      }
    } else {
      // on a un nombre
      if (idZone.includes('inputmq')) {
        // pour une zone mathquill, je dois le convertir
        if (repEleve.includes('infty')) {
          // c’est +/-infini
          repEleve = repEleve.replace('\\infty', 'Infinity')
        } else {
          repEleve = this._calculeValeur(repEleve)
        }
      } else {
        // Dans le cas d’un nombre entier, un copier-coller peut mettre des espaces donc il faudra se débarrasser. Cela por
        if (Number.isInteger(j3pNombre(j3pSupprimeEspaces(repEleve)))) repEleve = repEleve.replace(/\s([0-9]{3})/g, '$1')
        if (!Number.isNaN(j3pNombre(repEleve))) { repEleve = j3pNombre(repEleve) }
      }
      let ordrePuissance // en lien avec puisPrecision;
      if (zone.typeReponse[1] === 'exact') {
        if (reponse === 0) {
          puisPrecision = 15
        } else {
          ordrePuissance = String(Math.abs(reponse)).indexOf('.')
          puisPrecision = (ordrePuissance === '-1') ? 13 - String(Math.abs(reponse)).length : 13 - ordrePuissance
        }
        bonneReponse = (Math.abs(repEleve - reponse) < Math.pow(10, -puisPrecision))
      } else if (zone.typeReponse[1] === 'approche') {
        bonneReponse = (Math.abs(repEleve - reponse) <= zone.typeReponse[2])
      } else if (zone.typeReponse[1] === 'arrondi') {
        const nbRep1 = Math.floor(reponse / zone.typeReponse[2]) * zone.typeReponse[2]
        const nbRep2 = Math.ceil(reponse / zone.typeReponse[2]) * zone.typeReponse[2]
        // ordrePuissance de nbRep1 est le nombre n tel que 10^n<= nbRep1 < 10^{n+1}
        if (nbRep1 === 0) {
          puisPrecision = 15
        } else {
          ordrePuissance = String(Math.abs(reponse)).indexOf('.')
          // ordrePuissance = Math.floor(Math.log(Math.abs(nbRep1))/Math.log(10))+1;
          puisPrecision = (ordrePuissance === '-1') ? 15 - String(Math.abs(reponse)).length : 15 - ordrePuissance
        }
        let bonArrondi
        if (Math.round((reponse - nbRep1) * Math.pow(10, puisPrecision)) / Math.pow(10, puisPrecision) < zone.typeReponse[2] / 2) {
          bonArrondi = nbRep1
        } else {
          bonArrondi = nbRep2
        }

        // console.log(bonArrondi, puisPrecision, repEleve) // ne pas laisser ça en prod ! ça donne la solution en console
        bonneReponse = (Math.abs(repEleve - bonArrondi) < Math.pow(10, -puisPrecision))
      }
      if (bonneReponse) {
        for (let k = 0; k <= 1; k++) {
          if ((zone.solutionSimplifiee[k] === 'réponse fausse') && !this.zones.reponseSimplifiee[zone.indexZone][k]) {
            // la réponse n’est pas simplifiée (somme ou racine restante pour k=0, fraction réductible pour k=1),
            // et on demande dans la section à ce que cela soit considéré comme une réponse non valide (l’élève doit proposer autre chose)
            bonneReponse = false
          }
        }
      }
    }
  } else {
    // on a une liste
    bonneReponse = (repEleve === Number(reponse))
  }
  return { bonneReponse, detailPolynome }
} // fin valideUneZone
/**
 * À documenter
 */
ValidationZones.prototype.redonneFocus = function redonneFocus () {
  // cette fonction permet de redonner le focus à la première zone fausse dans le cas d’une validation
  // où on aurait des zones gérées de manière perso'
  for (let i = this.zones.inputs.length - 1; i >= 0; i--) {
    if (!this.zones.bonneReponse[i]) {
      j3pFocus(this.zones.inputs[i], true)
    }
  }
}
/**
 * Utilisez plutôt {@link module:lib/utils/number.pgcd} si vous voulez un vrai pgcd
 * @deprecated
 * @param x
 * @param y
 * @return {string|number}
 */
ValidationZones.prototype.pgcd = function pgcd (x, y) {
  const pgcd = j3pPGCD(x, y)
  if (typeof pgcd !== 'number') return 'pb'
  return pgcd
}
/**
 * À documenter
 */
ValidationZones.prototype.reponseSimplifiee = function reponseSimplifiee (laZone) {
  // cette fonction vérifie si laZone contient une expression simplifiée
  // elle renvoie un tableau contenant 2 valeurs : [0] si l’expression n’est pas une somme simplifiable
  // [1] pour savoir si la fraction éventuellement présente est bien simplifiée'
  let elt, idZone
  if (typeof laZone === 'string') {
    elt = j3pElement(laZone)
    idZone = laZone
  } else {
    elt = laZone
    idZone = laZone.id
  }
  let repEleve
  if (idZone.includes('inputmq')) {
    // c’est une zone mq
    repEleve = $(elt).mathquill('latex')
  } else if (idZone.includes('input')) {
    // c’est une zone de saisie non mq'
    repEleve = j3pValeurde(elt)
  } else {
    // c’est une liste'
    repEleve = elt.selectedIndex
  }
  if (idZone.includes('inputmq')) {
    // pour une zone mathquill, je dois le convertir
    repEleve = j3pMathquillXcas(repEleve)
    // on doit remplacer sqrt par racine pour l’évaluation'
    while (repEleve.includes('sqrt')) {
      repEleve = repEleve.replace('sqrt', 'racine')
    }
  } else {
    repEleve = j3pNombre(repEleve)
  }
  let simplification
  let fractionsSimplifiees = true
  const sqrtfracRegexp1 = /racine\(\(\d+\)\/\(\d+\)\)/ // new RegExp('racine\\(\\([0-9]{1,}\\)/\\([0-9]{1,}\\)\\)', 'i')
  const sqrtfracRegexp2 = /\(racine\(\d+\)\)\/\(\d+\)/ // new RegExp('\\(racine\\([0-9]{1,}\\)\\)/\\([0-9]{1,}\\)', 'i')
  const nbrestantRegexp = /-?[\d,.]+\*/ // new RegExp('\\-?[0-9\\,\\.]{1,}\\*', 'i')
  let reponseSimplifiee = true
  if (String(repEleve).includes('racine')) {
    // J’ai une racine carrée, je vérifie que le radical est simplifié
    if (sqrtfracRegexp1.test(repEleve)) {
      // de la forme sqrt(...)/... ou ...*sqrt(...)/...
      const tabSqrtfrac1 = repEleve.match(sqrtfracRegexp1)
      reponseSimplifiee = (reponseSimplifiee && ((tabSqrtfrac1.length === 1) && ((repEleve.replace(tabSqrtfrac1[0], '') === '') || (nbrestantRegexp.test(repEleve.replace(tabSqrtfrac1[0], ''))))))
      let fracSqrt = tabSqrtfrac1[0].substring(tabSqrtfrac1[0].indexOf('(') + 1, tabSqrtfrac1[0].length - 1)
      while ((fracSqrt.includes('(')) || (fracSqrt.includes(')'))) {
        fracSqrt = fracSqrt.replace('(', '')
        fracSqrt = fracSqrt.replace(')', '')
      }
      reponseSimplifiee = (reponseSimplifiee && this.estNbFrac(fracSqrt)[0])
    }
    if (sqrtfracRegexp2.test(repEleve)) {
      // de la forme sqrt(.../...) ou ...*sqrt(.../...)
      const tabSqrtfrac2 = repEleve.match(sqrtfracRegexp2)
      reponseSimplifiee = (reponseSimplifiee && ((tabSqrtfrac2.length === 1) && ((repEleve.replace(tabSqrtfrac2[0], '') === '') || (nbrestantRegexp.test(repEleve.replace(tabSqrtfrac2[0], ''))))))
    }
    const sqrtRegexp0 = /[+-]?\d*\*?racine\(\d+\)/g // new RegExp('(\\+|\\-)?[0-9]{0,}\\*?racine\\([0-9]{1,}\\)', 'ig')
    const sqrtRegexp1 = /racine\(\d+\)/g // new RegExp('racine\\([0-9]{1,}\\)', 'ig')
    if (sqrtRegexp0.test(repEleve)) {
      // on a donc une racine carrée dans la réponse de l’élève'
      // reponseSimplifiee = true;
      const tabSqrt1 = repEleve.match(sqrtRegexp1)
      // var tabSqrt0 = repEleve.match(sqrtRegexp0)
      for (let k0 = 0; k0 < tabSqrt1.length; k0++) {
        const arbreSqrt = new Tarbre(tabSqrt1[k0], [])
        if (Math.abs(Math.round(arbreSqrt.evalue([])) - arbreSqrt.evalue([])) < Math.pow(10, -12)) {
          // l’une des racine carrée est simplifiable'
          reponseSimplifiee = false
        }
        if (tabSqrt1.length > 1) {
          // cela signifie qu’il existe plus d’une racine carrée'
          for (let k1 = 0; k1 < tabSqrt1.length; k1++) {
            if (k0 !== k1) {
              const arbreDif01 = new Tarbre(tabSqrt1[k0] + '/(' + tabSqrt1[k1] + ')', [])
              const maValeur = arbreDif01.evalue([])
              if (Math.abs(Math.round(maValeur) - maValeur) < Math.pow(10, -12)) {
                reponseSimplifiee = false
              }
            }
          }
        }
      }
    }
  } else if (!String(repEleve).includes('/')) {
    // on n’a pas de fraction
    const repValeur = this._calculeValeur(repEleve)
    if (Math.abs(Math.round(Math.pow(10, 8) * repValeur) / Math.pow(10, 8) - repValeur) < Math.pow(10, -12)) {
      // on a une valeur décimale
      // pour être écrite de manière simple, elle doit donc être décmale et ne pas comporter d’opération
      reponseSimplifiee = ((String(repEleve).indexOf('+') <= 0) && (String(repEleve).indexOf('-') <= 0) && (String(repEleve).indexOf('*') <= 0))
      fractionsSimplifiees = true
    }
  } else {
    try {
      simplification = this.estNbFrac(repEleve)
    } catch (e) {
      simplification = [false, false]
    }
    reponseSimplifiee = simplification[0]
    fractionsSimplifiees = simplification[1]
  }
  return [reponseSimplifiee, fractionsSimplifiees]
} // fin reponseSimplifiee
/**
 * À documenter
 * @param {string} texte
 * @return {boolean[]} Deux booléens, le premier si ??? et le 2e si ???
 */
ValidationZones.prototype.estNbFrac = function estNbFrac (texte) {
  // cette fonction teste si texte est juste un nombre ou juste une fraction (éventuellement sous la forme a*b/c)
  let repSimple = false
  let fractSimple = true
  let tabNum
  let num, den
  // on peut n’avoir qu’un nombre 'entier ou décimal'
  if (!isNaN(Number(j3pNombre(String(texte))))) {
    repSimple = true
  } else if (!texte.includes('/')) {
    // le nombre n’est pas une fraction
    repSimple = true
  }
  // on seulement une fraction ou bien un nb de la forme a*b/c
  const fractRegexp1 = /\(\d+\)\/\(\d+\)/ // new RegExp('\\([0-9]{1,}\\)/\\([0-9]{1,}\\)', 'i')
  const fractRegexp2 = /\(-\d+\)\/\(\d+\)/ // new RegExp('\\(\\-[0-9]{1,}\\)/\\([0-9]{1,}\\)', 'i')
  const fractRegexp3 = /-?[\d,.]+\*?\(-?\d+\)\/\(\d+\)/ // new RegExp('\\-?[0-9\\.\\,]{1,}\\*?\\(\\-?[0-9]{1,}\\)/\\([0-9]{1,}\\)', 'i')
  if (fractRegexp1.test(texte)) {
    const tabFrac1 = texte.match(fractRegexp1)
    repSimple = ((tabFrac1.length === 1) && ((texte.replace(tabFrac1[0], '') === '') || (texte.replace(tabFrac1[0], '') === '-') || (texte.replace(tabFrac1[0], '') === '+')))
    if (repSimple) {
      // c’est que je n’ai qu’une fraction de la forme -?(...)/(...)'
      // on vérifie que cette fraction est simplifiée
      tabNum = texte.split('/')
      num = j3pNombre(tabNum[0].substring(tabNum[0].indexOf('(') + 1, tabNum[0].length - 1))
      den = j3pNombre(tabNum[1].substring(1, tabNum[1].length - 1))
      fractSimple = ((this.pgcd(num, den) === 1) && (den !== 1))
    }
  }
  if (!repSimple) {
    if (fractRegexp2.test(texte)) {
      const tabFrac2 = texte.match(fractRegexp2)
      repSimple = ((tabFrac2.length === 1) && (texte.replace(tabFrac2[0], '') === ''))
      if (repSimple) {
        // c’est que je n’ai qu’une fraction de la forme (-...)/(...)'
        // on vérifie que cette fraction est simplifiée
        tabNum = texte.split('/')
        num = j3pNombre(tabNum[0].substring(2, tabNum[0].length - 1))
        den = j3pNombre(tabNum[1].substring(1, tabNum[1].length - 1))
        fractSimple = ((this.pgcd(num, den) === 1) && (den !== 1))
      }
    }
  }
  if (!repSimple) {
    if (fractRegexp3.test(texte)) {
      const tabFrac3 = texte.match(fractRegexp3)
      repSimple = ((tabFrac3.length === 1) && (texte.replace(tabFrac3[0], '') === ''))
      if (repSimple) {
        // c’est que je n’ai qu’une fraction de la forme ...*(-?...)/(...)'
        // on vérifie que cette fraction est simplifiée
        tabNum = texte.split('/')
        const posPar = tabNum[0].indexOf('(')
        if (tabNum[0].charAt(posPar - 1) === '*') {
          num = j3pNombre(tabNum[0].substring(0, posPar - 1)) * j3pNombre(tabNum[0].substring(posPar + 1, tabNum[0].length - 1))
        } else {
          num = j3pNombre(tabNum[0].substring(0, posPar)) * j3pNombre(tabNum[0].substring(posPar + 1, tabNum[0].length - 1))
        }
        den = j3pNombre(tabNum[1].substring(1, tabNum[1].length - 1))
        fractSimple = ((this.pgcd(num, den) === 1) && (den !== 1))
      }
    }
  }
  return [repSimple, fractSimple]
} // fin estNbFrac
/**
 * À documenter
 * @param fDeX
 * @return {string}
 */
ValidationZones.prototype.transformeExp = function transformeExp (fDeX) {
  // fDeX est une chaine de caractère correspondant à l’espression d’une fonction : f(x)
  // elle revoie la même chaine en remplaçant e^... par exp(...)
  // on appelle cette fonction après avoir utilisé j3pMathquillXcas, donc il n’y a plus frac{..}{..} mais (..)/(..)
  // Attention car e n’est pas seulement pour exp, il peut aussi l'être pour racine
  let newFDeX = ''
  let finFdex = fDeX
  while (finFdex.includes('ne')) {
    newFDeX += finFdex.substring(0, finFdex.indexOf('ne') + 2)
    finFdex = finFdex.substring(finFdex.indexOf('ne') + 2)
  }
  let posE = finFdex.indexOf('e')
  while (posE > -1) {
    // e est présent et ce n’est aps celui de racine
    newFDeX += finFdex.substring(0, posE)
    // on regarde ce qui suit e
    if ((finFdex.charAt(posE + 1) === 'x') && (finFdex.charAt(posE + 2) === 'p')) {
      // il est déjà écrit exp, donc on passe
      newFDeX += 'exp'
      finFdex = finFdex.substring(posE + 3)
    } else if ((finFdex.charAt(posE + 1) === '+') || (finFdex.charAt(posE + 1) === '-') || (finFdex.charAt(posE + 1) === '*') || (finFdex.charAt(posE + 1) === ')') || (finFdex.charAt(posE + 1) === 'x')) {
      // e est tout seul, je le remplace par exp(1)
      newFDeX += 'exp(1)'
      finFdex = finFdex.substring(posE + 1)
    } else {
      // ce qui suit e est ^. Il faut alors trouver les parenthèses qui encadrent ce dont on prend l’exponentielle
      if (finFdex.charAt(posE + 2) === '(') {
        // c’est de la forme e^(...)
        let parOuverte = 1
        let posParferme = posE + 3
        while (parOuverte > 0) {
          if (finFdex.charAt(posParferme) === '(') {
            parOuverte++
          } else if (finFdex.charAt(posParferme) === ')') {
            parOuverte--
          }
          posParferme++
        }
        newFDeX += 'exp(' + finFdex.substring(posE + 3, posParferme)
        finFdex = finFdex.substring(posParferme)
      } else {
        // c’est uste de la forme e^., cad que seul le caractère suivant est est en puissance
        newFDeX += 'exp(' + finFdex.substring(posE + 2, posE + 3) + ')'
        finFdex = finFdex.substring(posE + 3)
      }
    }
    posE = finFdex.indexOf('e')
  }
  newFDeX += finFdex
  // J’ai encore un souci car il ne reconnait pas 3e comme un produit, donc il faut le lui expliquer...
  newFDeX = newFDeX.replace(/(\d)e/g, '$1*e')
  return newFDeX
} // fin transformeExp
/**
 * À documenter
 * @param texteFonction
 * @return {string}
 * @private
 */
ValidationZones.prototype._getEcriturePourCalcul = function _getEcriturePourCalcul (texteFonction) {
  // texte fonction est au format latex ou tout autre format intermédiaire
  // on transforme cette écriture pour avoir l’expression sous la forme permettant de créer un arbre et d’appliquer la fonction evalue
  // je convertis pour que ce soit plus facile à utiliser
  let newExpressionFct = j3pMathquillXcas(texteFonction)
  // gestion de la racine carrée
  // il faut que je transforme sqrt en racine
  while (newExpressionFct.includes('sqrt')) {
    newExpressionFct = newExpressionFct.replace('sqrt', 'racine')
  }
  // mais j’ai encore un problème avec l’exponentielle : je peux avoir e isolé, ou bien e^x ou bien e^{...}. Ceschoses ne sont pas comprises, il faut modifier ça et écrire sous la forme exp(...)
  newExpressionFct = this.transformeExp(newExpressionFct)
  while (newExpressionFct.includes('exp()')) {
    newExpressionFct = newExpressionFct.replace('exp()', 'exp(1)')
  }
  return newExpressionFct
}
/**
 * À documenter
 * @param chaine
 * @return {number}
 * @private
 */
ValidationZones.prototype._calculeValeur = function _calculeValeur (chaine) {
  // un nombre écrit sous forme d’une chaine de caractères va être calculé pour qu’on ait sa valeur décimale
  const newChaine = this._getEcriturePourCalcul(chaine)
  const arbreChaine = new Tarbre(newChaine, [])
  return arbreChaine.evalue([])
} // _calculeValeur
/**
 * Gèle le DOM des zones d’input, pour empêcher toute modif du DOM via l’inspecteur d’élément
 * (contre la triche des élèves qui trafiquent la page avant de faire une capture)
 */
ValidationZones.prototype._freezeAll = function _freezeAll () {
  this.zones.inputs.forEach((input, index) => {
    // Cela ne s’appliquera pas à des zones dont la validation doit être traitée de manière particulière
    if (!this.zones.validePerso[index]) j3pFreezeElt(input)
  })
}

export default ValidationZones