public void ExecuteBlock_WithContextValues_ResolvesContextValuesCorrectly() { var input = @" {% execute type:'class' %} using Rock; using Rock.Data; using Rock.Model; public class MyScript { public string Execute() { using(RockContext rockContext = new RockContext()) { var person = new PersonService(rockContext).Get(""{{ Person | Property: 'Guid' }}"".AsGuid()); return person.FullName; } } } {% endexecute %} "; var expectedOutput = @"Ted Decker"; var context = new LavaDataDictionary(); context.Add("Person", TestHelper.GetTestPersonTedDecker()); var options = new LavaTestRenderOptions() { EnabledCommands = "execute", MergeFields = context }; TestHelper.AssertTemplateOutput(expectedOutput, input, options); }
/// <summary> /// Gets the default lava debug information. /// </summary> /// <param name="mergeObjectList">The merge object list.</param> /// <param name="globalMergeFields">The global merge fields.</param> /// <param name="preText">The pre text.</param> /// <returns></returns> public static string GetDefaultLavaDebugInfo(List <object> mergeObjectList, Dictionary <string, object> globalMergeFields, string preText = null) { var debugMergeFields = new LavaDataDictionary(); if (mergeObjectList.Count >= 1) { debugMergeFields.Add("Row", mergeObjectList[0]); } foreach (var mergeField in globalMergeFields) { debugMergeFields.Add(mergeField.Key, mergeField.Value); } return(debugMergeFields.lavaDebugInfo(null, preText)); }
public void FluidEqual_EnumOperands_PerformsEnumComparison(string expression, bool expectedResult) { var values = new LavaDataDictionary(); values.Add("DayOfWeekMonday", DayOfWeek.Monday); var template = "{% if " + expression + " %}True{% else %}False{% endif %}"; TestHelper.AssertTemplateOutput(expectedResult.ToString(), template, values, ignoreWhitespace: true); }
/// <summary> /// Creates the document. /// </summary> /// <param name="mergeTemplate">The merge template.</param> /// <param name="mergeObjectList">The merge object list.</param> /// <param name="globalMergeFields">The global merge fields.</param> /// <returns></returns> public override BinaryFile CreateDocument(MergeTemplate mergeTemplate, List <object> mergeObjectList, Dictionary <string, object> globalMergeFields) { this.Exceptions = new List <Exception>(); BinaryFile outputBinaryFile = null; var rockContext = new RockContext(); var binaryFileService = new BinaryFileService(rockContext); var templateBinaryFile = binaryFileService.Get(mergeTemplate.TemplateBinaryFileId); if (templateBinaryFile == null) { return(null); } // Start by creating a new document with the contents of the Template (so that Styles, etc get included) XDocument sourceTemplateDocX; // NOTE: On using multiple IDisposable, see https://stackoverflow.com/a/12603126/1755417 and https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement using (MemoryStream sourceTemplateStream = new MemoryStream(), outputDocStream = new MemoryStream()) { templateBinaryFile.ContentStream.CopyTo(outputDocStream); outputDocStream.Seek(0, SeekOrigin.Begin); // now that we have the outputdoc started, simplify the sourceTemplate templateBinaryFile.ContentStream.CopyTo(sourceTemplateStream); sourceTemplateStream.Seek(0, SeekOrigin.Begin); var simplifiedDoc = WordprocessingDocument.Open(sourceTemplateStream, true); MarkupSimplifier.SimplifyMarkup(simplifiedDoc, this.simplifyMarkupSettingsAll); //// simplify any nodes that have Lava in it that might not have been caught by the MarkupSimplifier //// MarkupSimplifier only merges superfluous runs that are children of a paragraph sourceTemplateDocX = simplifiedDoc.MainDocumentPart.GetXDocument(); OpenXmlRegex.Match( sourceTemplateDocX.Elements(), this.lavaRegEx, (x, m) => { foreach (var nonParagraphRunsParent in x.DescendantNodes().OfType <XElement>().Where(a => a.Parent != null && a.Name != null) .Where(a => (a.Name.LocalName == "r")).Select(a => a.Parent).Distinct().ToList()) { if (lavaRegEx.IsMatch(nonParagraphRunsParent.Value)) { var tempParent = XElement.Parse(new Paragraph().OuterXml); tempParent.Add(nonParagraphRunsParent.Nodes()); tempParent = MarkupSimplifier.MergeAdjacentSuperfluousRuns(tempParent); nonParagraphRunsParent.ReplaceNodes(tempParent.Nodes()); } } }); XElement lastLavaNode = sourceTemplateDocX.DescendantNodes().OfType <XElement>().LastOrDefault(a => lavaRegEx.IsMatch(a.Value)); // ensure there is a { Next } indicator after the last lava node in the template if (lastLavaNode != null) { var nextRecordMatch = nextRecordRegEx.Match(lastLavaNode.Value); if (nextRecordMatch == null || !nextRecordMatch.Success) { // if the last lava node doesn't have a { next }, append to the end lastLavaNode.Value += " {% next %} "; } else { if (!lastLavaNode.Value.EndsWith(nextRecordMatch.Value)) { // if the last lava node does have a { next }, but there is stuff after it, add it (just in case) lastLavaNode.Value += " {% next %} "; } } } bool?allSameParent = null; using (WordprocessingDocument outputDoc = WordprocessingDocument.Open(outputDocStream, true)) { var xdoc = outputDoc.MainDocumentPart.GetXDocument(); var outputBodyNode = xdoc.DescendantNodes().OfType <XElement>().FirstOrDefault(a => a.Name.LocalName.Equals("body")); outputBodyNode.RemoveNodes(); int recordIndex = 0; int?lastRecordIndex = null; int recordCount = mergeObjectList.Count(); while (recordIndex < recordCount) { if (lastRecordIndex.HasValue && lastRecordIndex == recordIndex) { // something went wrong, so throw to avoid spinning infinitely throw new Exception("Unexpected unchanged recordIndex"); } lastRecordIndex = recordIndex; using (var tempMergeTemplateStream = new MemoryStream()) { sourceTemplateStream.Position = 0; sourceTemplateStream.CopyTo(tempMergeTemplateStream); tempMergeTemplateStream.Position = 0; var tempMergeTemplateX = new XDocument(sourceTemplateDocX); var tempMergeTemplateBodyNode = tempMergeTemplateX.DescendantNodes().OfType <XElement>().FirstOrDefault(a => a.Name.LocalName.Equals("body")); // find all the Nodes that have a {% next %}. List <XElement> nextIndicatorNodes = new List <XElement>(); OpenXmlRegex.Match( tempMergeTemplateX.Elements(), this.nextRecordRegEx, (x, m) => { nextIndicatorNodes.Add(x); }); allSameParent = allSameParent ?? nextIndicatorNodes.Count > 1 && nextIndicatorNodes.Select(a => a.Parent).Distinct().Count() == 1; List <XContainer> recordContainerNodes = new List <XContainer>(); foreach (var nextIndicatorNodeParent in nextIndicatorNodes.Select(a => a.Parent).Where(a => a != null)) { XContainer recordContainerNode = nextIndicatorNodeParent; if (!allSameParent.Value) { // go up the parent nodes until we have more than one "Next" descendent so that we know what to consider our record container while (recordContainerNode.Parent != null) { if (this.nextRecordRegEx.Matches(recordContainerNode.Parent.Value).Count == 1) { // still just the one "next" indicator, so go out another parent recordContainerNode = recordContainerNode.Parent; } else { // we went too far up the parents and found multiple "next" children, so use this node as the recordContainerNode break; } } } if (!recordContainerNodes.Contains(recordContainerNode)) { recordContainerNodes.Add(recordContainerNode); } } foreach (var recordContainerNode in recordContainerNodes) { //// loop thru each of the recordContainerNodes //// If we have more records than nodes, we'll jump out to the outer "while" and append another template and keep going XContainer mergedXRecord; var recordContainerNodeXml = recordContainerNode.ToString(SaveOptions.DisableFormatting | SaveOptions.OmitDuplicateNamespaces).ReplaceWordChars(); if (recordIndex >= recordCount) { // out of records, so clear out any remaining template nodes that haven't been merged string xml = recordContainerNodeXml; mergedXRecord = XElement.Parse(xml) as XContainer; OpenXmlRegex.Replace(mergedXRecord.Nodes().OfType <XElement>(), this.regExDot, string.Empty, (a, b) => { return(true); }); recordIndex++; } else { //// just in case they have shared parent node, or if there is trailing {{ next }} after the last lava //// on the page, split the XML for each record and reassemble it when done List <string> xmlChunks = this.nextRecordRegEx.Split(recordContainerNodeXml).ToList(); string mergedXml = string.Empty; foreach (var xml in xmlChunks) { bool incRecordIndex = true; if (lavaRegEx.IsMatch(xml)) { if (recordIndex < recordCount) { try { var wordMergeObjects = new LavaDataDictionary(); wordMergeObjects.Add("Row", mergeObjectList[recordIndex]); foreach (var field in globalMergeFields) { wordMergeObjects.Add(field.Key, field.Value); } var resolvedXml = xml.ResolveMergeFields(wordMergeObjects, true, true); mergedXml += resolvedXml; if (resolvedXml == xml) { // there weren't any MergeFields after all, so don't move to the next record incRecordIndex = false; } } catch (Exception ex) { // if ResolveMergeFields failed, log the exception, then just return the orig xml this.Exceptions.Add(ex); mergedXml += xml; } if (incRecordIndex) { recordIndex++; } } else { // out of records, so put a special '{% next_empty %}' that we can use to clear up unmerged parts of the template mergedXml += " {% next_empty %} " + xml; } } else { mergedXml += xml; } } mergedXRecord = XElement.Parse(mergedXml) as XContainer; } // remove the orig nodes and replace with merged nodes recordContainerNode.RemoveNodes(); recordContainerNode.Add(mergedXRecord.Nodes().OfType <XElement>()); var mergedRecordContainer = XElement.Parse(recordContainerNode.ToString(SaveOptions.DisableFormatting)); if (recordContainerNode.Parent != null) { // the recordContainerNode is some child/descendent of <body> recordContainerNode.ReplaceWith(mergedRecordContainer); } else { // the recordContainerNode is the <body> recordContainerNode.RemoveNodes(); recordContainerNode.Add(mergedRecordContainer.Nodes()); if (recordIndex < recordCount) { // add page break var pageBreakXml = new Paragraph(new Run(new Break() { Type = BreakValues.Page })).OuterXml; var pageBreak = XElement.Parse(pageBreakXml, LoadOptions.None); var lastParagraph = recordContainerNode.Nodes().OfType <XElement>().Where(a => a.Name.LocalName == "p").LastOrDefault(); if (lastParagraph != null) { lastParagraph.AddAfterSelf(pageBreak); // Add page formatting for the page before the page break. var lastSectPr = recordContainerNode.Nodes().OfType <XElement>().Where(a => a.Name.LocalName == "sectPr").LastOrDefault(); if (lastSectPr != null) { var paragraphPropertiesXml = new Paragraph(new ParagraphProperties(new SectionProperties(lastSectPr.ToString()))).OuterXml; var paragraphProperties = XElement.Parse(paragraphPropertiesXml, LoadOptions.None); pageBreak.AddAfterSelf(paragraphProperties); } } } } } outputBodyNode.Add(tempMergeTemplateBodyNode.Nodes()); } } // remove all the 'next' delimiters OpenXmlRegex.Replace(outputBodyNode.Nodes().OfType <XElement>(), this.nextRecordRegEx, string.Empty, (xx, mm) => { return(true); }); // find all the 'next_empty' delimiters that we might have added and clear out the content in the paragraph nodes that follow OpenXmlRegex.Match( outputBodyNode.Nodes().OfType <XElement>(), this.nextEmptyRecordRegEx, (xx, mm) => { var afterSiblings = xx.ElementsAfterSelf().ToList(); // get all the paragraph elements after the 'next_empty' node and clear out the content var nodesToClean = afterSiblings.Where(a => a.Name.LocalName == "p").ToList(); // if the next_empty node has lava, clean that up too var xxContent = xx.ToString(); if (lavaRegEx.IsMatch(xxContent)) { nodesToClean.Add(xx); } foreach (var node in nodesToClean) { // remove all child nodes from each paragraph node if (node.HasElements) { node.RemoveNodes(); } } }); // remove all the 'next_empty' delimiters OpenXmlRegex.Replace(outputBodyNode.Nodes().OfType <XElement>(), this.nextEmptyRecordRegEx, string.Empty, (xx, mm) => { return(true); }); // remove all but the last SectionProperties element (there should only be one per section (body)) var sectPrItems = outputBodyNode.Nodes().OfType <XElement>().Where(a => a.Name.LocalName == "sectPr"); foreach (var extra in sectPrItems.Where(a => a != sectPrItems.Last()).ToList()) { extra.Remove(); } // renumber all the ids to make sure they are unique var idAttrs = xdoc.DescendantNodes().OfType <XElement>().Where(a => a.HasAttributes).Select(a => a.Attribute("id")).Where(s => s != null); int lastId = 1; foreach (var attr in idAttrs) { attr.Value = lastId.ToString(); lastId++; } LavaDataDictionary globalMergeHash = new LavaDataDictionary(); foreach (var field in globalMergeFields) { globalMergeHash.Add(field.Key, field.Value); } HeaderFooterGlobalMerge(outputDoc, globalMergeHash); // sweep thru any remaining un-merged body parts for any Lava having to do with Global merge fields foreach (var bodyTextPart in outputDoc.MainDocumentPart.Document.Body.Descendants <Text>()) { string nodeText = bodyTextPart.Text.ReplaceWordChars(); if (lavaRegEx.IsMatch(nodeText)) { bodyTextPart.Text = nodeText.ResolveMergeFields(globalMergeHash, true, true); } } // remove the last pagebreak MarkupSimplifier.SimplifyMarkup(outputDoc, new SimplifyMarkupSettings { RemoveLastRenderedPageBreak = true }); // If you want to see validation errors /* * var validator = new OpenXmlValidator(); * var errors = validator.Validate( outputDoc ).ToList(); */ } outputBinaryFile = new BinaryFile(); outputBinaryFile.IsTemporary = true; outputBinaryFile.ContentStream = outputDocStream; outputBinaryFile.FileName = "MergeTemplateOutput" + Path.GetExtension(templateBinaryFile.FileName); outputBinaryFile.MimeType = templateBinaryFile.MimeType; outputBinaryFile.BinaryFileTypeId = new BinaryFileTypeService(rockContext).Get(Rock.SystemGuid.BinaryFiletype.DEFAULT.AsGuid()).Id; binaryFileService.Add(outputBinaryFile); rockContext.SaveChanges(); } return(outputBinaryFile); }