UNPKG

14.4 kBJavaScriptView Raw
1/*
2 * Licensed under the Apache License, Version 2.0 (the "License");
3 * you may not use this file except in compliance with the License.
4 * You may obtain a copy of the License at
5 *
6 * http://www.apache.org/licenses/LICENSE-2.0
7 *
8 * Unless required by applicable law or agreed to in writing, software
9 * distributed under the License is distributed on an "AS IS" BASIS,
10 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and
12 * limitations under the License.
13 */
14
15'use strict';
16
17const Stream = require('stream');
18
19const dijkstra = require('dijkstrajs');
20const find_path = dijkstra.find_path;
21
22const ModelLoader = require('@accordproject/concerto-core').ModelLoader;
23
24const CommonMarkTransformer = require('@accordproject/markdown-common').CommonMarkTransformer;
25const CiceroMarkTransformer = require('@accordproject/markdown-cicero').CiceroMarkTransformer;
26const TemplateMarkTransformer = require('@accordproject/markdown-template').TemplateMarkTransformer;
27const SlateTransformer = require('@accordproject/markdown-slate').SlateTransformer;
28const HtmlTransformer = require('@accordproject/markdown-html').HtmlTransformer;
29const PdfTransformer = require('@accordproject/markdown-pdf').PdfTransformer;
30const DocxTransformer = require('@accordproject/markdown-docx').DocxTransformer;
31
32/**
33 * The graph of transformation supported
34 */
35const templateToTemplateMark = async (input,parameters,options) => {
36 const t = new TemplateMarkTransformer();
37 const modelOptions = { offline: false };
38 if (options && options.offline) {
39 modelOptions.offline = true;
40 }
41 const modelManager = await ModelLoader.loadModelManager(parameters.model, options);
42 return {
43 modelManager: modelManager,
44 templateMark: t.fromMarkdownTemplate({ fileName:parameters.inputFileName, content:input }, modelManager, parameters.templateKind, options)
45 };
46};
47const transformationGraph = {
48 markdown_template: {
49 docs: 'Template markdown (string)',
50 fileFormat: 'utf8',
51 templatemark_tokens: (input,parameters,options) => {
52 const t = new TemplateMarkTransformer();
53 return t.toTokens({ fileName:parameters.inputFileName, content:input }, options);
54 },
55 },
56 templatemark_tokens: {
57 docs: 'TemplateMark tokens (JSON)',
58 fileFormat: 'json',
59 templatemark: async (input,parameters,options) => {
60 const t = new TemplateMarkTransformer();
61 const modelManager = await ModelLoader.loadModelManager(parameters.model, options);
62 return t.tokensToMarkdownTemplate(input, modelManager, parameters.templateKind, options);
63 },
64 },
65 templatemark: {
66 docs: 'TemplateMark DOM (JSON)',
67 fileFormat: 'json',
68 markdown_template: (input,parameters,options) => {
69 const t = new TemplateMarkTransformer();
70 return t.toMarkdownTemplate(input);
71 },
72 },
73 markdown: {
74 docs: 'Markdown (string)',
75 fileFormat: 'utf8',
76 commonmark_tokens: (input,parameters,options) => {
77 const t = new CommonMarkTransformer();
78 return t.toTokens(input, options);
79 },
80 },
81 commonmark_tokens: {
82 docs: 'Markdown tokens (JSON)',
83 fileFormat: 'json',
84 commonmark: async (input,parameters,options) => {
85 const t = new CommonMarkTransformer();
86 return t.fromTokens(input);
87 },
88 },
89 markdown_cicero: {
90 docs: 'Cicero markdown (string)',
91 fileFormat: 'utf8',
92 ciceromark_tokens: (input,parameters,options) => {
93 const t = new CiceroMarkTransformer();
94 return t.toTokens(input, options);
95 },
96 },
97 ciceromark_tokens: {
98 docs: 'CiceroMark tokens (JSON)',
99 fileFormat: 'json',
100 ciceromark: async (input,parameters,options) => {
101 const t = new CiceroMarkTransformer();
102 return t.fromTokens(input);
103 },
104 },
105 commonmark: {
106 docs: 'CommonMark DOM (JSON)',
107 fileFormat: 'json',
108 markdown: (input,parameters,options) => {
109 const t = new CommonMarkTransformer();
110 return t.toMarkdown(input);
111 },
112 ciceromark: (input,parameters,options) => {
113 const t = new CiceroMarkTransformer();
114 return t.fromCommonMark(input, options);
115 },
116 plaintext: (input,parameters,options) => {
117 const t = new CommonMarkTransformer();
118 return t.toMarkdown(t.removeFormatting(input));
119 },
120 },
121 ciceromark: {
122 docs: 'CiceroMark DOM (JSON)',
123 fileFormat: 'json',
124 markdown_cicero: (input,parameters,options) => {
125 const t = new CiceroMarkTransformer();
126 const inputUnwrapped = t.toCiceroMarkUnwrapped(input,options);
127 return t.toMarkdownCicero(inputUnwrapped);
128 },
129 commonmark: (input,parameters,options) => {
130 const t = new CiceroMarkTransformer();
131 return t.toCommonMark(input, options);
132 },
133 ciceromark_parsed: (input,parameters,options) => {
134 return input;
135 },
136 data: async (input,parameters,options) => {
137 const t = new TemplateMarkTransformer(parameters.plugin);
138 const templateParameters = Object.assign({},parameters);
139 templateParameters.inputFileName = parameters.templateFileName;
140 const { templateMark, modelManager } = await templateToTemplateMark(parameters.template,templateParameters,options);
141 const result = await t.fromCiceroMark({ fileName:parameters.inputFileName, content:input }, templateMark, modelManager, parameters.templateKind, parameters.currentTime, parameters.utcOffset, options);
142 return result;
143 },
144 },
145 ciceromark_parsed: {
146 docs: 'Parsed CiceroMark DOM (JSON)',
147 fileFormat: 'json',
148 html: (input,parameters,options) => {
149 const t = new HtmlTransformer();
150 return t.toHtml(input);
151 },
152 ciceromark: (input,parameters,options) => {
153 const t = new CiceroMarkTransformer();
154 return t.toCiceroMarkUnwrapped(input, options);
155 },
156 ciceromark_unquoted: (input,parameters,options) => {
157 const t = new CiceroMarkTransformer();
158 return t.unquote(input, options);
159 },
160 slate: (input,parameters,options) => {
161 const t = new SlateTransformer();
162 return t.fromCiceroMark(input);
163 },
164 pdf: (input, parameters, options) => {
165 const t = new PdfTransformer();
166 const outputStream = new Stream.Writable();
167
168 let arrayBuffer = new ArrayBuffer(8);
169 let memStore = Buffer.from(arrayBuffer);
170 outputStream._write = (chunk, encoding, next) => {
171 let buffer = (Buffer.isBuffer(chunk))
172 ? chunk // already is Buffer use it
173 : new Buffer(chunk, encoding); // string, convert
174
175 // concat to the buffer already there
176 memStore = Buffer.concat([memStore, buffer]);
177 next();
178 };
179 return new Promise( (resolve) => {
180 outputStream.on('finish', () => {
181 resolve(memStore);
182 });
183 t.toPdf(input, options, outputStream );
184 });
185 },
186 },
187 data: {
188 docs: 'Contract Data (JSON)',
189 fileFormat: 'json',
190 ciceromark_parsed: async (input,parameters,options) => {
191 const t = new TemplateMarkTransformer(parameters.plugin);
192 const templateParameters = Object.assign({},parameters);
193 templateParameters.inputFileName = parameters.templateFileName;
194 const { templateMark, modelManager } = await templateToTemplateMark(parameters.template,templateParameters,options);
195 return t.instantiateCiceroMark(input, templateMark, modelManager, parameters.templateKind, parameters.currentTime, parameters.utcOffset, options);
196 },
197 },
198 plaintext: {
199 docs: 'Plain text (string)',
200 fileFormat: 'utf8',
201 markdown: (input,parameters,options) => {
202 return input;
203 },
204 },
205 ciceroedit: {
206 docs: 'CiceroEdit (string)',
207 fileFormat: 'utf8',
208 ciceromark_parsed: (input,parameters,options) => {
209 const t = new CiceroMarkTransformer();
210 return t.fromCiceroEdit(input, options);
211 },
212 },
213 ciceromark_unquoted: {
214 docs: 'CiceroMark DOM (JSON) with quotes around variables removed',
215 fileFormat: 'json',
216 ciceromark_parsed: (input,parameters,options) => {
217 return input;
218 }
219 },
220 pdf: {
221 docs: 'PDF (buffer)',
222 fileFormat: 'binary',
223 ciceromark_parsed: (input,parameters,options) => {
224 const t = new PdfTransformer();
225 return t.toCiceroMark(input, options);
226 },
227 },
228 docx: {
229 docs: 'DOCX (buffer)',
230 fileFormat: 'binary',
231 ciceromark_parsed: async (input,parameters,options) => {
232 const t = new DocxTransformer();
233 return t.toCiceroMark(input, options);
234 },
235 },
236 html: {
237 docs: 'HTML (string)',
238 fileFormat: 'utf8',
239 ciceromark_parsed: (input,parameters,options) => {
240 const t = new HtmlTransformer();
241 return t.toCiceroMark(input, options);
242 },
243 },
244 slate: {
245 docs: 'Slate DOM (JSON)',
246 fileFormat: 'json',
247 ciceromark_parsed: (input,parameters,options) => {
248 const t = new SlateTransformer();
249 return t.toCiceroMark(input, options);
250 },
251 },
252};
253
254/**
255 * Prune the graph for traversal
256 * @param {object} graph the input graph
257 * @returns {object} the raw graph for dijsktra
258 */
259function pruneGraph(graph) {
260 const result = {};
261 for (const sourceKey in graph) {
262 result[sourceKey] = {};
263 for (const targetKey in graph[sourceKey]) {
264 // Don't forget to remove the meta data which really isn't part of the graph
265 if (targetKey !== 'docs' && targetKey !== 'fileFormat') {
266 result[sourceKey][targetKey] = 1;
267 }
268 }
269 }
270 return result;
271}
272const rawGraph = pruneGraph(transformationGraph);
273
274/**
275 * Converts the graph of transformations into a PlantUML text string
276 * @returns {string} the PlantUML string
277 */
278function generateTransformationDiagram() {
279 let result = `@startuml
280hide empty description
281
282`;
283
284 Object.keys(transformationGraph).forEach(src => {
285 result += `${src} : \n`;
286 result += `${src} : ${transformationGraph[src].docs}\n`;
287 Object.keys(transformationGraph[src]).forEach(dest => {
288 if(dest !== 'docs' && dest !== 'fileFormat') {
289 result += `${src} --> ${dest}\n`;
290 }
291 });
292 result += '\n';
293 });
294
295 result += '@enduml';
296 return result;
297}
298
299/**
300 * Transforms from a source format to a single destination format or
301 * throws an exception if the transformation is not possible.
302 *
303 * @param {*} source the input for the transformation
304 * @param {string} sourceFormat the input format
305 * @param {string} destinationFormat the destination format
306 * @param {object} parameters the transform parameters
307 * @param {object} [options] the transform options
308 * @param {boolean} [options.verbose] output verbose console logs
309 * @returns {*} result of the transformation
310 */
311async function transformToDestination(source, sourceFormat, destinationFormat, parameters, options) {
312 let result = source;
313
314 const path = find_path(rawGraph, sourceFormat, destinationFormat);
315 for(let n=0; n < path.length-1; n++) {
316 const src = path[n];
317 const dest = path[n+1];
318 const srcNode = transformationGraph[src];
319 const destinationNode = transformationGraph[dest];
320 result = await srcNode[dest](result,parameters,options);
321 if(options && options.verbose) {
322 console.log(`Converted from ${src} to ${dest}. Result:`);
323 if(destinationNode.fileFormat !== 'binary') {
324 if(typeof result === 'object') {
325 console.log(JSON.stringify(result, null, 2));
326 } else {
327 console.log(result);
328 }
329 }
330 else {
331 console.log(`<binary ${dest} data>`);
332 }
333 }
334 }
335
336 return result;
337}
338
339/**
340 * Transforms from a source format to a list of destination formats, or
341 * throws an exception if the transformation is not possible.
342 *
343 * @param {*} source the input for the transformation
344 * @param {string} sourceFormat the input format
345 * @param {string[]} destinationFormat the destination format as an array,
346 * the transformation are applied in order to reach all formats in the array
347 * @param {object} parameters the transform parameters
348 * @param {object} [options] the transform options
349 * @param {boolean} [options.verbose] output verbose console logs
350 * @returns {Promise} result of the transformation
351 */
352async function transform(source, sourceFormat, destinationFormat, parameters, options) {
353 let result = source;
354 options = options ? options : {};
355 parameters = parameters ? parameters : {};
356 if (sourceFormat === 'markdown') {
357 options.source = source;
358 }
359
360 let currentSourceFormat = sourceFormat;
361
362 for(let i=0; i < destinationFormat.length; i++) {
363 let destination = destinationFormat[i];
364 result = await transformToDestination(result, currentSourceFormat, destination, parameters, options);
365 currentSourceFormat = destination;
366 }
367 return result;
368}
369
370/**
371 * Return the format descriptor for a given format
372 *
373 * @param {string} format the format
374 * @return {object} the descriptor for that format
375 */
376function formatDescriptor(format) {
377 if (Object.prototype.hasOwnProperty.call(transformationGraph,format)) {
378 return transformationGraph[format];
379 } else {
380 throw new Error('Unknown format ' + format);
381 }
382}
383
384module.exports.formatDescriptor = formatDescriptor;
385module.exports.transform = transform;
386module.exports.transformationGraph = transformationGraph;
387module.exports.generateTransformationDiagram = generateTransformationDiagram;