@hyperlex/mammoth 1.4.9-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.eslintrc.json +77 -0
  2. package/.github/ISSUE_TEMPLATE.md +12 -0
  3. package/.idea/mammoth.js.iml +12 -0
  4. package/.idea/modules.xml +8 -0
  5. package/.idea/vcs.xml +6 -0
  6. package/.travis.yml +10 -0
  7. package/LICENSE +22 -0
  8. package/NEWS +373 -0
  9. package/README.md +883 -0
  10. package/bin/mammoth +38 -0
  11. package/browser/docx/files.js +14 -0
  12. package/browser/unzip.js +12 -0
  13. package/lib/document-to-html.js +453 -0
  14. package/lib/documents.js +238 -0
  15. package/lib/docx/body-reader.js +636 -0
  16. package/lib/docx/comments-reader.js +31 -0
  17. package/lib/docx/content-types-reader.js +58 -0
  18. package/lib/docx/document-xml-reader.js +26 -0
  19. package/lib/docx/docx-reader.js +222 -0
  20. package/lib/docx/files.js +67 -0
  21. package/lib/docx/notes-reader.js +28 -0
  22. package/lib/docx/numbering-xml.js +69 -0
  23. package/lib/docx/office-xml-reader.js +58 -0
  24. package/lib/docx/relationships-reader.js +43 -0
  25. package/lib/docx/style-map.js +75 -0
  26. package/lib/docx/styles-reader.js +70 -0
  27. package/lib/docx/uris.js +21 -0
  28. package/lib/html/ast.js +50 -0
  29. package/lib/html/index.js +41 -0
  30. package/lib/html/simplify.js +88 -0
  31. package/lib/images.js +29 -0
  32. package/lib/index.js +115 -0
  33. package/lib/main.js +63 -0
  34. package/lib/options-reader.js +98 -0
  35. package/lib/promises.js +42 -0
  36. package/lib/results.js +72 -0
  37. package/lib/style-reader.js +321 -0
  38. package/lib/styles/document-matchers.js +74 -0
  39. package/lib/styles/html-paths.js +81 -0
  40. package/lib/styles/parser/tokeniser.js +30 -0
  41. package/lib/transforms.js +61 -0
  42. package/lib/underline.js +11 -0
  43. package/lib/unzip.js +22 -0
  44. package/lib/writers/html-writer.js +160 -0
  45. package/lib/writers/index.js +14 -0
  46. package/lib/writers/markdown-writer.js +163 -0
  47. package/lib/xml/index.js +7 -0
  48. package/lib/xml/nodes.js +69 -0
  49. package/lib/xml/reader.js +83 -0
  50. package/lib/xml/writer.js +61 -0
  51. package/lib/zipfile.js +77 -0
  52. package/mammoth.browser.js +32950 -0
  53. package/mammoth.browser.min.js +18 -0
  54. package/package.json +65 -0
  55. package/test/.eslintrc.json +7 -0
  56. package/test/document-to-html.tests.js +834 -0
  57. package/test/docx/body-reader.tests.js +1342 -0
  58. package/test/docx/comments-reader.tests.js +52 -0
  59. package/test/docx/content-types-reader.tests.js +45 -0
  60. package/test/docx/document-matchers.js +37 -0
  61. package/test/docx/docx-reader.tests.js +179 -0
  62. package/test/docx/files.tests.js +94 -0
  63. package/test/docx/notes-reader.tests.js +35 -0
  64. package/test/docx/numbering-xml.tests.js +65 -0
  65. package/test/docx/office-xml-reader.tests.js +24 -0
  66. package/test/docx/relationships-reader.tests.js +65 -0
  67. package/test/docx/style-map.tests.js +112 -0
  68. package/test/docx/styles-reader.tests.js +133 -0
  69. package/test/docx/uris.tests.js +22 -0
  70. package/test/html/simplify.tests.js +134 -0
  71. package/test/html/write.tests.js +42 -0
  72. package/test/images.tests.js +34 -0
  73. package/test/main.tests.js +89 -0
  74. package/test/mammoth.tests.js +429 -0
  75. package/test/mocha.opts +1 -0
  76. package/test/options-reader.tests.js +63 -0
  77. package/test/results.tests.js +15 -0
  78. package/test/style-reader.tests.js +256 -0
  79. package/test/styles/document-matchers.tests.js +71 -0
  80. package/test/styles/html-paths.tests.js +20 -0
  81. package/test/styles/parser/tokeniser.tests.js +104 -0
  82. package/test/test-data/comments.docx +0 -0
  83. package/test/test-data/embedded-style-map.docx +0 -0
  84. package/test/test-data/empty.docx +0 -0
  85. package/test/test-data/empty.zip +0 -0
  86. package/test/test-data/endnotes.docx +0 -0
  87. package/test/test-data/external-picture.docx +0 -0
  88. package/test/test-data/footnote-hyperlink.docx +0 -0
  89. package/test/test-data/footnotes.docx +0 -0
  90. package/test/test-data/hello.zip +0 -0
  91. package/test/test-data/hyperlinks/word/_rels/document.xml.rels +10 -0
  92. package/test/test-data/hyperlinks/word/document.xml +18 -0
  93. package/test/test-data/simple/word/document.xml +18 -0
  94. package/test/test-data/simple-list.docx +0 -0
  95. package/test/test-data/single-paragraph.docx +0 -0
  96. package/test/test-data/strikethrough.docx +0 -0
  97. package/test/test-data/tables.docx +0 -0
  98. package/test/test-data/text-box.docx +0 -0
  99. package/test/test-data/tiny-picture-target-base-relative.docx +0 -0
  100. package/test/test-data/tiny-picture.docx +0 -0
  101. package/test/test-data/tiny-picture.png +0 -0
  102. package/test/test-data/underline.docx +0 -0
  103. package/test/test-data/utf8-bom.docx +0 -0
  104. package/test/test.js +11 -0
  105. package/test/testing.js +55 -0
  106. package/test/transforms.tests.js +125 -0
  107. package/test/unzip.tests.js +38 -0
  108. package/test/writers/html-writer.tests.js +133 -0
  109. package/test/writers/markdown-writer.tests.js +304 -0
  110. package/test/xml/reader.tests.js +85 -0
  111. package/test/xml/writer.tests.js +81 -0
  112. package/test/zipfile.tests.js +59 -0
