298 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			298 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | ||
| 
 | ||
| var decimal = require('is-decimal')
 | ||
| var alphanumeric = require('is-alphanumeric')
 | ||
| var whitespace = require('is-whitespace-character')
 | ||
| var escapes = require('markdown-escapes')
 | ||
| var prefix = require('./util/entity-prefix-length')
 | ||
| 
 | ||
| module.exports = factory
 | ||
| 
 | ||
| var tab = '\t'
 | ||
| var lineFeed = '\n'
 | ||
| var space = ' '
 | ||
| var numberSign = '#'
 | ||
| var ampersand = '&'
 | ||
| var leftParenthesis = '('
 | ||
| var rightParenthesis = ')'
 | ||
| var asterisk = '*'
 | ||
| var plusSign = '+'
 | ||
| var dash = '-'
 | ||
| var dot = '.'
 | ||
| var colon = ':'
 | ||
| var lessThan = '<'
 | ||
| var greaterThan = '>'
 | ||
| var leftSquareBracket = '['
 | ||
| var backslash = '\\'
 | ||
| var rightSquareBracket = ']'
 | ||
| var underscore = '_'
 | ||
| var graveAccent = '`'
 | ||
| var verticalBar = '|'
 | ||
| var tilde = '~'
 | ||
| var exclamationMark = '!'
 | ||
| 
 | ||
| var entities = {
 | ||
|   '<': '<',
 | ||
|   ':': ':',
 | ||
|   '&': '&',
 | ||
|   '|': '|',
 | ||
|   '~': '~'
 | ||
| }
 | ||
| 
 | ||
| var shortcut = 'shortcut'
 | ||
| var mailto = 'mailto'
 | ||
| var https = 'https'
 | ||
| var http = 'http'
 | ||
| 
 | ||
| var blankExpression = /\n\s*$/
 | ||
| 
 | ||
| // Factory to escape characters.
 | ||
