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 void WriteError(TemplateError error) { ConsoleColor color = Console.ForegroundColor; Console.ForegroundColor = error.Warning ? ConsoleColor.Yellow : ConsoleColor.Red; Console.Error.Write(FormatError(error)); Console.ForegroundColor = color; }
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 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); } }
internal void AddError(ref TemplateOutput templateOutput, Exception ex, Enums.LogLevel logLevel) { while (ex != null) { TemplateError te = new TemplateError() { ErrorLevel = logLevel, Message = ex.Message, StackTrace = ex.StackTrace, TemplateIdentity = ProcessModel.TemplateIdentity.Copy() }; templateOutput.Errors.Add(te); ex = ex.InnerException; } }
private static void ProcessTemplatePart(XElement data, TemplateError te, OpenXmlPart part) { XDocument xDoc = part.GetXDocument(); XElement newRootElementWithMetadata = (XElement)TransformToMetadata(xDoc.Root, data, te); NormalizeTablesRepeatAndConditional(newRootElementWithMetadata, te); XElement newRootElement = newRootElementWithMetadata; // do the actual content replacement newRootElement = (XElement)ContentReplacementTransform(newRootElement, data, te); xDoc.Elements().First().ReplaceWith(newRootElement); part.PutXDocument(); return; }
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); }
public static CachedTemplate Parse(string input, bool bypass = false) { Guard.NotNull(input); CachedTemplate result; if (!bypass) { LockObject.EnterWriteLock(); try { if (Cache.TryGetValue(input, out result)) { return(result); } } finally { LockObject.ExitWriteLock(); } } Parser.TryParse(input, out var template, out var errorMessage); var error = TemplateError.Parse(errorMessage); result = new CachedTemplate(template, error); if (!bypass) { LockObject.EnterWriteLock(); try { Cache.Set(input, result); } finally { LockObject.ExitWriteLock(); } } return(result); }
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; } }
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 void GenerateError(TemplateError error) { GenerateError(error.Warning, error.Message, error.Line, error.Column); }
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); }
// 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 object?ContentReplacementTransform(XNode node, XElement data, TemplateError templateError) { if (node is XElement element) { if (element.Name == PA.Content) { var para = element.Descendants(W.p).FirstOrDefault(); var run = element.Descendants(W.r).FirstOrDefault(); var xPath = (string)element.Attribute(PA.Select); var optionalString = (string)element.Attribute(PA.Optional); var 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) { var p = new XElement(W.p, para.Elements(W.pPr)); foreach (var 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 { var list = new List <XElement>(); foreach (var 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) { var selector = (string)element.Attribute(PA.Select); var optionalString = (string)element.Attribute(PA.Optional); var 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); } 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.Any()) { return(CreateContextErrorMessage(element, "Table Select returned no data", templateError)); } var table = element.Element(W.tbl); var 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(); var 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 => { var paragraph = tc.Elements(W.p).FirstOrDefault(); var cellRun = paragraph.Elements(W.r).FirstOrDefault(); var xPath = paragraph.Value; string?newValue = null; try { newValue = EvaluateXPathToString(d, xPath, false); } catch (XPathException e) { var 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); } var 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) { var 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); }
public void Should_parse_without_line_numbers() { var result = TemplateError.Parse("Error Message"); Assert.Equal(new TemplateError("Error Message."), result); }
private static string FormatError(TemplateError error) { return($"{(error.Warning ? "Warning" : "Error")} on line {error.Line}, column {error.Column}:" + $"{Environment.NewLine}{error.Message}{Environment.NewLine}{Environment.NewLine}"); }
public void Should_return_null_on_parse_when_message_is_empty() { var result = TemplateError.Parse(string.Empty); Assert.Null(result); }
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.Element(W.p); XElement run = element.Element(W.r); IEnumerable<XObject> selectedData; string xPath = (string)element.Attribute(PA.Select); try { selectedData = ((IEnumerable)data.XPathEvaluate(xPath)).Cast<XObject>(); } catch (XPathException e) { var errorRun = CreateRunErrorMessage("XPathException: " + e.Message, templateError); if (para != null) return new XElement(W.p, errorRun); else return errorRun; } if (!selectedData.Any()) { var optionalString = (string)element.Attribute(PA.Optional); if (optionalString != null && optionalString.ToLower() == "true") { return null; } else { var errorRun = CreateRunErrorMessage(string.Format("Content XPath expression ({0}) returned no results", xPath), templateError); if (para != null) return new XElement(W.p, errorRun); else return errorRun; } } else if (selectedData.Count() > 1) { var errorRun = CreateRunErrorMessage(string.Format("Content XPath expression ({0}) returned more than one node", xPath), templateError); if (para != null) return new XElement(W.p, errorRun); else return errorRun; } else { string newValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) newValue = ((XElement)selectedDatum).Value; else if (selectedDatum is XAttribute) newValue = ((XAttribute)selectedDatum).Value; if (para != null) { return new XElement(W.p, para.Elements(W.pPr), new XElement(W.r, para.Elements(W.r).Elements(W.rPr).FirstOrDefault(), new XElement(W.t, newValue))); } else { return new XElement(W.r, run.Elements().Where(e => e.Name != W.t), new XElement(W.t, newValue)); } } } if (element.Name == PA.Repeat) { string selector = (string)element.Attribute(PA.Select); IEnumerable<XElement> repeatingData; try { repeatingData = data.XPathSelectElements(selector); } catch (XPathException e) { return CreateParaErrorMessage("XPathException: " + e.Message, templateError); } if (!repeatingData.Any()) return CreateParaErrorMessage("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 CreateParaErrorMessage("XPathException: " + e.Message, templateError); } if (tableData.Count() == 0) return CreateParaErrorMessage("Table Select returned no data", templateError); XElement table = element.Element(W.tbl); XElement protoRow = table.Elements(W.tr).Skip(1).FirstOrDefault(); if (protoRow == null) return CreateParaErrorMessage(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; IEnumerable<XObject> selectedData; try { selectedData = ((IEnumerable)d.XPathEvaluate(xPath)).Cast<XObject>(); } 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("XPathException: " + e.Message, templateError))); return errorCell; } if (!selectedData.Any()) { var errorRun = CreateRunErrorMessage(string.Format("XPath expression ({0}) returned no results", xPath), templateError); XElement errorCell = new XElement(W.tc, tc.Elements().Where(z => z.Name != W.p), new XElement(W.p, paragraph.Element(W.pPr), errorRun)); return errorCell; } else if (selectedData.Count() > 1) { var errorRun = CreateRunErrorMessage(string.Format("XPath expression ({0}) returned more than one node", xPath), templateError); XElement errorCell = new XElement(W.tc, tc.Elements().Where(z => z.Name != W.p), new XElement(W.p, paragraph.Element(W.pPr), errorRun)); return errorCell; } else { string newValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) newValue = ((XElement)selectedDatum).Value; else if (selectedDatum is XAttribute) newValue = ((XAttribute)selectedDatum).Value; 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.Element(W.rPr), new XElement(W.t, newValue)))); return newCell; } })))); return newTable; } if (element.Name == PA.Conditional) { IEnumerable<XObject> selectedData; string xPath = (string)element.Attribute(PA.Select); try { selectedData = ((IEnumerable)data.XPathEvaluate(xPath)).Cast<XObject>(); } catch (XPathException e) { return CreateParaErrorMessage("XPathException: " + e.Message, templateError); } if (!selectedData.Any()) { return CreateParaErrorMessage(string.Format("Conditional XPath expression ({0}) returned no results", xPath), templateError); } else if (selectedData.Count() > 1) { return CreateParaErrorMessage(string.Format("Conditional XPath expression ({0}) returned more than one node", xPath), templateError); } var match = (string)element.Attribute(PA.Match); string testValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) testValue = ((XElement)selectedDatum).Value; else if (selectedDatum is XAttribute) testValue = ((XAttribute)selectedDatum).Value; if (testValue == match) { var content = element.Elements().Select(e => ContentReplacementTransform(e, data, templateError)); return content; } else return null; } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => ContentReplacementTransform(n, data, templateError))); } return node; }
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.Element(W.p); XElement run = element.Element(W.r); IEnumerable <XObject> selectedData; string xPath = (string)element.Attribute(PA.Select); try { selectedData = ((IEnumerable)data.XPathEvaluate(xPath)).Cast <XObject>(); } catch (XPathException e) { var errorRun = CreateRunErrorMessage("XPathException: " + e.Message, templateError); if (para != null) { return(new XElement(W.p, errorRun)); } else { return(errorRun); } } if (!selectedData.Any()) { var optionalString = (string)element.Attribute(PA.Optional); if (optionalString != null && optionalString.ToLower() == "true") { return(null); } else { var errorRun = CreateRunErrorMessage(string.Format("Content XPath expression ({0}) returned no results", xPath), templateError); if (para != null) { return(new XElement(W.p, errorRun)); } else { return(errorRun); } } } else if (selectedData.Count() > 1) { var errorRun = CreateRunErrorMessage(string.Format("Content XPath expression ({0}) returned more than one node", xPath), templateError); if (para != null) { return(new XElement(W.p, errorRun)); } else { return(errorRun); } } else { string newValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) { newValue = ((XElement)selectedDatum).Value; } else if (selectedDatum is XAttribute) { newValue = ((XAttribute)selectedDatum).Value; } if (para != null) { return(new XElement(W.p, para.Elements(W.pPr), new XElement(W.r, para.Elements(W.r).Elements(W.rPr).FirstOrDefault(), new XElement(W.t, newValue)))); } else { return(new XElement(W.r, run.Elements().Where(e => e.Name != W.t), new XElement(W.t, newValue))); } } } if (element.Name == PA.Repeat) { string selector = (string)element.Attribute(PA.Select); IEnumerable <XElement> repeatingData; try { repeatingData = data.XPathSelectElements(selector); } catch (XPathException e) { return(CreateParaErrorMessage("XPathException: " + e.Message, templateError)); } if (!repeatingData.Any()) { return(CreateParaErrorMessage("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(CreateParaErrorMessage("XPathException: " + e.Message, templateError)); } if (tableData.Count() == 0) { return(CreateParaErrorMessage("Table Select returned no data", templateError)); } XElement table = element.Element(W.tbl); XElement protoRow = table.Elements(W.tr).Skip(1).FirstOrDefault(); if (protoRow == null) { return(CreateParaErrorMessage(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; IEnumerable <XObject> selectedData; try { selectedData = ((IEnumerable)d.XPathEvaluate(xPath)).Cast <XObject>(); } 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("XPathException: " + e.Message, templateError))); return(errorCell); } if (!selectedData.Any()) { var errorRun = CreateRunErrorMessage(string.Format("XPath expression ({0}) returned no results", xPath), templateError); XElement errorCell = new XElement(W.tc, tc.Elements().Where(z => z.Name != W.p), new XElement(W.p, paragraph.Element(W.pPr), errorRun)); return(errorCell); } else if (selectedData.Count() > 1) { var errorRun = CreateRunErrorMessage(string.Format("XPath expression ({0}) returned more than one node", xPath), templateError); XElement errorCell = new XElement(W.tc, tc.Elements().Where(z => z.Name != W.p), new XElement(W.p, paragraph.Element(W.pPr), errorRun)); return(errorCell); } else { string newValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) { newValue = ((XElement)selectedDatum).Value; } else if (selectedDatum is XAttribute) { newValue = ((XAttribute)selectedDatum).Value; } 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.Element(W.rPr), new XElement(W.t, newValue)))); return(newCell); } })))); return(newTable); } if (element.Name == PA.Conditional) { IEnumerable <XObject> selectedData; string xPath = (string)element.Attribute(PA.Select); try { selectedData = ((IEnumerable)data.XPathEvaluate(xPath)).Cast <XObject>(); } catch (XPathException e) { return(CreateParaErrorMessage("XPathException: " + e.Message, templateError)); } if (!selectedData.Any()) { return(CreateParaErrorMessage(string.Format("Conditional XPath expression ({0}) returned no results", xPath), templateError)); } else if (selectedData.Count() > 1) { return(CreateParaErrorMessage(string.Format("Conditional XPath expression ({0}) returned more than one node", xPath), templateError)); } var match = (string)element.Attribute(PA.Match); string testValue = null; XObject selectedDatum = selectedData.First(); if (selectedDatum is XElement) { testValue = ((XElement)selectedDatum).Value; } else if (selectedDatum is XAttribute) { testValue = ((XAttribute)selectedDatum).Value; } if (testValue == match) { var content = element.Elements().Select(e => ContentReplacementTransform(e, data, templateError)); return(content); } else { return(null); } } return(new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => ContentReplacementTransform(n, data, templateError)))); } return(node); }
public void Should_parse_with_open_line_numbers() { var result = TemplateError.Parse("Error Message at (10:20"); Assert.Equal(new TemplateError("Error Message at (10:20."), result); }
public void Should_parse_with_invalid_line_numbers() { var result = TemplateError.Parse("Error Message at (Test Value)"); Assert.Equal(new TemplateError("Error Message."), result); }
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; bool cellIsOptional = false; // There is probably a much better way of doing this, // similar to the ContentReplacementTransform used for the footer // but it means you probably couldn't use the simple xpath text // and you would need to always use <Content/> tag in each cell! TableCellContent(paragraph.Value, out xPath, out cellIsOptional); string newValue = null; try { newValue = EvaluateXPathToString(d, xPath, cellIsOptional); } 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 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 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); } }
// 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(); contentBetween.DescendantsAndSelf(W.bookmarkStart).Remove(); contentBetween.DescendantsAndSelf(W.bookmarkEnd).Remove(); foreach (var item in contentBetween) item.Remove(); metadata.Add(contentBetween); metadata.Attributes(PA.Depth).Remove(); matchingEnd.Remove(); didReplace = true; break; } if (!didReplace) break; } }
private static object TransformToMetadata(XNode node, XElement data, TemplateError te) { XElement element = node as XElement; if (element != null) { 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; 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); 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; }
public void Should_extract_line_number_only() { var result = TemplateError.Parse("Error at (10)"); Assert.Equal(new TemplateError("Error.", 10), result); }