@@ -0,0 +1,1342 @@
1
+ var assert = require("assert");
2
+ var path = require("path");
3
+
4
+ var _ = require("underscore");
5
+ var hamjest = require("hamjest");
6
+ var assertThat = hamjest.assertThat;
7
+ var promiseThat = hamjest.promiseThat;
8
+ var allOf = hamjest.allOf;
9
+ var contains = hamjest.contains;
10
+ var hasProperties = hamjest.hasProperties;
11
+ var willBe = hamjest.willBe;
12
+ var FeatureMatcher = hamjest.FeatureMatcher;
13
+
14
+ var documentMatchers = require("./document-matchers");
15
+ var isEmptyRun = documentMatchers.isEmptyRun;
16
+ var isHyperlink = documentMatchers.isHyperlink;
17
+ var isRun = documentMatchers.isRun;
18
+ var isText = documentMatchers.isText;
19
+ var isTable = documentMatchers.isTable;
20
+ var isRow = documentMatchers.isRow;
21
+
22
+ var createBodyReader = require("../../lib/docx/body-reader").createBodyReader;
23
+ var _readNumberingProperties = require("../../lib/docx/body-reader")._readNumberingProperties;
24
+ var documents = require("../../lib/documents");
25
+ var xml = require("../../lib/xml");
26
+ var XmlElement = xml.Element;
27
+ var Numbering = require("../../lib/docx/numbering-xml").Numbering;
28
+ var Relationships = require("../../lib/docx/relationships-reader").Relationships;
29
+ var Styles = require("../../lib/docx/styles-reader").Styles;
30
+ var warning = require("../../lib/results").warning;
31
+
32
+ var testing = require("../testing");
33
+ var test = require("../test")(module);
34
+ var createFakeDocxFile = testing.createFakeDocxFile;
35
+
36
+ function readXmlElement(element, options) {
37
+ options = Object.create(options || {});
38
+ options.styles = options.styles || new Styles({}, {});
39
+ return createBodyReader(options).readXmlElement(element);
40
+ }
41
+
42
+ function readXmlElementValue(element, options) {
43
+ var result = readXmlElement(element, options);
44
+ assert.deepEqual(result.messages, []);
45
+ return result.value;
46
+ }
47
+
48
+ var fakeContentTypes = {
49
+ findContentType: function(filePath) {
50
+ var extensionTypes = {
51
+ ".png": "image/png",
52
+ ".emf": "image/x-emf"
53
+ };
54
+ return extensionTypes[path.extname(filePath)];
55
+ }
56
+ };
57
+
58
+ test("paragraph has no style if it has no properties", function() {
59
+ var paragraphXml = new XmlElement("w:p", {}, []);
60
+ var paragraph = readXmlElementValue(paragraphXml);
61
+ assert.deepEqual(paragraph.styleId, null);
62
+ });
63
+
64
+ test("paragraph has style ID and name read from paragraph properties if present", function() {
65
+ var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
66
+ var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
67
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
68
+
69
+ var styles = new Styles({"Heading1": {name: "Heading 1"}}, {});
70
+
71
+ var paragraph = readXmlElementValue(paragraphXml, {styles: styles});
72
+ assert.deepEqual(paragraph.styleId, "Heading1");
73
+ assert.deepEqual(paragraph.styleName, "Heading 1");
74
+ });
75
+
76
+ test("warning is emitted when paragraph style cannot be found", function() {
77
+ var styleXml = new XmlElement("w:pStyle", {"w:val": "Heading1"}, []);
78
+ var propertiesXml = new XmlElement("w:pPr", {}, [styleXml]);
79
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
80
+
81
+ var styles = new Styles({}, {});
82
+
83
+ var result = readXmlElement(paragraphXml, {styles: styles});
84
+ var paragraph = result.value;
85
+ assert.deepEqual(paragraph.styleId, "Heading1");
86
+ assert.deepEqual(paragraph.styleName, null);
87
+ assert.deepEqual(result.messages, [warning("Paragraph style with ID Heading1 was referenced but not defined in the document")]);
88
+ });
89
+
90
+ test("paragraph has justification read from paragraph properties if present", function() {
91
+ var justificationXml = new XmlElement("w:jc", {"w:val": "center"}, []);
92
+ var propertiesXml = new XmlElement("w:pPr", {}, [justificationXml]);
93
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
94
+ var paragraph = readXmlElementValue(paragraphXml);
95
+ assert.deepEqual(paragraph.alignment, "center");
96
+ });
97
+
98
+ test("paragraph indent", {
99
+ "when w:start is set then start indent is read from w:start": function() {
100
+ var paragraphXml = paragraphWithIndent({"w:start": "720", "w:left": "40"});
101
+ var paragraph = readXmlElementValue(paragraphXml);
102
+ assert.equal(paragraph.indent.start, "720");
103
+ },
104
+
105
+ "when w:start is not set then start indent is read from w:left": function() {
106
+ var paragraphXml = paragraphWithIndent({"w:left": "720"});
107
+ var paragraph = readXmlElementValue(paragraphXml);
108
+ assert.equal(paragraph.indent.start, "720");
109
+ },
110
+
111
+ "when w:end is set then end indent is read from w:end": function() {
112
+ var paragraphXml = paragraphWithIndent({"w:end": "720", "w:right": "40"});
113
+ var paragraph = readXmlElementValue(paragraphXml);
114
+ assert.equal(paragraph.indent.end, "720");
115
+ },
116
+
117
+ "when w:end is not set then end indent is read from w:right": function() {
118
+ var paragraphXml = paragraphWithIndent({"w:right": "720"});
119
+ var paragraph = readXmlElementValue(paragraphXml);
120
+ assert.equal(paragraph.indent.end, "720");
121
+ },
122
+
123
+ "paragraph has indent firstLine read from paragraph properties if present": function() {
124
+ var paragraphXml = paragraphWithIndent({"w:firstLine": "720"});
125
+ var paragraph = readXmlElementValue(paragraphXml);
126
+ assert.equal(paragraph.indent.firstLine, "720");
127
+ },
128
+
129
+ "paragraph has indent hanging read from paragraph properties if present": function() {
130
+ var paragraphXml = paragraphWithIndent({"w:hanging": "720"});
131
+ var paragraph = readXmlElementValue(paragraphXml);
132
+ assert.equal(paragraph.indent.hanging, "720");
133
+ },
134
+
135
+ "when indent attributes aren't set then indents are null": function() {
136
+ var paragraphXml = paragraphWithIndent({});
137
+ var paragraph = readXmlElementValue(paragraphXml);
138
+ assert.equal(paragraph.indent.start, null);
139
+ assert.equal(paragraph.indent.end, null);
140
+ assert.equal(paragraph.indent.firstLine, null);
141
+ assert.equal(paragraph.indent.hanging, null);
142
+ }
143
+ });
144
+
145
+ function paragraphWithIndent(indentAttributes) {
146
+ var indentXml = new XmlElement("w:ind", indentAttributes, []);
147
+ var propertiesXml = new XmlElement("w:pPr", {}, [indentXml]);
148
+ return new XmlElement("w:p", {}, [propertiesXml]);
149
+ }
150
+
151
+ test("paragraph spacing", {
152
+ "paragraph has line spacing read from paragraph properties if present": function() {
153
+ var paragraphXml = paragraphWithSpacing({"w:lineRule": "auto", "w:line": "480", "w:before": "0", "w:after": "225"});
154
+ var paragraph = readXmlElementValue(paragraphXml, {styles: new Styles({}, {})});
155
+ assert.deepEqual(paragraph.spacing, {lineRule: "auto", line: "480", before: "0", after: "225"});
156
+ },
157
+ "when spacing attributes aren't set then spacings are null": function() {
158
+ var paragraphXml = paragraphWithSpacing({});
159
+ var paragraph = readXmlElementValue(paragraphXml);
160
+ assert.deepEqual(paragraph.spacing, {lineRule: null, line: null, before: null, after: null});
161
+
162
+ }
163
+ });
164
+
165
+ function paragraphWithSpacing(spacingAttributes) {
166
+ var spacingXml = new XmlElement("w:spacing", spacingAttributes, []);
167
+ var propertiesXml = new XmlElement("w:pPr", {}, [spacingXml]);
168
+ return new XmlElement("w:p", {}, [propertiesXml]);
169
+ }
170
+
171
+ test("paragraph border", {
172
+ "paragraph has border read from paragraph properties if present": function() {
173
+ var borderStyle = "single";
174
+ var borderWidth = "2";
175
+ var borderOffset = "1";
176
+ var borderColor = "c0ffee";
177
+ var paragraphXml = new XmlElement("w:p", {}, [new XmlElement("w:pPr", {}, [(new XmlElement("w:pBdr", {}, [
178
+ borderChild("top", borderStyle, borderWidth, borderOffset, borderColor),
179
+ borderChild("right", borderStyle, borderWidth, borderOffset, borderColor),
180
+ borderChild("bottom", borderStyle, borderWidth, borderOffset, borderColor),
181
+ borderChild("left", borderStyle, borderWidth, borderOffset, borderColor)]))])]);
182
+ var paragraph = readXmlElementValue(paragraphXml, {styles: new Styles({}, {})});
183
+ assert.deepEqual(paragraph.border, {
184
+ top: {style: borderStyle, width: borderWidth, offset: borderOffset, color: borderColor},
185
+ right: {style: borderStyle, width: borderWidth, offset: borderOffset, color: borderColor},
186
+ bottom: {style: borderStyle, width: borderWidth, offset: borderOffset, color: borderColor},
187
+ left: {style: borderStyle, width: borderWidth, offset: borderOffset, color: borderColor}
188
+ });
189
+ },
190
+ "when border attributes aren't set then borders are null": function() {
191
+ var paragraphXml = new XmlElement("w:p", {}, []);
192
+ var paragraph = readXmlElementValue(paragraphXml, {styles: new Styles({}, {})});
193
+ assert.deepEqual(paragraph.border, {top: null, right: null, bottom: null, left: null});
194
+ }
195
+ });
196
+
197
+ function borderChild(place, style, width, offset, color) {
198
+ return new XmlElement("w:" + place, {"w:val": style, "w:sz": width, "w:space": offset, "w:color": color});
199
+ }
200
+
201
+
202
+ test("paragraph has numbering properties from paragraph properties if present", function() {
203
+ var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
204
+ new XmlElement("w:ilvl", {"w:val": "1"}),
205
+ new XmlElement("w:numId", {"w:val": "42"})
206
+ ]);
207
+ var propertiesXml = new XmlElement("w:pPr", {}, [numberingPropertiesXml]);
208
+ var paragraphXml = new XmlElement("w:p", {}, [propertiesXml]);
209
+
210
+ var numbering = new NumberingMap({"42": {"1": {isOrdered: true, level: "1"}}});
211
+
212
+ var paragraph = readXmlElementValue(paragraphXml, {numbering: numbering});
213
+ assert.deepEqual(paragraph.numbering, {level: "1", isOrdered: true});
214
+ });
215
+
216
+ test("numbering properties are converted to numbering at specified level", function() {
217
+ var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
218
+ new XmlElement("w:ilvl", {"w:val": "1"}),
219
+ new XmlElement("w:numId", {"w:val": "42"})
220
+ ]);
221
+
222
+ var numbering = new NumberingMap({"42": {"1": {isOrdered: true, level: "1"}}});
223
+
224
+ var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering);
225
+ assert.deepEqual(numberingLevel, {level: "1", isOrdered: true});
226
+ });
227
+
228
+ test("numbering properties are ignored if w:ilvl is missing", function() {
229
+ var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
230
+ new XmlElement("w:numId", {"w:val": "42"})
231
+ ]);
232
+
233
+ var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}});
234
+
235
+ var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering);
236
+ assert.equal(numberingLevel, null);
237
+ });
238
+
239
+ test("numbering properties are ignored if w:numId is missing", function() {
240
+ var numberingPropertiesXml = new XmlElement("w:numPr", {}, [
241
+ new XmlElement("w:ilvl", {"w:val": "1"})
242
+ ]);
243
+
244
+ var numbering = new Numbering({"42": {"1": {isOrdered: true, level: "1"}}});
245
+
246
+ var numberingLevel = _readNumberingProperties(numberingPropertiesXml, numbering);
247
+ assert.equal(numberingLevel, null);
248
+ });
249
+
250
+ test("complex fields", (function() {
251
+ var uri = "http://example.com";
252
+ var beginXml = new XmlElement("w:r", {}, [
253
+ new XmlElement("w:fldChar", {"w:fldCharType": "begin"})
254
+ ]);
255
+ var endXml = new XmlElement("w:r", {}, [
256
+ new XmlElement("w:fldChar", {"w:fldCharType": "end"})
257
+ ]);
258
+ var separateXml = new XmlElement("w:r", {}, [
259
+ new XmlElement("w:fldChar", {"w:fldCharType": "separate"})
260
+ ]);
261
+ var hyperlinkInstrText = new XmlElement("w:instrText", {}, [
262
+ xml.text(' HYPERLINK "' + uri + '"')
263
+ ]);
264
+ var hyperlinkRunXml = runOfText("this is a hyperlink");
265
+
266
+ var isEmptyHyperlinkedRun = isHyperlinkedRun({children: []});
267
+
268
+ function isHyperlinkedRun(hyperlinkProperties) {
269
+ return isRun({
270
+ children: contains(
271
+ isHyperlink(_.extend({href: uri}, hyperlinkProperties))
272
+ )
273
+ });
274
+ }
275
+
276
+ return {
277
+ "stores instrText returns empty result": function() {
278
+ var instrText = readXmlElementValue(hyperlinkInstrText);
279
+ assert.deepEqual(instrText, []);
280
+ },
281
+
282
+ "runs in a complex field for hyperlinks are read as hyperlinks": function() {
283
+ var hyperlinkRunXml = runOfText("this is a hyperlink");
284
+ var paragraphXml = new XmlElement("w:p", {}, [
285
+ beginXml,
286
+ hyperlinkInstrText,
287
+ separateXml,
288
+ hyperlinkRunXml,
289
+ endXml
290
+ ]);
291
+ var paragraph = readXmlElementValue(paragraphXml);
292
+
293
+ assertThat(paragraph.children, contains(
294
+ isEmptyRun,
295
+ isEmptyHyperlinkedRun,
296
+ isHyperlinkedRun({
297
+ children: contains(
298
+ isText("this is a hyperlink")
299
+ )
300
+ }),
301
+ isEmptyRun
302
+ ));
303
+ },
304
+
305
+ "runs after a complex field for hyperlinks are not read as hyperlinks": function() {
306
+ var afterEndXml = runOfText("this will not be a hyperlink");
307
+ var paragraphXml = new XmlElement("w:p", {}, [
308
+ beginXml,
309
+ hyperlinkInstrText,
310
+ separateXml,
311
+ endXml,
312
+ afterEndXml
313
+ ]);
314
+ var paragraph = readXmlElementValue(paragraphXml);
315
+
316
+ assertThat(paragraph.children, contains(
317
+ isEmptyRun,
318
+ isEmptyHyperlinkedRun,
319
+ isEmptyRun,
320
+ isRun({
321
+ children: contains(
322
+ isText("this will not be a hyperlink")
323
+ )
324
+ })
325
+ ));
326
+ },
327
+
328
+ "can handle split instrText elements": function() {
329
+ var hyperlinkInstrTextPart1 = new XmlElement("w:instrText", {}, [
330
+ xml.text(" HYPE")
331
+ ]);
332
+ var hyperlinkInstrTextPart2 = new XmlElement("w:instrText", {}, [
333
+ xml.text('RLINK "' + uri + '"')
334
+ ]);
335
+ var paragraphXml = new XmlElement("w:p", {}, [
336
+ beginXml,
337
+ hyperlinkInstrTextPart1,
338
+ hyperlinkInstrTextPart2,
339
+ separateXml,
340
+ hyperlinkRunXml,
341
+ endXml
342
+ ]);
343
+ var paragraph = readXmlElementValue(paragraphXml);
344
+
345
+ assertThat(paragraph.children, contains(
346
+ isEmptyRun,
347
+ isEmptyHyperlinkedRun,
348
+ isHyperlinkedRun({
349
+ children: contains(
350
+ isText("this is a hyperlink")
351
+ )
352
+ }),
353
+ isEmptyRun
354
+ ));
355
+ },
356
+
357
+ "hyperlink is not ended by end of nested complex field": function() {
358
+ var authorInstrText = new XmlElement("w:instrText", {}, [
359
+ xml.text(' AUTHOR "John Doe"')
360
+ ]);
361
+ var paragraphXml = new XmlElement("w:p", {}, [
362
+ beginXml,
363
+ hyperlinkInstrText,
364
+ separateXml,
365
+ beginXml,
366
+ authorInstrText,
367
+ separateXml,
368
+ endXml,
369
+ hyperlinkRunXml,
370
+ endXml
371
+ ]);
372
+ var paragraph = readXmlElementValue(paragraphXml);
373
+
374
+ assertThat(paragraph.children, contains(
375
+ isEmptyRun,
376
+ isEmptyHyperlinkedRun,
377
+ isEmptyHyperlinkedRun,
378
+ isEmptyHyperlinkedRun,
379
+ isEmptyHyperlinkedRun,
380
+ isHyperlinkedRun({
381
+ children: contains(
382
+ isText("this is a hyperlink")
383
+ )
384
+ }),
385
+ isEmptyRun
386
+ ));
387
+ },
388
+
389
+ "complex field nested within a hyperlink complex field is wrapped with the hyperlink": function() {
390
+ var authorInstrText = new XmlElement("w:instrText", {}, [
391
+ xml.text(' AUTHOR "John Doe"')
392
+ ]);
393
+ var paragraphXml = new XmlElement("w:p", {}, [
394
+ beginXml,
395
+ hyperlinkInstrText,
396
+ separateXml,
397
+ beginXml,
398
+ authorInstrText,
399
+ separateXml,
400
+ runOfText("John Doe"),
401
+ endXml,
402
+ endXml
403
+ ]);
404
+ var paragraph = readXmlElementValue(paragraphXml);
405
+
406
+ assertThat(paragraph.children, contains(
407
+ isEmptyRun,
408
+ isEmptyHyperlinkedRun,
409
+ isEmptyHyperlinkedRun,
410
+ isEmptyHyperlinkedRun,
411
+ isHyperlinkedRun({
412
+ children: contains(
413
+ isText("John Doe")
414
+ )
415
+ }),
416
+ isEmptyHyperlinkedRun,
417
+ isEmptyRun
418
+ ));
419
+ },
420
+
421
+ "field without separate w:fldChar is ignored": function() {
422
+ var hyperlinkRunXml = runOfText("this is a hyperlink");
423
+ var paragraphXml = new XmlElement("w:p", {}, [
424
+ beginXml,
425
+ hyperlinkInstrText,
426
+ separateXml,
427
+ beginXml,
428
+ endXml,
429
+ hyperlinkRunXml,
430
+ endXml
431
+ ]);
432
+ var paragraph = readXmlElementValue(paragraphXml);
433
+
434
+ assertThat(paragraph.children, contains(
435
+ isEmptyRun,
436
+ isEmptyHyperlinkedRun,
437
+ isEmptyHyperlinkedRun,
438
+ isEmptyHyperlinkedRun,
439
+ isHyperlinkedRun({
440
+ children: contains(
441
+ isText("this is a hyperlink")
442
+ )
443
+ }),
444
+ isEmptyRun
445
+ ));
446
+ }
447
+ };
448
+ })());
449
+
450
+ test("run has no style if it has no properties", function() {
451
+ var runXml = runWithProperties([]);
452
+ var run = readXmlElementValue(runXml);
453
+ assert.deepEqual(run.styleId, null);
454
+ });
455
+
456
+ test("run has style ID and name read from run properties if present", function() {
457
+ var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
458
+ var runXml = runWithProperties([runStyleXml]);
459
+
460
+ var styles = new Styles({}, {"Heading1Char": {name: "Heading 1 Char"}});
461
+
462
+ var run = readXmlElementValue(runXml, {styles: styles});
463
+ assert.deepEqual(run.styleId, "Heading1Char");
464
+ assert.deepEqual(run.styleName, "Heading 1 Char");
465
+ });
466
+
467
+ test("warning is emitted when run style cannot be found", function() {
468
+ var runStyleXml = new XmlElement("w:rStyle", {"w:val": "Heading1Char"});
469
+ var runXml = runWithProperties([runStyleXml]);
470
+
471
+ var styles = new Styles({}, {});
472
+
473
+ var result = readXmlElement(runXml, {styles: styles});
474
+ var run = result.value;
475
+ assert.deepEqual(run.styleId, "Heading1Char");
476
+ assert.deepEqual(run.styleName, null);
477
+ assert.deepEqual(result.messages, [warning("Run style with ID Heading1Char was referenced but not defined in the document")]);
478
+ });
479
+
480
+ test("isBold is false if bold element is not present", function() {
481
+ var runXml = runWithProperties([]);
482
+ var run = readXmlElementValue(runXml);
483
+ assert.deepEqual(run.isBold, false);
484
+ });
485
+
486
+ test("isBold is true if bold element is present", function() {
487
+ var boldXml = new XmlElement("w:b");
488
+ var runXml = runWithProperties([boldXml]);
489
+ var run = readXmlElementValue(runXml);
490
+ assert.equal(run.isBold, true);
491
+ });
492
+
493
+ test("isBold is false if bold element is present and w:val is false", function() {
494
+ var boldXml = new XmlElement("w:b", {"w:val": "false"});
495
+ var runXml = runWithProperties([boldXml]);
496
+ var run = readXmlElementValue(runXml);
497
+ assert.equal(run.isBold, false);
498
+ });
499
+
500
+ test("isUnderline is false if underline element is not present", function() {
501
+ var runXml = runWithProperties([]);
502
+ var run = readXmlElementValue(runXml);
503
+ assert.deepEqual(run.isUnderline, false);
504
+ });
505
+
506
+ test("isUnderline is true if underline element is present", function() {
507
+ var underlineXml = new XmlElement("w:u");
508
+ var runXml = runWithProperties([underlineXml]);
509
+ var run = readXmlElementValue(runXml);
510
+ assert.equal(run.isUnderline, true);
511
+ });
512
+
513
+ test("isStrikethrough is false if strikethrough element is not present", function() {
514
+ var runXml = runWithProperties([]);
515
+ var run = readXmlElementValue(runXml);
516
+ assert.deepEqual(run.isStrikethrough, false);
517
+ });
518
+
519
+ test("isStrikethrough is true if strikethrough element is present", function() {
520
+ var strikethroughXml = new XmlElement("w:strike");
521
+ var runXml = runWithProperties([strikethroughXml]);
522
+ var run = readXmlElementValue(runXml);
523
+ assert.equal(run.isStrikethrough, true);
524
+ });
525
+
526
+ test("isItalic is false if bold element is not present", function() {
527
+ var runXml = runWithProperties([]);
528
+ var run = readXmlElementValue(runXml);
529
+ assert.deepEqual(run.isItalic, false);
530
+ });
531
+
532
+ test("isItalic is true if bold element is present", function() {
533
+ var italicXml = new XmlElement("w:i");
534
+ var runXml = runWithProperties([italicXml]);
535
+ var run = readXmlElementValue(runXml);
536
+ assert.equal(run.isItalic, true);
537
+ });
538
+
539
+ test("isSmallCaps is false if smallcaps element is not present", function() {
540
+ var runXml = runWithProperties([]);
541
+ var run = readXmlElementValue(runXml);
542
+ assert.deepEqual(run.isSmallCaps, false);
543
+ });
544
+
545
+ test("isSmallCaps is true if smallcaps element is present", function() {
546
+ var smallCapsXml = new XmlElement("w:smallCaps");
547
+ var runXml = runWithProperties([smallCapsXml]);
548
+ var run = readXmlElementValue(runXml);
549
+ assert.equal(run.isSmallCaps, true);
550
+ });
551
+
552
+ var booleanRunProperties = [
553
+ {name: "isBold", tagName: "w:b"},
554
+ {name: "isUnderline", tagName: "w:u"},
555
+ {name: "isItalic", tagName: "w:i"},
556
+ {name: "isStrikethrough", tagName: "w:strike"},
557
+ {name: "isSmallCaps", tagName: "w:smallCaps"}
558
+ ];
559
+
560
+ booleanRunProperties.forEach(function(runProperty) {
561
+ test(runProperty.name + " is false if " + runProperty.tagName + " is present and w:val is false", function() {
562
+ var propertyXml = new XmlElement(runProperty.tagName, {"w:val": "false"});
563
+ var runXml = runWithProperties([propertyXml]);
564
+ var run = readXmlElementValue(runXml);
565
+ assert.equal(run[runProperty.name], false);
566
+ });
567
+
568
+ test(runProperty.name + " is false if " + runProperty.tagName + " is present and w:val is 0", function() {
569
+ var propertyXml = new XmlElement(runProperty.tagName, {"w:val": "0"});
570
+ var runXml = runWithProperties([propertyXml]);
571
+ var run = readXmlElementValue(runXml);
572
+ assert.equal(run[runProperty.name], false);
573
+ });
574
+
575
+ test(runProperty.name + " is true if " + runProperty.tagName + " is present and w:val is true", function() {
576
+ var propertyXml = new XmlElement(runProperty.tagName, {"w:val": "true"});
577
+ var runXml = runWithProperties([propertyXml]);
578
+ var run = readXmlElementValue(runXml);
579
+ assert.equal(run[runProperty.name], true);
580
+ });
581
+
582
+ test(runProperty.name + " is true if " + runProperty.tagName + " is present and w:val is 1", function() {
583
+ var propertyXml = new XmlElement(runProperty.tagName, {"w:val": "1"});
584
+ var runXml = runWithProperties([propertyXml]);
585
+ var run = readXmlElementValue(runXml);
586
+ assert.equal(run[runProperty.name], true);
587
+ });
588
+ });
589
+
590
+ test("run has baseline vertical alignment by default", function() {
591
+ var runXml = runWithProperties([]);
592
+ var run = readXmlElementValue(runXml);
593
+ assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.baseline);
594
+ });
595
+
596
+ test("run has vertical alignment read from properties", function() {
597
+ var verticalAlignmentXml = new XmlElement("w:vertAlign", {"w:val": "superscript"});
598
+ var runXml = runWithProperties([verticalAlignmentXml]);
599
+
600
+ var run = readXmlElementValue(runXml);
601
+ assert.deepEqual(run.verticalAlignment, documents.verticalAlignment.superscript);
602
+ });
603
+
604
+ test("run has null font by default", function() {
605
+ var runXml = runWithProperties([]);
606
+
607
+ var run = readXmlElementValue(runXml);
608
+ assert.deepEqual(run.font, null);
609
+ });
610
+
611
+ test("run has font read from properties", function() {
612
+ var fontXml = new XmlElement("w:rFonts", {"w:ascii": "Arial"});
613
+ var runXml = runWithProperties([fontXml]);
614
+
615
+ var run = readXmlElementValue(runXml);
616
+ assert.deepEqual(run.font, "Arial");
617
+ });
618
+
619
+ test("run has color read from properties", function() {
620
+ var colorXml = new XmlElement("w:color", {"w:val": "FFFFFF"});
621
+ var runXml = runWithProperties([colorXml]);
622
+
623
+ var run = readXmlElementValue(runXml);
624
+ assert.deepEqual(run.color, "FFFFFF");
625
+ });
626
+
627
+ test("run has highlight read from properties", function() {
628
+ var colorXml = new XmlElement("w:highlight", {"w:val": "FFFFFF"});
629
+ var runXml = runWithProperties([colorXml]);
630
+
631
+ var run = readXmlElementValue(runXml);
632
+ assert.deepEqual(run.highlight, "FFFFFF");
633
+ });
634
+
635
+ test("run properties not included as child of run", function() {
636
+ var runStyleXml = new XmlElement("w:rStyle");
637
+ var runPropertiesXml = new XmlElement("w:rPr", {}, [runStyleXml]);
638
+ var runXml = new XmlElement("w:r", {}, [runPropertiesXml]);
639
+ var result = readXmlElement(runXml);
640
+ assert.deepEqual(result.value.children, []);
641
+ });
642
+
643
+ test("w:tab is read as document tab element", function() {
644
+ var tabXml = new XmlElement("w:tab");
645
+ var result = readXmlElement(tabXml);
646
+ assert.deepEqual(result.value, new documents.Tab());
647
+ });
648
+
649
+ test("w:noBreakHyphen is read as non-breaking hyphen character", function() {
650
+ var noBreakHyphenXml = new XmlElement("w:noBreakHyphen");
651
+ var result = readXmlElement(noBreakHyphenXml);
652
+ assert.deepEqual(result.value, new documents.Text("\u2011"));
653
+ });
654
+
655
+ test("w:tbl is read as document table element", function() {
656
+ var tableXml = new XmlElement("w:tbl", {}, [
657
+ new XmlElement("w:tr", {}, [
658
+ new XmlElement("w:tc", {}, [
659
+ new XmlElement("w:p", {}, [])
660
+ ])
661
+ ])
662
+ ]);
663
+ var result = readXmlElement(tableXml);
664
+ assert.deepEqual(result.value, new documents.Table([
665
+ new documents.TableRow([
666
+ new documents.TableCell([
667
+ new documents.Paragraph([])
668
+ ])
669
+ ])
670
+ ]));
671
+ });
672
+
673
+ test("table has no style if it has no properties", function() {
674
+ var tableXml = new XmlElement("w:tbl", {}, []);
675
+ var table = readXmlElementValue(tableXml);
676
+ assert.deepEqual(table.styleId, null);
677
+ });
678
+
679
+ test("table has style ID and name read from table properties if present", function() {
680
+ var styleXml = new XmlElement("w:tblStyle", {"w:val": "TableNormal"}, []);
681
+ var propertiesXml = new XmlElement("w:tblPr", {}, [styleXml]);
682
+ var tableXml = new XmlElement("w:tbl", {}, [propertiesXml]);
683
+
684
+ var styles = new Styles({}, {}, {"TableNormal": {name: "Normal Table"}});
685
+
686
+ var table = readXmlElementValue(tableXml, {styles: styles});
687
+ assert.deepEqual(table.styleId, "TableNormal");
688
+ assert.deepEqual(table.styleName, "Normal Table");
689
+ });
690
+
691
+ test("warning is emitted when table style cannot be found", function() {
692
+ var styleXml = new XmlElement("w:tblStyle", {"w:val": "TableNormal"}, []);
693
+ var propertiesXml = new XmlElement("w:tblPr", {}, [styleXml]);
694
+ var tableXml = new XmlElement("w:tbl", {}, [propertiesXml]);
695
+
696
+ var result = readXmlElement(tableXml, {styles: Styles.EMPTY});
697
+ var table = result.value;
698
+ assert.deepEqual(table.styleId, "TableNormal");
699
+ assert.deepEqual(table.styleName, null);
700
+ assert.deepEqual(result.messages, [warning("Table style with ID TableNormal was referenced but not defined in the document")]);
701
+ });
702
+
703
+ test("w:tblHeader marks table row as header", function() {
704
+ var tableXml = new XmlElement("w:tbl", {}, [
705
+ new XmlElement("w:tr", {}, [
706
+ new XmlElement("w:trPr", {}, [
707
+ new XmlElement("w:tblHeader")
708
+ ])
709
+ ]),
710
+ new XmlElement("w:tr")
711
+ ]);
712
+ var result = readXmlElementValue(tableXml);
713
+ assertThat(result, isTable({
714
+ children: contains(
715
+ isRow({isHeader: true}),
716
+ isRow({isHeader: false})
717
+ )
718
+ }));
719
+ });
720
+
721
+ test("w:gridSpan is read as colSpan for table cell", function() {
722
+ var tableXml = new XmlElement("w:tbl", {}, [
723
+ new XmlElement("w:tr", {}, [
724
+ new XmlElement("w:tc", {}, [
725
+ new XmlElement("w:tcPr", {}, [
726
+ new XmlElement("w:gridSpan", {"w:val": "2"})
727
+ ]),
728
+ new XmlElement("w:p", {}, [])
729
+ ])
730
+ ])
731
+ ]);
732
+ var result = readXmlElement(tableXml);
733
+ assert.deepEqual(result.value, new documents.Table([
734
+ new documents.TableRow([
735
+ new documents.TableCell([
736
+ new documents.Paragraph([])
737
+ ], {colSpan: 2})
738
+ ])
739
+ ]));
740
+ });
741
+
742
+ test("w:vMerge is read as rowSpan for table cell", function() {
743
+ var tableXml = new XmlElement("w:tbl", {}, [
744
+ row(emptyCell()),
745
+ row(emptyCell(vMerge("restart"))),
746
+ row(emptyCell(vMerge("continue"))),
747
+ row(emptyCell(vMerge("continue"))),
748
+ row(emptyCell())
749
+ ]);
750
+ var result = readXmlElement(tableXml);
751
+ assert.deepEqual(result.value, new documents.Table([
752
+ docRow([docEmptyCell()]),
753
+ docRow([docEmptyCell({rowSpan: 3})]),
754
+ docRow([]),
755
+ docRow([]),
756
+ docRow([docEmptyCell()])
757
+ ]));
758
+ });
759
+
760
+ test("w:vMerge without val is treated as continue", function() {
761
+ var tableXml = new XmlElement("w:tbl", {}, [
762
+ row(emptyCell(vMerge("restart"))),
763
+ row(emptyCell(vMerge()))
764
+ ]);
765
+ var result = readXmlElement(tableXml);
766
+ assert.deepEqual(result.value, new documents.Table([
767
+ docRow([docEmptyCell({rowSpan: 2})]),
768
+ docRow([])
769
+ ]));
770
+ });
771
+
772
+ test("w:vMerge accounts for cells spanning columns", function() {
773
+ var tableXml = new XmlElement("w:tbl", {}, [
774
+ row(emptyCell(), emptyCell(), emptyCell(vMerge("restart"))),
775
+ row(emptyCell(gridSpan("2")), emptyCell(vMerge("continue"))),
776
+ row(emptyCell(), emptyCell(), emptyCell(vMerge("continue"))),
777
+ row(emptyCell(), emptyCell(), emptyCell())
778
+ ]);
779
+ var result = readXmlElement(tableXml);
780
+ assert.deepEqual(result.value, new documents.Table([
781
+ docRow([docEmptyCell(), docEmptyCell(), docEmptyCell({rowSpan: 3})]),
782
+ docRow([docEmptyCell({colSpan: 2})]),
783
+ docRow([docEmptyCell(), docEmptyCell()]),
784
+ docRow([docEmptyCell(), docEmptyCell(), docEmptyCell()])
785
+ ]));
786
+ });
787
+
788
+ test("no vertical cell merging if merged cells do not line up", function() {
789
+ var tableXml = new XmlElement("w:tbl", {}, [
790
+ row(emptyCell(gridSpan("2"), vMerge("restart"))),
791
+ row(emptyCell(), emptyCell(vMerge("continue")))
792
+ ]);
793
+ var result = readXmlElement(tableXml);
794
+ assert.deepEqual(result.value, new documents.Table([
795
+ docRow([docEmptyCell({colSpan: 2})]),
796
+ docRow([docEmptyCell(), docEmptyCell()])
797
+ ]));
798
+ });
799
+
800
+ test("warning if non-row in table", function() {
801
+ var tableXml = new XmlElement("w:tbl", {}, [
802
+ new XmlElement("w:p")
803
+ ]);
804
+ var result = readXmlElement(tableXml);
805
+ assert.deepEqual(result.messages, [warning("unexpected non-row element in table, cell merging may be incorrect")]);
806
+ });
807
+
808
+ test("warning if non-cell in table row", function() {
809
+ var tableXml = new XmlElement("w:tbl", {}, [
810
+ row(new XmlElement("w:p"))
811
+ ]);
812
+ var result = readXmlElement(tableXml);
813
+ assert.deepEqual(result.messages, [warning("unexpected non-cell element in table row, cell merging may be incorrect")]);
814
+ });
815
+
816
+ function row() {
817
+ return new XmlElement("w:tr", {}, Array.prototype.slice.call(arguments));
818
+ }
819
+
820
+ function emptyCell() {
821
+ return new XmlElement("w:tc", {}, [
822
+ new XmlElement("w:tcPr", {}, Array.prototype.slice.call(arguments))
823
+ ]);
824
+ }
825
+
826
+ function vMerge(val) {
827
+ return new XmlElement("w:vMerge", {"w:val": val}, []);
828
+ }
829
+
830
+ function gridSpan(val) {
831
+ return new XmlElement("w:gridSpan", {"w:val": val});
832
+ }
833
+
834
+ function docRow(children) {
835
+ return new documents.TableRow(children);
836
+ }
837
+
838
+ function docEmptyCell(properties) {
839
+ return new documents.TableCell([], properties);
840
+ }
841
+
842
+ test("w:bookmarkStart is read as a bookmarkStart", function() {
843
+ var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_Peter", "w:id": "42"});
844
+ var result = readXmlElement(bookmarkStart);
845
+ assert.deepEqual(result.value.name, "_Peter");
846
+ assert.deepEqual(result.value.type, "bookmarkStart");
847
+ });
848
+
849
+ test('_GoBack bookmark is ignored', function() {
850
+ var bookmarkStart = new XmlElement("w:bookmarkStart", {"w:name": "_GoBack"});
851
+ var result = readXmlElement(bookmarkStart);
852
+ assert.deepEqual(result.value, []);
853
+ });
854
+
855
+ var IMAGE_BUFFER = new Buffer("Not an image at all!");
856
+ var IMAGE_RELATIONSHIP_ID = "rId5";
857
+
858
+ function isSuccess(valueMatcher) {
859
+ return hasProperties({
860
+ messages: [],
861
+ value: valueMatcher
862
+ });
863
+ }
864
+
865
+ function isImage(options) {
866
+ var matcher = hasProperties(_.extend({type: "image"}, _.omit(options, "buffer")));
867
+ if (options.buffer) {
868
+ return allOf(
869
+ matcher,
870
+ new FeatureMatcher(willBe(options.buffer), "buffer", "buffer", function(element) {
871
+ return element.read();
872
+ })
873
+ );
874
+ } else {
875
+ return matcher;
876
+ }
877
+ }
878
+
879
+ function readEmbeddedImage(element) {
880
+ return readXmlElement(element, {
881
+ relationships: new Relationships([
882
+ imageRelationship("rId5", "media/hat.png")
883
+ ]),
884
+ contentTypes: fakeContentTypes,
885
+ docxFile: createFakeDocxFile({
886
+ "word/media/hat.png": IMAGE_BUFFER
887
+ })
888
+ });
889
+ }
890
+
891
+ test("can read imagedata elements with r:id attribute", function() {
892
+ var imagedataElement = new XmlElement("v:imagedata", {
893
+ "r:id": IMAGE_RELATIONSHIP_ID,
894
+ "o:title": "It's a hat"
895
+ });
896
+
897
+ var result = readEmbeddedImage(imagedataElement);
898
+
899
+ return promiseThat(result, isSuccess(isImage({
900
+ altText: "It's a hat",
901
+ contentType: "image/png",
902
+ buffer: IMAGE_BUFFER
903
+ })));
904
+ });
905
+
906
+ test("when v:imagedata element has no relationship ID then it is ignored with warning", function() {
907
+ var imagedataElement = new XmlElement("v:imagedata");
908
+
909
+ var result = readXmlElement(imagedataElement);
910
+
911
+ assert.deepEqual(result.value, []);
912
+ assert.deepEqual(result.messages, [warning("A v:imagedata element without a relationship ID was ignored")]);
913
+ });
914
+
915
+ test("can read inline pictures", function() {
916
+ var drawing = createInlineImage({
917
+ blip: createEmbeddedBlip(IMAGE_RELATIONSHIP_ID),
918
+ description: "It's a hat"
919
+ });
920
+
921
+ var result = readEmbeddedImage(drawing);
922
+
923
+ return promiseThat(result, isSuccess(contains(isImage({
924
+ altText: "It's a hat",
925
+ contentType: "image/png",
926
+ buffer: IMAGE_BUFFER
927
+ }))));
928
+ });
929
+
930
+ test("alt text title is used if alt text description is missing", function() {
931
+ var drawing = createInlineImage({
932
+ blip: createEmbeddedBlip(IMAGE_RELATIONSHIP_ID),
933
+ title: "It's a hat"
934
+ });
935
+
936
+ var result = readEmbeddedImage(drawing);
937
+
938
+ return promiseThat(result, isSuccess(contains(isImage({
939
+ altText: "It's a hat"
940
+ }))));
941
+ });
942
+
943
+ test("alt text title is used if alt text description is blank", function() {
944
+ var drawing = createInlineImage({
945
+ blip: createEmbeddedBlip(IMAGE_RELATIONSHIP_ID),
946
+ description: " ",
947
+ title: "It's a hat"
948
+ });
949
+
950
+ var result = readEmbeddedImage(drawing);
951
+
952
+ return promiseThat(result, isSuccess(contains(isImage({
953
+ altText: "It's a hat"
954
+ }))));
955
+ });
956
+
957
+ test("alt text description is preferred to alt text title", function() {
958
+ var drawing = createInlineImage({
959
+ blip: createEmbeddedBlip(IMAGE_RELATIONSHIP_ID),
960
+ description: "It's a hat",
961
+ title: "hat"
962
+ });
963
+
964
+ var result = readEmbeddedImage(drawing);
965
+
966
+ return promiseThat(result, isSuccess(contains(isImage({
967
+ altText: "It's a hat"
968
+ }))));
969
+ });
970
+
971
+ test("can read anchored pictures", function() {
972
+ var drawing = new XmlElement("w:drawing", {}, [
973
+ new XmlElement("wp:anchor", {}, [
974
+ new XmlElement("wp:docPr", {descr: "It's a hat"}),
975
+ new XmlElement("a:graphic", {}, [
976
+ new XmlElement("a:graphicData", {}, [
977
+ new XmlElement("pic:pic", {}, [
978
+ new XmlElement("pic:blipFill", {}, [
979
+ new XmlElement("a:blip", {"r:embed": IMAGE_RELATIONSHIP_ID})
980
+ ])
981
+ ])
982
+ ])
983
+ ])
984
+ ])
985
+ ]);
986
+
987
+ var result = readEmbeddedImage(drawing);
988
+
989
+ return promiseThat(result, isSuccess(contains(isImage({
990
+ altText: "It's a hat",
991
+ contentType: "image/png",
992
+ buffer: IMAGE_BUFFER
993
+ }))));
994
+ });
995
+
996
+ test("can read linked pictures", function() {
997
+ var drawing = createInlineImage({
998
+ blip: createLinkedBlip("rId5"),
999
+ description: "It's a hat"
1000
+ });
1001
+
1002
+ var element = single(readXmlElementValue(drawing, {
1003
+ relationships: new Relationships([
1004
+ imageRelationship("rId5", "file:///media/hat.png")
1005
+ ]),
1006
+ contentTypes: fakeContentTypes,
1007
+ files: testing.createFakeFiles({
1008
+ "file:///media/hat.png": IMAGE_BUFFER
1009
+ })
1010
+ }));
1011
+ return promiseThat(element, isImage({
1012
+ altText: "It's a hat",
1013
+ contentType: "image/png",
1014
+ buffer: IMAGE_BUFFER
1015
+ }));
1016
+ });
1017
+
1018
+ test("warning if unsupported image type", function() {
1019
+ var drawing = createInlineImage({
1020
+ blip: createEmbeddedBlip("rId5"),
1021
+ description: "It's a hat"
1022
+ });
1023
+
1024
+ var result = readXmlElement(drawing, {
1025
+ relationships: new Relationships([
1026
+ imageRelationship("rId5", "media/hat.emf")
1027
+ ]),
1028
+ contentTypes: fakeContentTypes,
1029
+ docxFile: createFakeDocxFile({
1030
+ "word/media/hat.emf": IMAGE_BUFFER
1031
+ })
1032
+ });
1033
+ assert.deepEqual(result.messages, [warning("Image of type image/x-emf is unlikely to display in web browsers")]);
1034
+ var element = single(result.value);
1035
+ assert.equal(element.contentType, "image/x-emf");
1036
+ });
1037
+
1038
+ test("no elements created if image cannot be found in w:drawing", function() {
1039
+ var drawing = new XmlElement("w:drawing", {}, []);
1040
+
1041
+ var result = readXmlElement(drawing);
1042
+ assert.deepEqual(result.messages, []);
1043
+ assert.deepEqual(result.value, []);
1044
+ });
1045
+
1046
+ test("no elements created if image cannot be found in wp:inline", function() {
1047
+ var drawing = new XmlElement("wp:inline", {}, []);
1048
+
1049
+ var result = readXmlElement(drawing);
1050
+ assert.deepEqual(result.messages, []);
1051
+ assert.deepEqual(result.value, []);
1052
+ });
1053
+
1054
+ test("children of w:ins are converted normally", function() {
1055
+ assertChildrenAreConvertedNormally("w:ins");
1056
+ });
1057
+
1058
+ test("children of w:object are converted normally", function() {
1059
+ assertChildrenAreConvertedNormally("w:object");
1060
+ });
1061
+
1062
+ test("children of w:smartTag are converted normally", function() {
1063
+ assertChildrenAreConvertedNormally("w:smartTag");
1064
+ });
1065
+
1066
+ test("children of v:group are converted normally", function() {
1067
+ assertChildrenAreConvertedNormally("v:group");
1068
+ });
1069
+
1070
+ test("children of v:rect are converted normally", function() {
1071
+ assertChildrenAreConvertedNormally("v:rect");
1072
+ });
1073
+
1074
+ function assertChildrenAreConvertedNormally(tagName) {
1075
+ var runXml = new XmlElement("w:r", {}, []);
1076
+ var result = readXmlElement(new XmlElement(tagName, {}, [runXml]));
1077
+ assert.deepEqual(result.value[0].type, "run");
1078
+ }
1079
+
1080
+ test("w:hyperlink", {
1081
+ "is read as external hyperlink if it has a relationship ID": function() {
1082
+ var runXml = new XmlElement("w:r", {}, []);
1083
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42"}, [runXml]);
1084
+ var relationships = new Relationships([
1085
+ hyperlinkRelationship("r42", "http://example.com")
1086
+ ]);
1087
+ var result = readXmlElement(hyperlinkXml, {relationships: relationships});
1088
+ assert.deepEqual(result.value.href, "http://example.com");
1089
+ assert.deepEqual(result.value.children[0].type, "run");
1090
+ },
1091
+
1092
+ "is read as external hyperlink if it has a relationship ID and an anchor": function() {
1093
+ var runXml = new XmlElement("w:r", {}, []);
1094
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42", "w:anchor": "fragment"}, [runXml]);
1095
+ var relationships = new Relationships([
1096
+ hyperlinkRelationship("r42", "http://example.com/")
1097
+ ]);
1098
+ var result = readXmlElement(hyperlinkXml, {relationships: relationships});
1099
+ assert.deepEqual(result.value.href, "http://example.com/#fragment");
1100
+ assert.deepEqual(result.value.children[0].type, "run");
1101
+ },
1102
+
1103
+ "existing fragment is replaced when anchor is set on external link": function() {
1104
+ var runXml = new XmlElement("w:r", {}, []);
1105
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"r:id": "r42", "w:anchor": "fragment"}, [runXml]);
1106
+ var relationships = new Relationships([
1107
+ hyperlinkRelationship("r42", "http://example.com/#previous")
1108
+ ]);
1109
+ var result = readXmlElement(hyperlinkXml, {relationships: relationships});
1110
+ assert.deepEqual(result.value.href, "http://example.com/#fragment");
1111
+ assert.deepEqual(result.value.children[0].type, "run");
1112
+ },
1113
+
1114
+ "is read as internal hyperlink if it has an anchor": function() {
1115
+ var runXml = new XmlElement("w:r", {}, []);
1116
+ var hyperlinkXml = new XmlElement("w:hyperlink", {"w:anchor": "_Peter"}, [runXml]);
1117
+ var result = readXmlElement(hyperlinkXml);
1118
+ assert.deepEqual(result.value.anchor, "_Peter");
1119
+ assert.deepEqual(result.value.children[0].type, "run");
1120
+ },
1121
+
1122
+ "is ignored if it does not have a relationship ID nor anchor": function() {
1123
+ var runXml = new XmlElement("w:r", {}, []);
1124
+ var hyperlinkXml = new XmlElement("w:hyperlink", {}, [runXml]);
1125
+ var result = readXmlElement(hyperlinkXml);
1126
+ assert.deepEqual(result.value[0].type, "run");
1127
+ },
1128
+
1129
+ "target frame is read": function() {
1130
+ var hyperlinkXml = new XmlElement("w:hyperlink", {
1131
+ "w:anchor": "Introduction",
1132
+ "w:tgtFrame": "_blank"
1133
+ });
1134
+ var result = readXmlElementValue(hyperlinkXml);
1135
+ assertThat(result, hasProperties({targetFrame: "_blank"}));
1136
+ },
1137
+
1138
+ "empty target frame is ignored": function() {
1139
+ var hyperlinkXml = new XmlElement("w:hyperlink", {
1140
+ "w:anchor": "Introduction",
1141
+ "w:tgtFrame": ""
1142
+ });
1143
+ var result = readXmlElementValue(hyperlinkXml);
1144
+ assertThat(result, hasProperties({targetFrame: null}));
1145
+ }
1146
+ });
1147
+
1148
+ test("w:br without explicit type is read as line break", function() {
1149
+ var breakXml = new XmlElement("w:br", {}, []);
1150
+ var result = readXmlElementValue(breakXml);
1151
+ assert.deepEqual(result, documents.lineBreak);
1152
+ });
1153
+
1154
+ test("w:br with textWrapping type is read as line break", function() {
1155
+ var breakXml = new XmlElement("w:br", {"w:type": "textWrapping"}, []);
1156
+ var result = readXmlElementValue(breakXml);
1157
+ assert.deepEqual(result, documents.lineBreak);
1158
+ });
1159
+
1160
+ test("w:br with page type is read as page break", function() {
1161
+ var breakXml = new XmlElement("w:br", {"w:type": "page"}, []);
1162
+ var result = readXmlElementValue(breakXml);
1163
+ assert.deepEqual(result, documents.pageBreak);
1164
+ });
1165
+
1166
+ test("w:br with column type is read as column break", function() {
1167
+ var breakXml = new XmlElement("w:br", {"w:type": "column"}, []);
1168
+ var result = readXmlElementValue(breakXml);
1169
+ assert.deepEqual(result, documents.columnBreak);
1170
+ });
1171
+
1172
+ test("warning on breaks that aren't recognised", function() {
1173
+ var breakXml = new XmlElement("w:br", {"w:type": "unknownBreakType"}, []);
1174
+ var result = readXmlElement(breakXml);
1175
+ assert.deepEqual(result.value, []);
1176
+ assert.deepEqual(result.messages, [warning("Unsupported break type: unknownBreakType")]);
1177
+ });
1178
+
1179
+ test("w:footnoteReference has ID read", function() {
1180
+ var referenceXml = new XmlElement("w:footnoteReference", {"w:id": "4"});
1181
+ var result = readXmlElement(referenceXml);
1182
+ assert.deepEqual(
1183
+ result.value,
1184
+ documents.noteReference({noteType: "footnote", noteId: "4"})
1185
+ );
1186
+ assert.deepEqual(result.messages, []);
1187
+ });
1188
+
1189
+ test("w:commentReference has ID read", function() {
1190
+ var referenceXml = new XmlElement("w:commentReference", {"w:id": "4"});
1191
+ var result = readXmlElement(referenceXml);
1192
+ assert.deepEqual(
1193
+ result.value,
1194
+ documents.commentReference({commentId: "4"})
1195
+ );
1196
+ assert.deepEqual(result.messages, []);
1197
+ });
1198
+
1199
+ test("emits warning on unrecognised element", function() {
1200
+ var unrecognisedElement = new XmlElement("w:not-an-element");
1201
+ var result = readXmlElement(unrecognisedElement);
1202
+ assert.deepEqual(
1203
+ result.messages,
1204
+ [{
1205
+ type: "warning",
1206
+ message: "An unrecognised element was ignored: w:not-an-element"
1207
+ }]
1208
+ );
1209
+ assert.deepEqual(result.value, []);
1210
+ });
1211
+
1212
+ test("w:bookmarkEnd is ignored without warning", function() {
1213
+ var ignoredElement = new XmlElement("w:bookmarkEnd");
1214
+ var result = readXmlElement(ignoredElement);
1215
+ assert.deepEqual(result.messages, []);
1216
+ assert.deepEqual([], result.value);
1217
+ });
1218
+
1219
+ test("text boxes have content appended after containing paragraph", function() {
1220
+ var textbox = new XmlElement("w:pict", {}, [
1221
+ new XmlElement("v:shape", {}, [
1222
+ new XmlElement("v:textbox", {}, [
1223
+ new XmlElement("w:txbxContent", {}, [
1224
+ paragraphWithStyleId("textbox-content")
1225
+ ])
1226
+ ])
1227
+ ])
1228
+ ]);
1229
+ var paragraph = new XmlElement("w:p", {}, [
1230
+ new XmlElement("w:r", {}, [textbox])
1231
+ ]);
1232
+ var result = readXmlElement(paragraph);
1233
+ assert.deepEqual(result.value[1].styleId, "textbox-content");
1234
+ });
1235
+
1236
+ test("mc:Fallback is used when mc:AlternateContent is read", function() {
1237
+ var styles = new Styles({"first": {name: "First"}, "second": {name: "Second"}}, {});
1238
+ var textbox = new XmlElement("mc:AlternateContent", {}, [
1239
+ new XmlElement("mc:Choice", {"Requires": "wps"}, [
1240
+ paragraphWithStyleId("first")
1241
+ ]),
1242
+ new XmlElement("mc:Fallback", {}, [
1243
+ paragraphWithStyleId("second")
1244
+ ])
1245
+ ]);
1246
+ var result = readXmlElement(textbox, {styles: styles});
1247
+ assert.deepEqual(result.value[0].styleId, "second");
1248
+ });
1249
+
1250
+ test("w:sdtContent is used when w:sdt is read", function() {
1251
+ var element = xml.element("w:sdt", {}, [
1252
+ xml.element("w:sdtContent", {}, [
1253
+ xml.element("w:t", {}, [xml.text("Blackdown")])
1254
+ ])
1255
+ ]);
1256
+ var result = readXmlElement(element);
1257
+ assert.deepEqual(result.value, [new documents.Text("Blackdown")]);
1258
+ });
1259
+
1260
+ test("text nodes are ignored when reading children", function() {
1261
+ var runXml = new XmlElement("w:r", {}, [xml.text("[text]")]);
1262
+ var run = readXmlElementValue(runXml);
1263
+ assert.deepEqual(run, new documents.Run([]));
1264
+ });
1265
+
1266
+ function paragraphWithStyleId(styleId) {
1267
+ return new XmlElement("w:p", {}, [
1268
+ new XmlElement("w:pPr", {}, [
1269
+ new XmlElement("w:pStyle", {"w:val": styleId}, [])
1270
+ ])
1271
+ ]);
1272
+ }
1273
+
1274
+ function runWithProperties(children) {
1275
+ return new XmlElement("w:r", {}, [createRunPropertiesXml(children)]);
1276
+ }
1277
+
1278
+ function createRunPropertiesXml(children) {
1279
+ return new XmlElement("w:rPr", {}, children);
1280
+ }
1281
+
1282
+ function single(array) {
1283
+ if (array.length === 1) {
1284
+ return array[0];
1285
+ } else {
1286
+ throw new Error("Array has " + array.length + " elements");
1287
+ }
1288
+ }
1289
+
1290
+ function createInlineImage(options) {
1291
+ return new XmlElement("w:drawing", {}, [
1292
+ new XmlElement("wp:inline", {}, [
1293
+ new XmlElement("wp:docPr", {descr: options.description, title: options.title}),
1294
+ new XmlElement("a:graphic", {}, [
1295
+ new XmlElement("a:graphicData", {}, [
1296
+ new XmlElement("pic:pic", {}, [
1297
+ new XmlElement("pic:blipFill", {}, [
1298
+ options.blip
1299
+ ])
1300
+ ])
1301
+ ])
1302
+ ])
1303
+ ])
1304
+ ]);
1305
+ }
1306
+
1307
+ function createEmbeddedBlip(relationshipId) {
1308
+ return new XmlElement("a:blip", {"r:embed": relationshipId});
1309
+ }
1310
+
1311
+ function createLinkedBlip(relationshipId) {
1312
+ return new XmlElement("a:blip", {"r:link": relationshipId});
1313
+ }
1314
+
1315
+ function runOfText(text) {
1316
+ var textXml = new XmlElement("w:t", {}, [xml.text(text)]);
1317
+ return new XmlElement("w:r", {}, [textXml]);
1318
+ }
1319
+
1320
+ function hyperlinkRelationship(relationshipId, target) {
1321
+ return {
1322
+ relationshipId: relationshipId,
1323
+ target: target,
1324
+ type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink"
1325
+ };
1326
+ }
1327
+
1328
+ function imageRelationship(relationshipId, target) {
1329
+ return {
1330
+ relationshipId: relationshipId,
1331
+ target: target,
1332
+ type: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
1333
+ };
1334
+ }
1335
+
1336
+ function NumberingMap(nums) {
1337
+ return {
1338
+ findLevel: function(numId, level) {
1339
+ return nums[numId][level];
1340
+ }
1341
+ };
1342
+ }