From de694a27106de5b5dcba28fe06c619a5d3d9dd43 Mon Sep 17 00:00:00 2001 From: Kushal Kumar <59891164+K-Kumar-01@users.noreply.github.com> Date: Tue, 17 Aug 2021 19:29:55 +0530 Subject: [PATCH] feat(markdown-docx): add optional transformer - #397 (#441) Signed-off-by: k-kumar-01 --- .../omitted-acceptance-of-delivery.xml | 42 +- .../markdown-docx/src/ToCiceroMarkVisitor.js | 168 +++- .../markdown-docx/src/ToOOXMLVisitor/index.js | 156 +++- .../markdown-docx/src/ToOOXMLVisitor/rules.js | 60 +- packages/markdown-docx/src/constants.js | 9 +- .../ciceroMark/optional-hasSome-false.json | 1 + .../test/data/ciceroMark/optional.json | 1 + .../data/ooxml/acceptance-of-delivery.xml | 724 +++++++++++++++++- .../omitted-acceptance-of-delivery.xml | 42 +- 9 files changed, 1089 insertions(+), 114 deletions(-) create mode 100644 packages/markdown-docx/test/data/ciceroMark/optional-hasSome-false.json create mode 100644 packages/markdown-docx/test/data/ciceroMark/optional.json 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 5c61ef8a..8bdf2222 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 @@ -71,12 +71,13 @@ - + + "Party A" @@ -105,12 +106,13 @@ - + + "Party B" @@ -129,12 +131,13 @@ - + + "Widgets" @@ -163,12 +166,13 @@ - + + "Party B" @@ -187,12 +191,13 @@ - + + "Party A" @@ -221,12 +226,13 @@ - + + "Widgets" @@ -263,12 +269,13 @@ - + + "Party B" @@ -287,12 +294,13 @@ - + + 10 @@ -321,12 +329,13 @@ - + + "Widgets" @@ -349,12 +358,13 @@ - + + "Party A" @@ -377,12 +387,13 @@ - + + "Widgets" @@ -425,12 +436,13 @@ - + + "Widgets" @@ -453,12 +465,13 @@ - + + "Party A" @@ -487,12 +500,13 @@ - + + "Attachment X" diff --git a/packages/markdown-docx/src/ToCiceroMarkVisitor.js b/packages/markdown-docx/src/ToCiceroMarkVisitor.js index ac5fac88..58a0b868 100644 --- a/packages/markdown-docx/src/ToCiceroMarkVisitor.js +++ b/packages/markdown-docx/src/ToCiceroMarkVisitor.js @@ -16,7 +16,7 @@ const xmljs = require('xml-js'); -const { TRANSFORMED_NODES } = require('./constants'); +const { TRANSFORMED_NODES, SEPARATOR } = require('./constants'); /** * Transforms OOXML to CiceroMark @@ -32,7 +32,7 @@ class ToCiceroMarkVisitor { // All the nodes generated from given OOXML this.nodes = []; - // contains the realtionship part of a given OOXML + // contains the relationship part of a given OOXML this.relationshipXML = []; } @@ -64,7 +64,7 @@ class ToCiceroMarkVisitor { getName(variableProperties) { for (const property of variableProperties) { if (property.name === 'w:tag') { - return property.attributes['w:val']; + return property.attributes['w:val'].split(SEPARATOR)[1]; } } } @@ -81,13 +81,13 @@ class ToCiceroMarkVisitor { // eg. "Shipper1 | org.accordproject.organization.Organization" const combinedTitle = property.attributes['w:val']; // Index 1 will return the type - return combinedTitle.split(' | ')[1]; + return combinedTitle.split(SEPARATOR)[1]; } } } /** - * Gets the node type based on the color property. + * Gets the node type based on the tag value. * * @param {Array} properties the variable elements * @returns {string} the type of the node @@ -95,11 +95,8 @@ class ToCiceroMarkVisitor { getNodeType(properties) { let nodeType = TRANSFORMED_NODES.variable; for (const property of properties) { - if (property.name === 'w15:color') { - // eg. "Shipper1 | org.accordproject.organization.Organization" - if (property.attributes['w:val'] === '99CCFF') { - nodeType = TRANSFORMED_NODES.clause; - } + if (property.name === 'w:tag') { + return property.attributes['w:val'].split(SEPARATOR)[0]; } } return nodeType; @@ -217,6 +214,22 @@ class ToCiceroMarkVisitor { $class: nodeInformation.properties[nodePropertyIndex], nodes: [ciceroMarkNode], }; + if (nodeInformation.properties[nodePropertyIndex] === TRANSFORMED_NODES.optional) { + let currentNodes = [...ciceroMarkNode.nodes]; + ciceroMarkNode = { + $class: TRANSFORMED_NODES.optional, + ...nodeInformation.optionalProperties, + hasSome: nodeInformation.hasSome, + nodes: currentNodes, + }; + if (nodeInformation.whenSomeType) { + ciceroMarkNode.whenSome = currentNodes; + ciceroMarkNode.whenNone = []; + } else { + ciceroMarkNode.whenNone = currentNodes; + ciceroMarkNode.whenSome = []; + } + } if (nodeInformation.properties[nodePropertyIndex] === TRANSFORMED_NODES.link) { ciceroMarkNode.title = ''; for (const relationshipElement of this.relationshipXML) { @@ -230,6 +243,21 @@ class ToCiceroMarkVisitor { return ciceroMarkNode; } + /** + * Generates a nesting for the nodes with common level. + * + * @param {number} commonLevel Common level of nesting + * @returns {string} Expression for the nesting + */ + generateNesting(commonLevel) { + let expression = 'rootNode.nodes[rootNodesLength - 1].'; + for (let currLevel = 2; currLevel <= commonLevel; currLevel++) { + const lengthExpression = expression + 'nodes.length-1'; + expression = expression + `nodes[${lengthExpression}].`; + } + return expression; + } + /** * Generates all nodes present in a block element( paragraph, heading ). * @@ -243,6 +271,8 @@ class ToCiceroMarkVisitor { constructedNode = this.constructCiceroMarkNodeJSON(this.JSONXML[0]); rootNode.nodes = [...rootNode.nodes, constructedNode]; + // not used explicitly but used via eval function + //eslint-disable-next-line let rootNodesLength = 1; for (let nodeIndex = 1; nodeIndex < this.JSONXML.length; nodeIndex++) { let propertiesPrevious = this.JSONXML[nodeIndex - 1].properties; @@ -266,28 +296,25 @@ class ToCiceroMarkVisitor { }; constructedNode = this.constructCiceroMarkNodeJSON(updatedProperties); + const nestedExpression = this.generateNesting(commonPropertiesLength); if (commonPropertiesLength === 0) { rootNode.nodes = [...rootNode.nodes, constructedNode]; rootNodesLength++; - } else if (commonPropertiesLength === 1) { - rootNode.nodes[rootNodesLength - 1].nodes = [ - ...rootNode.nodes[rootNodesLength - 1].nodes, - constructedNode, - ]; - } else if (commonPropertiesLength === 2) { - const subNodeLength = rootNode.nodes[rootNodesLength - 1].nodes.length; - rootNode.nodes[rootNodesLength - 1].nodes[subNodeLength - 1].nodes = [ - ...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, - ]; + } else { + if (propertiesCurrent[commonPropertiesLength - 1] === TRANSFORMED_NODES.optional) { + if (this.JSONXML[nodeIndex].whenSomeType) { + eval(`${nestedExpression}whenSome=[...${nestedExpression}whenSome, constructedNode]`); + } else { + eval(`${nestedExpression}whenNone=[...${nestedExpression}whenNone, constructedNode]`); + } + if (eval(`${nestedExpression}hasSome`)) { + eval(`${nestedExpression}nodes=${nestedExpression}whenSome`); + } else { + eval(`${nestedExpression}nodes=${nestedExpression}whenNone`); + } + } else { + eval(`${nestedExpression}nodes=[...${nestedExpression}nodes, constructedNode]`); + } } } this.JSONXML = []; @@ -305,10 +332,11 @@ class ToCiceroMarkVisitor { * @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 - * @returns {string} Value in tags + * @returns {(object | string)} nodeInformation object(properties, value, type, etc.) if optional else value in tags */ - fetchFormattingProperties(node, calledBy = TRANSFORMED_NODES.paragraph, nodeInformation = null) { + fetchFormattingProperties(node, calledBy = TRANSFORMED_NODES.paragraph, nodeInformation = {}) { let ooxmlTagTextValue = ''; + // let currentNode; if (calledBy === TRANSFORMED_NODES.link) { nodeInformation.properties = [...nodeInformation.properties, calledBy]; } @@ -316,6 +344,8 @@ class ToCiceroMarkVisitor { if (runTimeNodes.name === 'w:rPr') { let colorCodePresent = false; let shadeCodePresent = false; + let vanishPropertyPresent = false; + let fontFamilyPresent = false; for (let runTimeProperties of runTimeNodes.elements) { if (runTimeProperties.name === 'w:i') { nodeInformation.properties = [...nodeInformation.properties, TRANSFORMED_NODES.emphasize]; @@ -334,14 +364,40 @@ class ToCiceroMarkVisitor { if (runTimeProperties.attributes['w:fill'] === 'F9F2F4') { shadeCodePresent = true; } + } else if (runTimeProperties.name === 'w:vanish') { + vanishPropertyPresent = true; + } else if (runTimeProperties.name === 'w:rFonts') { + if (runTimeProperties.attributes['w:ascii'] === 'Baskerville Old Face') { + fontFamilyPresent = true; + } } } if (colorCodePresent && shadeCodePresent) { nodeInformation.nodeType = TRANSFORMED_NODES.code; } + if (vanishPropertyPresent) { + if (fontFamilyPresent) { + nodeInformation.whenSomeType = false; + nodeInformation.hasSome = true; + } else { + nodeInformation.whenSomeType = true; + nodeInformation.hasSome = false; + } + } else { + if (fontFamilyPresent) { + nodeInformation.whenSomeType = false; + nodeInformation.hasSome = false; + } else { + nodeInformation.whenSomeType = true; + nodeInformation.hasSome = true; + } + } } else if (runTimeNodes.name === 'w:t') { if (calledBy === TRANSFORMED_NODES.codeBlock) { ooxmlTagTextValue += runTimeNodes.elements ? runTimeNodes.elements[0].text : ''; + } else if (calledBy === TRANSFORMED_NODES.optional) { + ooxmlTagTextValue = runTimeNodes.elements && runTimeNodes.elements[0].text; + nodeInformation.value = ooxmlTagTextValue; } else { ooxmlTagTextValue = runTimeNodes.elements ? runTimeNodes.elements[0].text : ' '; nodeInformation.value = ooxmlTagTextValue; @@ -351,9 +407,21 @@ class ToCiceroMarkVisitor { ooxmlTagTextValue += '\n'; } else if (runTimeNodes.name === 'w:sym') { nodeInformation.nodeType = TRANSFORMED_NODES.softbreak; - this.JSONXML = [...this.JSONXML, nodeInformation]; + if (calledBy !== TRANSFORMED_NODES.optional) { + this.JSONXML = [...this.JSONXML, nodeInformation]; + } } } + // if no w:rPr is present then it is still possible that + // node is of optional type with no formatting properties + if (typeof nodeInformation.hasSome === 'undefined') { + nodeInformation.whenSomeType = true; + nodeInformation.hasSome = true; + } + + if (calledBy === TRANSFORMED_NODES.optional) { + return nodeInformation; + } return ooxmlTagTextValue; } @@ -374,6 +442,9 @@ class ToCiceroMarkVisitor { // Contains node present in a codeblock or blockquote, etc. let blockNodes = []; + + // Generated inline nodes using w:r tag + let inlineNodes = []; for (const subNode of node) { if (subNode.name === 'w:p') { if (!subNode.elements) { @@ -469,14 +540,32 @@ class ToCiceroMarkVisitor { nodes, }; this.nodes = [...this.nodes, clauseNode]; + } else if (nodeInformation.nodeType === TRANSFORMED_NODES.optional) { + if (variableSubNodes.elements) { + const optionalNodes = this.traverseElements( + variableSubNodes.elements, + TRANSFORMED_NODES.optional + ); + for (const node of optionalNodes) { + node.properties = [TRANSFORMED_NODES.optional, ...node.properties]; + node.optionalProperties = { + elementType: nodeInformation.elementType, + name: nodeInformation.name, + }; + this.JSONXML = [...this.JSONXML, { ...node }]; + } + } } else { for (const variableContentNodes of variableSubNodes.elements) { if (variableContentNodes.name === 'w:r') { - this.fetchFormattingProperties( - variableContentNodes, - TRANSFORMED_NODES.paragraph, - nodeInformation - ); + inlineNodes = [ + ...inlineNodes, + this.fetchFormattingProperties( + variableContentNodes, + parent, + nodeInformation + ), + ]; } } } @@ -490,9 +579,12 @@ class ToCiceroMarkVisitor { if (parent === TRANSFORMED_NODES.link) { nodeInformation.linkId = id; } - this.fetchFormattingProperties(subNode, parent, nodeInformation); + inlineNodes = [...inlineNodes, this.fetchFormattingProperties(subNode, parent, nodeInformation)]; } } + if (parent === TRANSFORMED_NODES.optional) { + return inlineNodes; + } return blockNodes; } diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/index.js b/packages/markdown-docx/src/ToOOXMLVisitor/index.js index 28311bff..9e430009 100644 --- a/packages/markdown-docx/src/ToOOXMLVisitor/index.js +++ b/packages/markdown-docx/src/ToOOXMLVisitor/index.js @@ -31,6 +31,9 @@ const { CLAUSE_RULE, LINK_RULE, LINK_PROPERTY_RULE, + OPTIONAL_RULE, + VANISH_PROPERTY_RULE, + CONDITIONAL_OR_OPTIONAL_FONT_FAMILY_RULE, } = require('./rules'); const { wrapAroundDefaultDocxTags, wrapAroundLockedContentControls } = require('./helpers'); const { TRANSFORMED_NODES, RELATIONSHIP_OFFSET } = require('../constants'); @@ -66,12 +69,13 @@ class ToOOXMLVisitor { /** * 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 + * @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 + * @param {object} parentProperties Properties of parent on which children depend for certain styles(e.g. vanish property for hidden nodes) * @returns {string} Generated OOXML */ - generateTextOrCodeOOXML(value, nodeProperties, calledByCode = false) { + generateTextOrCodeOOXML(value, nodeProperties, calledByCode = false, parentProperties) { let propertyTag = ''; if (calledByCode) { propertyTag = CODE_PROPERTIES_RULE(); @@ -87,6 +91,12 @@ class ToOOXMLVisitor { propertyTag += LINK_PROPERTY_RULE(); } } + if (parentProperties.traversingNodeHiddenInConditional) { + propertyTag += VANISH_PROPERTY_RULE(); + } + if (parentProperties.traversingNodePresentInWhenFalseOrWhenNoneCondtion) { + propertyTag += CONDITIONAL_OR_OPTIONAL_FONT_FAMILY_RULE(); + } if (propertyTag) { propertyTag = TEXT_STYLES_RULE(propertyTag); } @@ -102,23 +112,56 @@ class ToOOXMLVisitor { return tag; } + /** + * Checks if an object contains the property and updates counter for it. + * + * @param {string} tag Tag of the property + * @param {string} type Type of the property + */ + createOrUpdateCounter(tag, type) { + if (Object.prototype.hasOwnProperty.call(this.counter, tag)) { + this.counter = { + ...this.counter, + [tag]: { + ...this.counter[tag], + count: ++this.counter[tag].count, + }, + }; + } else { + this.counter[tag] = { + count: 1, + type, + }; + } + } + /** * Traverses CiceroMark nodes in a DFS approach * - * @param {object} node CiceroMark Node - * @param {array} properties Properties to be applied on current node + * @param {object} node CiceroMark Node + * @param {array} properties Properties to be applied on current node + * @param {string} parent Parent element of the node(paragraph, clause, heading, and optional) + * @param {object} parentProperties Properties of parent on which children depend for certain styles(vanish property for hidden nodes) + * @returns {string} OOXML for the inline nodes of the current parent */ - traverseNodes(node, properties = []) { + traverseNodes(node, properties = [], parent, parentProperties = {}) { if (this.getClass(node) === 'org.accordproject.commonmark.Document') { - this.traverseNodes(node.nodes, properties); + this.traverseNodes(node.nodes, properties, parent, parentProperties); } else { + let inlineOOXML = ''; for (let subNode of node) { if (this.getClass(subNode) === TRANSFORMED_NODES.text) { - const tag = this.generateTextOrCodeOOXML(subNode.text, properties); - this.tags = [...this.tags, tag]; + const tag = this.generateTextOrCodeOOXML(subNode.text, properties, false, parentProperties); + inlineOOXML += tag; + if (parent !== TRANSFORMED_NODES.optional) { + this.tags = [...this.tags, tag]; + } } else if (this.getClass(subNode) === TRANSFORMED_NODES.code) { - const tag = this.generateTextOrCodeOOXML(subNode.text, properties, true); - this.tags = [...this.tags, tag]; + const tag = this.generateTextOrCodeOOXML(subNode.text, properties, true, parentProperties); + inlineOOXML += tag; + if (parent !== TRANSFORMED_NODES.optional) { + this.tags = [...this.tags, tag]; + } } else if (this.getClass(subNode) === TRANSFORMED_NODES.codeBlock) { let ooxml = CODEBLOCK_PROPERTIES_RULE(); let textValues = subNode.text.split('\n'); @@ -136,26 +179,25 @@ class ToOOXMLVisitor { } else if (this.getClass(subNode) === TRANSFORMED_NODES.variable) { const tag = subNode.name; const type = subNode.elementType; - if (Object.prototype.hasOwnProperty.call(this.counter, tag)) { - this.counter = { - ...this.counter, - [tag]: { - ...this.counter[tag], - count: ++this.counter[tag].count, - }, - }; - } else { - this.counter[tag] = { - count: 1, - type, - }; - } + this.createOrUpdateCounter(tag, type); const value = subNode.value; const title = `${tag.toUpperCase()[0]}${tag.substring(1)}${this.counter[tag].count}`; - this.tags = [...this.tags, VARIABLE_RULE(title, tag, value, type)]; + inlineOOXML += VARIABLE_RULE( + title, + tag, + value, + type, + parentProperties.traversingNodeHiddenInConditional + ); + if (parent !== TRANSFORMED_NODES.optional) { + this.tags = [...this.tags, VARIABLE_RULE(title, tag, value, type)]; + } } else if (this.getClass(subNode) === TRANSFORMED_NODES.softbreak) { - this.tags = [...this.tags, SOFTBREAK_RULE()]; + inlineOOXML += SOFTBREAK_RULE(); + if (parent !== TRANSFORMED_NODES.optional) { + this.tags = [...this.tags, SOFTBREAK_RULE()]; + } } else if (this.getClass(subNode) === TRANSFORMED_NODES.thematicBreak) { this.globalOOXML += THEMATICBREAK_RULE(); } else if (this.getClass(subNode) === TRANSFORMED_NODES.clause) { @@ -163,7 +205,12 @@ class ToOOXMLVisitor { if (subNode.nodes) { for (const deepNode of subNode.nodes) { if (this.getClass(deepNode) === TRANSFORMED_NODES.paragraph) { - this.traverseNodes(deepNode.nodes, properties); + this.traverseNodes( + deepNode.nodes, + properties, + TRANSFORMED_NODES.paragraph, + parentProperties + ); let ooxml = ''; for (let xmlTag of this.tags) { ooxml += xmlTag; @@ -174,7 +221,12 @@ class ToOOXMLVisitor { // Clear all the tags as all nodes of paragraph have been traversed. this.tags = []; } else if (this.getClass(deepNode) === TRANSFORMED_NODES.heading) { - this.traverseNodes(deepNode.nodes, properties); + this.traverseNodes( + deepNode.nodes, + properties, + TRANSFORMED_NODES.heading, + parentProperties + ); let ooxml = ''; for (let xmlTag of this.tags) { let headingPropertiesTag = ''; @@ -190,7 +242,7 @@ class ToOOXMLVisitor { this.tags = []; } else { let newProperties = [...properties, deepNode.$class]; - this.traverseNodes(deepNode.nodes, newProperties); + this.traverseNodes(deepNode.nodes, newProperties, parent, parentProperties); } } const tag = subNode.name; @@ -215,7 +267,12 @@ class ToOOXMLVisitor { } else { if (subNode.nodes) { if (this.getClass(subNode) === TRANSFORMED_NODES.paragraph) { - this.traverseNodes(subNode.nodes, properties); + this.traverseNodes( + subNode.nodes, + properties, + TRANSFORMED_NODES.paragraph, + parentProperties + ); let ooxml = ''; for (let xmlTag of this.tags) { ooxml += xmlTag; @@ -226,7 +283,7 @@ class ToOOXMLVisitor { // Clear all the tags as all nodes of paragraph have been traversed. this.tags = []; } else if (this.getClass(subNode) === TRANSFORMED_NODES.heading) { - this.traverseNodes(subNode.nodes, properties); + this.traverseNodes(subNode.nodes, properties, TRANSFORMED_NODES.heading, parentProperties); let ooxml = ''; for (let xmlTag of this.tags) { let headingPropertiesTag = ''; @@ -240,6 +297,34 @@ class ToOOXMLVisitor { this.globalOOXML += ooxml; this.tags = []; + } else if (this.getClass(subNode) === TRANSFORMED_NODES.optional) { + parentProperties.traversingNodeHiddenInConditional = !subNode.hasSome; + // traverse the whenSome properties now + const whenSomeOOXML = this.traverseNodes( + subNode.whenSome, + properties, + TRANSFORMED_NODES.optional, + parentProperties + ); + parentProperties.traversingNodePresentInWhenFalseOrWhenNoneCondtion = true; + // traverse whenNone properties now + const whenNoneOOXML = this.traverseNodes( + subNode.whenNone, + properties, + TRANSFORMED_NODES.optional, + parentProperties + ); + + const ooxml = `${whenSomeOOXML} ${whenNoneOOXML}`; + const tag = subNode.name; + const type = subNode.elementType; + this.createOrUpdateCounter(tag, type); + const title = `${tag.toUpperCase()[0]}${tag.substring(1)}${this.counter[tag].count}`; + const optionalTag = OPTIONAL_RULE(title, tag, ooxml, type); + this.tags = [...this.tags, optionalTag]; + // make the parentProperties false as traversal is done + parentProperties.traversingNodeHiddenInConditional = false; + parentProperties.traversingNodePresentInWhenFalseOrWhenNoneCondtion = false; } else { if (this.getClass(subNode) === TRANSFORMED_NODES.link) { this.relationships = [ @@ -251,11 +336,12 @@ class ToOOXMLVisitor { ]; } let newProperties = [...properties, subNode.$class]; - this.traverseNodes(subNode.nodes, newProperties); + this.traverseNodes(subNode.nodes, newProperties, parent, parentProperties); } } } } + return inlineOOXML; } } @@ -266,7 +352,7 @@ class ToOOXMLVisitor { * @returns {string} OOXML string */ toOOXML(ciceromark) { - this.traverseNodes(ciceromark, []); + this.traverseNodes(ciceromark, [], TRANSFORMED_NODES.document); this.globalOOXML = wrapAroundLockedContentControls(this.globalOOXML); this.globalOOXML = wrapAroundDefaultDocxTags(this.globalOOXML, this.relationships); diff --git a/packages/markdown-docx/src/ToOOXMLVisitor/rules.js b/packages/markdown-docx/src/ToOOXMLVisitor/rules.js index 6620bb4a..eedffa2e 100644 --- a/packages/markdown-docx/src/ToOOXMLVisitor/rules.js +++ b/packages/markdown-docx/src/ToOOXMLVisitor/rules.js @@ -14,6 +14,7 @@ 'use strict'; +const { TRANSFORMED_NODES, SEPARATOR } = require('../constants'); const { sanitizeHtmlChars, titleGenerator } = require('./helpers'); /** @@ -121,13 +122,14 @@ const STRONG_RULE = () => { /** * Inserts a variable. * - * @param {string} title Title of the variable. Eg. receiver-1, shipper-1 - * @param {string} tag Name of the variable. Eg. receiver, shipper - * @param {string} value Value of the variable - * @param {string} type Type of the variable - Long, Double, etc. + * @param {string} title Title of the variable. Eg. receiver-1, shipper-1 + * @param {string} tag Name of the variable. Eg. receiver, shipper + * @param {string} value Value of the variable + * @param {string} type Type of the variable - Long, Double, etc. + * @param {boolean} vanish Should vanish property be present * @returns {string} OOXML string for the variable */ -const VARIABLE_RULE = (title, tag, value, type) => { +const VARIABLE_RULE = (title, tag, value, type, vanish = false) => { return ` @@ -135,12 +137,13 @@ const VARIABLE_RULE = (title, tag, value, type) => { - + + ${vanish ? VANISH_PROPERTY_RULE() : ''} ${sanitizeHtmlChars(value)} @@ -220,7 +223,7 @@ const CLAUSE_RULE = (title, tag, type, content) => { - + ${content} @@ -248,6 +251,46 @@ const LINK_RULE = (value, relationshipId) => { `; }; +/** + * Inserts optional node in OOXML form. + * + * @param {string} title Title of the optional node. Eg. receiver-1, shipper-1 + * @param {string} tag Name of the optional node. Eg. receiver, shipper + * @param {string} value Value of the optional node + * @param {string} type Type of the optional node - Long, Double, etc. + * @returns {string} OOXML string for the variable + */ +const OPTIONAL_RULE = (title, tag, value, type) => { + return ` + + + + + + + + ${value} + + + `; +}; + +/** + * Inserts a vanish(http://officeopenxml.com/WPtextFormatting.php) OOXML tag. + * + * @returns {string} OOXML tag for vanish + */ +const VANISH_PROPERTY_RULE = () => ''; + +/** + * Inserts a different font family so that the `whenNone` nodes for optional content can be distinguished from `whenSome` nodes. + * + * @returns {string} OOXML tag for Baskerville Old Face font family + */ +const CONDITIONAL_OR_OPTIONAL_FONT_FAMILY_RULE = () => { + return ''; +}; + module.exports = { TEXT_RULE, EMPHASIS_RULE, @@ -265,4 +308,7 @@ module.exports = { CLAUSE_RULE, LINK_PROPERTY_RULE, LINK_RULE, + OPTIONAL_RULE, + VANISH_PROPERTY_RULE, + CONDITIONAL_OR_OPTIONAL_FONT_FAMILY_RULE, }; diff --git a/packages/markdown-docx/src/constants.js b/packages/markdown-docx/src/constants.js index 844ee40a..4014c688 100644 --- a/packages/markdown-docx/src/constants.js +++ b/packages/markdown-docx/src/constants.js @@ -21,6 +21,7 @@ const TRANSFORMED_NODES = { code: `${NS_PREFIX_CommonMarkModel}Code`, codeBlock: `${NS_PREFIX_CommonMarkModel}CodeBlock`, computedVariable: `${NS_PREFIX_CiceroMarkModel}ComputedVariable`, + optional: `${NS_PREFIX_CiceroMarkModel}Optional`, document: `${NS_PREFIX_CommonMarkModel}Document`, emphasize: `${NS_PREFIX_CommonMarkModel}Emph`, heading: `${NS_PREFIX_CommonMarkModel}Heading`, @@ -41,4 +42,10 @@ const TRANSFORMED_NODES = { // Used in './ToOOXMLVisitor/index.js' const RELATIONSHIP_OFFSET = 2; -module.exports = { TRANSFORMED_NODES, RELATIONSHIP_OFFSET }; +// Used to separate entities in w:tag and w:alias +// example +// Then use the separator to extract those entities while transforming +// from ooxml to ciceromark. +const SEPARATOR = ' | '; + +module.exports = { TRANSFORMED_NODES, RELATIONSHIP_OFFSET, SEPARATOR }; diff --git a/packages/markdown-docx/test/data/ciceroMark/optional-hasSome-false.json b/packages/markdown-docx/test/data/ciceroMark/optional-hasSome-false.json new file mode 100644 index 00000000..c3e29468 --- /dev/null +++ b/packages/markdown-docx/test/data/ciceroMark/optional-hasSome-false.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.Text","text":"Late Delivery and Penalty."}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"In case of delayed delivery"},{"$class":"org.accordproject.ciceromark.Optional","hasSome":false,"whenSome":[{"$class":"org.accordproject.commonmark.Text","text":" except for Force Majeure cases in a "},{"$class":"org.accordproject.ciceromark.Variable","value":"100.0","name":"miles","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":" miles radius,"}],"whenNone":[{"$class":"org.accordproject.commonmark.Text","text":"This applies even in case a force majeure."}],"name":"forceMajeure","elementType":"org.accordproject.latedeliveryandpenalty.Distance","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"This applies even in case a force majeure."}]},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.ciceromark.Variable","value":"\"Dan\"","name":"seller","elementType":"org.accordproject.party.Party"},{"$class":"org.accordproject.commonmark.Text","text":" (the Seller) shall pay to "},{"$class":"org.accordproject.ciceromark.Variable","value":"\"Steve\"","name":"buyer","elementType":"org.accordproject.party.Party"},{"$class":"org.accordproject.commonmark.Text","text":" (the Buyer) for every "},{"$class":"org.accordproject.ciceromark.Variable","value":"2","name":"amount","elementType":"Long"},{"$class":"org.accordproject.commonmark.Text","text":" "},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"of delay penalty amounting to "},{"$class":"org.accordproject.ciceromark.Variable","value":"10.5","name":"penaltyPercentage","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":"% of the total value of the Equipment"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"whose delivery has been delayed. Any fractional part of a "},{"$class":"org.accordproject.commonmark.Text","text":" is to be"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"considered a full "},{"$class":"org.accordproject.commonmark.Text","text":". The total amount of penalty shall not however,"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"exceed "},{"$class":"org.accordproject.ciceromark.Variable","value":"55.0","name":"capPercentage","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":"% of the total value of the Equipment involved in late delivery."},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"If the delay is more than "},{"$class":"org.accordproject.ciceromark.Variable","value":"15","name":"amount","elementType":"Long"},{"$class":"org.accordproject.commonmark.Text","text":" "},{"$class":"org.accordproject.commonmark.Text","text":", the Buyer is entitled to terminate this Contract."}]}]} \ No newline at end of file diff --git a/packages/markdown-docx/test/data/ciceroMark/optional.json b/packages/markdown-docx/test/data/ciceroMark/optional.json new file mode 100644 index 00000000..cf023cec --- /dev/null +++ b/packages/markdown-docx/test/data/ciceroMark/optional.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.Text","text":"Late Delivery and Penalty."}]},{"$class":"org.accordproject.commonmark.Paragraph","nodes":[{"$class":"org.accordproject.commonmark.Text","text":"In case of delayed delivery"},{"$class":"org.accordproject.ciceromark.Optional","hasSome":true,"whenSome":[{"$class":"org.accordproject.commonmark.Text","text":" except for Force Majeure cases in a "},{"$class":"org.accordproject.ciceromark.Variable","value":"100.0","name":"miles","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":" miles radius,"}],"whenNone":[],"name":"forceMajeure","elementType":"org.accordproject.latedeliveryandpenalty.Distance","nodes":[{"$class":"org.accordproject.commonmark.Text","text":" except for Force Majeure cases in a "},{"$class":"org.accordproject.ciceromark.Variable","value":"100.0","name":"miles","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":" miles radius,"}]},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.ciceromark.Variable","value":"\"Dan\"","name":"seller","elementType":"org.accordproject.party.Party"},{"$class":"org.accordproject.commonmark.Text","text":" (the Seller) shall pay to "},{"$class":"org.accordproject.ciceromark.Variable","value":"\"Steve\"","name":"buyer","elementType":"org.accordproject.party.Party"},{"$class":"org.accordproject.commonmark.Text","text":" (the Buyer) for every "},{"$class":"org.accordproject.ciceromark.Variable","value":"2","name":"amount","elementType":"Long"},{"$class":"org.accordproject.commonmark.Text","text":" "},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"of delay penalty amounting to "},{"$class":"org.accordproject.ciceromark.Variable","value":"10.5","name":"penaltyPercentage","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":"% of the total value of the Equipment"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"whose delivery has been delayed. Any fractional part of a "},{"$class":"org.accordproject.commonmark.Text","text":" is to be"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"considered a full "},{"$class":"org.accordproject.commonmark.Text","text":". The total amount of penalty shall not however,"},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"exceed "},{"$class":"org.accordproject.ciceromark.Variable","value":"55.0","name":"capPercentage","elementType":"Double"},{"$class":"org.accordproject.commonmark.Text","text":"% of the total value of the Equipment involved in late delivery."},{"$class":"org.accordproject.commonmark.Softbreak"},{"$class":"org.accordproject.commonmark.Text","text":"If the delay is more than "},{"$class":"org.accordproject.ciceromark.Variable","value":"15","name":"amount","elementType":"Long"},{"$class":"org.accordproject.commonmark.Text","text":" "},{"$class":"org.accordproject.commonmark.Text","text":", the Buyer is entitled to terminate this Contract."}]}]} \ No newline at end of file diff --git a/packages/markdown-docx/test/data/ooxml/acceptance-of-delivery.xml b/packages/markdown-docx/test/data/ooxml/acceptance-of-delivery.xml index 4af6e2fb..8bdf2222 100644 --- a/packages/markdown-docx/test/data/ooxml/acceptance-of-delivery.xml +++ b/packages/markdown-docx/test/data/ooxml/acceptance-of-delivery.xml @@ -1,5 +1,719 @@ - - - - -Acceptance of Delivery."Party A" will be deemed to have completed its delivery obligationsif in "Party B"'s opinion, the "Widgets" satisfies theAcceptance Criteria, and "Party B" notifies "Party A" in writingthat it is accepting the "Widgets".Inspection and Notice."Party B" will have 10 Business Days to inspect andevaluate the "Widgets" on the delivery date before notifying"Party A" that it is either accepting or rejecting the"Widgets".Acceptance Criteria.The "Acceptance Criteria" are the specifications the "Widgets"must meet for the "Party A" to comply with its requirements andobligations under this agreement, detailed in "Attachment X", attachedto this agreement. \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Acceptance of Delivery. + + + + + + + + + + + + + + + + + + + + + "Party A" + + + + + + + + will be deemed to have completed its delivery obligations + + + + + + + + + + if in + + + + + + + + + + + + + + + + + "Party B" + + + + + + + + 's opinion, the + + + + + + + + + + + + + + + + + "Widgets" + + + + + + + + satisfies the + + + + + + + + + + Acceptance Criteria, and + + + + + + + + + + + + + + + + + "Party B" + + + + + + + + notifies + + + + + + + + + + + + + + + + + "Party A" + + + + + + + + in writing + + + + + + + + + + that it is accepting the + + + + + + + + + + + + + + + + + "Widgets" + + + + + + + + . + + + + + + + + + + + + + + Inspection and Notice. + + + + + + + + + + + + + + + + + + + + + "Party B" + + + + + + + + will have + + + + + + + + + + + + + + + + + 10 + + + + + + + + Business Days to inspect and + + + + + + + + + + evaluate the + + + + + + + + + + + + + + + + + "Widgets" + + + + + + + + on the delivery date before notifying + + + + + + + + + + + + + + + + + + + + + "Party A" + + + + + + + + that it is either accepting or rejecting the + + + + + + + + + + + + + + + + + + + + + "Widgets" + + + + + + + + . + + + + + + + + + + + + + + Acceptance Criteria. + + + + + + + + + + The "Acceptance Criteria" are the specifications the + + + + + + + + + + + + + + + + + "Widgets" + + + + + + + + + + + + must meet for the + + + + + + + + + + + + + + + + + "Party A" + + + + + + + + to comply with its requirements and + + + + + + + + + + obligations under this agreement, detailed in + + + + + + + + + + + + + + + + + "Attachment X" + + + + + + + + , attached + + + + + + + + + + to this agreement. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 5c61ef8a..8bdf2222 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 @@ -71,12 +71,13 @@ - + + "Party A" @@ -105,12 +106,13 @@ - + + "Party B" @@ -129,12 +131,13 @@ - + + "Widgets" @@ -163,12 +166,13 @@ - + + "Party B" @@ -187,12 +191,13 @@ - + + "Party A" @@ -221,12 +226,13 @@ - + + "Widgets" @@ -263,12 +269,13 @@ - + + "Party B" @@ -287,12 +294,13 @@ - + + 10 @@ -321,12 +329,13 @@ - + + "Widgets" @@ -349,12 +358,13 @@ - + + "Party A" @@ -377,12 +387,13 @@ - + + "Widgets" @@ -425,12 +436,13 @@ - + + "Widgets" @@ -453,12 +465,13 @@ - + + "Party A" @@ -487,12 +500,13 @@ - + + "Attachment X"