.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / parse-entities / index.js
1 'use strict'
2
3 var legacy = require('character-entities-legacy')
4 var invalid = require('character-reference-invalid')
5 var decimal = require('is-decimal')
6 var hexadecimal = require('is-hexadecimal')
7 var alphanumerical = require('is-alphanumerical')
8 var decodeEntity = require('./decode-entity')
9
10 module.exports = parseEntities
11
12 var own = {}.hasOwnProperty
13 var fromCharCode = String.fromCharCode
14 var noop = Function.prototype
15
16 // Default settings.
17 var defaults = {
18   warning: null,
19   reference: null,
20   text: null,
21   warningContext: null,
22   referenceContext: null,
23   textContext: null,
24   position: {},
25   additional: null,
26   attribute: false,
27   nonTerminated: true
28 }
29
30 // Characters.
31 var tab = 9 // '\t'
32 var lineFeed = 10 // '\n'
33 var formFeed = 12 //  '\f'
34 var space = 32 // ' '
35 var ampersand = 38 //  '&'
36 var semicolon = 59 //  ';'
37 var lessThan = 60 //  '<'
38 var equalsTo = 61 //  '='
39 var numberSign = 35 //  '#'
40 var uppercaseX = 88 //  'X'
41 var lowercaseX = 120 //  'x'
42 var replacementCharacter = 65533 // '�'
43
44 // Reference types.
45 var name = 'named'
46 var hexa = 'hexadecimal'
47 var deci = 'decimal'
48
49 // Map of bases.
50 var bases = {}
51
52 bases[hexa] = 16
53 bases[deci] = 10
54
55 // Map of types to tests.
56 // Each type of character reference accepts different characters.
57 // This test is used to detect whether a reference has ended (as the semicolon
58 // is not strictly needed).
59 var tests = {}
60
61 tests[name] = alphanumerical
62 tests[deci] = decimal
63 tests[hexa] = hexadecimal
64
65 // Warning types.
66 var namedNotTerminated = 1
67 var numericNotTerminated = 2
68 var namedEmpty = 3
69 var numericEmpty = 4
70 var namedUnknown = 5
71 var numericDisallowed = 6
72 var numericProhibited = 7
73
74 // Warning messages.
75 var messages = {}
76
77 messages[namedNotTerminated] =
78   'Named character references must be terminated by a semicolon'
79 messages[numericNotTerminated] =
80   'Numeric character references must be terminated by a semicolon'
81 messages[namedEmpty] = 'Named character references cannot be empty'
82 messages[numericEmpty] = 'Numeric character references cannot be empty'
83 messages[namedUnknown] = 'Named character references must be known'
84 messages[numericDisallowed] =
85   'Numeric character references cannot be disallowed'
86 messages[numericProhibited] =
87   'Numeric character references cannot be outside the permissible Unicode range'
88
89 // Wrap to ensure clean parameters are given to `parse`.
90 function parseEntities(value, options) {
91   var settings = {}
92   var option
93   var key
94
95   if (!options) {
96     options = {}
97   }
98
99   for (key in defaults) {
100     option = options[key]
101     settings[key] =
102       option === null || option === undefined ? defaults[key] : option
103   }
104
105   if (settings.position.indent || settings.position.start) {
106     settings.indent = settings.position.indent || []
107     settings.position = settings.position.start
108   }
109
110   return parse(value, settings)
111 }
112
113 // Parse entities.
114 // eslint-disable-next-line complexity
115 function parse(value, settings) {
116   var additional = settings.additional
117   var nonTerminated = settings.nonTerminated
118   var handleText = settings.text
119   var handleReference = settings.reference
120   var handleWarning = settings.warning
121   var textContext = settings.textContext
122   var referenceContext = settings.referenceContext
123   var warningContext = settings.warningContext
124   var pos = settings.position
125   var indent = settings.indent || []
126   var length = value.length
127   var index = 0
128   var lines = -1
129   var column = pos.column || 1
130   var line = pos.line || 1
131   var queue = ''
132   var result = []
133   var entityCharacters
134   var namedEntity
135   var terminated
136   var characters
137   var character
138   var reference
139   var following
140   var warning
141   var reason
142   var output
143   var entity
144   var begin
145   var start
146   var type
147   var test
148   var prev
149   var next
150   var diff
151   var end
152
153   if (typeof additional === 'string') {
154     additional = additional.charCodeAt(0)
155   }
156
157   // Cache the current point.
158   prev = now()
159
160   // Wrap `handleWarning`.
161   warning = handleWarning ? parseError : noop
162
163   // Ensure the algorithm walks over the first character and the end (inclusive).
164   index--
165   length++
166
167   while (++index < length) {
168     // If the previous character was a newline.
169     if (character === lineFeed) {
170       column = indent[lines] || 1
171     }
172
173     character = value.charCodeAt(index)
174
175     if (character === ampersand) {
176       following = value.charCodeAt(index + 1)
177
178       // The behaviour depends on the identity of the next character.
179       if (
180         following === tab ||
181         following === lineFeed ||
182         following === formFeed ||
183         following === space ||
184         following === ampersand ||
185         following === lessThan ||
186         following !== following ||
187         (additional && following === additional)
188       ) {
189         // Not a character reference.
190         // No characters are consumed, and nothing is returned.
191         // This is not an error, either.
192         queue += fromCharCode(character)
193         column++
194
195         continue
196       }
197
198       start = index + 1
199       begin = start
200       end = start
201
202       if (following === numberSign) {
203         // Numerical entity.
204         end = ++begin
205
206         // The behaviour further depends on the next character.
207         following = value.charCodeAt(end)
208
209         if (following === uppercaseX || following === lowercaseX) {
210           // ASCII hex digits.
211           type = hexa
212           end = ++begin
213         } else {
214           // ASCII digits.
215           type = deci
216         }
217       } else {
218         // Named entity.
219         type = name
220       }
221
222       entityCharacters = ''
223       entity = ''
224       characters = ''
225       test = tests[type]
226       end--
227
228       while (++end < length) {
229         following = value.charCodeAt(end)
230
231         if (!test(following)) {
232           break
233         }
234
235         characters += fromCharCode(following)
236
237         // Check if we can match a legacy named reference.
238         // If so, we cache that as the last viable named reference.
239         // This ensures we do not need to walk backwards later.
240         if (type === name && own.call(legacy, characters)) {
241           entityCharacters = characters
242           entity = legacy[characters]
243         }
244       }
245
246       terminated = value.charCodeAt(end) === semicolon
247
248       if (terminated) {
249         end++
250
251         namedEntity = type === name ? decodeEntity(characters) : false
252
253         if (namedEntity) {
254           entityCharacters = characters
255           entity = namedEntity
256         }
257       }
258
259       diff = 1 + end - start
260
261       if (!terminated && !nonTerminated) {
262         // Empty.
263       } else if (!characters) {
264         // An empty (possible) entity is valid, unless it’s numeric (thus an
265         // ampersand followed by an octothorp).
266         if (type !== name) {
267           warning(numericEmpty, diff)
268         }
269       } else if (type === name) {
270         // An ampersand followed by anything unknown, and not terminated, is
271         // invalid.
272         if (terminated && !entity) {
273           warning(namedUnknown, 1)
274         } else {
275           // If theres something after an entity name which is not known, cap
276           // the reference.
277           if (entityCharacters !== characters) {
278             end = begin + entityCharacters.length
279             diff = 1 + end - begin
280             terminated = false
281           }
282
283           // If the reference is not terminated, warn.
284           if (!terminated) {
285             reason = entityCharacters ? namedNotTerminated : namedEmpty
286
287             if (settings.attribute) {
288               following = value.charCodeAt(end)
289
290               if (following === equalsTo) {
291                 warning(reason, diff)
292                 entity = null
293               } else if (alphanumerical(following)) {
294                 entity = null
295               } else {
296                 warning(reason, diff)
297               }
298             } else {
299               warning(reason, diff)
300             }
301           }
302         }
303
304         reference = entity
305       } else {
306         if (!terminated) {
307           // All non-terminated numeric entities are not rendered, and trigger a
308           // warning.
309           warning(numericNotTerminated, diff)
310         }
311
312         // When terminated and number, parse as either hexadecimal or decimal.
313         reference = parseInt(characters, bases[type])
314
315         // Trigger a warning when the parsed number is prohibited, and replace
316         // with replacement character.
317         if (prohibited(reference)) {
318           warning(numericProhibited, diff)
319           reference = fromCharCode(replacementCharacter)
320         } else if (reference in invalid) {
321           // Trigger a warning when the parsed number is disallowed, and replace
322           // by an alternative.
323           warning(numericDisallowed, diff)
324           reference = invalid[reference]
325         } else {
326           // Parse the number.
327           output = ''
328
329           // Trigger a warning when the parsed number should not be used.
330           if (disallowed(reference)) {
331             warning(numericDisallowed, diff)
332           }
333
334           // Stringify the number.
335           if (reference > 0xffff) {
336             reference -= 0x10000
337             output += fromCharCode((reference >>> (10 & 0x3ff)) | 0xd800)
338             reference = 0xdc00 | (reference & 0x3ff)
339           }
340
341           reference = output + fromCharCode(reference)
342         }
343       }
344
345       // Found it!
346       // First eat the queued characters as normal text, then eat an entity.
347       if (reference) {
348         flush()
349
350         prev = now()
351         index = end - 1
352         column += end - start + 1
353         result.push(reference)
354         next = now()
355         next.offset++
356
357         if (handleReference) {
358           handleReference.call(
359             referenceContext,
360             reference,
361             {start: prev, end: next},
362             value.slice(start - 1, end)
363           )
364         }
365
366         prev = next
367       } else {
368         // If we could not find a reference, queue the checked characters (as
369         // normal characters), and move the pointer to their end.
370         // This is possible because we can be certain neither newlines nor
371         // ampersands are included.
372         characters = value.slice(start - 1, end)
373         queue += characters
374         column += characters.length
375         index = end - 1
376       }
377     } else {
378       // Handle anything other than an ampersand, including newlines and EOF.
379       if (
380         character === 10 // Line feed
381       ) {
382         line++
383         lines++
384         column = 0
385       }
386
387       if (character === character) {
388         queue += fromCharCode(character)
389         column++
390       } else {
391         flush()
392       }
393     }
394   }
395
396   // Return the reduced nodes, and any possible warnings.
397   return result.join('')
398
399   // Get current position.
400   function now() {
401     return {
402       line: line,
403       column: column,
404       offset: index + (pos.offset || 0)
405     }
406   }
407
408   // “Throw” a parse-error: a warning.
409   function parseError(code, offset) {
410     var position = now()
411
412     position.column += offset
413     position.offset += offset
414
415     handleWarning.call(warningContext, messages[code], position, code)
416   }
417
418   // Flush `queue` (normal text).
419   // Macro invoked before each entity and at the end of `value`.
420   // Does nothing when `queue` is empty.
421   function flush() {
422     if (queue) {
423       result.push(queue)
424
425       if (handleText) {
426         handleText.call(textContext, queue, {start: prev, end: now()})
427       }
428
429       queue = ''
430     }
431   }
432 }
433
434 // Check if `character` is outside the permissible unicode range.
435 function prohibited(code) {
436   return (code >= 0xd800 && code <= 0xdfff) || code > 0x10ffff
437 }
438
439 // Check if `character` is disallowed.
440 function disallowed(code) {
441   return (
442     (code >= 0x0001 && code <= 0x0008) ||
443     code === 0x000b ||
444     (code >= 0x000d && code <= 0x001f) ||
445     (code >= 0x007f && code <= 0x009f) ||
446     (code >= 0xfdd0 && code <= 0xfdef) ||
447     (code & 0xffff) === 0xffff ||
448     (code & 0xffff) === 0xfffe
449   )
450 }