diff --git a/packages/markdown-cli/test/data/acceptance/omitted-acceptance-of-delivery.xml b/packages/markdown-cli/test/data/acceptance/omitted-acceptance-of-delivery.xml
index 9708a0ed..5c61ef8a 100644
--- a/packages/markdown-cli/test/data/acceptance/omitted-acceptance-of-delivery.xml
+++ b/packages/markdown-cli/test/data/acceptance/omitted-acceptance-of-delivery.xml
@@ -531,6 +531,7 @@
+
@@ -684,6 +685,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/markdown-docx/src/ToCiceroMarkVisitor.js b/packages/markdown-docx/src/ToCiceroMarkVisitor.js
index 871cea40..ac5fac88 100644
--- a/packages/markdown-docx/src/ToCiceroMarkVisitor.js
+++ b/packages/markdown-docx/src/ToCiceroMarkVisitor.js
@@ -31,6 +31,9 @@ class ToCiceroMarkVisitor {
// All the nodes generated from given OOXML
this.nodes = [];
+
+ // contains the realtionship part of a given OOXML
+ this.relationshipXML = [];
}
/**
@@ -214,6 +217,15 @@ class ToCiceroMarkVisitor {
$class: nodeInformation.properties[nodePropertyIndex],
nodes: [ciceroMarkNode],
};
+ if (nodeInformation.properties[nodePropertyIndex] === TRANSFORMED_NODES.link) {
+ ciceroMarkNode.title = '';
+ for (const relationshipElement of this.relationshipXML) {
+ if (relationshipElement.attributes.Id === nodeInformation.linkId) {
+ ciceroMarkNode.destination = relationshipElement.attributes.Target;
+ break;
+ }
+ }
+ }
}
return ciceroMarkNode;
}
@@ -268,6 +280,14 @@ class ToCiceroMarkVisitor {
...rootNode.nodes[rootNodesLength - 1].nodes[subNodeLength - 1].nodes,
constructedNode,
];
+ } else if (commonPropertiesLength === 3) {
+ const subNodeLength = rootNode.nodes[rootNodesLength - 1].nodes.length;
+ const deepSubNodeLength = rootNode.nodes[rootNodesLength - 1].nodes[subNodeLength - 1].nodes.length;
+ rootNode.nodes[rootNodesLength - 1].nodes[subNodeLength - 1].nodes[deepSubNodeLength - 1].nodes = [
+ ...rootNode.nodes[rootNodesLength - 1].nodes[subNodeLength - 1].nodes[deepSubNodeLength - 1]
+ .nodes,
+ constructedNode,
+ ];
}
}
this.JSONXML = [];
@@ -283,12 +303,15 @@ class ToCiceroMarkVisitor {
* Traverses for properties and value.
*
* @param {Array} node Node to be traversed
+ * @param {string} calledBy Parent node class that called the function
* @param {object} nodeInformation Information for the current node
- * @param {Boolean} calledByCodeBlock Is function called by codeblock checker
* @returns {string} Value in tags
*/
- fetchFormattingProperties(node, nodeInformation, calledByCodeBlock = false) {
+ fetchFormattingProperties(node, calledBy = TRANSFORMED_NODES.paragraph, nodeInformation = null) {
let ooxmlTagTextValue = '';
+ if (calledBy === TRANSFORMED_NODES.link) {
+ nodeInformation.properties = [...nodeInformation.properties, calledBy];
+ }
for (const runTimeNodes of node.elements) {
if (runTimeNodes.name === 'w:rPr') {
let colorCodePresent = false;
@@ -317,7 +340,7 @@ class ToCiceroMarkVisitor {
nodeInformation.nodeType = TRANSFORMED_NODES.code;
}
} else if (runTimeNodes.name === 'w:t') {
- if (calledByCodeBlock) {
+ if (calledBy === TRANSFORMED_NODES.codeBlock) {
ooxmlTagTextValue += runTimeNodes.elements ? runTimeNodes.elements[0].text : '';
} else {
ooxmlTagTextValue = runTimeNodes.elements ? runTimeNodes.elements[0].text : ' ';
@@ -337,11 +360,18 @@ class ToCiceroMarkVisitor {
/**
* Traverses the JSON object of XML elements in DFS approach.
*
- * @param {object} node Node object to be traversed
+ * @param {object} node Node object to be traversed
* @param {string} parent Parent node name
+ * @param {syring} id Relation Id for link in OOXML
* @returns {*} GeneratedNode if parent is of type clause else none
*/
- traverseElements(node, parent = '') {
+ traverseElements(node, parent = TRANSFORMED_NODES.paragraph, id = undefined) {
+ /**
+ * The parent argument is useful in cases where parent is a clause or link.
+ * If parent argument is not present, then everything would have been treated
+ * as pargraphs and transformation would be faulty.
+ */
+
// Contains node present in a codeblock or blockquote, etc.
let blockNodes = [];
for (const subNode of node) {
@@ -362,7 +392,7 @@ class ToCiceroMarkVisitor {
let text = '';
for (const codeBlockSubNode of subNode.elements) {
if (codeBlockSubNode.name === 'w:r') {
- text = this.fetchFormattingProperties(codeBlockSubNode, undefined, true);
+ text = this.fetchFormattingProperties(codeBlockSubNode, TRANSFORMED_NODES.codeBlock);
}
}
const codeBlockNode = {
@@ -428,7 +458,10 @@ class ToCiceroMarkVisitor {
}
if (variableSubNodes.name === 'w:sdtContent') {
if (nodeInformation.nodeType === TRANSFORMED_NODES.clause) {
- const nodes = this.traverseElements(variableSubNodes.elements, TRANSFORMED_NODES.clause);
+ const nodes = this.traverseElements(
+ variableSubNodes.elements,
+ TRANSFORMED_NODES.clause
+ );
const clauseNode = {
$class: TRANSFORMED_NODES.clause,
elementType: nodeInformation.elementType,
@@ -439,16 +472,25 @@ class ToCiceroMarkVisitor {
} else {
for (const variableContentNodes of variableSubNodes.elements) {
if (variableContentNodes.name === 'w:r') {
- this.fetchFormattingProperties(variableContentNodes, nodeInformation);
+ this.fetchFormattingProperties(
+ variableContentNodes,
+ TRANSFORMED_NODES.paragraph,
+ nodeInformation
+ );
}
}
}
}
}
}
+ } else if (subNode.name === 'w:hyperlink') {
+ this.traverseElements(subNode.elements, TRANSFORMED_NODES.link, subNode.attributes['r:id']);
} else if (subNode.name === 'w:r') {
let nodeInformation = { properties: [], value: '' };
- this.fetchFormattingProperties(subNode, nodeInformation);
+ if (parent === TRANSFORMED_NODES.link) {
+ nodeInformation.linkId = id;
+ }
+ this.fetchFormattingProperties(subNode, parent, nodeInformation);
}
}
return blockNodes;
@@ -474,7 +516,9 @@ class ToCiceroMarkVisitor {
if (node.attributes['pkg:name'] === pkgName) {
// Gets the document node
documentNode = node.elements[0].elements[0];
- break;
+ }
+ if (node.attributes['pkg:name'] === '/word/_rels/document.xml.rels') {
+ this.relationshipXML = node.elements[0].elements[0].elements;
}
}
diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js b/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js
index ace57bd4..2462ec64 100644
--- a/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js
+++ b/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js
@@ -58,10 +58,26 @@ function wrapAroundLockedContentControls(ooxml) {
/**
* Wraps OOXML in docx headers.
*
- * @param {string} ooxml OOXML to be wrapped
+ * @param {string} ooxml OOXML to be wrapped
+ * @param {Array} relationships Relationship tags
* @returns {string} OOXML wraped in docx headers
*/
-function wrapAroundDefaultDocxTags(ooxml) {
+function wrapAroundDefaultDocxTags(ooxml, relationships) {
+
+ const LINK_STYLE_SPEC = `
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
const HEADING_STYLE_SPEC = `
@@ -211,17 +227,25 @@ function wrapAroundDefaultDocxTags(ooxml) {
+ ${LINK_STYLE_SPEC}
`;
+ let relationshipOOXML = '';
+ relationships.forEach(
+ ({ id, destination }) =>
+ (relationshipOOXML += ``)
+ );
+
const RELATIONSHIP_SPEC = `
+ ${relationshipOOXML}
diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/index.js b/packages/markdown-docx/src/ToOOXMLVisitor/index.js
index fbdd2b6c..28311bff 100644
--- a/packages/markdown-docx/src/ToOOXMLVisitor/index.js
+++ b/packages/markdown-docx/src/ToOOXMLVisitor/index.js
@@ -29,9 +29,11 @@ const {
CODEBLOCK_PROPERTIES_RULE,
CODEBLOCK_FONTPROPERTIES_RULE,
CLAUSE_RULE,
+ LINK_RULE,
+ LINK_PROPERTY_RULE,
} = require('./rules');
const { wrapAroundDefaultDocxTags, wrapAroundLockedContentControls } = require('./helpers');
-const { TRANSFORMED_NODES } = require('../constants');
+const { TRANSFORMED_NODES, RELATIONSHIP_OFFSET } = require('../constants');
/**
* Transforms the ciceromark to OOXML
@@ -47,6 +49,8 @@ class ToOOXMLVisitor {
this.counter = {};
// OOXML tags for a given block node(heading, paragraph, etc.)
this.tags = [];
+ // Relationship tags for links in a document
+ this.relationships = [];
}
/**
@@ -59,6 +63,45 @@ class ToOOXMLVisitor {
return node.$class;
}
+ /**
+ * Generates the OOXML for text and code ciceromark nodes.
+ *
+ * @param {string} value Text value of the node
+ * @param {Array} nodeProperties Properties of the node
+ * @param {boolean} calledByCode Is function called by code node or not
+ * @returns {string} Generated OOXML
+ */
+ generateTextOrCodeOOXML(value, nodeProperties, calledByCode = false) {
+ let propertyTag = '';
+ if (calledByCode) {
+ propertyTag = CODE_PROPERTIES_RULE();
+ }
+ let isLinkPropertyPresent = false;
+ for (const property of nodeProperties) {
+ if (property === TRANSFORMED_NODES.emphasize) {
+ propertyTag += EMPHASIS_RULE();
+ } else if (property === TRANSFORMED_NODES.strong) {
+ propertyTag += STRONG_RULE();
+ } else if (property === TRANSFORMED_NODES.link) {
+ isLinkPropertyPresent = true;
+ propertyTag += LINK_PROPERTY_RULE();
+ }
+ }
+ if (propertyTag) {
+ propertyTag = TEXT_STYLES_RULE(propertyTag);
+ }
+
+ let textValueTag = TEXT_RULE(value);
+
+ let tag = TEXT_WRAPPER_RULE(propertyTag, textValueTag);
+
+ if (isLinkPropertyPresent) {
+ let relationshipId = 'rId' + (this.relationships.length + RELATIONSHIP_OFFSET).toString();
+ tag = LINK_RULE(tag, relationshipId);
+ }
+ return tag;
+ }
+
/**
* Traverses CiceroMark nodes in a DFS approach
*
@@ -71,36 +114,10 @@ class ToOOXMLVisitor {
} else {
for (let subNode of node) {
if (this.getClass(subNode) === TRANSFORMED_NODES.text) {
- let propertyTag = '';
- for (let property of properties) {
- if (property === TRANSFORMED_NODES.emphasize) {
- propertyTag += EMPHASIS_RULE();
- } else if (property === TRANSFORMED_NODES.strong) {
- propertyTag += STRONG_RULE();
- }
- }
- if (propertyTag) {
- propertyTag = TEXT_STYLES_RULE(propertyTag);
- }
-
- let textValueTag = TEXT_RULE(subNode.text);
-
- let tag = TEXT_WRAPPER_RULE(propertyTag, textValueTag);
+ const tag = this.generateTextOrCodeOOXML(subNode.text, properties);
this.tags = [...this.tags, tag];
} else if (this.getClass(subNode) === TRANSFORMED_NODES.code) {
- let propertyTag = CODE_PROPERTIES_RULE();
- for (let property of properties) {
- if (property === TRANSFORMED_NODES.emphasize) {
- propertyTag += EMPHASIS_RULE();
- } else if (property === TRANSFORMED_NODES.strong) {
- propertyTag += STRONG_RULE();
- }
- }
- propertyTag = TEXT_STYLES_RULE(propertyTag);
-
- let textValueTag = TEXT_RULE(subNode.text);
-
- let tag = TEXT_WRAPPER_RULE(propertyTag, textValueTag);
+ const tag = this.generateTextOrCodeOOXML(subNode.text, properties, true);
this.tags = [...this.tags, tag];
} else if (this.getClass(subNode) === TRANSFORMED_NODES.codeBlock) {
let ooxml = CODEBLOCK_PROPERTIES_RULE();
@@ -224,6 +241,15 @@ class ToOOXMLVisitor {
this.globalOOXML += ooxml;
this.tags = [];
} else {
+ if (this.getClass(subNode) === TRANSFORMED_NODES.link) {
+ this.relationships = [
+ ...this.relationships,
+ {
+ id: this.relationships.length + RELATIONSHIP_OFFSET + 1,
+ destination: subNode.destination,
+ },
+ ];
+ }
let newProperties = [...properties, subNode.$class];
this.traverseNodes(subNode.nodes, newProperties);
}
@@ -242,7 +268,7 @@ class ToOOXMLVisitor {
toOOXML(ciceromark) {
this.traverseNodes(ciceromark, []);
this.globalOOXML = wrapAroundLockedContentControls(this.globalOOXML);
- this.globalOOXML = wrapAroundDefaultDocxTags(this.globalOOXML);
+ this.globalOOXML = wrapAroundDefaultDocxTags(this.globalOOXML, this.relationships);
return this.globalOOXML;
}
diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/rules.js b/packages/markdown-docx/src/ToOOXMLVisitor/rules.js
index 49ae6fae..6620bb4a 100644
--- a/packages/markdown-docx/src/ToOOXMLVisitor/rules.js
+++ b/packages/markdown-docx/src/ToOOXMLVisitor/rules.js
@@ -229,6 +229,25 @@ const CLAUSE_RULE = (title, tag, type, content) => {
`;
};
+const LINK_PROPERTY_RULE = () => {
+ return '';
+};
+
+/**
+ * Inserts a link node in OOXML syntax.
+ *
+ * @param {string} value Value to be rendered in the link
+ * @param {string} relationshipId Specifies the ID of the relationship in the relationships part for an external link rId5
+ * @returns {string} Link OOXML
+ */
+const LINK_RULE = (value, relationshipId) => {
+ return `
+
+ ${value}
+
+ `;
+};
+
module.exports = {
TEXT_RULE,
EMPHASIS_RULE,
@@ -244,4 +263,6 @@ module.exports = {
CODEBLOCK_FONTPROPERTIES_RULE,
THEMATICBREAK_RULE,
CLAUSE_RULE,
+ LINK_PROPERTY_RULE,
+ LINK_RULE,
};
diff --git a/packages/markdown-docx/src/constants.js b/packages/markdown-docx/src/constants.js
index 3853561a..844ee40a 100644
--- a/packages/markdown-docx/src/constants.js
+++ b/packages/markdown-docx/src/constants.js
@@ -25,13 +25,20 @@ const TRANSFORMED_NODES = {
emphasize: `${NS_PREFIX_CommonMarkModel}Emph`,
heading: `${NS_PREFIX_CommonMarkModel}Heading`,
item: `${NS_PREFIX_CommonMarkModel}Item`,
+ link: `${NS_PREFIX_CommonMarkModel}Link`,
paragraph: `${NS_PREFIX_CommonMarkModel}Paragraph`,
softbreak: `${NS_PREFIX_CommonMarkModel}Softbreak`,
strong: `${NS_PREFIX_CommonMarkModel}Strong`,
text: `${NS_PREFIX_CommonMarkModel}Text`,
thematicBreak: `${NS_PREFIX_CommonMarkModel}ThematicBreak`,
variable: `${NS_PREFIX_CiceroMarkModel}Variable`,
- clause:`${NS_PREFIX_CiceroMarkModel}Clause`
+ clause: `${NS_PREFIX_CiceroMarkModel}Clause`,
};
-module.exports = { TRANSFORMED_NODES };
+// Two relationships for numbering and style are already present
+// and since we need to accommodate for link styles as well, we need a unique ID
+// to represent them. Hence, 2 is added to offset the enumeration of `rId`.
+// Used in './ToOOXMLVisitor/index.js'
+const RELATIONSHIP_OFFSET = 2;
+
+module.exports = { TRANSFORMED_NODES, RELATIONSHIP_OFFSET };
diff --git a/packages/markdown-docx/test/data/ciceroMark/link-variants.json b/packages/markdown-docx/test/data/ciceroMark/link-variants.json
new file mode 100644
index 00000000..5f55fd2b
--- /dev/null
+++ b/packages/markdown-docx/test/data/ciceroMark/link-variants.json
@@ -0,0 +1 @@
+{"$class":"org.accordproject.commonmark.Document","xmlns":"http://commonmark.org/xml/1.0","nodes":[{"$class":"org.accordproject.commonmark.Heading","level":"2","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://google.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Heading Link"}]}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://github.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Simple Link"}]}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://twitter.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Emph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Emph Link"}]}]}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://templatemark-dingus.netlify.app/","title":"","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"_"},{"$class":"org.accordproject.commonmark.Emph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Strong Link"}]}]}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://facebook.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Emph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Nested "},{"$class":"org.accordproject.commonmark.Strong","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Link"}]}]}]}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://google.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Diffrent"}]},{"$class":"org.accordproject.commonmark.Text","text":" "},{"$class":"org.accordproject.commonmark.Link","destination":"https://github.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Links"}]}]}]}
\ No newline at end of file
diff --git a/packages/markdown-docx/test/data/ciceroMark/link.json b/packages/markdown-docx/test/data/ciceroMark/link.json
new file mode 100644
index 00000000..183b3bca
--- /dev/null
+++ b/packages/markdown-docx/test/data/ciceroMark/link.json
@@ -0,0 +1 @@
+{"$class":"org.accordproject.commonmark.Document","xmlns":"http://commonmark.org/xml/1.0","nodes":[{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Link","destination":"https://google.com","title":"","nodes":[{"$class":"org.accordproject.commonmark.Strong","nodes":[{"$class":"org.accordproject.commonmark.Emph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"Check "},{"$class":"org.accordproject.commonmark.Code","text":"link"}]}]}]}]}]}
\ No newline at end of file
diff --git a/packages/markdown-transform/test/data/acceptance/omitted-acceptance-of-delivery.xml b/packages/markdown-transform/test/data/acceptance/omitted-acceptance-of-delivery.xml
index 9708a0ed..5c61ef8a 100644
--- a/packages/markdown-transform/test/data/acceptance/omitted-acceptance-of-delivery.xml
+++ b/packages/markdown-transform/test/data/acceptance/omitted-acceptance-of-delivery.xml
@@ -531,6 +531,7 @@
+
@@ -684,6 +685,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+