diff --git a/bin/index.js b/bin/index.js
index 4f697a3d..09f2f48c 100755
--- a/bin/index.js
+++ b/bin/index.js
@@ -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
});
@@ -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);}
})
diff --git a/src/Commands.js b/src/Commands.js
index 4c296407..02ad3ed0 100644
--- a/src/Commands.js
+++ b/src/Commands.js
@@ -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.
@@ -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);
diff --git a/src/Commonmark.test.js b/src/Commonmark.test.js
index 0d4cdcd4..50ed48b8 100644
--- a/src/Commonmark.test.js
+++ b/src/Commonmark.test.js
@@ -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);
diff --git a/src/CommonmarkAP.test.js b/src/CommonmarkAP.test.js
index 9ddb4259..b62e25bf 100644
--- a/src/CommonmarkAP.test.js
+++ b/src/CommonmarkAP.test.js
@@ -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;
@@ -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);
diff --git a/src/CommonmarkFromAP.js b/src/CommonmarkFromAP.js
new file mode 100644
index 00000000..18e3d66c
--- /dev/null
+++ b/src/CommonmarkFromAP.js
@@ -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;
\ No newline at end of file
diff --git a/src/CommonmarkToAP.js b/src/CommonmarkToAP.js
index 87430a4d..e19c90ab 100644
--- a/src/CommonmarkToAP.js
+++ b/src/CommonmarkToAP.js
@@ -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');
@@ -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');
@@ -35,6 +39,7 @@ function commonMarkToAP(concertoObject) {
// Add AP nodes
const parameters = {
+ parser: commonmarkParser,
modelManager : modelManager ,
factory : factory,
serializer : serializer
diff --git a/src/FromAPVisitor.js b/src/FromAPVisitor.js
new file mode 100644
index 00000000..b27d55db
--- /dev/null
+++ b/src/FromAPVisitor.js
@@ -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 = `\n${content}\n\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 = ``;
+
+ // 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 = ``;
+
+ // 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;
\ No newline at end of file
diff --git a/src/ToAPVisitor.js b/src/ToAPVisitor.js
index 6648b8a0..8bea2185 100644
--- a/src/ToAPVisitor.js
+++ b/src/ToAPVisitor.js
@@ -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 ');
}
@@ -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 ');
}
@@ -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 ');
diff --git a/src/ToStringVisitor.js b/src/ToStringVisitor.js
index b9f4c6b7..8e041e72 100644
--- a/src/ToStringVisitor.js
+++ b/src/ToStringVisitor.js
@@ -123,16 +123,13 @@ class ToStringVisitor {
switch(thing.getType()) {
case 'CodeBlock':
- case 'Clause':
ToStringVisitor.newParagraph(parameters);
- parameters.result += `\`\`\` ${thing.info ? thing.info : ''}\n${thing.text}\`\`\`\n\n`;
+ parameters.result += `\`\`\`${thing.info ? thing.info : ''}\n${thing.text}\`\`\`\n\n`;
break;
case 'Code':
parameters.result += `\`${thing.text}\``;
break;
case 'HtmlInline':
- case 'Variable':
- case 'ComputedVariable':
parameters.result += thing.text;
break;
case 'Emph':