1 | 'use strict';
|
2 |
|
3 | var Scalar = require('../nodes/Scalar.js');
|
4 |
|
5 | function resolveBlockScalar(ctx, scalar, onError) {
|
6 | const start = scalar.offset;
|
7 | const header = parseBlockScalarHeader(scalar, ctx.options.strict, onError);
|
8 | if (!header)
|
9 | return { value: '', type: null, comment: '', range: [start, start, start] };
|
10 | const type = header.mode === '>' ? Scalar.Scalar.BLOCK_FOLDED : Scalar.Scalar.BLOCK_LITERAL;
|
11 | const lines = scalar.source ? splitLines(scalar.source) : [];
|
12 |
|
13 | let chompStart = lines.length;
|
14 | for (let i = lines.length - 1; i >= 0; --i) {
|
15 | const content = lines[i][1];
|
16 | if (content === '' || content === '\r')
|
17 | chompStart = i;
|
18 | else
|
19 | break;
|
20 | }
|
21 |
|
22 | if (chompStart === 0) {
|
23 | const value = header.chomp === '+' && lines.length > 0
|
24 | ? '\n'.repeat(Math.max(1, lines.length - 1))
|
25 | : '';
|
26 | let end = start + header.length;
|
27 | if (scalar.source)
|
28 | end += scalar.source.length;
|
29 | return { value, type, comment: header.comment, range: [start, end, end] };
|
30 | }
|
31 |
|
32 | let trimIndent = scalar.indent + header.indent;
|
33 | let offset = scalar.offset + header.length;
|
34 | let contentStart = 0;
|
35 | for (let i = 0; i < chompStart; ++i) {
|
36 | const [indent, content] = lines[i];
|
37 | if (content === '' || content === '\r') {
|
38 | if (header.indent === 0 && indent.length > trimIndent)
|
39 | trimIndent = indent.length;
|
40 | }
|
41 | else {
|
42 | if (indent.length < trimIndent) {
|
43 | const message = 'Block scalars with more-indented leading empty lines must use an explicit indentation indicator';
|
44 | onError(offset + indent.length, 'MISSING_CHAR', message);
|
45 | }
|
46 | if (header.indent === 0)
|
47 | trimIndent = indent.length;
|
48 | contentStart = i;
|
49 | if (trimIndent === 0 && !ctx.atRoot) {
|
50 | const message = 'Block scalar values in collections must be indented';
|
51 | onError(offset, 'BAD_INDENT', message);
|
52 | }
|
53 | break;
|
54 | }
|
55 | offset += indent.length + content.length + 1;
|
56 | }
|
57 |
|
58 | for (let i = lines.length - 1; i >= chompStart; --i) {
|
59 | if (lines[i][0].length > trimIndent)
|
60 | chompStart = i + 1;
|
61 | }
|
62 | let value = '';
|
63 | let sep = '';
|
64 | let prevMoreIndented = false;
|
65 |
|
66 | for (let i = 0; i < contentStart; ++i)
|
67 | value += lines[i][0].slice(trimIndent) + '\n';
|
68 | for (let i = contentStart; i < chompStart; ++i) {
|
69 | let [indent, content] = lines[i];
|
70 | offset += indent.length + content.length + 1;
|
71 | const crlf = content[content.length - 1] === '\r';
|
72 | if (crlf)
|
73 | content = content.slice(0, -1);
|
74 |
|
75 | if (content && indent.length < trimIndent) {
|
76 | const src = header.indent
|
77 | ? 'explicit indentation indicator'
|
78 | : 'first line';
|
79 | const message = `Block scalar lines must not be less indented than their ${src}`;
|
80 | onError(offset - content.length - (crlf ? 2 : 1), 'BAD_INDENT', message);
|
81 | indent = '';
|
82 | }
|
83 | if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
84 | value += sep + indent.slice(trimIndent) + content;
|
85 | sep = '\n';
|
86 | }
|
87 | else if (indent.length > trimIndent || content[0] === '\t') {
|
88 |
|
89 | if (sep === ' ')
|
90 | sep = '\n';
|
91 | else if (!prevMoreIndented && sep === '\n')
|
92 | sep = '\n\n';
|
93 | value += sep + indent.slice(trimIndent) + content;
|
94 | sep = '\n';
|
95 | prevMoreIndented = true;
|
96 | }
|
97 | else if (content === '') {
|
98 |
|
99 | if (sep === '\n')
|
100 | value += '\n';
|
101 | else
|
102 | sep = '\n';
|
103 | }
|
104 | else {
|
105 | value += sep + content;
|
106 | sep = ' ';
|
107 | prevMoreIndented = false;
|
108 | }
|
109 | }
|
110 | switch (header.chomp) {
|
111 | case '-':
|
112 | break;
|
113 | case '+':
|
114 | for (let i = chompStart; i < lines.length; ++i)
|
115 | value += '\n' + lines[i][0].slice(trimIndent);
|
116 | if (value[value.length - 1] !== '\n')
|
117 | value += '\n';
|
118 | break;
|
119 | default:
|
120 | value += '\n';
|
121 | }
|
122 | const end = start + header.length + scalar.source.length;
|
123 | return { value, type, comment: header.comment, range: [start, end, end] };
|
124 | }
|
125 | function parseBlockScalarHeader({ offset, props }, strict, onError) {
|
126 |
|
127 | if (props[0].type !== 'block-scalar-header') {
|
128 | onError(props[0], 'IMPOSSIBLE', 'Block scalar header not found');
|
129 | return null;
|
130 | }
|
131 | const { source } = props[0];
|
132 | const mode = source[0];
|
133 | let indent = 0;
|
134 | let chomp = '';
|
135 | let error = -1;
|
136 | for (let i = 1; i < source.length; ++i) {
|
137 | const ch = source[i];
|
138 | if (!chomp && (ch === '-' || ch === '+'))
|
139 | chomp = ch;
|
140 | else {
|
141 | const n = Number(ch);
|
142 | if (!indent && n)
|
143 | indent = n;
|
144 | else if (error === -1)
|
145 | error = offset + i;
|
146 | }
|
147 | }
|
148 | if (error !== -1)
|
149 | onError(error, 'UNEXPECTED_TOKEN', `Block scalar header includes extra characters: ${source}`);
|
150 | let hasSpace = false;
|
151 | let comment = '';
|
152 | let length = source.length;
|
153 | for (let i = 1; i < props.length; ++i) {
|
154 | const token = props[i];
|
155 | switch (token.type) {
|
156 | case 'space':
|
157 | hasSpace = true;
|
158 |
|
159 | case 'newline':
|
160 | length += token.source.length;
|
161 | break;
|
162 | case 'comment':
|
163 | if (strict && !hasSpace) {
|
164 | const message = 'Comments must be separated from other tokens by white space characters';
|
165 | onError(token, 'MISSING_CHAR', message);
|
166 | }
|
167 | length += token.source.length;
|
168 | comment = token.source.substring(1);
|
169 | break;
|
170 | case 'error':
|
171 | onError(token, 'UNEXPECTED_TOKEN', token.message);
|
172 | length += token.source.length;
|
173 | break;
|
174 |
|
175 | default: {
|
176 | const message = `Unexpected token in block scalar header: ${token.type}`;
|
177 | onError(token, 'UNEXPECTED_TOKEN', message);
|
178 | const ts = token.source;
|
179 | if (ts && typeof ts === 'string')
|
180 | length += ts.length;
|
181 | }
|
182 | }
|
183 | }
|
184 | return { mode, indent, chomp, comment, length };
|
185 | }
|
186 |
|
187 | function splitLines(source) {
|
188 | const split = source.split(/\n( *)/);
|
189 | const first = split[0];
|
190 | const m = first.match(/^( *)/);
|
191 | const line0 = m?.[1]
|
192 | ? [m[1], first.slice(m[1].length)]
|
193 | : ['', first];
|
194 | const lines = [line0];
|
195 | for (let i = 1; i < split.length; i += 2)
|
196 | lines.push([split[i], split[i + 1]]);
|
197 | return lines;
|
198 | }
|
199 |
|
200 | exports.resolveBlockScalar = resolveBlockScalar;
|