CoolFace
Apppublic

AK-21/Graphite-Industrial-Intelligence

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
index.js408 linesDownload Raw Back to lib
1/**2 * @import {Point} from 'unist'3 * @import {Options} from '../index.js'4 */5 6import {characterEntitiesLegacy} from 'character-entities-legacy'7import {characterReferenceInvalid} from 'character-reference-invalid'8import {isDecimal} from 'is-decimal'9import {isHexadecimal} from 'is-hexadecimal'10import {isAlphanumerical} from 'is-alphanumerical'11import {decodeNamedCharacterReference} from 'decode-named-character-reference'12 13// Warning messages.14const messages = [15  '',16  /* 1: Non terminated (named) */17  'Named character references must be terminated by a semicolon',18  /* 2: Non terminated (numeric) */19  'Numeric character references must be terminated by a semicolon',20  /* 3: Empty (named) */21  'Named character references cannot be empty',22  /* 4: Empty (numeric) */23  'Numeric character references cannot be empty',24  /* 5: Unknown (named) */25  'Named character references must be known',26  /* 6: Disallowed (numeric) */27  'Numeric character references cannot be disallowed',28  /* 7: Prohibited (numeric) */29  'Numeric character references cannot be outside the permissible Unicode range'30]31 32/**33 * Parse HTML character references.34 *35 * @param {string} value36 * @param {Readonly<Options> | null | undefined} [options]37 */38export function parseEntities(value, options) {39  const settings = options || {}40  const additional =41    typeof settings.additional === 'string'42      ? settings.additional.charCodeAt(0)43      : settings.additional44  /** @type {Array<string>} */45  const result = []46  let index = 047  let lines = -148  let queue = ''49  /** @type {Point | undefined} */50  let point51  /** @type {Array<number>|undefined} */52  let indent53 54  if (settings.position) {55    if ('start' in settings.position || 'indent' in settings.position) {56      // @ts-expect-error: points don’t have indent.57      indent = settings.position.indent58      // @ts-expect-error: points don’t have indent.59      point = settings.position.start60    } else {61      point = settings.position62    }63  }64 65  let line = (point ? point.line : 0) || 166  let column = (point ? point.column : 0) || 167 68  // Cache the current point.69  let previous = now()70  /** @type {number|undefined} */71  let character72 73  // Ensure the algorithm walks over the first character (inclusive).74  index--75 76  while (++index <= value.length) {77    // If the previous character was a newline.78    if (character === 10 /* `\n` */) {79      column = (indent ? indent[lines] : 0) || 180    }81 82    character = value.charCodeAt(index)83 84    if (character === 38 /* `&` */) {85      const following = value.charCodeAt(index + 1)86 87      // The behavior depends on the identity of the next character.88      if (89        following === 9 /* `\t` */ ||90        following === 10 /* `\n` */ ||91        following === 12 /* `\f` */ ||92        following === 32 /* ` ` */ ||93        following === 38 /* `&` */ ||94        following === 60 /* `<` */ ||95        Number.isNaN(following) ||96        (additional && following === additional)97      ) {98        // Not a character reference.99        // No characters are consumed, and nothing is returned.100        // This is not an error, either.101        queue += String.fromCharCode(character)102        column++103        continue104      }105 106      const start = index + 1107      let begin = start108      let end = start109      /** @type {string} */110      let type111 112      if (following === 35 /* `#` */) {113        // Numerical reference.114        end = ++begin115 116        // The behavior further depends on the next character.117        const following = value.charCodeAt(end)118 119        if (following === 88 /* `X` */ || following === 120 /* `x` */) {120          // ASCII hexadecimal digits.121          type = 'hexadecimal'122          end = ++begin123        } else {124          // ASCII decimal digits.125          type = 'decimal'126        }127      } else {128        // Named reference.129        type = 'named'130      }131 132      let characterReferenceCharacters = ''133      let characterReference = ''134      let characters = ''135      // Each type of character reference accepts different characters.136      // This test is used to detect whether a reference has ended (as the semicolon137      // is not strictly needed).138      const test =139        type === 'named'140          ? isAlphanumerical141          : type === 'decimal'142            ? isDecimal143            : isHexadecimal144 145      end--146 147      while (++end <= value.length) {148        const following = value.charCodeAt(end)149 150        if (!test(following)) {151          break152        }153 154        characters += String.fromCharCode(following)155 156        // Check if we can match a legacy named reference.157        // If so, we cache that as the last viable named reference.158        // This ensures we do not need to walk backwards later.159        if (type === 'named' && characterEntitiesLegacy.includes(characters)) {160          characterReferenceCharacters = characters161          // @ts-expect-error: always able to decode.162          characterReference = decodeNamedCharacterReference(characters)163        }164      }165 166      let terminated = value.charCodeAt(end) === 59 /* `;` */167 168      if (terminated) {169        end++170 171        const namedReference =172          type === 'named' ? decodeNamedCharacterReference(characters) : false173 174        if (namedReference) {175          characterReferenceCharacters = characters176          characterReference = namedReference177        }178      }179 180      let diff = 1 + end - start181      let reference = ''182 183      if (!terminated && settings.nonTerminated === false) {184        // Empty.185      } else if (!characters) {186        // An empty (possible) reference is valid, unless it’s numeric (thus an187        // ampersand followed by an octothorp).188        if (type !== 'named') {189          warning(4 /* Empty (numeric) */, diff)190        }191      } else if (type === 'named') {192        // An ampersand followed by anything unknown, and not terminated, is193        // invalid.194        if (terminated && !characterReference) {195          warning(5 /* Unknown (named) */, 1)196        } else {197          // If there’s something after an named reference which is not known,198          // cap the reference.199          if (characterReferenceCharacters !== characters) {200            end = begin + characterReferenceCharacters.length201            diff = 1 + end - begin202            terminated = false203          }204 205          // If the reference is not terminated, warn.206          if (!terminated) {207            const reason = characterReferenceCharacters208              ? 1 /* Non terminated (named) */209              : 3 /* Empty (named) */210 211            if (settings.attribute) {212              const following = value.charCodeAt(end)213 214              if (following === 61 /* `=` */) {215                warning(reason, diff)216                characterReference = ''217              } else if (isAlphanumerical(following)) {218                characterReference = ''219              } else {220                warning(reason, diff)221              }222            } else {223              warning(reason, diff)224            }225          }226        }227 228        reference = characterReference229      } else {230        if (!terminated) {231          // All nonterminated numeric references are not rendered, and emit a232          // warning.233          warning(2 /* Non terminated (numeric) */, diff)234        }235 236        // When terminated and numerical, parse as either hexadecimal or237        // decimal.238        let referenceCode = Number.parseInt(239          characters,240          type === 'hexadecimal' ? 16 : 10241        )242 243        // Emit a warning when the parsed number is prohibited, and replace with244        // replacement character.245        if (prohibited(referenceCode)) {246          warning(7 /* Prohibited (numeric) */, diff)247          reference = String.fromCharCode(65533 /* `�` */)248        } else if (referenceCode in characterReferenceInvalid) {249          // Emit a warning when the parsed number is disallowed, and replace by250          // an alternative.251          warning(6 /* Disallowed (numeric) */, diff)252          reference = characterReferenceInvalid[referenceCode]253        } else {254          // Parse the number.255          let output = ''256 257          // Emit a warning when the parsed number should not be used.258          if (disallowed(referenceCode)) {259            warning(6 /* Disallowed (numeric) */, diff)260          }261 262          // Serialize the number.263          if (referenceCode > 0xffff) {264            referenceCode -= 0x10000265            output += String.fromCharCode(266              (referenceCode >>> (10 & 0x3ff)) | 0xd800267            )268            referenceCode = 0xdc00 | (referenceCode & 0x3ff)269          }270 271          reference = output + String.fromCharCode(referenceCode)272        }273      }274 275      // Found it!276      // First eat the queued characters as normal text, then eat a reference.277      if (reference) {278        flush()279 280        previous = now()281        index = end - 1282        column += end - start + 1283        result.push(reference)284        const next = now()285        next.offset++286 287        if (settings.reference) {288          settings.reference.call(289            settings.referenceContext || undefined,290            reference,291            {start: previous, end: next},292            value.slice(start - 1, end)293          )294        }295 296        previous = next297      } else {298        // If we could not find a reference, queue the checked characters (as299        // normal characters), and move the pointer to their end.300        // This is possible because we can be certain neither newlines nor301        // ampersands are included.302        characters = value.slice(start - 1, end)303        queue += characters304        column += characters.length305        index = end - 1306      }307    } else {308      // Handle anything other than an ampersand, including newlines and EOF.309      if (character === 10 /* `\n` */) {310        line++311        lines++312        column = 0313      }314 315      if (Number.isNaN(character)) {316        flush()317      } else {318        queue += String.fromCharCode(character)319        column++320      }321    }322  }323 324  // Return the reduced nodes.325  return result.join('')326 327  // Get current position.328  function now() {329    return {330      line,331      column,332      offset: index + ((point ? point.offset : 0) || 0)333    }334  }335 336  /**337   * Handle the warning.338   *339   * @param {1|2|3|4|5|6|7} code340   * @param {number} offset341   */342  function warning(code, offset) {343    /** @type {ReturnType<now>} */344    let position345 346    if (settings.warning) {347      position = now()348      position.column += offset349      position.offset += offset350 351      settings.warning.call(352        settings.warningContext || undefined,353        messages[code],354        position,355        code356      )357    }358  }359 360  /**361   * Flush `queue` (normal text).362   * Macro invoked before each reference and at the end of `value`.363   * Does nothing when `queue` is empty.364   */365  function flush() {366    if (queue) {367      result.push(queue)368 369      if (settings.text) {370        settings.text.call(settings.textContext || undefined, queue, {371          start: previous,372          end: now()373        })374      }375 376      queue = ''377    }378  }379}380 381/**382 * Check if `character` is outside the permissible unicode range.383 *384 * @param {number} code385 * @returns {boolean}386 */387function prohibited(code) {388  return (code >= 0xd800 && code <= 0xdfff) || code > 0x10ffff389}390 391/**392 * Check if `character` is disallowed.393 *394 * @param {number} code395 * @returns {boolean}396 */397function disallowed(code) {398  return (399    (code >= 0x0001 && code <= 0x0008) ||400    code === 0x000b ||401    (code >= 0x000d && code <= 0x001f) ||402    (code >= 0x007f && code <= 0x009f) ||403    (code >= 0xfdd0 && code <= 0xfdef) ||404    (code & 0xffff) === 0xffff ||405    (code & 0xffff) === 0xfffe406  )407}408