123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857 |
- // Copyright (c) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Text.RegularExpressions;
- using System.Xml;
- using System.Xml.Linq;
- using System.Xml.XPath;
- using System.Xml.Schema;
- using DocumentFormat.OpenXml.Office.CustomUI;
- using DocumentFormat.OpenXml.Packaging;
- using OpenXmlPowerTools;
- using System.Collections;
- namespace OpenXmlPowerTools
- {
- public class DocumentAssembler
- {
- public static WmlDocument AssembleDocument(WmlDocument templateDoc, XmlDocument data, out bool templateError)
- {
- XDocument xDoc = data.GetXDocument();
- return AssembleDocument(templateDoc, xDoc.Root, out templateError);
- }
- public static WmlDocument AssembleDocument(WmlDocument templateDoc, XElement data, out bool templateError)
- {
- byte[] byteArray = templateDoc.DocumentByteArray;
- using (MemoryStream mem = new MemoryStream())
- {
- mem.Write(byteArray, 0, (int)byteArray.Length);
- using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(mem, true))
- {
- if (RevisionAccepter.HasTrackedRevisions(wordDoc))
- throw new OpenXmlPowerToolsException("Invalid DocumentAssembler template - contains tracked revisions");
- var te = new TemplateError();
- foreach (var part in wordDoc.ContentParts())
- {
- ProcessTemplatePart(data, te, part);
- }
- templateError = te.HasError;
- }
- WmlDocument assembledDocument = new WmlDocument("TempFileName.docx", mem.ToArray());
- return assembledDocument;
- }
- }
- private static void ProcessTemplatePart(XElement data, TemplateError te, OpenXmlPart part)
- {
- XDocument xDoc = part.GetXDocument();
- var xDocRoot = RemoveGoBackBookmarks(xDoc.Root);
- // content controls in cells can surround the W.tc element, so transform so that such content controls are within the cell content
- xDocRoot = (XElement)NormalizeContentControlsInCells(xDocRoot);
- xDocRoot = (XElement)TransformToMetadata(xDocRoot, data, te);
- // Table might have been placed at run-level, when it should be at block-level, so fix this.
- // Repeat, EndRepeat, Conditional, EndConditional are allowed at run level, but only if there is a matching pair
- // if there is only one Repeat, EndRepeat, Conditional, EndConditional, then move to block level.
- // if there is a matching pair, then is OK.
- xDocRoot = (XElement)ForceBlockLevelAsAppropriate(xDocRoot, te);
- NormalizeTablesRepeatAndConditional(xDocRoot, te);
- // any EndRepeat, EndConditional that remain are orphans, so replace with an error
- ProcessOrphanEndRepeatEndConditional(xDocRoot, te);
- // do the actual content replacement
- xDocRoot = (XElement)ContentReplacementTransform(xDocRoot, data, te);
- xDoc.Elements().First().ReplaceWith(xDocRoot);
- part.PutXDocument();
- return;
- }
- private static XName[] s_MetaToForceToBlock = new XName[] {
- PA.Conditional,
- PA.EndConditional,
- PA.Repeat,
- PA.EndRepeat,
- PA.Table,
- };
- private static object ForceBlockLevelAsAppropriate(XNode node, TemplateError te)
- {
- XElement element = node as XElement;
- if (element != null)
- {
- if (element.Name == W.p)
- {
- var childMeta = element.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).ToList();
- if (childMeta.Count() == 1)
- {
- var child = childMeta.First();
- var otherTextInParagraph = element.Elements(W.r).Elements(W.t).Select(t => (string)t).StringConcatenate().Trim();
- if (otherTextInParagraph != "")
- {
- var newPara = new XElement(element);
- var newMeta = newPara.Elements().Where(n => s_MetaToForceToBlock.Contains(n.Name)).First();
- newMeta.ReplaceWith(CreateRunErrorMessage("Error: Unmatched metadata can't be in paragraph with other text", te));
- return newPara;
- }
- var meta = new XElement(child.Name,
- child.Attributes(),
- new XElement(W.p,
- element.Attributes(),
- element.Elements(W.pPr),
- child.Elements()));
- return meta;
- }
- var count = childMeta.Count();
- if (count % 2 == 0)
- {
- if (childMeta.Where(c => c.Name == PA.Repeat).Count() != childMeta.Where(c => c.Name == PA.EndRepeat).Count())
- return CreateContextErrorMessage(element, "Error: Mismatch Repeat / EndRepeat at run level", te);
- if (childMeta.Where(c => c.Name == PA.Conditional).Count() != childMeta.Where(c => c.Name == PA.EndConditional).Count())
- return CreateContextErrorMessage(element, "Error: Mismatch Conditional / EndConditional at run level", te);
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
- }
- else
- {
- return CreateContextErrorMessage(element, "Error: Invalid metadata at run level", te);
- }
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => ForceBlockLevelAsAppropriate(n, te)));
- }
- return node;
- }
- private static void ProcessOrphanEndRepeatEndConditional(XElement xDocRoot, TemplateError te)
- {
- foreach (var element in xDocRoot.Descendants(PA.EndRepeat).ToList())
- {
- var error = CreateContextErrorMessage(element, "Error: EndRepeat without matching Repeat", te);
- element.ReplaceWith(error);
- }
- foreach (var element in xDocRoot.Descendants(PA.EndConditional).ToList())
- {
- var error = CreateContextErrorMessage(element, "Error: EndConditional without matching Conditional", te);
- element.ReplaceWith(error);
- }
- }
- private static XElement RemoveGoBackBookmarks(XElement xElement)
- {
- var cloneXDoc = new XElement(xElement);
- while (true)
- {
- var bm = cloneXDoc.DescendantsAndSelf(W.bookmarkStart).FirstOrDefault(b => (string)b.Attribute(W.name) == "_GoBack");
- if (bm == null)
- break;
- var id = (string)bm.Attribute(W.id);
- var endBm = cloneXDoc.DescendantsAndSelf(W.bookmarkEnd).FirstOrDefault(b => (string)b.Attribute(W.id) == id);
- bm.Remove();
- endBm.Remove();
- }
- return cloneXDoc;
- }
- // this transform inverts content controls that surround W.tc elements. After transforming, the W.tc will contain
- // the content control, which contains the paragraph content of the cell.
- private static object NormalizeContentControlsInCells(XNode node)
- {
- XElement element = node as XElement;
- if (element != null)
- {
- if (element.Name == W.sdt && element.Parent.Name == W.tr)
- {
- var newCell = new XElement(W.tc,
- element.Elements(W.tc).Elements(W.tcPr),
- new XElement(W.sdt,
- element.Elements(W.sdtPr),
- element.Elements(W.sdtEndPr),
- new XElement(W.sdtContent,
- element.Elements(W.sdtContent).Elements(W.tc).Elements().Where(e => e.Name != W.tcPr))));
- return newCell;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => NormalizeContentControlsInCells(n)));
- }
- return node;
- }
- // The following method is written using tree modification, not RPFT, because it is easier to write in this fashion.
- // These types of operations are not as easy to write using RPFT.
- // Unless you are completely clear on the semantics of LINQ to XML DML, do not make modifications to this method.
- private static void NormalizeTablesRepeatAndConditional(XElement xDoc, TemplateError te)
- {
- var tables = xDoc.Descendants(PA.Table).ToList();
- foreach (var table in tables)
- {
- var followingElement = table.ElementsAfterSelf().Where(e => e.Name == W.tbl || e.Name == W.p).FirstOrDefault();
- if (followingElement == null || followingElement.Name != W.tbl)
- {
- table.ReplaceWith(CreateParaErrorMessage("Table metadata is not immediately followed by a table", te));
- continue;
- }
- // remove superflous paragraph from Table metadata
- table.RemoveNodes();
- // detach w:tbl from parent, and add to Table metadata
- followingElement.Remove();
- table.Add(followingElement);
- }
- int repeatDepth = 0;
- int conditionalDepth = 0;
- foreach (var metadata in xDoc.Descendants().Where(d =>
- d.Name == PA.Repeat ||
- d.Name == PA.Conditional ||
- d.Name == PA.EndRepeat ||
- d.Name == PA.EndConditional))
- {
- if (metadata.Name == PA.Repeat)
- {
- ++repeatDepth;
- metadata.Add(new XAttribute(PA.Depth, repeatDepth));
- continue;
- }
- if (metadata.Name == PA.EndRepeat)
- {
- metadata.Add(new XAttribute(PA.Depth, repeatDepth));
- --repeatDepth;
- continue;
- }
- if (metadata.Name == PA.Conditional)
- {
- ++conditionalDepth;
- metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
- continue;
- }
- if (metadata.Name == PA.EndConditional)
- {
- metadata.Add(new XAttribute(PA.Depth, conditionalDepth));
- --conditionalDepth;
- continue;
- }
- }
- while (true)
- {
- bool didReplace = false;
- foreach (var metadata in xDoc.Descendants().Where(d => (d.Name == PA.Repeat || d.Name == PA.Conditional) && d.Attribute(PA.Depth) != null).ToList())
- {
- var depth = (int)metadata.Attribute(PA.Depth);
- XName matchingEndName = null;
- if (metadata.Name == PA.Repeat)
- matchingEndName = PA.EndRepeat;
- else if (metadata.Name == PA.Conditional)
- matchingEndName = PA.EndConditional;
- if (matchingEndName == null)
- throw new OpenXmlPowerToolsException("Internal error");
- var matchingEnd = metadata.ElementsAfterSelf(matchingEndName).FirstOrDefault(end => { return (int)end.Attribute(PA.Depth) == depth; });
- if (matchingEnd == null)
- {
- metadata.ReplaceWith(CreateParaErrorMessage(string.Format("{0} does not have matching {1}", metadata.Name.LocalName, matchingEndName.LocalName), te));
- continue;
- }
- metadata.RemoveNodes();
- var contentBetween = metadata.ElementsAfterSelf().TakeWhile(after => after != matchingEnd).ToList();
- foreach (var item in contentBetween)
- item.Remove();
- contentBetween = contentBetween.Where(n => n.Name != W.bookmarkStart && n.Name != W.bookmarkEnd).ToList();
- metadata.Add(contentBetween);
- metadata.Attributes(PA.Depth).Remove();
- matchingEnd.Remove();
- didReplace = true;
- break;
- }
- if (!didReplace)
- break;
- }
- }
- private static List<string> s_AliasList = new List<string>()
- {
- "Content",
- "Table",
- "Repeat",
- "EndRepeat",
- "Conditional",
- "EndConditional",
- };
- private static object TransformToMetadata(XNode node, XElement data, TemplateError te)
- {
- XElement element = node as XElement;
- if (element != null)
- {
- if (element.Name == W.sdt)
- {
- var alias = (string)element.Elements(W.sdtPr).Elements(W.alias).Attributes(W.val).FirstOrDefault();
- if (alias == null || alias == "" || s_AliasList.Contains(alias))
- {
- var ccContents = element
- .DescendantsTrimmed(W.txbxContent)
- .Where(e => e.Name == W.t)
- .Select(t => (string)t)
- .StringConcatenate()
- .Trim()
- .Replace('“', '"')
- .Replace('”', '"');
- if (ccContents.StartsWith("<"))
- {
- XElement xml = TransformXmlTextToMetadata(te, ccContents);
- if (xml.Name == W.p || xml.Name == W.r) // this means there was an error processing the XML.
- {
- if (element.Parent.Name == W.p)
- return xml.Elements(W.r);
- return xml;
- }
- if (alias != null && xml.Name.LocalName != alias)
- {
- if (element.Parent.Name == W.p)
- return CreateRunErrorMessage("Error: Content control alias does not match metadata element name", te);
- else
- return CreateParaErrorMessage("Error: Content control alias does not match metadata element name", te);
- }
- xml.Add(element.Elements(W.sdtContent).Elements());
- return xml;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => TransformToMetadata(n, data, te)));
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => TransformToMetadata(n, data, te)));
- }
- if (element.Name == W.p)
- {
- var paraContents = element
- .DescendantsTrimmed(W.txbxContent)
- .Where(e => e.Name == W.t)
- .Select(t => (string)t)
- .StringConcatenate()
- .Trim();
- int occurances = paraContents.Select((c, i) => paraContents.Substring(i)).Count(sub => sub.StartsWith("<#"));
- if (paraContents.StartsWith("<#") && paraContents.EndsWith("#>") && occurances == 1)
- {
- var xmlText = paraContents.Substring(2, paraContents.Length - 4).Trim();
- XElement xml = TransformXmlTextToMetadata(te, xmlText);
- if (xml.Name == W.p || xml.Name == W.r)
- return xml;
- xml.Add(element);
- return xml;
- }
- if (paraContents.Contains("<#"))
- {
- List<RunReplacementInfo> runReplacementInfo = new List<RunReplacementInfo>();
- var thisGuid = Guid.NewGuid().ToString();
- var r = new Regex("<#.*?#>");
- XElement xml = null;
- OpenXmlRegex.Replace(new[] { element }, r, thisGuid, (para, match) =>
- {
- var matchString = match.Value.Trim();
- var xmlText = matchString.Substring(2, matchString.Length - 4).Trim().Replace('“', '"').Replace('”', '"');
- try
- {
- xml = XElement.Parse(xmlText);
- }
- catch (XmlException e)
- {
- RunReplacementInfo rri = new RunReplacementInfo()
- {
- Xml = null,
- XmlExceptionMessage = "XmlException: " + e.Message,
- SchemaValidationMessage = null,
- };
- runReplacementInfo.Add(rri);
- return true;
- }
- string schemaError = ValidatePerSchema(xml);
- if (schemaError != null)
- {
- RunReplacementInfo rri = new RunReplacementInfo()
- {
- Xml = null,
- XmlExceptionMessage = null,
- SchemaValidationMessage = "Schema Validation Error: " + schemaError,
- };
- runReplacementInfo.Add(rri);
- return true;
- }
- RunReplacementInfo rri2 = new RunReplacementInfo()
- {
- Xml = xml,
- XmlExceptionMessage = null,
- SchemaValidationMessage = null,
- };
- runReplacementInfo.Add(rri2);
- return true;
- }, false);
- var newPara = new XElement(element);
- foreach (var rri in runReplacementInfo)
- {
- var runToReplace = newPara.Descendants(W.r).FirstOrDefault(rn => rn.Value == thisGuid && rn.Parent.Name != PA.Content);
- if (runToReplace == null)
- throw new OpenXmlPowerToolsException("Internal error");
- if (rri.XmlExceptionMessage != null)
- runToReplace.ReplaceWith(CreateRunErrorMessage(rri.XmlExceptionMessage, te));
- else if (rri.SchemaValidationMessage != null)
- runToReplace.ReplaceWith(CreateRunErrorMessage(rri.SchemaValidationMessage, te));
- else
- {
- var newXml = new XElement(rri.Xml);
- newXml.Add(runToReplace);
- runToReplace.ReplaceWith(newXml);
- }
- }
- var coalescedParagraph = WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(newPara);
- return coalescedParagraph;
- }
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => TransformToMetadata(n, data, te)));
- }
- return node;
- }
- private static XElement TransformXmlTextToMetadata(TemplateError te, string xmlText)
- {
- XElement xml;
- try
- {
- xml = XElement.Parse(xmlText);
- }
- catch (XmlException e)
- {
- return CreateParaErrorMessage("XmlException: " + e.Message, te);
- }
- string schemaError = ValidatePerSchema(xml);
- if (schemaError != null)
- return CreateParaErrorMessage("Schema Validation Error: " + schemaError, te);
- return xml;
- }
- private class RunReplacementInfo
- {
- public XElement Xml;
- public string XmlExceptionMessage;
- public string SchemaValidationMessage;
- }
- private static string ValidatePerSchema(XElement element)
- {
- if (s_PASchemaSets == null)
- {
- s_PASchemaSets = new Dictionary<XName, PASchemaSet>()
- {
- {
- PA.Content,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='Content'>
- <xs:complexType>
- <xs:attribute name='Select' type='xs:string' use='required' />
- <xs:attribute name='Optional' type='xs:boolean' use='optional' />
- </xs:complexType>
- </xs:element>
- </xs:schema>",
- }
- },
- {
- PA.Table,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='Table'>
- <xs:complexType>
- <xs:attribute name='Select' type='xs:string' use='required' />
- </xs:complexType>
- </xs:element>
- </xs:schema>",
- }
- },
- {
- PA.Repeat,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='Repeat'>
- <xs:complexType>
- <xs:attribute name='Select' type='xs:string' use='required' />
- <xs:attribute name='Optional' type='xs:boolean' use='optional' />
- </xs:complexType>
- </xs:element>
- </xs:schema>",
- }
- },
- {
- PA.EndRepeat,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='EndRepeat' />
- </xs:schema>",
- }
- },
- {
- PA.Conditional,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='Conditional'>
- <xs:complexType>
- <xs:attribute name='Select' type='xs:string' use='required' />
- <xs:attribute name='Match' type='xs:string' use='optional' />
- <xs:attribute name='NotMatch' type='xs:string' use='optional' />
- </xs:complexType>
- </xs:element>
- </xs:schema>",
- }
- },
- {
- PA.EndConditional,
- new PASchemaSet() {
- XsdMarkup =
- @"<xs:schema attributeFormDefault='unqualified' elementFormDefault='qualified' xmlns:xs='http://www.w3.org/2001/XMLSchema'>
- <xs:element name='EndConditional' />
- </xs:schema>",
- }
- },
- };
- foreach (var item in s_PASchemaSets)
- {
- var itemPAss = item.Value;
- XmlSchemaSet schemas = new XmlSchemaSet();
- schemas.Add("", XmlReader.Create(new StringReader(itemPAss.XsdMarkup)));
- itemPAss.SchemaSet = schemas;
- }
- }
- if (!s_PASchemaSets.ContainsKey(element.Name))
- {
- return string.Format("Invalid XML: {0} is not a valid element", element.Name.LocalName);
- }
- var paSchemaSet = s_PASchemaSets[element.Name];
- XDocument d = new XDocument(element);
- string message = null;
- d.Validate(paSchemaSet.SchemaSet, (sender, e) =>
- {
- if (message == null)
- message = e.Message;
- }, true);
- if (message != null)
- return message;
- return null;
- }
- private class PA
- {
- public static XName Content = "Content";
- public static XName Table = "Table";
- public static XName Repeat = "Repeat";
- public static XName EndRepeat = "EndRepeat";
- public static XName Conditional = "Conditional";
- public static XName EndConditional = "EndConditional";
- public static XName Select = "Select";
- public static XName Optional = "Optional";
- public static XName Match = "Match";
- public static XName NotMatch = "NotMatch";
- public static XName Depth = "Depth";
- }
- private class PASchemaSet
- {
- public string XsdMarkup;
- public XmlSchemaSet SchemaSet;
- }
- private static Dictionary<XName, PASchemaSet> s_PASchemaSets = null;
- private class TemplateError
- {
- public bool HasError = false;
- }
- static object ContentReplacementTransform(XNode node, XElement data, TemplateError templateError)
- {
- XElement element = node as XElement;
- if (element != null)
- {
- if (element.Name == PA.Content)
- {
- XElement para = element.Descendants(W.p).FirstOrDefault();
- XElement run = element.Descendants(W.r).FirstOrDefault();
- var xPath = (string) element.Attribute(PA.Select);
- var optionalString = (string) element.Attribute(PA.Optional);
- bool optional = (optionalString != null && optionalString.ToLower() == "true");
- string newValue;
- try
- {
- newValue = EvaluateXPathToString(data, xPath, optional);
- }
- catch (XPathException e)
- {
- return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
- }
- if (para != null)
- {
- XElement p = new XElement(W.p, para.Elements(W.pPr));
- foreach(string line in newValue.Split('\n'))
- {
- p.Add(new XElement(W.r,
- para.Elements(W.r).Elements(W.rPr).FirstOrDefault(),
- (p.Elements().Count() > 1) ? new XElement(W.br) : null,
- new XElement(W.t, line)));
- }
- return p;
- }
- else
- {
- List<XElement> list = new List<XElement>();
- foreach(string line in newValue.Split('\n'))
- {
- list.Add(new XElement(W.r,
- run.Elements().Where(e => e.Name != W.t),
- (list.Count > 0) ? new XElement(W.br) : null,
- new XElement(W.t, line)));
- }
- return list;
- }
- }
- if (element.Name == PA.Repeat)
- {
- string selector = (string)element.Attribute(PA.Select);
- var optionalString = (string)element.Attribute(PA.Optional);
- bool optional = (optionalString != null && optionalString.ToLower() == "true");
- IEnumerable<XElement> repeatingData;
- try
- {
- repeatingData = data.XPathSelectElements(selector);
- }
- catch (XPathException e)
- {
- return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
- }
- if (!repeatingData.Any())
- {
- if (optional)
- {
- return null;
- //XElement para = element.Descendants(W.p).FirstOrDefault();
- //if (para != null)
- // return new XElement(W.p, new XElement(W.r));
- //else
- // return new XElement(W.r);
- }
- return CreateContextErrorMessage(element, "Repeat: Select returned no data", templateError);
- }
- var newContent = repeatingData.Select(d =>
- {
- var content = element
- .Elements()
- .Select(e => ContentReplacementTransform(e, d, templateError))
- .ToList();
- return content;
- })
- .ToList();
- return newContent;
- }
- if (element.Name == PA.Table)
- {
- IEnumerable<XElement> tableData;
- try
- {
- tableData = data.XPathSelectElements((string)element.Attribute(PA.Select));
- }
- catch (XPathException e)
- {
- return CreateContextErrorMessage(element, "XPathException: " + e.Message, templateError);
- }
- if (tableData.Count() == 0)
- return CreateContextErrorMessage(element, "Table Select returned no data", templateError);
- XElement table = element.Element(W.tbl);
- XElement protoRow = table.Elements(W.tr).Skip(1).FirstOrDefault();
- var footerRowsBeforeTransform = table
- .Elements(W.tr)
- .Skip(2)
- .ToList();
- var footerRows = footerRowsBeforeTransform
- .Select(x => ContentReplacementTransform(x, data, templateError))
- .ToList();
- if (protoRow == null)
- return CreateContextErrorMessage(element, string.Format("Table does not contain a prototype row"), templateError);
- protoRow.Descendants(W.bookmarkStart).Remove();
- protoRow.Descendants(W.bookmarkEnd).Remove();
- XElement newTable = new XElement(W.tbl,
- table.Elements().Where(e => e.Name != W.tr),
- table.Elements(W.tr).FirstOrDefault(),
- tableData.Select(d =>
- new XElement(W.tr,
- protoRow.Elements().Where(r => r.Name != W.tc),
- protoRow.Elements(W.tc)
- .Select(tc =>
- {
- XElement paragraph = tc.Elements(W.p).FirstOrDefault();
- XElement cellRun = paragraph.Elements(W.r).FirstOrDefault();
- string xPath = paragraph.Value;
- string newValue = null;
- try
- {
- newValue = EvaluateXPathToString(d, xPath, false);
- }
- catch (XPathException e)
- {
- XElement errorCell = new XElement(W.tc,
- tc.Elements().Where(z => z.Name != W.p),
- new XElement(W.p,
- paragraph.Element(W.pPr),
- CreateRunErrorMessage(e.Message, templateError)));
- return errorCell;
- }
- XElement newCell = new XElement(W.tc,
- tc.Elements().Where(z => z.Name != W.p),
- new XElement(W.p,
- paragraph.Element(W.pPr),
- new XElement(W.r,
- cellRun != null ? cellRun.Element(W.rPr) : new XElement(W.rPr), //if the cell was empty there is no cellrun
- new XElement(W.t, newValue))));
- return newCell;
- }))),
- footerRows
- );
- return newTable;
- }
- if (element.Name == PA.Conditional)
- {
- string xPath = (string)element.Attribute(PA.Select);
- var match = (string)element.Attribute(PA.Match);
- var notMatch = (string)element.Attribute(PA.NotMatch);
- if (match == null && notMatch == null)
- return CreateContextErrorMessage(element, "Conditional: Must specify either Match or NotMatch", templateError);
- if (match != null && notMatch != null)
- return CreateContextErrorMessage(element, "Conditional: Cannot specify both Match and NotMatch", templateError);
- string testValue = null;
-
- try
- {
- testValue = EvaluateXPathToString(data, xPath, false);
- }
- catch (XPathException e)
- {
- return CreateContextErrorMessage(element, e.Message, templateError);
- }
-
- if ((match != null && testValue == match) || (notMatch != null && testValue != notMatch))
- {
- var content = element.Elements().Select(e => ContentReplacementTransform(e, data, templateError));
- return content;
- }
- return null;
- }
- return new XElement(element.Name,
- element.Attributes(),
- element.Nodes().Select(n => ContentReplacementTransform(n, data, templateError)));
- }
- return node;
- }
- private static object CreateContextErrorMessage(XElement element, string errorMessage, TemplateError templateError)
- {
- XElement para = element.Descendants(W.p).FirstOrDefault();
- XElement run = element.Descendants(W.r).FirstOrDefault();
- var errorRun = CreateRunErrorMessage(errorMessage, templateError);
- if (para != null)
- return new XElement(W.p, errorRun);
- else
- return errorRun;
- }
- private static XElement CreateRunErrorMessage(string errorMessage, TemplateError templateError)
- {
- templateError.HasError = true;
- var errorRun = new XElement(W.r,
- new XElement(W.rPr,
- new XElement(W.color, new XAttribute(W.val, "FF0000")),
- new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
- new XElement(W.t, errorMessage));
- return errorRun;
- }
- private static XElement CreateParaErrorMessage(string errorMessage, TemplateError templateError)
- {
- templateError.HasError = true;
- var errorPara = new XElement(W.p,
- new XElement(W.r,
- new XElement(W.rPr,
- new XElement(W.color, new XAttribute(W.val, "FF0000")),
- new XElement(W.highlight, new XAttribute(W.val, "yellow"))),
- new XElement(W.t, errorMessage)));
- return errorPara;
- }
- private static string EvaluateXPathToString(XElement element, string xPath, bool optional )
- {
- object xPathSelectResult;
- try
- {
- //support some cells in the table may not have an xpath expression.
- if (String.IsNullOrWhiteSpace(xPath)) return String.Empty;
-
- xPathSelectResult = element.XPathEvaluate(xPath);
- }
- catch (XPathException e)
- {
- throw new XPathException("XPathException: " + e.Message, e);
- }
- if ((xPathSelectResult is IEnumerable) && !(xPathSelectResult is string))
- {
- var selectedData = ((IEnumerable) xPathSelectResult).Cast<XObject>();
- if (!selectedData.Any())
- {
- if (optional) return string.Empty;
- throw new XPathException(string.Format("XPath expression ({0}) returned no results", xPath));
- }
- if (selectedData.Count() > 1)
- {
- throw new XPathException(string.Format("XPath expression ({0}) returned more than one node", xPath));
- }
- XObject selectedDatum = selectedData.First();
-
- if (selectedDatum is XElement) return ((XElement) selectedDatum).Value;
- if (selectedDatum is XAttribute) return ((XAttribute) selectedDatum).Value;
- }
- return xPathSelectResult.ToString();
- }
- }
- }
|