diff --git a/packages/markdown-docx/src/ToCiceroMarkVisitor.js b/packages/markdown-docx/src/ToCiceroMarkVisitor.js index 871cea40..67ded217 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 = []; @@ -284,11 +304,14 @@ class ToCiceroMarkVisitor { * * @param {Array} node Node to be traversed * @param {object} nodeInformation Information for the current node - * @param {Boolean} calledByCodeBlock Is function called by codeblock checker + * @param {string} calledBy Parent node class that called the function * @returns {string} Value in tags */ - fetchFormattingProperties(node, nodeInformation, calledByCodeBlock = false) { + fetchFormattingProperties(node, nodeInformation, calledBy = TRANSFORMED_NODES.paragraph) { 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,12 @@ 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 = '', id = undefined) { // Contains node present in a codeblock or blockquote, etc. let blockNodes = []; for (const subNode of node) { @@ -362,7 +386,11 @@ 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, + undefined, + TRANSFORMED_NODES.codeBlock + ); } } const codeBlockNode = { @@ -428,7 +456,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, @@ -446,9 +477,18 @@ class ToCiceroMarkVisitor { } } } + } 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, + nodeInformation, + parent === TRANSFORMED_NODES.link ? TRANSFORMED_NODES.link : TRANSFORMED_NODES.paragraph + ); } } return blockNodes; @@ -478,6 +518,12 @@ class ToCiceroMarkVisitor { } } + for (const node of rootNode) { + if (node.attributes['pkg:name'] === '/word/_rels/document.xml.rels') { + this.relationshipXML = node.elements[0].elements[0].elements; + } + } + this.traverseElements(documentNode.elements[0].elements, 'body'); return { diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js b/packages/markdown-docx/src/ToOOXMLVisitor/helpers.js index ace57bd4..4390f1e0 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,22 @@ function wrapAroundDefaultDocxTags(ooxml) { + ${LINK_STYLE_SPEC} `; + let relationshipOOXML = ''; + relationships.forEach(relationship => (relationshipOOXML += relationship)); + const RELATIONSHIP_SPEC = ` + ${relationshipOOXML} diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/index.js b/packages/markdown-docx/src/ToOOXMLVisitor/index.js index fbdd2b6c..1870d3c2 100644 --- a/packages/markdown-docx/src/ToOOXMLVisitor/index.js +++ b/packages/markdown-docx/src/ToOOXMLVisitor/index.js @@ -29,6 +29,8 @@ 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'); @@ -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 = []; } /** @@ -72,11 +76,15 @@ class ToOOXMLVisitor { for (let subNode of node) { if (this.getClass(subNode) === TRANSFORMED_NODES.text) { let propertyTag = ''; + let isLinkPropertyPresent = false; for (let property of properties) { 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) { @@ -86,14 +94,26 @@ class ToOOXMLVisitor { let textValueTag = TEXT_RULE(subNode.text); let tag = TEXT_WRAPPER_RULE(propertyTag, textValueTag); + + if (isLinkPropertyPresent) { + // some rels are already present + // To avoid overwrite ids start our + // rels from 10 + let relationShipId = 'rId' + (this.relationShips.length + 10).toString(); + tag = LINK_RULE(tag, relationShipId); + } this.tags = [...this.tags, tag]; } else if (this.getClass(subNode) === TRANSFORMED_NODES.code) { let propertyTag = CODE_PROPERTIES_RULE(); + let isLinkPropertyPresent = false; for (let property of properties) { 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(); } } propertyTag = TEXT_STYLES_RULE(propertyTag); @@ -101,6 +121,13 @@ class ToOOXMLVisitor { let textValueTag = TEXT_RULE(subNode.text); let tag = TEXT_WRAPPER_RULE(propertyTag, textValueTag); + if (isLinkPropertyPresent) { + // some rels are already present + // To avoid overwrite ids start our + // rels from 10 + let relationShipId = 'rId' + (this.relationShips.length + 10).toString(); + tag = LINK_RULE(tag, relationShipId); + } this.tags = [...this.tags, tag]; } else if (this.getClass(subNode) === TRANSFORMED_NODES.codeBlock) { let ooxml = CODEBLOCK_PROPERTIES_RULE(); @@ -224,6 +251,14 @@ class ToOOXMLVisitor { this.globalOOXML += ooxml; this.tags = []; } else { + if (this.getClass(subNode) === TRANSFORMED_NODES.link) { + const relationshipTag = ``; + this.relationShips = [...this.relationShips, relationshipTag]; + } let newProperties = [...properties, subNode.$class]; this.traverseNodes(subNode.nodes, newProperties); } @@ -242,7 +277,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..01511e6f 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..4b25069a 100644 --- a/packages/markdown-docx/src/constants.js +++ b/packages/markdown-docx/src/constants.js @@ -25,6 +25,7 @@ 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`, 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..bf41ff57 --- /dev/null +++ b/packages/markdown-docx/test/data/ciceroMark/link.json @@ -0,0 +1,36 @@ +{ + "$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