Skip to content

Commit d1adb51

Browse files
authored
refactor(markdown-docx): merge methods for roundtripping conversion between OOXML and CiceroMark into one class - OOXMLTransformer (#425)
Signed-off-by: K-Kumar-01 <[email protected]>
1 parent da05f4e commit d1adb51

File tree

8 files changed

+339
-304
lines changed

8 files changed

+339
-304
lines changed

Diff for: packages/markdown-docx/index.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@
2020
*/
2121

2222
module.exports.DocxTransformer = require('./lib/DocxTransformer');
23-
module.exports.OoxmlTransformer = require('./lib/OOXMLTransformer');
24-
module.exports.CiceroMarkToOOXMLTransfomer = require('./lib/CiceroMarkToOOXML');
23+
module.exports.OOXMLTransformer = require('./lib/OOXMLTransformer');

Diff for: packages/markdown-docx/src/OOXMLTransformer/index.js

+17-274
Original file line numberDiff line numberDiff line change
@@ -14,291 +14,34 @@
1414

1515
'use strict';
1616

17-
const xmljs = require('xml-js');
18-
19-
const { NS_PREFIX_CommonMarkModel } = require('@accordproject/markdown-common').CommonMarkModel;
20-
const { NS_PREFIX_CiceroMarkModel } = require('@accordproject/markdown-cicero').CiceroMarkModel;
17+
const ToCiceroMarkVisitor = require('../ToCiceroMarkVisitor');
18+
const ToOOXMLVisitor = require('../ToOOXMLVisitor');
2119

2220
/**
23-
* Transforms OOXML to CiceroMark
21+
* Transforms OOXML to/from CiceroMark
2422
*/
25-
class OoxmlTransformer {
26-
/**
27-
* Defines the JSON XML array for blocks
28-
*/
29-
constructor() {
30-
// Stores the properties of each node which lies within a block node ( heading, paragraph, etc. )
31-
this.JSONXML = [];
32-
33-
// All the nodes generated from given OOXML
34-
this.nodes = [];
35-
}
36-
37-
/**
38-
* @param {object} headingElement the element to be checked
39-
* @returns {object} object of `isHeading` and its level
40-
*/
41-
getHeading(headingElement) {
42-
if (headingElement && headingElement.attributes !== undefined) {
43-
const headingLevel = headingElement.attributes['w:val'];
44-
if (headingElement.name === 'w:pStyle' && headingLevel.includes('Heading')) {
45-
return {
46-
isHeading: true,
47-
level: headingLevel[headingLevel.length - 1],
48-
};
49-
}
50-
}
51-
return {
52-
isHeading: false,
53-
};
54-
}
55-
56-
/**
57-
* Gets the id of the variable.
58-
*
59-
* @param {Array} variableProperties the variable elements
60-
* @returns {string} the name of the variable
61-
*/
62-
getName(variableProperties) {
63-
for (const property of variableProperties) {
64-
if (property.name === 'w:tag') {
65-
return property.attributes['w:val'];
66-
}
67-
}
68-
}
69-
23+
class OOXMLTransformer {
7024
/**
71-
* Get the type of the element.
25+
* Converts OOXML to CiceroMarkJSON
7226
*
73-
* @param {Array} variableProperties the variable elements
74-
* @returns {string} the type of the element
27+
* @param {string} input OOXML string to be converted
28+
* @returns {object} CiceroMark JSON
7529
*/
76-
getElementType(variableProperties) {
77-
for (const property of variableProperties) {
78-
if (property.name === 'w:alias') {
79-
// eg. "Shipper1 | org.accordproject.organization.Organization"
80-
const combinedTitle = property.attributes['w:val'];
81-
// Index 1 will return the type
82-
return combinedTitle.split(' | ')[1];
83-
}
84-
}
30+
toCiceroMark(input) {
31+
const visitor = new ToCiceroMarkVisitor();
32+
return visitor.toCiceroMark(input);
8533
}
8634

8735
/**
88-
* Constructs a ciceroMark Node for inline element from the information.
36+
* Converts CiceroMark to OOXML
8937
*
90-
* @param {object} nodeInformation Contains properties and value of a node
91-
* @return {object} CiceroMark Node
38+
* @param {object} input CiceroMark object
39+
* @returns {string} OOXML string
9240
*/
93-
constructCiceroMarkNodeJSON(nodeInformation) {
94-
let ciceroMarkNode = {};
95-
if (nodeInformation.nodeType === 'softbreak') {
96-
ciceroMarkNode = {
97-
$class: `${NS_PREFIX_CommonMarkModel}Softbreak`,
98-
};
99-
} else if (nodeInformation.nodeType === 'variable') {
100-
ciceroMarkNode = {
101-
$class: `${NS_PREFIX_CiceroMarkModel}Variable`,
102-
value: nodeInformation.value,
103-
elementType: nodeInformation.elementType,
104-
name: nodeInformation.name,
105-
};
106-
} else {
107-
ciceroMarkNode = {
108-
$class: `${NS_PREFIX_CommonMarkModel}Text`,
109-
text: nodeInformation.value,
110-
};
111-
}
112-
for (
113-
let nodePropertyIndex = nodeInformation.properties.length - 1;
114-
nodePropertyIndex >= 0;
115-
nodePropertyIndex--
116-
) {
117-
ciceroMarkNode = {
118-
$class: nodeInformation.properties[nodePropertyIndex],
119-
nodes: [ciceroMarkNode],
120-
};
121-
}
122-
return ciceroMarkNode;
123-
}
124-
125-
/**
126-
* Generates all nodes present in a block element( paragraph, heading ).
127-
*
128-
* @param {object} rootNode Block node like paragraph, heading, etc.
129-
*/
130-
generateNodes(rootNode) {
131-
if (this.JSONXML.length > 0) {
132-
let constructedNode;
133-
constructedNode = this.constructCiceroMarkNodeJSON(this.JSONXML[0]);
134-
rootNode.nodes = [...rootNode.nodes, constructedNode];
135-
136-
let rootNodesLength = 1;
137-
for (let nodeIndex = 1; nodeIndex < this.JSONXML.length; nodeIndex++) {
138-
let propertiesPrevious = this.JSONXML[nodeIndex - 1].properties;
139-
let propertiesCurrent = this.JSONXML[nodeIndex].properties;
140-
141-
let commonPropertiesLength = 0;
142-
for (
143-
let propertyIndex = 0;
144-
propertyIndex < Math.min(propertiesPrevious.length, propertiesCurrent.length);
145-
propertyIndex++
146-
) {
147-
if (propertiesCurrent[propertyIndex] === propertiesPrevious[propertyIndex]) {
148-
commonPropertiesLength++;
149-
} else {
150-
break;
151-
}
152-
}
153-
let updatedProperties = {
154-
...this.JSONXML[nodeIndex],
155-
properties: [...this.JSONXML[nodeIndex].properties.slice(commonPropertiesLength)],
156-
};
157-
constructedNode = this.constructCiceroMarkNodeJSON(updatedProperties);
158-
159-
if (commonPropertiesLength === 0) {
160-
rootNode.nodes = [...rootNode.nodes, constructedNode];
161-
rootNodesLength++;
162-
} else if (commonPropertiesLength === 1) {
163-
rootNode.nodes[rootNodesLength - 1].nodes = [
164-
...rootNode.nodes[rootNodesLength - 1].nodes,
165-
constructedNode,
166-
];
167-
}
168-
}
169-
this.JSONXML = [];
170-
this.nodes = [...this.nodes, rootNode];
171-
}
172-
}
173-
174-
/**
175-
* Traverses for properties and value.
176-
*
177-
* @param {Array} node Node to be traversed
178-
* @param {object} nodeInformation Information for the current node
179-
*/
180-
fetchFormattingProperties(node, nodeInformation) {
181-
for (const runTimeNodes of node.elements) {
182-
if (runTimeNodes.name === 'w:rPr') {
183-
for (let runTimeProperties of runTimeNodes.elements) {
184-
if (runTimeProperties.name === 'w:i') {
185-
nodeInformation.properties = [
186-
...nodeInformation.properties,
187-
`${NS_PREFIX_CommonMarkModel}Emph`,
188-
];
189-
} else if (runTimeProperties.name === 'w:b') {
190-
nodeInformation.properties = [
191-
...nodeInformation.properties,
192-
`${NS_PREFIX_CommonMarkModel}Strong`,
193-
];
194-
}
195-
}
196-
} else if (runTimeNodes.name === 'w:t') {
197-
nodeInformation.value = runTimeNodes.elements ? runTimeNodes.elements[0].text : ' ';
198-
this.JSONXML = [...this.JSONXML, nodeInformation];
199-
} else if (runTimeNodes.name === 'w:sym') {
200-
nodeInformation.nodeType = 'softbreak';
201-
this.JSONXML = [...this.JSONXML, nodeInformation];
202-
}
203-
}
204-
}
205-
206-
/**
207-
* Traverses the JSON object of XML elements in DFS approach.
208-
*
209-
* @param {object} node Node object to be traversed
210-
* @param {object} parent Parent node name
211-
*/
212-
traverseElements(node, parent = '') {
213-
for (const subNode of node) {
214-
if (subNode.name === 'w:p') {
215-
const { isHeading, level } = this.getHeading(
216-
subNode.elements && subNode.elements[0].elements && subNode.elements[0].elements[0]
217-
);
218-
219-
if (subNode.elements) {
220-
this.traverseElements(subNode.elements);
221-
}
222-
223-
if (isHeading) {
224-
let headingNode = {
225-
$class: `${NS_PREFIX_CommonMarkModel}Heading`,
226-
level,
227-
nodes: [],
228-
};
229-
this.generateNodes(headingNode);
230-
} else {
231-
let paragraphNode = {
232-
$class: `${NS_PREFIX_CommonMarkModel}Paragraph`,
233-
nodes: [],
234-
};
235-
this.generateNodes(paragraphNode);
236-
}
237-
} else if (subNode.name === 'w:sdt') {
238-
// denotes the whole template if parent is body
239-
if (parent === 'body') {
240-
this.traverseElements(subNode.elements[1].elements);
241-
} else {
242-
let nodeInformation = {
243-
properties: [],
244-
value: '',
245-
nodeType: 'variable',
246-
name: null,
247-
elementType: null,
248-
};
249-
for (const variableSubNodes of subNode.elements) {
250-
if (variableSubNodes.name === 'w:sdtPr') {
251-
nodeInformation.name = this.getName(variableSubNodes.elements);
252-
nodeInformation.elementType = this.getElementType(variableSubNodes.elements);
253-
}
254-
if (variableSubNodes.name === 'w:sdtContent') {
255-
for (const variableContentNodes of variableSubNodes.elements) {
256-
if (variableContentNodes.name === 'w:r') {
257-
this.fetchFormattingProperties(variableContentNodes, nodeInformation);
258-
}
259-
}
260-
}
261-
}
262-
}
263-
} else if (subNode.name === 'w:r') {
264-
let nodeInformation = { properties: [], value: '' };
265-
this.fetchFormattingProperties(subNode, nodeInformation);
266-
}
267-
}
268-
}
269-
270-
/**
271-
* Transform OOXML -> CiceroMark.
272-
*
273-
* @param {string} input the ooxml string
274-
* @param {string} pkgName the package name of the xml to be converted
275-
* @returns {object} CiceroMark object
276-
*/
277-
toCiceroMark(input, pkgName = '/word/document.xml') {
278-
// Parses the OOXML 'test/data/ooxml/document.xml' to JSON
279-
const convertedJSObject = xmljs.xml2js(input, {
280-
ignoreDeclaration: true,
281-
ignoreInstruction: true,
282-
});
283-
const rootNode = convertedJSObject.elements[0].elements;
284-
let documentNode;
285-
286-
for (const node of rootNode) {
287-
if (node.attributes['pkg:name'] === pkgName) {
288-
// Gets the document node
289-
documentNode = node.elements[0].elements[0];
290-
break;
291-
}
292-
}
293-
294-
this.traverseElements(documentNode.elements[0].elements, 'body');
295-
296-
return {
297-
$class: `${NS_PREFIX_CommonMarkModel}${'Document'}`,
298-
xmlns: 'http://commonmark.org/xml/1.0',
299-
nodes: this.nodes,
300-
};
41+
toOOXML(input) {
42+
const visitor = new ToOOXMLVisitor();
43+
return visitor.toOOXML(input);
30144
}
30245
}
30346

304-
module.exports = OoxmlTransformer;
47+
module.exports = OOXMLTransformer;

0 commit comments

Comments
 (0)