Ejemplo n.º 1
0
/* Transform `code` into an entity. */
function toNamed(name, next, omit, attribute) {
  var value = '&' + name;

  if (
    omit &&
    own.call(legacy, name) &&
    dangerous.indexOf(name) === -1 &&
    (!attribute || (next && next !== '=' && !alphanumerical(next)))
  ) {
    return value;
  }

  return value + ';';
}
Ejemplo n.º 2
0
/* Parse a BCP 47 language tag. */
/* eslint-disable complexity */
function parse(tag, options) {
  var settings = options || {}
  var warning = settings.warning
  var forgiving = settings.forgiving
  var normalize = settings.normalize
  var result = {}
  var sensitive
  var code
  var index
  var count
  var groupCount
  var offset

  /* Check input. */
  if (tag === null || tag === undefined) {
    throw new Error('Expected string, got `' + tag + '`')
  }

  normalize = normalize === null || normalize === undefined ? true : normalize

  /* Let’s start. */
  result = empty()
  tag = String(tag)
  sensitive = tag
  tag = tag.toLowerCase()

  if (own.call(normal, tag)) {
    if (normalize && normal[tag]) {
      return parse(normal[tag])
    }

    result[regular.indexOf(tag) === -1 ? 'irregular' : 'regular'] = sensitive

    return result
  }

  index = 0
  code = char(index)

  while (alphabetical(code)) {
    index++
    code = char(index)
  }

  /* Parse a normal tag. */
  if (index < MIN_ISO_639 || index > MAX_SUBTAG) {
    index = 0
  } else {
    if (index > MAX_RESERVED) {
      /* Subtag language. */
      result.language = sensitive.slice(0, index)
    } else if (index > MAX_ISO_639) {
      /* Reserved language. */
      result.language = sensitive.slice(0, index)
    } else {
      groupCount = 0

      /* ISO 639 language. */
      result.language = sensitive.slice(0, index)

      while (
        dash(char(index)) &&
        alphabetical(char(index + 1)) &&
        alphabetical(char(index + 2)) &&
        alphabetical(char(index + 3)) &&
        !alphabetical(char(index + 4))
      ) {
        if (groupCount >= MAX_EXTENDED_LANGUAGE_SUBTAG_COUNT) {
          return fail(index, ERR_TOO_MANY_SUBTAGS)
        }

        /* Extended language subtag. */
        result.extendedLanguageSubtags.push(
          sensitive.slice(index + 1, index + 4)
        )

        index += 4
        groupCount++
      }
    }

    if (
      dash(char(index)) &&
      alphabetical(char(index + 1)) &&
      alphabetical(char(index + 2)) &&
      alphabetical(char(index + 3)) &&
      alphabetical(char(index + 4)) &&
      !alphabetical(char(index + 5))
    ) {
      /* ISO 15924 script. */
      result.script = sensitive.slice(index + 1, index + 5)

      index += 5
    }

    if (dash(char(index))) {
      if (
        alphabetical(char(index + 1)) &&
        alphabetical(char(index + 2)) &&
        !alphabetical(char(index + 3))
      ) {
        /* ISO 3166-1 region. */
        result.region = sensitive.slice(index + 1, index + 3)
        index += 3
      } else if (
        decimal(char(index + 1)) &&
        decimal(char(index + 2)) &&
        decimal(char(index + 3)) &&
        !decimal(char(index + 4))
      ) {
        /* UN M49 region. */
        result.region = sensitive.slice(index + 1, index + 4)
        index += 4
      }
    }

    while (dash(char(index))) {
      offset = index + 1
      count = 0

      while (alphanumeric(char(offset))) {
        offset++
        count++

        if (count > MAX_VARIANT) {
          return fail(offset, ERR_VARIANT_TOO_LONG)
        }
      }

      if (count >= MIN_ALPHANUMERIC_VARIANT) {
        /* Long variant. */
        result.variants.push(sensitive.slice(index + 1, offset))
        index = offset
      } else if (decimal(char(index + 1)) && count >= MIN_VARIANT) {
        /* Short variant. */
        result.variants.push(sensitive.slice(index + 1, offset))
        index = offset
      } else {
        break
      }
    }

    while (dash(char(index))) {
      if (
        x(char(index + 1)) ||
        !alphanumeric(char(index + 1)) ||
        !dash(char(index + 2)) ||
        !alphanumeric(char(index + 3))
      ) {
        break
      }

      offset = index + 2
      groupCount = 0

      while (
        dash(char(offset)) &&
        alphanumeric(char(offset + 1)) &&
        alphanumeric(char(offset + 2))
      ) {
        offset += 2
        count = 2
        groupCount++

        while (alphanumeric(char(offset))) {
          if (count > MAX_EXTENSION) {
            return fail(offset, ERR_EXTENSION_TOO_LONG)
          }

          offset++
          count++
        }
      }

      if (!groupCount) {
        return fail(offset, ERR_EMPTY_EXTENSION)
      }

      /* Extension. */
      result.extensions.push({
        singleton: sensitive.charAt(index + 1),
        extensions: sensitive.slice(index + 3, offset).split('-')
      })

      index = offset
    }
  }

  if (
    (index === 0 && x(char(0))) ||
    (index !== 1 && dash(char(index)) && x(char(index + 1)))
  ) {
    index = index ? index + 2 : 1
    offset = index

    while (dash(char(offset)) && alphanumeric(char(offset + 1))) {
      offset += 2
      count = 1

      while (alphanumeric(char(offset))) {
        if (count >= MAX_PRIVATE_USE) {
          return fail(offset, ERR_PRIVATE_USE_TOO_LONG)
        }

        offset++
        count++
      }

      result.privateuse.push(sensitive.slice(index + 1, offset))
      index = offset
    }
  }

  if (index !== tag.length) {
    return fail(index, ERR_EXTRA_CONTENT)
  }

  return result

  function char(pos) {
    return tag.charCodeAt(pos)
  }

  function fail(offset, code) {
    if (warning) {
      warning(ERRORS[code], code, offset)
    }

    return forgiving ? result : empty()
  }
}
Ejemplo n.º 3
0
/* Parse entities. */
function parse(value, settings) {
  var additional = settings.additional
  var nonTerminated = settings.nonTerminated
  var handleText = settings.text
  var handleReference = settings.reference
  var handleWarning = settings.warning
  var textContext = settings.textContext
  var referenceContext = settings.referenceContext
  var warningContext = settings.warningContext
  var pos = settings.position
  var indent = settings.indent || []
  var length = value.length
  var index = 0
  var lines = -1
  var column = pos.column || 1
  var line = pos.line || 1
  var queue = ''
  var result = []
  var entityCharacters
  var namedEntity
  var terminated
  var characters
  var character
  var reference
  var following
  var warning
  var reason
  var output
  var entity
  var begin
  var start
  var type
  var test
  var prev
  var next
  var diff
  var end

  /* Cache the current point. */
  prev = now()

  /* Wrap `handleWarning`. */
  warning = handleWarning ? parseError : noop

  /* Ensure the algorithm walks over the first character
   * and the end (inclusive). */
  index--
  length++

  while (++index < length) {
    /* If the previous character was a newline. */
    if (character === '\n') {
      column = indent[lines] || 1
    }

    character = at(index)

    /* Handle anything other than an ampersand,
     * including newlines and EOF. */
    if (character !== '&') {
      if (character === '\n') {
        line++
        lines++
        column = 0
      }

      if (character) {
        queue += character
        column++
      } else {
        flush()
      }
    } else {
      following = at(index + 1)

      /* The behaviour depends on the identity of the next
       * character. */
      if (
        following === '\t' /* Tab */ ||
        following === '\n' /* Newline */ ||
        following === '\f' /* Form feed */ ||
        following === ' ' /* Space */ ||
        following === '<' /* Less-than */ ||
        following === '&' /* Ampersand */ ||
        following === '' ||
        (additional && following === additional)
      ) {
        /* Not a character reference. No characters
         * are consumed, and nothing is returned.
         * This is not an error, either. */
        queue += character
        column++

        continue
      }

      start = index + 1
      begin = start
      end = start

      /* Numerical entity. */
      if (following !== '#') {
        type = NAMED
      } else {
        end = ++begin

        /* The behaviour further depends on the
         * character after the U+0023 NUMBER SIGN. */
        following = at(end)

        if (following === 'x' || following === 'X') {
          /* ASCII hex digits. */
          type = HEXADECIMAL
          end = ++begin
        } else {
          /* ASCII digits. */
          type = DECIMAL
        }
      }

      entityCharacters = ''
      entity = ''
      characters = ''
      test = TESTS[type]
      end--

      while (++end < length) {
        following = at(end)

        if (!test(following)) {
          break
        }

        characters += following

        /* Check if we can match a legacy named
         * reference.  If so, we cache that as the
         * last viable named reference.  This
         * ensures we do not need to walk backwards
         * later. */
        if (type === NAMED && own.call(legacy, characters)) {
          entityCharacters = characters
          entity = legacy[characters]
        }
      }

      terminated = at(end) === ';'

      if (terminated) {
        end++

        namedEntity = type === NAMED ? decodeEntity(characters) : false

        if (namedEntity) {
          entityCharacters = characters
          entity = namedEntity
        }
      }

      diff = 1 + end - start

      if (!terminated && !nonTerminated) {
        /* Empty. */
      } else if (!characters) {
        /* An empty (possible) entity is valid, unless
         * its numeric (thus an ampersand followed by
         * an octothorp). */
        if (type !== NAMED) {
          warning(NUMERIC_EMPTY, diff)
        }
      } else if (type === NAMED) {
        /* An ampersand followed by anything
         * unknown, and not terminated, is invalid. */
        if (terminated && !entity) {
          warning(NAMED_UNKNOWN, 1)
        } else {
          /* If theres something after an entity
           * name which is not known, cap the
           * reference. */
          if (entityCharacters !== characters) {
            end = begin + entityCharacters.length
            diff = 1 + end - begin
            terminated = false
          }

          /* If the reference is not terminated,
           * warn. */
          if (!terminated) {
            reason = entityCharacters ? NAMED_NOT_TERMINATED : NAMED_EMPTY

            if (!settings.attribute) {
              warning(reason, diff)
            } else {
              following = at(end)

              if (following === '=') {
                warning(reason, diff)
                entity = null
              } else if (alphanumerical(following)) {
                entity = null
              } else {
                warning(reason, diff)
              }
            }
          }
        }

        reference = entity
      } else {
        if (!terminated) {
          /* All non-terminated numeric entities are
           * not rendered, and trigger a warning. */
          warning(NUMERIC_NOT_TERMINATED, diff)
        }

        /* When terminated and number, parse as
         * either hexadecimal or decimal. */
        reference = parseInt(characters, BASE[type])

        /* Trigger a warning when the parsed number
         * is prohibited, and replace with
         * replacement character. */
        if (prohibited(reference)) {
          warning(NUMERIC_PROHIBITED, diff)
          reference = '\uFFFD'
        } else if (reference in invalid) {
          /* Trigger a warning when the parsed number
           * is disallowed, and replace by an
           * alternative. */
          warning(NUMERIC_DISALLOWED, diff)
          reference = invalid[reference]
        } else {
          /* Parse the number. */
          output = ''

          /* Trigger a warning when the parsed
           * number should not be used. */
          if (disallowed(reference)) {
            warning(NUMERIC_DISALLOWED, diff)
          }

          /* Stringify the number. */
          if (reference > 0xffff) {
            reference -= 0x10000
            output += fromCharCode((reference >>> (10 & 0x3ff)) | 0xd800)
            reference = 0xdc00 | (reference & 0x3ff)
          }

          reference = output + fromCharCode(reference)
        }
      }

      /* If we could not find a reference, queue the
       * checked characters (as normal characters),
       * and move the pointer to their end. This is
       * possible because we can be certain neither
       * newlines nor ampersands are included. */
      if (!reference) {
        characters = value.slice(start - 1, end)
        queue += characters
        column += characters.length
        index = end - 1
      } else {
        /* Found it! First eat the queued
         * characters as normal text, then eat
         * an entity. */
        flush()

        prev = now()
        index = end - 1
        column += end - start + 1
        result.push(reference)
        next = now()
        next.offset++

        if (handleReference) {
          handleReference.call(
            referenceContext,
            reference,
            {start: prev, end: next},
            value.slice(start - 1, end)
          )
        }

        prev = next
      }
    }
  }

  /* Return the reduced nodes, and any possible warnings. */
  return result.join('')

  /* Get current position. */
  function now() {
    return {
      line: line,
      column: column,
      offset: index + (pos.offset || 0)
    }
  }

  /* “Throw” a parse-error: a warning. */
  function parseError(code, offset) {
    var position = now()

    position.column += offset
    position.offset += offset

    handleWarning.call(warningContext, MESSAGES[code], position, code)
  }

  /* Get character at position. */
  function at(position) {
    return value.charAt(position)
  }

  /* Flush `queue` (normal text). Macro invoked before
   * each entity and at the end of `value`.
   * Does nothing when `queue` is empty. */
  function flush() {
    if (queue) {
      result.push(queue)

      if (handleText) {
        handleText.call(textContext, queue, {start: prev, end: now()})
      }

      queue = ''
    }
  }
}