--- /dev/null
+'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 BACKSLASH = '\\';
+var BULLETS = ['*', '-', '+'];
+var ALLIGNMENT = [':', '-', ' ', '|'];
+var entities = {'<': '<', ':': ':', '&': '&', '|': '|', '~': '~'};
+
+/* 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 ? ['.', ')'] : ['.'];
+ var siblings = parent && parent.children;
+ var index = siblings && siblings.indexOf(node);
+ var prev = 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 (prev) {
+ afterNewLine = text(prev) && /\n\s*$/.test(prev.value);
+ } else {
+ afterNewLine = !parent || parent.type === 'root' || parent.type === 'paragraph';
+ }
+
+ function one(character) {
+ return escapable.indexOf(character) === -1 ?
+ entities[character] : BACKSLASH + character;
+ }
+
+ while (++position < length) {
+ character = value.charAt(position);
+ replace = false;
+
+ if (character === '\n') {
+ afterNewLine = true;
+ } else if (
+ character === BACKSLASH ||
+ character === '`' ||
+ character === '*' ||
+ character === '[' ||
+ character === '<' ||
+ (character === '&' && prefix(value.slice(position)) > 0) ||
+ (character === ']' && self.inLink) ||
+ (gfm && character === '~' && value.charAt(position + 1) === '~') ||
+ (gfm && character === '|' && (self.inTable || alignment(value, position))) ||
+ (
+ character === '_' &&
+ /* 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 === ':' && protocol(queue.join('')))
+ ) {
+ replace = true;
+ } else if (afterNewLine) {
+ if (
+ character === '>' ||
+ character === '#' ||
+ BULLETS.indexOf(character) !== -1
+ ) {
+ 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 === ' ' || next === '\t' || next === '\n') {
+ 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 (prev && prev.referenceType === 'shortcut') {
+ position = -1;
+ length = escaped.length;
+
+ while (++position < length) {
+ character = escaped[position];
+
+ if (character === ' ' || character === '\t') {
+ continue;
+ }
+
+ if (character === '(' || character === ':') {
+ 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) === '('
+ ) {
+ 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(prev) &&
+ value.charAt(0) === ':' &&
+ protocol(prev.value.slice(-6))
+ ) {
+ escaped[0] = one(':');
+ }
+
+ /* Escape ampersand if it would otherwise
+ * start an entity. */
+ if (
+ text(next) &&
+ value.charAt(length - 1) === '&' &&
+ prefix('&' + next.value) !== 0
+ ) {
+ escaped[escaped.length - 1] = one('&');
+ }
+
+ /* Escape double tildes in GFM. */
+ if (
+ gfm &&
+ text(next) &&
+ value.charAt(length - 1) === '~' &&
+ next.value.charAt(0) === '~'
+ ) {
+ escaped.splice(escaped.length - 1, 0, BACKSLASH);
+ }
+
+ /* Escape underscores, but not mid-word (unless
+ * in pedantic mode). */
+ wordCharBefore = text(prev) && alphanumeric(prev.value.slice(-1));
+ wordCharAfter = text(next) && alphanumeric(next.value.charAt(0));
+
+ if (length === 1) {
+ if (value === '_' && (pedantic || !wordCharBefore || !wordCharAfter)) {
+ escaped.unshift(BACKSLASH);
+ }
+ } else {
+ if (
+ value.charAt(0) === '_' &&
+ (pedantic || !wordCharBefore || !alphanumeric(value.charAt(1)))
+ ) {
+ escaped.unshift(BACKSLASH);
+ }
+
+ if (
+ value.charAt(length - 1) === '_' &&
+ (pedantic || !wordCharAfter || !alphanumeric(value.charAt(length - 2)))
+ ) {
+ escaped.splice(escaped.length - 1, 0, BACKSLASH);
+ }
+ }
+ }
+
+ return escaped.join('');
+ }
+}
+
+/* Check if `index` in `value` is inside an alignment row. */
+function alignment(value, index) {
+ var start = value.lastIndexOf('\n', index);
+ var end = value.indexOf('\n', index);
+
+ start = start === -1 ? -1 : start;
+ end = end === -1 ? value.length : end;
+
+ while (++start < end) {
+ if (ALLIGNMENT.indexOf(value.charAt(start)) === -1) {
+ 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 val = value.slice(-6).toLowerCase();
+ return val === 'mailto' || val.slice(-5) === 'https' || val.slice(-4) === 'http';
+}