DocumentAssembler.cs 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. // Copyright (c) Microsoft. All rights reserved.
  2. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Xml;
  10. using System.Xml.Linq;
  11. using System.Xml.XPath;
  12. using System.Xml.Schema;
  13. using DocumentFormat.OpenXml.Office.CustomUI;
  14. using DocumentFormat.OpenXml.Packaging;
  15. using OpenXmlPowerTools;
  16. using System.Collections;
  17. namespace OpenXmlPowerTools
  18. {
  19. public class DocumentAssembler
  20. {
  21. public static WmlDocument AssembleDocument(WmlDocument templateDoc, XmlDocument data, out bool templateError)
  22. {
  23. XDocument xDoc = data.GetXDocument();
  24. return AssembleDocument(templateDoc, xDoc.Root, out templateError);
  25. }
  26. public static WmlDocument AssembleDocument(WmlDocument templateDoc, XElement data, out bool templateError)
  27. {
  28. byte[] byteArray = templateDoc.DocumentByteArray;
  29. using (MemoryStream mem = new MemoryStream())
  30. {
  31. mem.Write(byteArray, 0, (int)byteArray.Length);
  32. using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
  33. {
  34. if (RevisionAccepter.HasTrackedRevisions(wordDoc))
  35. throw new OpenXmlPowerToolsException("Invalid DocumentAssembler template - contains tracked revisions");
  36. var te = new TemplateError();
  37. foreach (var part in wordDoc.ContentParts())
  38. {
  39. ProcessTemplatePart(data, te, part);
  40. }
  41. templateError = te.HasError;
  42. }
  43. WmlDocument assembledDocument = new WmlDocument("TempFileName.docx", mem.ToArray());
  44. return assembledDocument;
  45. }
  46. }
  47. private static void ProcessTemplatePart(XElement data, TemplateError te, OpenXmlPart part)
  48. {
  49. XDocument xDoc = part.GetXDocument();
  50. var xDocRoot = RemoveGoBackBookmarks(xDoc.Root);
  51. // content controls in cells can surround the W.tc element, so transform so that such content controls are within the cell content
  52. xDocRoot = (XElement)NormalizeContentControlsInCells(xDocRoot);
  53. xDocRoot = (XElement)TransformToMetadata(xDocRoot, data, te);
  54. // Table might have been placed at run-level, when it should be at block-level, so fix this.
  55. // Repeat, EndRepeat, Conditional, EndConditional are allowed at run level, but only if there is a matching pair
  56. // if there is only one Repeat, EndRepeat, Conditional, EndConditional, then move to block level.
  57. // if there is a matching pair, then is OK.
  58. xDocRoot = (XElement)ForceBlockLevelAsAppropriate(xDocRoot, te);
  59. NormalizeTablesRepeatAndConditional(xDocRoot, te);
  60. // any EndRepeat, EndConditional that remain are orphans, so replace with an error
  61. ProcessOrphanEndRepeatEndConditional(xDocRoot, te);
  62. // do the actual content replacement
  63. xDocRoot = (XElement)ContentReplacementTransform(xDocRoot, data, te);
  64. xDoc.Elements().First().ReplaceWith(xDocRoot);
  65. part.PutXDocument();
  66. return;
  67. }
  68. private static XName[] s_MetaToForceToBlock = new XName[] {
  69. PA.Conditional,
  70. PA.EndConditional,
  71. PA.Repeat,
  72. PA.EndRepeat,
  73. PA.Table,
  74. };
  75. private static object ForceBlockLevelAsAppropriate(XNode node, TemplateError te)
  76. {
  77. XElement element = node as XElement;
  78. if (element != null)
  79. {
  80. if (element.Name == W.p)
  81. {
  82. var childMeta = element.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).ToList();
  83. if (childMeta.Count() == 1)
  84. {
  85. var child = childMeta.First();
  86. var otherTextInParagraph = element.Elements(W.r).Elements(W.t).Select(t => (string)t).StringConcatenate().Trim();
  87. if (otherTextInParagraph != "")
  88. {
  89. var newPara = new XElement(element);
  90. var newMeta = newPara.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).First();
  91. newMeta.ReplaceWith(CreateRunErrorMessage("Error: Unmatched metadata can't be in paragraph with other text", te));
  92. return newPara;
  93. }
  94. var meta = new XElement(child.Name,
  95. child.Attributes(),
  96. new XElement(W.p,
  97. element.Attributes(),
  98. element.Elements(W.pPr),
  99. child.Elements()));
  100. return meta;
  101. }
  102. var count = childMeta.Count();
  103. if (count % 2 == 0)
  104. {
  105. if (childMeta.Where(c => c.Name == PA.Repeat).Count() != childMeta.Where(c => c.Name == PA.EndRepeat).Count())
  106. return CreateContextErrorMessage(element, "Error: Mismatch Repeat / EndRepeat at run level", te);
  107. if (childMeta.Where(c => c.Name == PA.Conditional).Count() != childMeta.Where(c => c.Name == PA.EndConditional).Count())
  108. return CreateContextErrorMessage(element, "Error: Mismatch Conditional / EndConditional at run level", te);
  109. return new XElement(element.Name,
  110. element.Attributes(),
  111. element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
  112. }
  113. else
  114. {
  115. return CreateContextErrorMessage(element, "Error: Invalid metadata at run level", te);
  116. }
  117. }
  118. return new XElement(element.Name,
  119. element.Attributes(),
  120. element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
  121. }
  122. return node;
  123. }
  124. private static void ProcessOrphanEndRepeatEndConditional(XElement xDocRoot, TemplateError te)
  125. {
  126. foreach (var element in xDocRoot.Descendants(PA.EndRepeat).ToList())
  127. {
  128. var error = CreateContextErrorMessage(element, "Error: EndRepeat without matching Repeat", te);
  129. element.ReplaceWith(error);
  130. }
  131. foreach (var element in xDocRoot.Descendants(PA.EndConditional).ToList())
  132. {
  133. var error = CreateContextErrorMessage(element, "Error: EndConditional without matching Conditional", te);
  134. element.ReplaceWith(error);
  135. }
  136. }
  137. private static XElement RemoveGoBackBookmarks(XElement xElement)
  138. {
  139. var cloneXDoc = new XElement(xElement);
  140. while (true)
  141. {
  142. var bm = cloneXDoc.DescendantsAndSelf(W.bookmarkStart).FirstOrDefault(b => (string)b.Attribute(W.name) == "_GoBack");
  143. if (bm == null)
  144. break;
  145. var id = (string)bm.Attribute(W.id);
  146. var endBm = cloneXDoc.DescendantsAndSelf(W.bookmarkEnd).FirstOrDefault(b => (string)b.Attribute(W.id) == id);
  147. bm.Remove();
  148. endBm.Remove();
  149. }
  150. return cloneXDoc;
  151. }
  152. // this transform inverts content controls that surround W.tc elements. After transforming, the W.tc will contain
  153. // the content control, which contains the paragraph content of the cell.
  154. private static object NormalizeContentControlsInCells(XNode node)
  155. {
  156. XElement element = node as XElement;
  157. if (element != null)
  158. {
  159. if (element.Name == W.sdt && element.Parent.Name == W.tr)
  160. {
  161. var newCell = new XElement(W.tc,
  162. element.Elements(W.tc).Elements(W.tcPr),
  163. new XElement(W.sdt,
  164. element.Elements(W.sdtPr),
  165. element.Elements(W.sdtEndPr),
  166. new XElement(W.sdtContent,
  167. element.Elements(W.sdtContent).Elements(W.tc).Elements().Where(e => e.Name != W.tcPr))));
  168. return newCell;
  169. }
  170. return new XElement(element.Name,
  171. element.Attributes(),
  172. element.Nodes().Select(n => NormalizeContentControlsInCells(n)));
  173. }
  174. return node;
  175. }
  176. // The following method is written using tree modification, not RPFT, because it is easier to write in this fashion.
  177. // These types of operations are not as easy to write using RPFT.
  178. // Unless you are completely clear on the semantics of LINQ to XML DML, do not make modifications to this method.
  179. private static void NormalizeTablesRepeatAndConditional(XElement xDoc, TemplateError te)
  180. {
  181. var tables = xDoc.Descendants(PA.Table).ToList();
  182. foreach (var table in tables)
  183. {
  184. var followingElement = table.ElementsAfterSelf().Where(e => e.Name == W.tbl || e.Name == W.p).FirstOrDefault();
  185. if (followingElement == null || followingElement.Name != W.tbl)
  186. {
  187. table.ReplaceWith(CreateParaErrorMessage("Table metadata is not immediately followed by a table", te));
  188. continue;
  189. }
  190. // remove superflous paragraph from Table metadata
  191. table.RemoveNodes();
  192. // detach w:tbl from parent, and add to Table metadata
  193. followingElement.Remove();
  194. table.Add(followingElement);
  195. }
  196. int repeatDepth = 0;
  197. int conditionalDepth = 0;
  198. foreach (var metadata in xDoc.Descendants().Where(d =>
  199. d.Name == PA.Repeat ||
  200. d.Name == PA.Conditional ||
  201. d.Name == PA.EndRepeat ||
  202. d.Name == PA.EndConditional))
  203. {
  204. if (metadata.Name == PA.Repeat)
  205. {
  206. ++repeatDepth;
  207. metadata.Add(new XAttribute(PA.Depth, repeatDepth));
  208. continue;
  209. }
  210. if (metadata.Name == PA.EndRepeat)
  211. {
  212. metadata.Add(new XAttribute(PA.Depth, repeatDepth));
  213. --repeatDepth;
  214. continue;
  215. }
  216. if (metadata.Name == PA.Conditional)
  217. {
  218. ++conditionalDepth;
  219. metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
  220. continue;
  221. }
  222. if (metadata.Name == PA.EndConditional)
  223. {
  224. metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
  225. --conditionalDepth;
  226. continue;
  227. }
  228. }
  229. while (true)
  230. {
  231. bool didReplace = false;
  232. foreach (var metadata in xDoc.Descendants().Where(d => (d.Name == PA.Repeat || d.Name == PA.Conditional) && d.Attribute(PA.Depth) != null).ToList())
  233. {
  234. var depth = (int)metadata.Attribute(PA.Depth);
  235. XName matchingEndName = null;
  236. if (metadata.Name == PA.Repeat)
  237. matchingEndName = PA.EndRepeat;
  238. else if (metadata.Name == PA.Conditional)
  239. matchingEndName = PA.EndConditional;
  240. if (matchingEndName == null)
  241. throw new OpenXmlPowerToolsException("Internal error");
  242. var matchingEnd = metadata.ElementsAfterSelf(matchingEndName).FirstOrDefault(end => { return (int)end.Attribute(PA.Depth) == depth; });
  243. if (matchingEnd == null)
  244. {
  245. metadata.ReplaceWith(CreateParaErrorMessage(string.Format("{0} does not have matching {1}", metadata.Name.LocalName, matchingEndName.LocalName), te));
  246. continue;
  247. }
  248. metadata.RemoveNodes();
  249. var contentBetween = metadata.ElementsAfterSelf().TakeWhile(after => after != matchingEnd).ToList();
  250. foreach (var item in contentBetween)
  251. item.Remove();
  252. contentBetween = contentBetween.Where(n => n.Name != W.bookmarkStart && n.Name != W.bookmarkEnd).ToList();
  253. metadata.Add(contentBetween);
  254. metadata.Attributes(PA.Depth).Remove();
  255. matchingEnd.Remove();
  256. didReplace = true;
  257. break;
  258. }
  259. if (!didReplace)
  260. break;
  261. }
  262. }
  263. private static List<string> s_AliasList = new List<string>()
  264. {
  265. "Content",
  266. "Table",
  267. "Repeat",
  268. "EndRepeat",
  269. "Conditional",
  270. "EndConditional",
  271. };
  272. private static object TransformToMetadata(XNode node, XElement data, TemplateError te)
  273. {
  274. XElement element = node as XElement;
  275. if (element != null)
  276. {
  277. if (element.Name == W.sdt)
  278. {
  279. var alias = (string)element.Elements(W.sdtPr).Elements(W.alias).Attributes(W.val).FirstOrDefault();
  280. if (alias == null || alias == "" || s_AliasList.Contains(alias))
  281. {
  282. var ccContents = element
  283. .DescendantsTrimmed(W.txbxContent)
  284. .Where(e => e.Name == W.t)
  285. .Select(t => (string)t)
  286. .StringConcatenate()
  287. .Trim()
  288. .Replace('“', '"')
  289. .Replace('”', '"');
  290. if (ccContents.StartsWith("<"))
  291. {
  292. XElement xml = TransformXmlTextToMetadata(te, ccContents);
  293. if (xml.Name == W.p || xml.Name == W.r) // this means there was an error processing the XML.
  294. {
  295. if (element.Parent.Name == W.p)
  296. return xml.Elements(W.r);
  297. return xml;
  298. }
  299. if (alias != null && xml.Name.LocalName != alias)
  300. {
  301. if (element.Parent.Name == W.p)
  302. return CreateRunErrorMessage("Error: Content control alias does not match metadata element name", te);
  303. else
  304. return CreateParaErrorMessage("Error: Content control alias does not match metadata element name", te);
  305. }
  306. xml.Add(element.Elements(W.sdtContent).Elements());
  307. return xml;
  308. }
  309. return new XElement(element.Name,
  310. element.Attributes(),
  311. element.Nodes().Select(n => TransformToMetadata(n, data, te)));
  312. }
  313. return new XElement(element.Name,
  314. element.Attributes(),
  315. element.Nodes().Select(n => TransformToMetadata(n, data, te)));
  316. }
  317. if (element.Name == W.p)
  318. {
  319. var paraContents = element
  320. .DescendantsTrimmed(W.txbxContent)
  321. .Where(e => e.Name == W.t)
  322. .Select(t => (string)t)
  323. .StringConcatenate()
  324. .Trim();
  325. int occurances = paraContents.Select((c, i) => paraContents.Substring(i)).Count(sub => sub.StartsWith("<#"));
  326. if (paraContents.StartsWith("<#") && paraContents.EndsWith("#>") && occurances == 1)
  327. {
  328. var xmlText = paraContents.Substring(2, paraContents.Length - 4).Trim();
  329. XElement xml = TransformXmlTextToMetadata(te, xmlText);
  330. if (xml.Name == W.p || xml.Name == W.r)
  331. return xml;
  332. xml.Add(element);
  333. return xml;
  334. }
  335. if (paraContents.Contains("<#"))
  336. {
  337. List<RunReplacementInfo> runReplacementInfo = new List<RunReplacementInfo>();
  338. var thisGuid = Guid.NewGuid().ToString();
  339. var r = new Regex("<#.*?#>");
  340. XElement xml = null;
  341. OpenXmlRegex.Replace(new[] { element }, r, thisGuid, (para, match) =>
  342. {
  343. var matchString = match.Value.Trim();
  344. var xmlText = matchString.Substring(2, matchString.Length - 4).Trim().Replace('“', '"').Replace('”', '"');
  345. try
  346. {
  347. xml = XElement.Parse(xmlText);
  348. }
  349. catch (XmlException e)
  350. {
  351. RunReplacementInfo rri = new RunReplacementInfo()
  352. {
  353. Xml = null,
  354. XmlExceptionMessage = "XmlException: " + e.Message,
  355. SchemaValidationMessage = null,
  356. };
  357. runReplacementInfo.Add(rri);
  358. return true;
  359. }
  360. string schemaError = ValidatePerSchema(xml);
  361. if (schemaError != null)
  362. {
  363. RunReplacementInfo rri = new RunReplacementInfo()
  364. {
  365. Xml = null,
  366. XmlExceptionMessage = null,
  367. SchemaValidationMessage = "Schema Validation Error: " + schemaError,
  368. };
  369. runReplacementInfo.Add(rri);
  370. return true;
  371. }
  372. RunReplacementInfo rri2 = new RunReplacementInfo()
  373. {
  374. Xml = xml,
  375. XmlExceptionMessage = null,
  376. SchemaValidationMessage = null,
  377. };
  378. runReplacementInfo.Add(rri2);
  379. return true;
  380. }, false);
  381. var newPara = new XElement(element);
  382. foreach (var rri in runReplacementInfo)
  383. {
  384. var runToReplace = newPara.Descendants(W.r).FirstOrDefault(rn => rn.Value == thisGuid && rn.Parent.Name != PA.Content);
  385. if (runToReplace == null)
  386. throw new OpenXmlPowerToolsException("Internal error");
  387. if (rri.XmlExceptionMessage != null)
  388. runToReplace.ReplaceWith(CreateRunErrorMessage(rri.XmlExceptionMessage, te));
  389. else if (rri.SchemaValidationMessage != null)
  390. runToReplace.ReplaceWith(CreateRunErrorMessage(rri.SchemaValidationMessage, te));
  391. else
  392. {
  393. var newXml = new XElement(rri.Xml);
  394. newXml.Add(runToReplace);
  395. runToReplace.ReplaceWith(newXml);
  396. }
  397. }
  398. var coalescedParagraph = WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(newPara);
  399. return coalescedParagraph;
  400. }
  401. }
  402. return new XElement(element.Name,
  403. element.Attributes(),
  404. element.Nodes().Select(n => TransformToMetadata(n, data, te)));
  405. }
  406. return node;
  407. }
  408. private static XElement TransformXmlTextToMetadata(TemplateError te, string xmlText)
  409. {
  410. XElement xml;
  411. try
  412. {
  413. xml = XElement.Parse(xmlText);
  414. }
  415. catch (XmlException e)
  416. {
  417. return CreateParaErrorMessage("XmlException: " + e.Message, te);
  418. }
  419. string schemaError = ValidatePerSchema(xml);
  420. if (schemaError != null)
  421. return CreateParaErrorMessage("Schema Validation Error: " + schemaError, te);
  422. return xml;
  423. }
  424. private class RunReplacementInfo
  425. {
  426. public XElement Xml;
  427. public string XmlExceptionMessage;
  428. public string SchemaValidationMessage;
  429. }
  430. private static string ValidatePerSchema(XElement element)
  431. {
  432. if (s_PASchemaSets == null)
  433. {
  434. s_PASchemaSets = new Dictionary<XName, PASchemaSet>()
  435. {
  436. {
  437. PA.Content,
  438. new PASchemaSet() {
  439. XsdMarkup =
  440. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  441. <xs:element name='Content'>
  442. <xs:complexType>
  443. <xs:attribute name='Select' type='xs:string' use='required' />
  444. <xs:attribute name='Optional' type='xs:boolean' use='optional' />
  445. </xs:complexType>
  446. </xs:element>
  447. </xs:schema>",
  448. }
  449. },
  450. {
  451. PA.Table,
  452. new PASchemaSet() {
  453. XsdMarkup =
  454. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  455. <xs:element name='Table'>
  456. <xs:complexType>
  457. <xs:attribute name='Select' type='xs:string' use='required' />
  458. </xs:complexType>
  459. </xs:element>
  460. </xs:schema>",
  461. }
  462. },
  463. {
  464. PA.Repeat,
  465. new PASchemaSet() {
  466. XsdMarkup =
  467. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  468. <xs:element name='Repeat'>
  469. <xs:complexType>
  470. <xs:attribute name='Select' type='xs:string' use='required' />
  471. <xs:attribute name='Optional' type='xs:boolean' use='optional' />
  472. </xs:complexType>
  473. </xs:element>
  474. </xs:schema>",
  475. }
  476. },
  477. {
  478. PA.EndRepeat,
  479. new PASchemaSet() {
  480. XsdMarkup =
  481. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  482. <xs:element name='EndRepeat' />
  483. </xs:schema>",
  484. }
  485. },
  486. {
  487. PA.Conditional,
  488. new PASchemaSet() {
  489. XsdMarkup =
  490. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  491. <xs:element name='Conditional'>
  492. <xs:complexType>
  493. <xs:attribute name='Select' type='xs:string' use='required' />
  494. <xs:attribute name='Match' type='xs:string' use='optional' />
  495. <xs:attribute name='NotMatch' type='xs:string' use='optional' />
  496. </xs:complexType>
  497. </xs:element>
  498. </xs:schema>",
  499. }
  500. },
  501. {
  502. PA.EndConditional,
  503. new PASchemaSet() {
  504. XsdMarkup =
  505. @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
  506. <xs:element name='EndConditional' />
  507. </xs:schema>",
  508. }
  509. },
  510. };
  511. foreach (var item in s_PASchemaSets)
  512. {
  513. var itemPAss = item.Value;
  514. XmlSchemaSet schemas = new XmlSchemaSet();
  515. schemas.Add("", XmlReader.Create(new StringReader(itemPAss.XsdMarkup)));
  516. itemPAss.SchemaSet = schemas;
  517. }
  518. }
  519. if (!s_PASchemaSets.ContainsKey(element.Name))
  520. {
  521. return string.Format("Invalid XML: {0} is not a valid element", element.Name.LocalName);
  522. }
  523. var paSchemaSet = s_PASchemaSets[element.Name];
  524. XDocument d = new XDocument(element);
  525. string message = null;
  526. d.Validate(paSchemaSet.SchemaSet, (sender, e) =>
  527. {
  528. if (message == null)
  529. message = e.Message;
  530. }, true);
  531. if (message != null)
  532. return message;
  533. return null;
  534. }
  535. private class PA
  536. {
  537. public static XName Content = "Content";
  538. public static XName Table = "Table";
  539. public static XName Repeat = "Repeat";
  540. public static XName EndRepeat = "EndRepeat";
  541. public static XName Conditional = "Conditional";
  542. public static XName EndConditional = "EndConditional";
  543. public static XName Select = "Select";
  544. public static XName Optional = "Optional";
  545. public static XName Match = "Match";
  546. public static XName NotMatch = "NotMatch";
  547. public static XName Depth = "Depth";
  548. }
  549. private class PASchemaSet
  550. {
  551. public string XsdMarkup;
  552. public XmlSchemaSet SchemaSet;
  553. }
  554. private static Dictionary<XName, PASchemaSet> s_PASchemaSets = null;
  555. private class TemplateError
  556. {
  557. public bool HasError = false;
  558. }
  559. static object ContentReplacementTransform(XNode node, XElement data, TemplateError templateError)
  560. {
  561. XElement element = node as XElement;
  562. if (element != null)
  563. {
  564. if (element.Name == PA.Content)
  565. {
  566. XElement para = element.Descendants(W.p).FirstOrDefault();
  567. XElement run = element.Descendants(W.r).FirstOrDefault();
  568. var xPath = (string) element.Attribute(PA.Select);
  569. var optionalString = (string) element.Attribute(PA.Optional);
  570. bool optional = (optionalString != null && optionalString.ToLower() == "true");
  571. string newValue;
  572. try
  573. {
  574. newValue = EvaluateXPathToString(data, xPath, optional);
  575. }
  576. catch (XPathException e)
  577. {
  578. return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
  579. }
  580. if (para != null)
  581. {
  582. XElement p = new XElement(W.p, para.Elements(W.pPr));
  583. foreach(string line in newValue.Split('\n'))
  584. {
  585. p.Add(new XElement(W.r,
  586. para.Elements(W.r).Elements(W.rPr).FirstOrDefault(),
  587. (p.Elements().Count() > 1) ? new XElement(W.br) : null,
  588. new XElement(W.t, line)));
  589. }
  590. return p;
  591. }
  592. else
  593. {
  594. List<XElement> list = new List<XElement>();
  595. foreach(string line in newValue.Split('\n'))
  596. {
  597. list.Add(new XElement(W.r,
  598. run.Elements().Where(e => e.Name != W.t),
  599. (list.Count > 0) ? new XElement(W.br) : null,
  600. new XElement(W.t, line)));
  601. }
  602. return list;
  603. }
  604. }
  605. if (element.Name == PA.Repeat)
  606. {
  607. string selector = (string)element.Attribute(PA.Select);
  608. var optionalString = (string)element.Attribute(PA.Optional);
  609. bool optional = (optionalString != null && optionalString.ToLower() == "true");
  610. IEnumerable<XElement> repeatingData;
  611. try
  612. {
  613. repeatingData = data.XPathSelectElements(selector);
  614. }
  615. catch (XPathException e)
  616. {
  617. return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
  618. }
  619. if (!repeatingData.Any())
  620. {
  621. if (optional)
  622. {
  623. return null;
  624. //XElement para = element.Descendants(W.p).FirstOrDefault();
  625. //if (para != null)
  626. // return new XElement(W.p, new XElement(W.r));
  627. //else
  628. // return new XElement(W.r);
  629. }
  630. return CreateContextErrorMessage(element, "Repeat: Select returned no data", templateError);
  631. }
  632. var newContent = repeatingData.Select(d =>
  633. {
  634. var content = element
  635. .Elements()
  636. .Select(e => ContentReplacementTransform(e, d, templateError))
  637. .ToList();
  638. return content;
  639. })
  640. .ToList();
  641. return newContent;
  642. }
  643. if (element.Name == PA.Table)
  644. {
  645. IEnumerable<XElement> tableData;
  646. try
  647. {
  648. tableData = data.XPathSelectElements((string)element.Attribute(PA.Select));
  649. }
  650. catch (XPathException e)
  651. {
  652. return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
  653. }
  654. if (tableData.Count() == 0)
  655. return CreateContextErrorMessage(element, "Table Select returned no data", templateError);
  656. XElement table = element.Element(W.tbl);
  657. XElement protoRow = table.Elements(W.tr).Skip(1).FirstOrDefault();
  658. var footerRowsBeforeTransform = table
  659. .Elements(W.tr)
  660. .Skip(2)
  661. .ToList();
  662. var footerRows = footerRowsBeforeTransform
  663. .Select(x => ContentReplacementTransform(x, data, templateError))
  664. .ToList();
  665. if (protoRow == null)
  666. return CreateContextErrorMessage(element, string.Format("Table does not contain a prototype row"), templateError);
  667. protoRow.Descendants(W.bookmarkStart).Remove();
  668. protoRow.Descendants(W.bookmarkEnd).Remove();
  669. XElement newTable = new XElement(W.tbl,
  670. table.Elements().Where(e => e.Name != W.tr),
  671. table.Elements(W.tr).FirstOrDefault(),
  672. tableData.Select(d =>
  673. new XElement(W.tr,
  674. protoRow.Elements().Where(r => r.Name != W.tc),
  675. protoRow.Elements(W.tc)
  676. .Select(tc =>
  677. {
  678. XElement paragraph = tc.Elements(W.p).FirstOrDefault();
  679. XElement cellRun = paragraph.Elements(W.r).FirstOrDefault();
  680. string xPath = paragraph.Value;
  681. string newValue = null;
  682. try
  683. {
  684. newValue = EvaluateXPathToString(d, xPath, false);
  685. }
  686. catch (XPathException e)
  687. {
  688. XElement errorCell = new XElement(W.tc,
  689. tc.Elements().Where(z => z.Name != W.p),
  690. new XElement(W.p,
  691. paragraph.Element(W.pPr),
  692. CreateRunErrorMessage(e.Message, templateError)));
  693. return errorCell;
  694. }
  695. XElement newCell = new XElement(W.tc,
  696. tc.Elements().Where(z => z.Name != W.p),
  697. new XElement(W.p,
  698. paragraph.Element(W.pPr),
  699. new XElement(W.r,
  700. cellRun != null ? cellRun.Element(W.rPr) : new XElement(W.rPr), //if the cell was empty there is no cellrun
  701. new XElement(W.t, newValue))));
  702. return newCell;
  703. }))),
  704. footerRows
  705. );
  706. return newTable;
  707. }
  708. if (element.Name == PA.Conditional)
  709. {
  710. string xPath = (string)element.Attribute(PA.Select);
  711. var match = (string)element.Attribute(PA.Match);
  712. var notMatch = (string)element.Attribute(PA.NotMatch);
  713. if (match == null && notMatch == null)
  714. return CreateContextErrorMessage(element, "Conditional: Must specify either Match or NotMatch", templateError);
  715. if (match != null && notMatch != null)
  716. return CreateContextErrorMessage(element, "Conditional: Cannot specify both Match and NotMatch", templateError);
  717. string testValue = null;
  718. try
  719. {
  720. testValue = EvaluateXPathToString(data, xPath, false);
  721. }
  722. catch (XPathException e)
  723. {
  724. return CreateContextErrorMessage(element, e.Message, templateError);
  725. }
  726. if ((match != null && testValue == match) || (notMatch != null && testValue != notMatch))
  727. {
  728. var content = element.Elements().Select(e => ContentReplacementTransform(e, data, templateError));
  729. return content;
  730. }
  731. return null;
  732. }
  733. return new XElement(element.Name,
  734. element.Attributes(),
  735. element.Nodes().Select(n => ContentReplacementTransform(n, data, templateError)));
  736. }
  737. return node;
  738. }
  739. private static object CreateContextErrorMessage(XElement element, string errorMessage, TemplateError templateError)
  740. {
  741. XElement para = element.Descendants(W.p).FirstOrDefault();
  742. XElement run = element.Descendants(W.r).FirstOrDefault();
  743. var errorRun = CreateRunErrorMessage(errorMessage, templateError);
  744. if (para != null)
  745. return new XElement(W.p, errorRun);
  746. else
  747. return errorRun;
  748. }
  749. private static XElement CreateRunErrorMessage(string errorMessage, TemplateError templateError)
  750. {
  751. templateError.HasError = true;
  752. var errorRun = new XElement(W.r,
  753. new XElement(W.rPr,
  754. new XElement(W.color, new XAttribute(W.val, "FF0000")),
  755. new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
  756. new XElement(W.t, errorMessage));
  757. return errorRun;
  758. }
  759. private static XElement CreateParaErrorMessage(string errorMessage, TemplateError templateError)
  760. {
  761. templateError.HasError = true;
  762. var errorPara = new XElement(W.p,
  763. new XElement(W.r,
  764. new XElement(W.rPr,
  765. new XElement(W.color, new XAttribute(W.val, "FF0000")),
  766. new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
  767. new XElement(W.t, errorMessage)));
  768. return errorPara;
  769. }
  770. private static string EvaluateXPathToString(XElement element, string xPath, bool optional )
  771. {
  772. object xPathSelectResult;
  773. try
  774. {
  775. //support some cells in the table may not have an xpath expression.
  776. if (String.IsNullOrWhiteSpace(xPath)) return String.Empty;
  777. xPathSelectResult = element.XPathEvaluate(xPath);
  778. }
  779. catch (XPathException e)
  780. {
  781. throw new XPathException("XPathException: " + e.Message, e);
  782. }
  783. if ((xPathSelectResult is IEnumerable) && !(xPathSelectResult is string))
  784. {
  785. var selectedData = ((IEnumerable) xPathSelectResult).Cast<XObject>();
  786. if (!selectedData.Any())
  787. {
  788. if (optional) return string.Empty;
  789. throw new XPathException(string.Format("XPath expression ({0}) returned no results", xPath));
  790. }
  791. if (selectedData.Count() > 1)
  792. {
  793. throw new XPathException(string.Format("XPath expression ({0}) returned more than one node", xPath));
  794. }
  795. XObject selectedDatum = selectedData.First();
  796. if (selectedDatum is XElement) return ((XElement) selectedDatum).Value;
  797. if (selectedDatum is XAttribute) return ((XAttribute) selectedDatum).Value;
  798. }
  799. return xPathSelectResult.ToString();
  800. }
  801. }
  802. }