| function factory(options) {
 | ||
|   return escape
 | ||
| 
 | ||
|   // Escape punctuation characters in a node’s value.
 | ||
|   function escape(value, node, parent) {
 | ||
|     var self = this
 | ||
|     var gfm = options.gfm
 | ||
|     var commonmark = options.commonmark
 | ||
|     var pedantic = options.pedantic
 | ||
|     var markers = commonmark ? [dot, rightParenthesis] : [dot]
 | ||
|     var siblings = parent && parent.children
 | ||
|     var index = siblings && siblings.indexOf(node)
 | ||
|     var previous = siblings && siblings[index - 1]
 | ||
|     var next = siblings && siblings[index + 1]
 | ||
|     var length = value.length
 | ||
|     var escapable = escapes(options)
 | ||
|     var position = -1
 | ||
|     var queue = []
 | ||
|     var escaped = queue
 | ||
|     var afterNewLine
 | ||
|     var character
 | ||
|     var wordCharBefore
 | ||
|     var wordCharAfter
 | ||
|     var offset
 | ||
|     var replace
 | ||
| 
 | ||
|     if (previous) {
 | ||
|       afterNewLine = text(previous) && blankExpression.test(previous.value)
 | ||
|     } else {
 | ||
|       afterNewLine =
 | ||
|         !parent || parent.type === 'root' || parent.type === 'paragraph'
 | ||
|     }
 | ||
| 
 | ||
|     while (++position < length) {
 | ||
|       character = value.charAt(position)
 | ||
|       replace = false
 | ||
| 
 | ||
|       if (character === '\n') {
 | ||
|         afterNewLine = true
 | ||
|       } else if (
 | ||
|         character === backslash ||
 | ||
|         character === graveAccent ||
 | ||
|         character === asterisk ||
 | ||
|         character === leftSquareBracket ||
 | ||
|         character === lessThan ||
 | ||
|         (character === ampersand && prefix(value.slice(position)) > 0) ||
 | ||
|         (character === rightSquareBracket && self.inLink) ||
 | ||
|         (gfm && character === tilde && value.charAt(position + 1) === tilde) ||
 | ||
|         (gfm &&
 | ||
|           character === verticalBar &&
 | ||
|           (self.inTable || alignment(value, position))) ||
 | ||
|         (character === underscore &&
 | ||
|           // Delegate leading/trailing underscores to the multinode version below.
 | ||
|           position > 0 &&
 | ||
|           position < length - 1 &&
 | ||
|           (pedantic ||
 | ||
|             !alphanumeric(value.charAt(position - 1)) ||
 | ||
|             !alphanumeric(value.charAt(position + 1)))) ||
 | ||
|         (gfm && !self.inLink && character === colon && protocol(queue.join('')))
 | ||
|       ) {
 | ||
|         replace = true
 | ||
|       } else if (afterNewLine) {
 | ||
|         if (
 | ||
|           character === greaterThan ||
 | ||
|           character === numberSign ||
 | ||
|           character === asterisk ||
 | ||
|           character === dash ||
 | ||
|           character === plusSign
 | ||
|         ) {
 | ||
|           replace = true
 | ||
|         } else if (decimal(character)) {
 | ||
|           offset = position + 1
 | ||
| 
 | ||
|           while (offset < length) {
 | ||
|             if (!decimal(value.charAt(offset))) {
 | ||
|               break
 | ||
|             }
 | ||
| 
 | ||
|             offset++
 | ||
|           }
 | ||
| 
 | ||
|           if (markers.indexOf(value.charAt(offset)) !== -1) {
 | ||
|             next = value.charAt(offset + 1)
 | ||
| 
 | ||
|             if (!next || next === space || next === tab || next === lineFeed) {
 | ||
|               queue.push(value.slice(position, offset))
 | ||
|               position = offset
 | ||
|               character = value.charAt(position)
 | ||
|               replace = true
 | ||
|             }
 | ||
|           }
 | ||
|         }
 | ||
|       }
 | ||
| 
 | ||
|       if (afterNewLine && !whitespace(character)) {
 | ||
|         afterNewLine = false
 | ||
|       }
 | ||
| 
 | ||
|       queue.push(replace ? one(character) : character)
 | ||
|     }
 | ||
| 
 | ||
|     // Multi-node versions.
 | ||
|     if (siblings && text(node)) {
 | ||
|       // Check for an opening parentheses after a link-reference (which can be
 | ||
|       // joined by white-space).
 | ||
|       if (previous && previous.referenceType === shortcut) {
 | ||
|         position = -1
 | ||
|         length = escaped.length
 | ||
| 
 | ||
|         while (++position < length) {
 | ||
|           character = escaped[position]
 | ||
| 
 | ||
|           if (character === space || character === tab) {
 | ||
|             continue
 | ||
|           }
 | ||
| 
 | ||
|           if (character === leftParenthesis || character === colon) {
 | ||
|             escaped[position] = one(character)
 | ||
|           }
 | ||
| 
 | ||
|           break
 | ||
|         }
 | ||
| 
 | ||
|         // If the current node is all spaces / tabs, preceded by a shortcut,
 | ||
|         // and followed by a text starting with `(`, escape it.
 | ||
|         if (
 | ||
|           text(next) &&
 | ||
|           position === length &&
 | ||
|           next.value.charAt(0) === leftParenthesis
 | ||
|         ) {
 | ||
|           escaped.push(backslash)
 | ||
|         }
 | ||
|       }
 | ||
| 
 | ||
|       // Ensure non-auto-links are not seen as links.  This pattern needs to
 | ||
|       // check the preceding nodes too.
 | ||
|       if (
 | ||
|         gfm &&
 | ||
|         !self.inLink &&
 | ||
|         text(previous) &&
 | ||
|         value.charAt(0) === colon &&
 | ||
|         protocol(previous.value.slice(-6))
 | ||
|       ) {
 | ||
|         escaped[0] = one(colon)
 | ||
|       }
 | ||
| 
 | ||
|       // Escape ampersand if it would otherwise start an entity.
 | ||
|       if (
 | ||
|         text(next) &&
 | ||
|         value.charAt(length - 1) === ampersand &&
 | ||
|         prefix(ampersand + next.value) !== 0
 | ||
|       ) {
 | ||
|         escaped[escaped.length - 1] = one(ampersand)
 | ||
|       }
 | ||
| 
 | ||
|       // Escape exclamation marks immediately followed by links.
 | ||
|       if (
 | ||
|         next &&
 | ||
|         next.type === 'link' &&
 | ||
|         value.charAt(length - 1) === exclamationMark
 | ||
|       ) {
 | ||
|         escaped[escaped.length - 1] = one(exclamationMark)
 | ||
|       }
 | ||
| 
 | ||
|       // Escape double tildes in GFM.
 | ||
|       if (
 | ||
|         gfm &&
 | ||
|         text(next) &&
 | ||
|         value.charAt(length - 1) === tilde &&
 | ||
|         next.value.charAt(0) === tilde
 | ||
|       ) {
 | ||
|         escaped.splice(-1, 0, backslash)
 | ||
|       }
 | ||
| 
 | ||
|       // Escape underscores, but not mid-word (unless in pedantic mode).
 | ||
|       wordCharBefore = text(previous) && alphanumeric(previous.value.slice(-1))
 | ||
|       wordCharAfter = text(next) && alphanumeric(next.value.charAt(0))
 | ||
| 
 | ||
|       if (length === 1) {
 | ||
|         if (
 | ||
|           value === underscore &&
 | ||
|           (pedantic || !wordCharBefore || !wordCharAfter)
 | ||
|         ) {
 | ||
|           escaped.unshift(backslash)
 | ||
|         }
 | ||
|       } else {
 | ||
|         if (
 | ||
|           value.charAt(0) === underscore &&
 | ||
|           (pedantic || !wordCharBefore || !alphanumeric(value.charAt(1)))
 | ||
|         ) {
 | ||
|           escaped.unshift(backslash)
 | ||
|         }
 | ||
| 
 | ||
|         if (
 | ||
|           value.charAt(length - 1) === underscore &&
 | ||
|           (pedantic ||
 | ||
|             !wordCharAfter ||
 | ||
|             !alphanumeric(value.charAt(length - 2)))
 | ||
|         ) {
 | ||
|           escaped.splice(-1, 0, backslash)
 | ||
|         }
 | ||
|       }
 | ||
|     }
 | ||
| 
 | ||
|     return escaped.join('')
 | ||
| 
 | ||
|     function one(character) {
 | ||
|       return escapable.indexOf(character) === -1
 | ||
|         ? entities[character]
 | ||
|         : backslash + character
 | ||
|     }
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| // Check if `index` in `value` is inside an alignment row.
 | ||
| function alignment(value, index) {
 | ||
|   var start = value.lastIndexOf(lineFeed, index)
 | ||
|   var end = value.indexOf(lineFeed, index)
 | ||
|   var char
 | ||
| 
 | ||
|   end = end === -1 ? value.length : end
 | ||
| 
 | ||
|   while (++start < end) {
 | ||
|     char = value.charAt(start)
 | ||
| 
 | ||
|     if (
 | ||
|       char !== colon &&
 | ||
|       char !== dash &&
 | ||
|       char !== space &&
 | ||
|       char !== verticalBar
 | ||
|     ) {
 | ||
|       return false
 | ||
|     }
 | ||
|   }
 | ||
| 
 | ||
|   return true
 | ||
| }
 | ||
| 
 | ||
| // Check if `node` is a text node.
 | ||
| function text(node) {
 | ||
|   return node && node.type === 'text'
 | ||
| }
 | ||
| 
 | ||
| // Check if `value` ends in a protocol.
 | ||
| function protocol(value) {
 | ||
|   var tail = value.slice(-6).toLowerCase()
 | ||
|   return tail === mailto || tail.slice(-5) === https || tail.slice(-4) === http
 | ||
| }
 | 
