n3.js-messages 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,395 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.TurtleScanner = exports.LineScanner = void 0;
7
+ // **MessageScanner** finds RDF Message delimiters in a text stream.
8
+ //
9
+ // N3.js has no extension point for new directives, and its lexer rejects
10
+ // `MESSAGE` and `@message`. The scanners in this file therefore sit in front of
11
+ // the N3.js lexer: they track just enough lexical context (IRIs, strings,
12
+ // long strings, comments, words, braces) to recognize *real* delimiters, and
13
+ // rewrite them into text that N3.js can lex and that is easy to recognize
14
+ // again in N3.js' public parser callbacks:
15
+ //
16
+ // - Turtle and TriG: a delimiter becomes `@version "<sentinel>" .`.
17
+ // N3.js only accepts version directives at statement boundaries, so the
18
+ // N3.js parser itself rejects delimiters in other grammar positions, and
19
+ // reports the delimiter through its public `onVersion` callback, exactly in
20
+ // order with the emitted quads. The dot matters: the N3.js lexer reads an
21
+ // `@` after a literal as a language tag, so a literal that ends a SPARQL-style
22
+ // `VERSION "label"` directive would swallow a following `@prefix`.
23
+ // For the same reason, SPARQL-style `VERSION "label"` directives in the input
24
+ // are rewritten to `@version "label" .`.
25
+ // - N-Triples and N-Quads: N3.js has no version directive in line mode.
26
+ // The grammar is line-based, so a delimiter or version line becomes the
27
+ // comment `#<sentinel>M<line>` / `#<sentinel>V<label>`, which is reported through
28
+ // the public `onComment` callback, again in order with the quads.
29
+ //
30
+ // Delimiters that are not recognized (inside strings, IRIs, comments, prefixed
31
+ // names or graph blocks) are passed through unchanged. The scanners never
32
+ // throw: whatever they do not rewrite is rejected by N3.js as a syntax error.
33
+
34
+ const TOP = 0,
35
+ IRI = 1,
36
+ LT = 2,
37
+ COMMENT = 3,
38
+ STRING = 4,
39
+ QUOTE1 = 5,
40
+ QUOTE2 = 6,
41
+ LONG = 7,
42
+ CANDIDATE = 8;
43
+ const MESSAGE = 'MESSAGE',
44
+ AT_MESSAGE = '@message',
45
+ VERSION = 'VERSION';
46
+ function isWhitespace(code) {
47
+ return code === 32 || code === 9 || code === 10 || code === 13;
48
+ }
49
+
50
+ // Compares a character to a position of a keyword; `VERSION` is case-insensitive
51
+ function matches(keyword, index, code) {
52
+ return keyword === VERSION ? String.fromCharCode(code).toUpperCase() === keyword[index] : code === keyword.charCodeAt(index);
53
+ }
54
+
55
+ // Characters that may follow `MESSAGE`: whitespace, a comment, or an IRI
56
+ function endsMessageKeyword(code) {
57
+ return isWhitespace(code) || code === 35 || code === 60;
58
+ }
59
+
60
+ // Characters that may follow `@message`: whitespace, a comment, or the dot
61
+ function endsAtMessageKeyword(code) {
62
+ return isWhitespace(code) || code === 35 || code === 46;
63
+ }
64
+
65
+ // ## Turtle and TriG
66
+ class TurtleScanner {
67
+ constructor(sentinel) {
68
+ this._sentinel = sentinel;
69
+ this._state = TOP;
70
+ // Whether the next character can start a new token
71
+ this._tokenStart = true;
72
+ // Whether we are inside a bare word (prefixed name, number, keyword)
73
+ this._inWord = false;
74
+ this._wordEscape = false;
75
+ this._quote = 0;
76
+ this._escaped = false;
77
+ this._quoteRun = 0;
78
+ this._depth = 0;
79
+ this._candidate = '';
80
+ this._target = '';
81
+ this._candidateLine = 1;
82
+ this._beforeCandidate = 32;
83
+ this._versionPending = false;
84
+ this._line = 1;
85
+ this._afterCR = false;
86
+ this._started = false;
87
+ this._lastOut = 32;
88
+ // The line of every rewritten delimiter that was not yet consumed
89
+ this.delimiterLines = [];
90
+ }
91
+
92
+ // ### `push` scans a chunk of text and returns the text to hand to N3.js
93
+ push(text) {
94
+ let out = '';
95
+ let segmentStart = 0;
96
+ for (let i = 0; i < text.length; i++) {
97
+ const code = text.charCodeAt(i);
98
+ // Keep track of the line number (CRLF counts once, also across chunks)
99
+ if (code === 10) {
100
+ if (!this._afterCR) this._line++;
101
+ this._afterCR = false;
102
+ } else if (code === 13) {
103
+ this._line++;
104
+ this._afterCR = true;
105
+ } else this._afterCR = false;
106
+
107
+ // A state change may require the same character to be handled again
108
+ let again;
109
+ do {
110
+ again = false;
111
+ switch (this._state) {
112
+ case TOP:
113
+ if (this._scanTop(code)) {
114
+ // A possible delimiter starts here: hold it back until it is resolved
115
+ out += text.substring(segmentStart, i);
116
+ segmentStart = i + 1;
117
+ this._candidate = text[i];
118
+ this._candidateLine = this._line;
119
+ this._beforeCandidate = i > 0 ? text.charCodeAt(i - 1) : this._lastOut;
120
+ this._target = code === 77 ? MESSAGE : code === 64 ? AT_MESSAGE : VERSION;
121
+ this._state = CANDIDATE;
122
+ }
123
+ break;
124
+ case CANDIDATE:
125
+ {
126
+ const index = this._candidate.length,
127
+ target = this._target;
128
+ if (index < target.length && matches(target, index, code)) {
129
+ this._candidate += text[i];
130
+ segmentStart = i + 1;
131
+ } else {
132
+ const complete = index === target.length && (target === AT_MESSAGE ? endsAtMessageKeyword(code) : endsMessageKeyword(code));
133
+ out += this._resolveCandidate(complete);
134
+ segmentStart = i;
135
+ again = true;
136
+ }
137
+ break;
138
+ }
139
+ case IRI:
140
+ if (code === 62) this._endToken(true);
141
+ break;
142
+ case LT:
143
+ // `<<` starts a reified triple, everything else an IRI
144
+ if (code === 60) this._endToken(true);else {
145
+ this._state = IRI;
146
+ again = true;
147
+ }
148
+ break;
149
+ case COMMENT:
150
+ if (code === 10 || code === 13) this._endToken(true);
151
+ break;
152
+ case STRING:
153
+ if (this._escaped) this._escaped = false;else if (code === 92) this._escaped = true;else if (code === this._quote) {
154
+ this._endToken(false);
155
+ if (this._versionPending) {
156
+ // Terminate a SPARQL-style version directive, which we turned into an `@version` directive
157
+ this._versionPending = false;
158
+ out += `${text.substring(segmentStart, i + 1)} .`;
159
+ segmentStart = i + 1;
160
+ }
161
+ }
162
+ break;
163
+ case QUOTE1:
164
+ // A second quote either starts a long string or ends an empty string
165
+ if (code === this._quote) this._state = QUOTE2;else {
166
+ this._state = STRING;
167
+ again = true;
168
+ }
169
+ break;
170
+ case QUOTE2:
171
+ if (code === this._quote) {
172
+ this._state = LONG;
173
+ this._escaped = false;
174
+ this._quoteRun = 0;
175
+ } else {
176
+ this._endToken(false);
177
+ again = true;
178
+ }
179
+ break;
180
+ default:
181
+ // LONG
182
+ if (this._escaped) {
183
+ this._escaped = false;
184
+ this._quoteRun = 0;
185
+ } else if (code === 92) {
186
+ this._escaped = true;
187
+ this._quoteRun = 0;
188
+ } else if (code === this._quote) {
189
+ if (++this._quoteRun === 3) this._endToken(false);
190
+ } else this._quoteRun = 0;
191
+ }
192
+ } while (again);
193
+ }
194
+ out += text.substring(segmentStart);
195
+ return this._remember(out);
196
+ }
197
+
198
+ // ### `end` resolves any held-back text at the end of the input
199
+ end() {
200
+ let out = '';
201
+ if (this._state === CANDIDATE) out = this._resolveCandidate(this._candidate.length === this._target.length);
202
+ return this._remember(out);
203
+ }
204
+
205
+ // ### `_scanTop` handles a character between tokens or inside a word.
206
+ // It returns true if the character may start a message delimiter.
207
+ _scanTop(code) {
208
+ // An escape inside a word makes the next character part of the word
209
+ if (this._wordEscape) {
210
+ this._wordEscape = false;
211
+ return false;
212
+ }
213
+ switch (code) {
214
+ case 32:
215
+ case 9:
216
+ case 10:
217
+ case 13:
218
+ this._structural();
219
+ break;
220
+ case 0xFEFF:
221
+ // A byte order mark at the very start is not part of any token
222
+ if (this._started) this._word();
223
+ break;
224
+ case 35:
225
+ // #
226
+ this._state = COMMENT;
227
+ this._inWord = false;
228
+ break;
229
+ case 60:
230
+ // <
231
+ this._state = LT;
232
+ this._inWord = false;
233
+ break;
234
+ case 34:
235
+ case 39:
236
+ // " '
237
+ this._state = QUOTE1;
238
+ this._quote = code;
239
+ this._inWord = false;
240
+ break;
241
+ case 123:
242
+ // {
243
+ this._depth++;
244
+ this._structural();
245
+ break;
246
+ case 125:
247
+ // }
248
+ this._depth = Math.max(0, this._depth - 1);
249
+ this._structural();
250
+ break;
251
+ case 40:
252
+ case 41:
253
+ case 91:
254
+ case 93:
255
+ case 44:
256
+ case 59:
257
+ // ( ) [ ] , ;
258
+ this._structural();
259
+ break;
260
+ case 46:
261
+ // . ends a statement, unless it is part of a word
262
+ if (!this._inWord) this._structural();
263
+ break;
264
+ case 92:
265
+ // \
266
+ this._word();
267
+ this._wordEscape = true;
268
+ break;
269
+ case 77:
270
+ case 64:
271
+ case 86:
272
+ case 118:
273
+ // M @ V v
274
+ if (this._tokenStart) {
275
+ this._started = true;
276
+ return true;
277
+ }
278
+ this._word();
279
+ break;
280
+ default:
281
+ this._word();
282
+ }
283
+ this._started = true;
284
+ return false;
285
+ }
286
+ _structural() {
287
+ this._inWord = false;
288
+ this._tokenStart = true;
289
+ }
290
+ _word() {
291
+ this._inWord = true;
292
+ this._tokenStart = false;
293
+ }
294
+
295
+ // ### `_endToken` returns to the top level after an IRI, string or comment
296
+ _endToken(tokenStart) {
297
+ this._state = TOP;
298
+ this._inWord = false;
299
+ this._tokenStart = tokenStart;
300
+ }
301
+
302
+ // ### `_resolveCandidate` decides whether a held-back candidate is a delimiter
303
+ // and returns the text to emit for it
304
+ _resolveCandidate(complete) {
305
+ const candidate = this._candidate;
306
+ this._state = TOP;
307
+ this._candidate = '';
308
+ // A delimiter in a graph block is not rewritten, so that N3.js rejects it
309
+ if (!complete || this._depth > 0 && this._target !== VERSION) {
310
+ this._word();
311
+ return candidate;
312
+ }
313
+ this._structural();
314
+ const previous = this._beforeCandidate;
315
+ const space = isWhitespace(previous) || previous === 0xFEFF ? '' : ' ';
316
+ if (this._target === VERSION) {
317
+ this._versionPending = true;
318
+ return `${space}@version`;
319
+ }
320
+ this.delimiterLines.push(this._candidateLine);
321
+ return `${space}@version "${this._sentinel}"${candidate === MESSAGE ? ' .' : ''}`;
322
+ }
323
+ _remember(out) {
324
+ if (out.length > 0) this._lastOut = out.charCodeAt(out.length - 1);
325
+ return out;
326
+ }
327
+ }
328
+
329
+ // ## N-Triples and N-Quads
330
+ // Lines of the form `MESSAGE` or `VERSION "label"` are the only directives.
331
+ // They cannot be confused with strings or IRIs, as those never span lines.
332
+ exports.TurtleScanner = TurtleScanner;
333
+ class LineScanner {
334
+ constructor(sentinel) {
335
+ this._sentinel = sentinel;
336
+ this._buffer = '';
337
+ this._line = 1;
338
+ this._messageLine = new RegExp(`^[ \\t]*${MESSAGE}[ \\t]*(?:#.*)?$`);
339
+ this._versionLine = /^[ \t]*VERSION[ \t]*(?:"([^"\\]*)"|'([^'\\]*)')[ \t]*(?:#.*)?$/i;
340
+ }
341
+
342
+ // ### `push` scans a chunk of text and returns the complete lines, rewritten
343
+ push(text) {
344
+ const data = this._buffer + text;
345
+ // A final carriage return waits for a possible line feed, so that CRLF counts as one line break
346
+ const limit = data.endsWith('\r') ? data.length - 1 : data.length;
347
+ const lastBreak = Math.max(data.lastIndexOf('\n', limit - 1), data.lastIndexOf('\r', limit - 1));
348
+ if (lastBreak < 0) {
349
+ this._buffer = data;
350
+ return '';
351
+ }
352
+ this._buffer = data.substring(lastBreak + 1);
353
+ return this._rewrite(data.substring(0, lastBreak + 1));
354
+ }
355
+
356
+ // ### `end` returns the last line, which has no line break
357
+ end() {
358
+ const rest = this._buffer;
359
+ this._buffer = '';
360
+ return rest.length > 0 ? this._rewrite(rest) : '';
361
+ }
362
+ _rewrite(block) {
363
+ let out = '';
364
+ let copyFrom = 0,
365
+ lineStart = 0;
366
+ const breaks = /\r\n|\n|\r/g;
367
+ let match;
368
+ do {
369
+ match = breaks.exec(block);
370
+ const lineEnd = match ? match.index : block.length;
371
+ const replacement = this._rewriteLine(block, lineStart, lineEnd);
372
+ if (replacement !== null) {
373
+ out += block.substring(copyFrom, lineStart) + replacement;
374
+ copyFrom = lineEnd;
375
+ }
376
+ lineStart = match ? breaks.lastIndex : block.length + 1;
377
+ if (match) this._line++;
378
+ } while (lineStart <= block.length);
379
+ return out + block.substring(copyFrom);
380
+ }
381
+
382
+ // Only lines that start with `MESSAGE` or `VERSION` need a closer look
383
+ _rewriteLine(block, start, end) {
384
+ let first = start;
385
+ while (first < end && (block[first] === ' ' || block[first] === '\t')) first++;
386
+ const code = block.charCodeAt(first);
387
+ if (first === end || code !== 77 && code !== 86 && code !== 118) return null;
388
+ const line = block.substring(first, end);
389
+ if (this._messageLine.test(line)) return `#${this._sentinel}M${this._line}`;
390
+ const version = this._versionLine.exec(line);
391
+ if (version) return `#${this._sentinel}V${version[1] === undefined ? version[2] : version[1]}`;
392
+ return null;
393
+ }
394
+ }
395
+ exports.LineScanner = LineScanner;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _stream = require("stream");
8
+ var _string_decoder = require("string_decoder");
9
+ var _MessageParserCore = _interopRequireDefault(require("./MessageParserCore.js"));
10
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
+ // **MessageStreamParser** is a Node.js stream that turns text into RDF Messages.
12
+ //
13
+ // It consumes strings or buffers, and produces one array of quads per message,
14
+ // as soon as that message is complete.
15
+
16
+ class MessageStreamParser extends _stream.Transform {
17
+ constructor(options) {
18
+ super({
19
+ decodeStrings: false,
20
+ readableObjectMode: true
21
+ });
22
+ this._decoder = new _string_decoder.StringDecoder('utf8');
23
+ this._error = null;
24
+ this._core = new _MessageParserCore.default(options, {
25
+ message: quads => this.push(quads),
26
+ end: () => {},
27
+ error: error => {
28
+ this._error = this._error || error;
29
+ },
30
+ prefix: (prefix, term) => this.emit('prefix', prefix, term),
31
+ version: label => this.emit('version', label)
32
+ });
33
+ }
34
+ _transform(chunk, encoding, done) {
35
+ this._core.write(typeof chunk === 'string' ? chunk : this._decoder.write(chunk));
36
+ done(this._error);
37
+ }
38
+ _flush(done) {
39
+ this._core.write(this._decoder.end());
40
+ this._core.end();
41
+ done(this._error);
42
+ }
43
+ }
44
+ exports.default = MessageStreamParser;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _stream = require("stream");
8
+ var _MessageWriter = _interopRequireDefault(require("./MessageWriter.js"));
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ // **MessageStreamWriter** is a Node.js stream that serializes RDF Messages.
11
+ //
12
+ // It consumes messages (arrays of quads) and produces text.
13
+
14
+ class MessageStreamWriter extends _stream.Transform {
15
+ constructor(options) {
16
+ super({
17
+ writableObjectMode: true,
18
+ encoding: 'utf8'
19
+ });
20
+ // Writing into this stream never blocks, and output is pushed synchronously
21
+ const output = {
22
+ write: (chunk, encoding, done) => (this.push(chunk), done && done())
23
+ };
24
+ this._writer = new _MessageWriter.default(output, Object.assign({}, options, {
25
+ end: false
26
+ }));
27
+ }
28
+ _transform(message, encoding, done) {
29
+ this._writer.addMessage(message, done);
30
+ }
31
+ _flush(done) {
32
+ this._writer.end(done);
33
+ }
34
+ }
35
+ exports.default = MessageStreamWriter;
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ exports.writeMessages = writeMessages;
8
+ var _n = require("n3");
9
+ var _formats = require("./formats.js");
10
+ // **MessageWriter** writes RDF Message Logs (Turtle, TriG, N-Triples and N-Quads).
11
+ //
12
+ // Every message is written by a fresh N3.js writer, so no serialization state
13
+ // (current graph, subject, predicate) leaks from one message into the next.
14
+ // Every message is followed by a delimiter (`MESSAGE` in line formats, `@message .` in Turtle and TriG), so that consumers receive
15
+ // a message as soon as it is written, and so that trailing empty messages survive
16
+ // a round trip (a delimiter before the end of the input does not create a message).
17
+
18
+ class MessageWriter {
19
+ // `outputStream` is optional: without it, `end` returns the output as a string
20
+ constructor(outputStream, options) {
21
+ if (outputStream && typeof outputStream.write !== 'function') options = outputStream, outputStream = null;
22
+ options = options || {};
23
+ this._format = (0, _formats.resolveFormat)(options.format);
24
+ this._options = options;
25
+ this._version = options.version === undefined ? '1.2-messages' : options.version;
26
+ if (!(0, _formats.isMessagesVersion)(this._version) || !(0, _formats.isSupportedVersion)((0, _formats.baseVersion)(this._version))) throw new TypeError(`Unsupported RDF Messages version: "${this._version}"`);
27
+ this._output = '';
28
+ this._stream = outputStream;
29
+ this._endStream = !outputStream || options.end !== false;
30
+ this._pending = 0;
31
+ this._waiting = [];
32
+ this._error = null;
33
+ this._muted = false;
34
+ this._started = false;
35
+ this._ended = false;
36
+ this._declared = Object.assign({}, options.prefixes);
37
+ this._newPrefixes = {};
38
+ // The N3.js writers write to this sink, which they never end
39
+ this._sink = {
40
+ write: (chunk, encoding, done) => this._write(chunk, done)
41
+ };
42
+ }
43
+
44
+ // ### `addMessage` writes the quads of one message, followed by a delimiter.
45
+ // The callback (or returned promise) completes once the output is flushed.
46
+ addMessage(quads, done) {
47
+ return this._complete(done, () => {
48
+ this._start();
49
+ this._writeMessage(quads || []);
50
+ });
51
+ }
52
+
53
+ // ### `addPrefix` declares a prefix, which is written before the next message
54
+ // and stays in effect for all messages after it
55
+ addPrefix(prefix, iri, done) {
56
+ return this._complete(done, () => {
57
+ if (!this._format.lineMode && this._declared[prefix] !== (iri.value || iri)) this._newPrefixes[prefix] = iri.value || iri;
58
+ });
59
+ }
60
+
61
+ // ### `end` ends the log and, unless disabled, the output stream.
62
+ // The callback receives the output as a string if no output stream was given.
63
+ end(done) {
64
+ // Ending twice is harmless
65
+ if (this._ended) return typeof done === 'function' ? done(null) : Promise.resolve();
66
+ this._start();
67
+ if (typeof done === 'function') return this._finish(done);
68
+ return new Promise((resolve, reject) => this._finish((error, result) => error ? reject(error) : resolve(result)));
69
+ }
70
+
71
+ // ## Private methods
72
+
73
+ // Runs an action and reports completion with a callback or a promise
74
+ _complete(done, action) {
75
+ if (this._ended) this._error = this._error || new Error('Cannot write after end');else try {
76
+ action();
77
+ } catch (error) {
78
+ this._error = this._error || error;
79
+ }
80
+ if (typeof done === 'function') return this._whenFlushed(done);
81
+ return new Promise((resolve, reject) => this._whenFlushed(error => error ? reject(error) : resolve()));
82
+ }
83
+
84
+ // Ends the output once all queued writes were accepted
85
+ _finish(callback) {
86
+ this._ended = true;
87
+ this._whenFlushed(error => this._endOutput(error, callback));
88
+ }
89
+ _endOutput(error, callback) {
90
+ if (error || !this._endStream) return callback(error);
91
+ if (!this._stream) return callback(null, this._output);
92
+ try {
93
+ this._stream.end(endError => callback(endError));
94
+ } catch (endError) {
95
+ callback(endError);
96
+ }
97
+ }
98
+
99
+ // Calls back once all queued writes were accepted by the output
100
+ _whenFlushed(done) {
101
+ if (this._pending === 0) this._settle(done);else this._waiting.push(done);
102
+ }
103
+ _settle(done) {
104
+ const error = this._error;
105
+ this._error = null;
106
+ done(error || null);
107
+ }
108
+ _write(chunk, done) {
109
+ if (this._muted || !this._stream) {
110
+ if (!this._muted) this._output += chunk;
111
+ return done && done();
112
+ }
113
+ this._pending++;
114
+ try {
115
+ this._stream.write(chunk, 'utf8', error => this._written(error, done));
116
+ } catch (error) {
117
+ this._written(error, done);
118
+ }
119
+ }
120
+
121
+ // Registers that the output accepted a write, and wakes up those waiting for the output
122
+ _written(error, done) {
123
+ this._pending--;
124
+ this._error = this._error || error || null;
125
+ done && done(error);
126
+ if (this._pending === 0) this._waiting.splice(0).forEach(callback => this._settle(callback));
127
+ }
128
+
129
+ // Announces the RDF Messages version, which makes parsers expect delimiters
130
+ _start() {
131
+ if (!this._started) {
132
+ this._started = true;
133
+ this._write(this._format.lineMode ? `VERSION "${this._version}"\n` : `@version "${this._version}" .\n`);
134
+ }
135
+ }
136
+ _writeMessage(quads) {
137
+ const first = !this._messageWritten;
138
+ const options = {
139
+ format: this._format.name,
140
+ end: false
141
+ };
142
+ if (this._options.baseIRI) {
143
+ options.baseIRI = this._options.baseIRI;
144
+ // The base is declared by us, once, as it stays in effect (and older N3.js writers lack `writeBase`)
145
+ if (first && this._options.writeBase && !this._format.lineMode) this._write(`@base <${this._options.baseIRI}>.\n`);
146
+ }
147
+ // Prefixes and base are declared once, as they stay in effect
148
+ options.prefixes = this._declared;
149
+ this._muted = !first;
150
+ const writer = new _n.Writer(this._sink, options);
151
+ this._muted = false;
152
+ this._messageWritten = true;
153
+ for (const prefix of Object.keys(this._newPrefixes)) {
154
+ writer.addPrefix(prefix, this._newPrefixes[prefix]);
155
+ this._declared[prefix] = this._newPrefixes[prefix];
156
+ }
157
+ this._newPrefixes = {};
158
+ const record = error => error && (this._error = this._error || error);
159
+ for (const quad of quads) writer.addQuad(quad.subject, quad.predicate, quad.object, quad.graph, record);
160
+ writer.end(record);
161
+ this._write(this._format.lineMode ? 'MESSAGE\n' : '@message .\n');
162
+ }
163
+ }
164
+
165
+ // ### `writeMessages` synchronously serializes an array of messages to a string
166
+ exports.default = MessageWriter;
167
+ function writeMessages(messages, options) {
168
+ const writer = new MessageWriter(options);
169
+ const errors = [];
170
+ let output;
171
+ function collect(error) {
172
+ if (error) errors.push(error);
173
+ }
174
+ for (const message of messages) writer.addMessage(message, collect);
175
+ writer.end((error, result) => (collect(error), output = result));
176
+ if (errors.length > 0) throw errors[0];
177
+ return output;
178
+ }