private static void PrepareTemplatePart(OpenXmlPart part, FieldTransformIndex xm, TemplateErrorList te) { 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); // transform OpenDocx fields into temporary parsed metadata objects (??) xDocRoot = (XElement)ParseFields(xDocRoot, xm, te); // 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 set, then is OK. xDocRoot = (XElement)ForceBlockLevelAsAppropriate(xDocRoot, te); NormalizeRepeatAndConditional(xDocRoot, te); // any EndRepeat, EndConditional that remain are orphans, so replace with an error ProcessOrphanEndRepeatEndConditional(xDocRoot, te); // add placeholders for list punctuation xDocRoot = (XElement)AddListPunctuationPlaceholders(xDocRoot, te); // finally, transform the metadata objects BACK into document content, but this time in DocxGen syntax! xDocRoot = (XElement)ContentReplacementTransform(xDocRoot, xm, te); xDoc.Elements().First().ReplaceWith(xDocRoot); part.PutXDocument(); }
private static object ParseFields(XNode node, FieldTransformIndex xm, TemplateErrorList 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 (string.IsNullOrEmpty(alias)) { var tag = (string)element.Elements(W.sdtPr).Elements(W.tag).Attributes(W.val).FirstOrDefault(); if (!string.IsNullOrEmpty(tag) && xm.TryGetValue(tag, out var fieldInfo)) { XElement xml = new XElement(fieldInfo.fieldType, new XAttribute(OD.Id, tag)); xml.Add(element.Elements(W.sdtContent).Elements()); return(xml); } } } return(new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => ParseFields(n, xm, te)))); } return(node); }
private static TemplateErrorList PrepareTemplate(WordprocessingDocument wordDoc, FieldTransformIndex xm) { if (RevisionAccepter.HasTrackedRevisions(wordDoc)) { throw new FieldParseException("Invalid template - contains tracked revisions"); } SimplifyTemplateMarkup(wordDoc); var te = new TemplateErrorList(); foreach (var part in wordDoc.ContentParts()) { PrepareTemplatePart(part, xm, te); } return(te); }
static object ContentReplacementTransform(XNode node, FieldTransformIndex xm, TemplateErrorList templateError) { XElement element = node as XElement; if (element != null) { if (element.Name == OD.Content) { var selector = "./" + xm[element.Attribute(OD.Id).Value].atomizedExpr; if (element.Attribute(OD.Punc) == null) // regular content field { selector += "[1]"; // if xpath query returns multiple elements, just take the first one } else { selector += "1"; // the list selector with a "1" appended to it is where punctuation will be in the XML } var fieldText = "<" + PA.Content + " " + PA.Select + "=\"" + selector + "\" " + PA.Optional + "=\"true\"/>"; XElement ccc = null; XElement para = element.Descendants(W.p).FirstOrDefault(); XElement run = element.Descendants(W.r).FirstOrDefault(); if (para != null) { XElement pPr = para.Elements(W.pPr).FirstOrDefault(); XElement rPr = pPr?.Elements(W.rPr).FirstOrDefault(); XElement r = new XElement(W.r, rPr, new XElement(W.t, fieldText)); ccc = PWrap(para.Elements(W.pPr), r); } else { ccc = new XElement(W.r, run?.Elements(W.rPr).FirstOrDefault(), new XElement(W.t, fieldText)); } return(CCWrap(ccc)); } if (element.Name == OD.List) { var listAtom = xm[element.Attribute(OD.Id).Value].atomizedExpr; var selector = "./" + listAtom + "[1]/" + listAtom + "0"; var startText = "<" + PA.Repeat + " " + PA.Select + "=\"" + selector + "\" " + PA.Optional + "=\"true\"/>"; var endText = "<" + PA.EndRepeat + "/>"; XElement para = element.Descendants(W.p).FirstOrDefault(); var repeatingContent = element .Elements() .Select(e => ContentReplacementTransform(e, xm, templateError)) .ToList(); XElement startElem = new XElement(W.r, new XElement(W.t, startText)); XElement endElem = new XElement(W.r, new XElement(W.t, endText)); if (para != null) // block-level list { // prefix and suffix repeating content with block-level repeat elements/tags repeatingContent.Insert(0, CCWrap(PWrap(startElem))); // repeatingContent here repeatingContent.Add(CCWrap(PWrap(endElem))); } else // run-level { repeatingContent.Insert(0, CCWrap(startElem)); // repeatingContent here repeatingContent.Add(CCWrap(endElem)); } return(repeatingContent); } if (element.Name == OD.If || element.Name == OD.ElseIf || element.Name == OD.Else) { var endText = "<" + PA.EndConditional + "/>"; XElement endElem = new XElement(W.r, new XElement(W.t, endText)); bool blockLevel = element.IsEmpty || (element.Descendants(W.p).FirstOrDefault() != null); if (element.Name == OD.If) { var selector = xm[element.Attribute(OD.Id).Value].atomizedExpr + "2"; var startText = "<" + PA.Conditional + " " + PA.Select + "=\"" + selector + "[1]\" " + PA.Match + "=\"true\"/>"; var content = element .Elements() .Select(e => ContentReplacementTransform(e, xm, templateError)) .ToList(); XElement startElem = new XElement(W.r, new XElement(W.t, startText)); if (blockLevel) { content.Insert(0, CCWrap(PWrap(startElem))); // content here content.Add(CCWrap(PWrap(endElem))); } else // run-level { content.Insert(0, CCWrap(startElem)); // content here content.Add(CCWrap(endElem)); } return(content); } if (element.Name == OD.ElseIf) { XElement lookUp = element.Parent; while (lookUp.Name != OD.If && lookUp.Name != OD.ElseIf) { lookUp = lookUp.Parent; } var selector = xm[lookUp.Attribute(OD.Id).Value].atomizedExpr + "2"; var startElseText = "<" + PA.Conditional + " " + PA.Select + "=\"" + selector + "[1]\" " + PA.NotMatch + "=\"true\"/>"; // NotMatch instead of Match, represents "Else" branch selector = xm[element.Attribute(OD.Id).Value].atomizedExpr + "2"; var nestedIfText = "<" + PA.Conditional + " " + PA.Select + "=\"" + selector + "[1]\" " + PA.Match + "=\"true\"/>"; var content = element .Elements() .Select(e => ContentReplacementTransform(e, xm, templateError)) .ToList(); XElement startElseElem = new XElement(W.r, new XElement(W.t, startElseText)); XElement nestedIfElem = new XElement(W.r, new XElement(W.t, nestedIfText)); if (blockLevel) // block-level conditional { content.Insert(0, CCWrap(PWrap(endElem))); content.Insert(1, CCWrap(PWrap(startElseElem))); content.Insert(2, CCWrap(PWrap(nestedIfElem))); // content here content.Add(CCWrap(PWrap(endElem))); } else // run-level { content.Insert(0, CCWrap(endElem)); content.Insert(1, CCWrap(startElseElem)); content.Insert(2, CCWrap(nestedIfElem)); // content here content.Add(CCWrap(endElem)); } // no "end" tag for the "else" branch, because the end is inserted by the If element after all its contents return(content); } if (element.Name == OD.Else) { XElement lookUp = element.Parent; while (lookUp != null && lookUp.Name != OD.If && lookUp.Name != OD.ElseIf) { lookUp = lookUp.Parent; } // if lookUp == null, Something is wrong -- else not inside an if? if (lookUp != null) { var selector = xm[lookUp.Attribute(OD.Id).Value].atomizedExpr + "2"; var startElseText = "<" + PA.Conditional + " " + PA.Select + "=\"" + selector + "[1]\" " + PA.NotMatch + "=\"true\"/>"; // NotMatch instead of Match, represents "Else" branch var content = element .Elements() .Select(e => ContentReplacementTransform(e, xm, templateError)) .ToList(); XElement startElseElem = new XElement(W.r, new XElement(W.t, startElseText)); if (blockLevel) // block-level conditional { content.Insert(0, CCWrap(PWrap(endElem))); content.Insert(1, CCWrap(PWrap(startElseElem))); } else // run-level { content.Insert(0, CCWrap(endElem)); content.Insert(1, CCWrap(startElseElem)); } // no "end" tag for the "else" branch, because the end is inserted by the If element after all its contents return(content); } } } return(new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => ContentReplacementTransform(n, xm, templateError)))); } return(node); }
#pragma warning restore CS1998 private static CompileResult TransformTemplate(string originalTemplateFile, string preProcessedTemplateFile, FieldTransformIndex xm) { string newDocxFilename = originalTemplateFile + "gen.docx"; byte[] byteArray = File.ReadAllBytes(preProcessedTemplateFile); WmlDocument transformedTemplate = null; TemplateErrorList templateErrors; using (MemoryStream memStream = new MemoryStream()) { memStream.Write(byteArray, 0, byteArray.Length); // copy the bytes into an expandable MemoryStream using (WordprocessingDocument wordDoc = WordprocessingDocument.Open(memStream, true)) // read & parse that memory stream into an editable OXML document (also in memory) { templateErrors = PrepareTemplate(wordDoc, xm); } transformedTemplate = new WmlDocument(newDocxFilename, memStream.ToArray()); } // delete output file if it already exists (Save() below is supposed to always overwrite, but I just want to be sure) if (File.Exists(newDocxFilename)) { File.Delete(newDocxFilename); } // save the output (even in the case of error, since error messages are in the file) transformedTemplate.Save(); return(new CompileResult(transformedTemplate.FileName, templateErrors.ErrorList.Select(e => e.ToString()).ToArray())); }