123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- import { Scalar } from '../nodes/Scalar.js';
- import { resolveEnd } from './resolve-end.js';
- function resolveFlowScalar(scalar, strict, onError) {
- const { offset, type, source, end } = scalar;
- let _type;
- let value;
- const _onError = (rel, code, msg) => onError(offset + rel, code, msg);
- switch (type) {
- case 'scalar':
- _type = Scalar.PLAIN;
- value = plainValue(source, _onError);
- break;
- case 'single-quoted-scalar':
- _type = Scalar.QUOTE_SINGLE;
- value = singleQuotedValue(source, _onError);
- break;
- case 'double-quoted-scalar':
- _type = Scalar.QUOTE_DOUBLE;
- value = doubleQuotedValue(source, _onError);
- break;
- /* istanbul ignore next should not happen */
- default:
- onError(scalar, 'UNEXPECTED_TOKEN', `Expected a flow scalar value, but found: ${type}`);
- return {
- value: '',
- type: null,
- comment: '',
- range: [offset, offset + source.length, offset + source.length]
- };
- }
- const valueEnd = offset + source.length;
- const re = resolveEnd(end, valueEnd, strict, onError);
- return {
- value,
- type: _type,
- comment: re.comment,
- range: [offset, valueEnd, re.offset]
- };
- }
- function plainValue(source, onError) {
- let badChar = '';
- switch (source[0]) {
- /* istanbul ignore next should not happen */
- case '\t':
- badChar = 'a tab character';
- break;
- case ',':
- badChar = 'flow indicator character ,';
- break;
- case '%':
- badChar = 'directive indicator character %';
- break;
- case '|':
- case '>': {
- badChar = `block scalar indicator ${source[0]}`;
- break;
- }
- case '@':
- case '`': {
- badChar = `reserved character ${source[0]}`;
- break;
- }
- }
- if (badChar)
- onError(0, 'BAD_SCALAR_START', `Plain value cannot start with ${badChar}`);
- return foldLines(source);
- }
- function singleQuotedValue(source, onError) {
- if (source[source.length - 1] !== "'" || source.length === 1)
- onError(source.length, 'MISSING_CHAR', "Missing closing 'quote");
- return foldLines(source.slice(1, -1)).replace(/''/g, "'");
- }
- function foldLines(source) {
- /**
- * The negative lookbehind here and in the `re` RegExp is to
- * prevent causing a polynomial search time in certain cases.
- *
- * The try-catch is for Safari, which doesn't support this yet:
- * https://caniuse.com/js-regexp-lookbehind
- */
- let first, line;
- try {
- first = new RegExp('(.*?)(?<![ \t])[ \t]*\r?\n', 'sy');
- line = new RegExp('[ \t]*(.*?)(?:(?<![ \t])[ \t]*)?\r?\n', 'sy');
- }
- catch {
- first = /(.*?)[ \t]*\r?\n/sy;
- line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
- }
- let match = first.exec(source);
- if (!match)
- return source;
- let res = match[1];
- let sep = ' ';
- let pos = first.lastIndex;
- line.lastIndex = pos;
- while ((match = line.exec(source))) {
- if (match[1] === '') {
- if (sep === '\n')
- res += sep;
- else
- sep = '\n';
- }
- else {
- res += sep + match[1];
- sep = ' ';
- }
- pos = line.lastIndex;
- }
- const last = /[ \t]*(.*)/sy;
- last.lastIndex = pos;
- match = last.exec(source);
- return res + sep + (match?.[1] ?? '');
- }
- function doubleQuotedValue(source, onError) {
- let res = '';
- for (let i = 1; i < source.length - 1; ++i) {
- const ch = source[i];
- if (ch === '\r' && source[i + 1] === '\n')
- continue;
- if (ch === '\n') {
- const { fold, offset } = foldNewline(source, i);
- res += fold;
- i = offset;
- }
- else if (ch === '\\') {
- let next = source[++i];
- const cc = escapeCodes[next];
- if (cc)
- res += cc;
- else if (next === '\n') {
- // skip escaped newlines, but still trim the following line
- next = source[i + 1];
- while (next === ' ' || next === '\t')
- next = source[++i + 1];
- }
- else if (next === '\r' && source[i + 1] === '\n') {
- // skip escaped CRLF newlines, but still trim the following line
- next = source[++i + 1];
- while (next === ' ' || next === '\t')
- next = source[++i + 1];
- }
- else if (next === 'x' || next === 'u' || next === 'U') {
- const length = { x: 2, u: 4, U: 8 }[next];
- res += parseCharCode(source, i + 1, length, onError);
- i += length;
- }
- else {
- const raw = source.substr(i - 1, 2);
- onError(i - 1, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
- res += raw;
- }
- }
- else if (ch === ' ' || ch === '\t') {
- // trim trailing whitespace
- const wsStart = i;
- let next = source[i + 1];
- while (next === ' ' || next === '\t')
- next = source[++i + 1];
- if (next !== '\n' && !(next === '\r' && source[i + 2] === '\n'))
- res += i > wsStart ? source.slice(wsStart, i + 1) : ch;
- }
- else {
- res += ch;
- }
- }
- if (source[source.length - 1] !== '"' || source.length === 1)
- onError(source.length, 'MISSING_CHAR', 'Missing closing "quote');
- return res;
- }
- /**
- * Fold a single newline into a space, multiple newlines to N - 1 newlines.
- * Presumes `source[offset] === '\n'`
- */
- function foldNewline(source, offset) {
- let fold = '';
- let ch = source[offset + 1];
- while (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
- if (ch === '\r' && source[offset + 2] !== '\n')
- break;
- if (ch === '\n')
- fold += '\n';
- offset += 1;
- ch = source[offset + 1];
- }
- if (!fold)
- fold = ' ';
- return { fold, offset };
- }
- const escapeCodes = {
- '0': '\0', // null character
- a: '\x07', // bell character
- b: '\b', // backspace
- e: '\x1b', // escape character
- f: '\f', // form feed
- n: '\n', // line feed
- r: '\r', // carriage return
- t: '\t', // horizontal tab
- v: '\v', // vertical tab
- N: '\u0085', // Unicode next line
- _: '\u00a0', // Unicode non-breaking space
- L: '\u2028', // Unicode line separator
- P: '\u2029', // Unicode paragraph separator
- ' ': ' ',
- '"': '"',
- '/': '/',
- '\\': '\\',
- '\t': '\t'
- };
- function parseCharCode(source, offset, length, onError) {
- const cc = source.substr(offset, length);
- const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
- const code = ok ? parseInt(cc, 16) : NaN;
- if (isNaN(code)) {
- const raw = source.substr(offset - 2, length + 2);
- onError(offset - 2, 'BAD_DQ_ESCAPE', `Invalid escape sequence ${raw}`);
- return raw;
- }
- return String.fromCodePoint(code);
- }
- export { resolveFlowScalar };
|