Skip to content

Commit

Permalink
feature(AP) Add nested parsing of clause and AP to/from markdown tran…
Browse files Browse the repository at this point in the history
…sforms

Signed-off-by: Jerome Simeon <[email protected]>
  • Loading branch information
jeromesimeon committed Sep 10, 2019
1 parent 80d9f65 commit 52c4936
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 19 deletions.
9 changes: 7 additions & 2 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ require('yargs')
type: 'string'
});
yargs.option('--generateMarkdown', {
describe: 'path to the output file',
describe: 'roundtrip back to Markdown',
type: 'boolean',
default: false
});
yargs.option('--withAP', {
describe: 'further transform for AP',
type: 'boolean',
default: false
});
Expand All @@ -40,7 +45,7 @@ require('yargs')

try {
argv = Commands.validateParseArgs(argv);
return Commands.parse(argv.sample, argv.out, argv.generateMarkdown)
return Commands.parse(argv.sample, argv.out, argv.generateMarkdown, argv.withAP)
.then((result) => {
if(result) {Logger.info('\n'+result);}
})
Expand Down
11 changes: 9 additions & 2 deletions src/Commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const Logger = require('./Logger');
const CommonmarkParser = require('./CommonmarkParser');
const CommonmarkToString = require('./CommonmarkToString');
const CommonmarkToAP = require('./CommonmarkToAP');
const CommonmarkFromAP = require('./CommonmarkFromAP');

/**
* Utility class that implements the commands exposed by the CLI.
Expand Down Expand Up @@ -85,15 +86,21 @@ class Commands {
* @param {string} samplePath to the sample file
* @param {string} outPath to an output file
* @param {boolean} generateMarkdown whether to transform back to markdown
* @param {boolean} withAP whether to further transform for AP
* @returns {object} Promise to the result of parsing
*/
static parse(samplePath, outPath, generateMarkdown) {
static parse(samplePath, outPath, generateMarkdown, withAP) {
const parser = new CommonmarkParser();
const markdownText = fs.readFileSync(samplePath, 'utf8');
let concertoObject = parser.parse(markdownText);
concertoObject = CommonmarkToAP(concertoObject);
if (withAP) {
concertoObject = CommonmarkToAP(concertoObject);
}
let result;
if (generateMarkdown) {
if (withAP) {
concertoObject = CommonmarkFromAP(concertoObject);
}
result = CommonmarkToString(concertoObject);
} else {
const json = parser.getSerializer().toJSON(concertoObject);
Expand Down
2 changes: 1 addition & 1 deletion src/Commonmark.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ function extractSpecTests(testfile) {
return examples;
}

describe('markdown', () => {
describe.only('markdown', () => {
getMarkdownFiles().forEach( ([file, markdownText]) => {
it(`converts ${file} to concerto`, () => {
const concertoObject = parser.parse(markdownText);
Expand Down
3 changes: 2 additions & 1 deletion src/CommonmarkAP.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const fs = require('fs');
const diff = require('jest-diff');
const CommonmarkParser = require('./CommonmarkParser');
const CommonmarkToAP = require('./CommonmarkToAP');
const CommonmarkFromAP = require('./CommonmarkFromAP');
const CommonmarkToString = require('./CommonmarkToString');
let parser = null;

Expand All @@ -30,7 +31,7 @@ expect.extend({
const json1 = parser.getSerializer().toJSON(concertoObject1);
const newMarkdown = CommonmarkToString(concertoObject1);
let concertoObject2 = parser.parse(newMarkdown);
concertoObject2 = CommonmarkToAP(concertoObject2);
concertoObject2 = CommonmarkFromAP(CommonmarkToAP(concertoObject2));
const json2 = parser.getSerializer().toJSON(concertoObject2);
const pass = JSON.stringify(json1) === JSON.stringify(json2);

Expand Down
52 changes: 52 additions & 0 deletions src/CommonmarkFromAP.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const ModelManager = require('composer-concerto').ModelManager;
const Factory = require('composer-concerto').Factory;
const Serializer = require('composer-concerto').Serializer;

const FromAPVisitor = require('./FromAPVisitor');
const CommonmarkToString = require('./CommonmarkToString');
const { commonmarkModel } = require('./Models');

/**
* Converts a commonmark document to a markdown string
* @param {*} concertoObject concerto commonmark object
* @returns {*} concertoObject concerto commonmark for AP object
*/
function commonMarkFromAP(concertoObject) {
// Setup for validation
const modelManager = new ModelManager();
modelManager.addModelFile( commonmarkModel, 'commonmark.cto');
const factory = new Factory(modelManager);
const serializer = new Serializer(factory, modelManager);

// Add AP nodes
const parameters = {
commonmarkToString: CommonmarkToString,
modelManager : modelManager ,
factory : factory,
serializer : serializer
};
const visitor = new FromAPVisitor();
concertoObject.accept( visitor, parameters );

// Validate
const json = serializer.toJSON(concertoObject);
return serializer.fromJSON(json);
}

module.exports = commonMarkFromAP;
5 changes: 5 additions & 0 deletions src/CommonmarkToAP.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const ModelManager = require('composer-concerto').ModelManager;
const Factory = require('composer-concerto').Factory;
const Serializer = require('composer-concerto').Serializer;

const CommonmarkParser = require('./CommonmarkParser');
const ToAPVisitor = require('./ToAPVisitor');
const { commonmarkModel } = require('./Models');

Expand All @@ -27,6 +28,9 @@ const { commonmarkModel } = require('./Models');
* @returns {*} concertoObject concerto commonmark for AP object
*/
function commonMarkToAP(concertoObject) {
// Setup for Nested Parsing
const commonmarkParser = new CommonmarkParser();

// Setup for validation
const modelManager = new ModelManager();
modelManager.addModelFile( commonmarkModel, 'commonmark.cto');
Expand All @@ -35,6 +39,7 @@ function commonMarkToAP(concertoObject) {

// Add AP nodes
const parameters = {
parser: commonmarkParser,
modelManager : modelManager ,
factory : factory,
serializer : serializer
Expand Down
172 changes: 172 additions & 0 deletions src/FromAPVisitor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const { NS_PREFIX } = require('./Models');

/**
* Converts a commonmark model instance to a markdown string.
*
* Note that there are several ways of representing the same markdown AST as text,
* so this transformation is not guaranteed to equivalent if you roundtrip
* markdown content. The resulting AST *should* be equivalent however.
*/
class FromAPVisitor {

/**
* Visits a sub-tree and return the markdown
* @param {*} visitor the visitor to use
* @param {*} thing the node to visit
* @param {*} [parameters] optional parameters
*/
static visitChildren(visitor, thing, parameters) {
if(thing.nodes) {
thing.nodes.forEach(node => {
node.accept(visitor, parameters);
});
}
}

/**
* Visit a node
* @param {*} thing the object being visited
* @param {*} parameters the parameters
*/
visit(thing, parameters) {
switch(thing.getType()) {
case 'Clause': {
let jsonSource = {};
let jsonTarget = {};

// Revert to CodeBlock
jsonTarget.$class = NS_PREFIX + 'CodeBlock';

// Get the content
const clauseJson = parameters.serializer.toJSON(thing);
jsonSource.$class = NS_PREFIX + 'Document';
jsonSource.xmlns = 'http://commonmark.org/xml/1.0';
jsonSource.nodes = clauseJson.nodes;

const content = parameters.commonmarkToString(parameters.serializer.fromJSON(jsonSource));
const attributeString = `src="${clauseJson.src}" clauseid="${clauseJson.clauseid}"`;

jsonTarget.text = `<clause ${attributeString}>\n${content}\n</clause>\n`;

// Create the proper tag
let tag = {};
tag.$class = NS_PREFIX + 'TagInfo';
tag.tagName = 'clause';
tag.attributeString = attributeString;
tag.content = content;
tag.closed = false;
tag.attributes = [];

let attribute1 = {};
attribute1.$class = NS_PREFIX + 'Attribute';
attribute1.name = 'src';
attribute1.value = clauseJson.src;
tag.attributes.push(attribute1);

let attribute2 = {};
attribute2.$class = NS_PREFIX + 'Attribute';
attribute2.name = 'clauseid';
attribute2.value = clauseJson.clauseid;
tag.attributes.push(attribute2);

jsonTarget.tag = tag;

let validatedTarget = parameters.serializer.fromJSON(jsonTarget);

delete thing.clauseid;
delete thing.src;

thing.$classDeclaration = validatedTarget.$classDeclaration;
thing.tag = validatedTarget.tag;
thing.nodes = validatedTarget.nodes;
thing.text = validatedTarget.text;
}
break;
case 'Variable': {
// Revert to HtmlInline
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'HtmlInline');

// Create the text for that document
const content = '';
const attributeString = `id="${thing.id}" value="${thing.value}"`;
thing.text = `<variable ${attributeString}/>`;

// Create the proper tag
let tag = {};
tag.$class = NS_PREFIX + 'TagInfo';
tag.tagName = 'variable';
tag.attributeString = attributeString;
tag.content = content;
tag.closed = true;
tag.attributes = [];

let attribute1 = {};
attribute1.$class = NS_PREFIX + 'Attribute';
attribute1.name = 'id';
attribute1.value = thing.id;
tag.attributes.push(attribute1);

let attribute2 = {};
attribute2.$class = NS_PREFIX + 'Attribute';
attribute2.name = 'value';
attribute2.value = thing.value;
tag.attributes.push(attribute2);

thing.tag = parameters.serializer.fromJSON(tag);

delete thing.id;
delete thing.value;
}
break;
case 'ComputedVariable': {
// Revert to HtmlInline
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'HtmlInline');

// Create the text for that document
const content = '';
const attributeString = `value="${thing.value}"`;
thing.text = `<computed ${attributeString}/>`;

// Create the proper tag
let tag = {};
tag.$class = NS_PREFIX + 'TagInfo';
tag.tagName = 'computed';
tag.attributeString = attributeString;
tag.content = content;
tag.closed = true;
tag.attributes = [];

let attribute1 = {};
attribute1.$class = NS_PREFIX + 'Attribute';
attribute1.name = 'value';
attribute1.value = thing.value;
tag.attributes.push(attribute1);

thing.tag = parameters.serializer.fromJSON(tag);

delete thing.value;
}
break;
default:
FromAPVisitor.visitChildren(this, thing, parameters);
}
}
}

module.exports = FromAPVisitor;
23 changes: 14 additions & 9 deletions src/ToAPVisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,22 @@ class ToAPVisitor {
case 'CodeBlock':
if (thing.tag && thing.tag.tagName === 'clause' && thing.tag.attributes.length === 2) {
const tag = thing.tag;
//console.log('CONTENT! : ' + tag.content);
if (tag.attributes[0].name === 'src' &&
tag.attributes[1].name === 'clauseid') {
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'Clause');
thing.src = tag.attributes[0].name;
thing.clauseid = tag.attributes[1].name;
thing.src = tag.attributes[0].value;
thing.clauseid = tag.attributes[1].value;
thing.nodes = parameters.parser.parse(tag.content).nodes; // Parse text as markdown (in the nodes for the root)
thing.text = null; // Remove text
}
else if (tag.attributes[1].name === 'src' &&
tag.attributes[0].name === 'clauseid') {
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'Clause');
thing.clauseid = tag.attributes[0].name;
thing.src = tag.attributes[1].name;
thing.clauseid = tag.attributes[0].value;
thing.src = tag.attributes[1].value;
thing.nodes = parameters.parser.parse(tag.content).nodes; // Parse text as markdown (in the nodes for the root)
thing.text = null; // Remove text
} else {
//console.log('Found Clause but without \'clauseid\' and \'src\' attributes ');
}
Expand All @@ -72,14 +77,14 @@ class ToAPVisitor {
if (tag.attributes[0].name === 'id' &&
tag.attributes[1].name === 'value') {
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'Variable');
thing.id = tag.attributes[0].name;
thing.value = tag.attributes[1].name;
thing.id = tag.attributes[0].value;
thing.value = tag.attributes[1].value;
}
else if (tag.attributes[1].name === 'id' &&
tag.attributes[0].name === 'value') {
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'Clause');
thing.value = tag.attributes[0].name;
thing.id = tag.attributes[1].name;
thing.value = tag.attributes[0].value;
thing.id = tag.attributes[1].value;
} else {
//console.log('Found Variable but without \'id\' and \'value\' attributes ');
}
Expand All @@ -88,7 +93,7 @@ class ToAPVisitor {
const tag = thing.tag;
if (tag.attributes[0].name === 'value') {
thing.$classDeclaration = parameters.modelManager.getType(NS_PREFIX + 'ComputedVariable');
thing.value = tag.attributes[0].name;
thing.value = tag.attributes[0].value;
}
else {
//console.log('Found ComputedVariable but without \'value\' attributes ');
Expand Down
Loading

0 comments on commit 52c4936

Please sign in to comment.