body-reader.tests.js 54 KB

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