|
14 | 14 |
|
15 | 15 | 'use strict';
|
16 | 16 |
|
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'); |
21 | 19 |
|
22 | 20 | /**
|
23 |
| - * Transforms OOXML to CiceroMark |
| 21 | + * Transforms OOXML to/from CiceroMark |
24 | 22 | */
|
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 { |
70 | 24 | /**
|
71 |
| - * Get the type of the element. |
| 25 | + * Converts OOXML to CiceroMarkJSON |
72 | 26 | *
|
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 |
75 | 29 | */
|
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); |
85 | 33 | }
|
86 | 34 |
|
87 | 35 | /**
|
88 |
| - * Constructs a ciceroMark Node for inline element from the information. |
| 36 | + * Converts CiceroMark to OOXML |
89 | 37 | *
|
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 |
92 | 40 | */
|
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); |
301 | 44 | }
|
302 | 45 | }
|
303 | 46 |
|
304 |
| -module.exports = OoxmlTransformer; |
| 47 | +module.exports = OOXMLTransformer; |
0 commit comments