3 var decimal = require('is-decimal');
4 var alphanumeric = require('is-alphanumeric');
5 var whitespace = require('is-whitespace-character');
6 var escapes = require('markdown-escapes');
7 var prefix = require('./util/entity-prefix-length');
9 module.exports = factory;
12 var BULLETS = ['*', '-', '+'];
13 var ALLIGNMENT = [':', '-', ' ', '|'];
14 var entities = {'<': '<', ':': ':', '&': '&', '|': '|', '~': '~'};
16 /* Factory to escape characters. */
17 function factory(options) {
20 /* Escape punctuation characters in a node's value. */
21 function escape(value, node, parent) {
23 var gfm = options.gfm;
24 var commonmark = options.commonmark;
25 var pedantic = options.pedantic;
26 var markers = commonmark ? ['.', ')'] : ['.'];
27 var siblings = parent && parent.children;
28 var index = siblings && siblings.indexOf(node);
29 var prev = siblings && siblings[index - 1];
30 var next = siblings && siblings[index + 1];
31 var length = value.length;
32 var escapable = escapes(options);
44 afterNewLine = text(prev) && /\n\s*$/.test(prev.value);
46 afterNewLine = !parent || parent.type === 'root' || parent.type === 'paragraph';
49 function one(character) {
50 return escapable.indexOf(character) === -1 ?
51 entities[character] : BACKSLASH + character;
54 while (++position < length) {
55 character = value.charAt(position);
58 if (character === '\n') {
61 character === BACKSLASH ||
66 (character === '&' && prefix(value.slice(position)) > 0) ||
67 (character === ']' && self.inLink) ||
68 (gfm && character === '~' && value.charAt(position + 1) === '~') ||
69 (gfm && character === '|' && (self.inTable || alignment(value, position))) ||
72 /* Delegate leading/trailing underscores
73 * to the multinode version below. */
75 position < length - 1 &&
78 !alphanumeric(value.charAt(position - 1)) ||
79 !alphanumeric(value.charAt(position + 1))
82 (gfm && !self.inLink && character === ':' && protocol(queue.join('')))
85 } else if (afterNewLine) {
89 BULLETS.indexOf(character) !== -1
92 } else if (decimal(character)) {
93 offset = position + 1;
95 while (offset < length) {
96 if (!decimal(value.charAt(offset))) {
103 if (markers.indexOf(value.charAt(offset)) !== -1) {
104 next = value.charAt(offset + 1);
106 if (!next || next === ' ' || next === '\t' || next === '\n') {
107 queue.push(value.slice(position, offset));
109 character = value.charAt(position);
116 if (afterNewLine && !whitespace(character)) {
117 afterNewLine = false;
120 queue.push(replace ? one(character) : character);
123 /* Multi-node versions. */
124 if (siblings && text(node)) {
125 /* Check for an opening parentheses after a
126 * link-reference (which can be joined by
128 if (prev && prev.referenceType === 'shortcut') {
130 length = escaped.length;
132 while (++position < length) {
133 character = escaped[position];
135 if (character === ' ' || character === '\t') {
139 if (character === '(' || character === ':') {
140 escaped[position] = one(character);
146 /* If the current node is all spaces / tabs,
147 * preceded by a shortcut, and followed by
148 * a text starting with `(`, escape it. */
151 position === length &&
152 next.value.charAt(0) === '('
154 escaped.push(BACKSLASH);
158 /* Ensure non-auto-links are not seen as links.
159 * This pattern needs to check the preceding
165 value.charAt(0) === ':' &&
166 protocol(prev.value.slice(-6))
168 escaped[0] = one(':');
171 /* Escape ampersand if it would otherwise
172 * start an entity. */
175 value.charAt(length - 1) === '&' &&
176 prefix('&' + next.value) !== 0
178 escaped[escaped.length - 1] = one('&');
181 /* Escape double tildes in GFM. */
185 value.charAt(length - 1) === '~' &&
186 next.value.charAt(0) === '~'
188 escaped.splice(escaped.length - 1, 0, BACKSLASH);
191 /* Escape underscores, but not mid-word (unless
192 * in pedantic mode). */
193 wordCharBefore = text(prev) && alphanumeric(prev.value.slice(-1));
194 wordCharAfter = text(next) && alphanumeric(next.value.charAt(0));
197 if (value === '_' && (pedantic || !wordCharBefore || !wordCharAfter)) {
198 escaped.unshift(BACKSLASH);
202 value.charAt(0) === '_' &&
203 (pedantic || !wordCharBefore || !alphanumeric(value.charAt(1)))
205 escaped.unshift(BACKSLASH);
209 value.charAt(length - 1) === '_' &&
210 (pedantic || !wordCharAfter || !alphanumeric(value.charAt(length - 2)))
212 escaped.splice(escaped.length - 1, 0, BACKSLASH);
217 return escaped.join('');
221 /* Check if `index` in `value` is inside an alignment row. */
222 function alignment(value, index) {
223 var start = value.lastIndexOf('\n', index);
224 var end = value.indexOf('\n', index);
226 start = start === -1 ? -1 : start;
227 end = end === -1 ? value.length : end;
229 while (++start < end) {
230 if (ALLIGNMENT.indexOf(value.charAt(start)) === -1) {
238 /* Check if `node` is a text node. */
239 function text(node) {
240 return node && node.type === 'text';
243 /* Check if `value` ends in a protocol. */
244 function protocol(value) {
245 var val = value.slice(-6).toLowerCase();
246 return val === 'mailto' || val.slice(-5) === 'https' || val.slice(-4) === 'http';