/// <summary> /// Create a DateField from the Document Object Model. /// </summary> /// <param name="fieldStart">The starting node in the DOM from which the DateField is constructed.</param> public DateField(FieldStart fieldStart) : base(fieldStart) { // Extract from the entire text of the field from the 'Run' nodes in the DOM. The field seperator divides the text of the merge field from the // current contents of that merged field, otherwise the end of the field is used as the delimiter. String fieldText = String.Empty; for (Node node = this.FieldStart; node.NodeType != NodeType.FieldSeparator && node.NodeType != NodeType.FieldEnd; node = node.NextSibling) { if (node.NodeType == NodeType.Run) { fieldText += ((Run)node).Text; } } // A regular expression is used to extract the name of the data element referenced by this DateField. Match match = DateField.dateExpression.Match(fieldText); // The default formatting is to use the native format for the data type. This can be overridden with additional codes in the merge field. this.format = "{0}"; // The field can have explicit formatting. This will pull the format out of the DATE field. Group dateFormat = match.Groups["dateFormat"]; if (dateFormat.Success) { this.format = String.Format("{{0:{0}}}", dateFormat.Value); } }
internal Hyperlink(FieldStart fieldStart) { if (fieldStart == null) { throw new ArgumentNullException("fieldStart"); } if (!fieldStart.FieldType.Equals(FieldType.FieldHyperlink)) { throw new ArgumentException("Field start type must be FieldHyperlink."); } mFieldStart = fieldStart; // Find the field separator node. mFieldSeparator = fieldStart.GetField().Separator; if (mFieldSeparator == null) { throw new InvalidOperationException("Cannot find field separator."); } mFieldEnd = fieldStart.GetField().End; // Field code looks something like [ HYPERLINK "http:\\www.myurl.com" ], but it can consist of several runs. string fieldCode = fieldStart.GetField().GetFieldCode(); Match match = gRegex.Match(fieldCode.Trim()); mIsLocal = (match.Groups[1].Length > 0); //The link is local if \l is present in the field code. mTarget = match.Groups[2].Value; }
internal Hyperlink(FieldStart fieldStart) { if (fieldStart == null) { throw new ArgumentNullException("fieldStart"); } if (!fieldStart.FieldType.Equals(FieldType.FieldHyperlink)) { throw new ArgumentException("Field start type must be FieldHyperlink."); } mFieldStart = fieldStart; // Find the field separator node mFieldSeparator = FindNextSibling(mFieldStart, NodeType.FieldSeparator); if (mFieldSeparator == null) { throw new InvalidOperationException("Cannot find field separator."); } // Find the field end node. Normally field end will always be found, but in the example document // there happens to be a paragraph break included in the hyperlink and this puts the field end // in the next paragraph. It will be much more complicated to handle fields which span several // paragraphs correctly, but in this case allowing field end to be null is enough for our purposes mFieldEnd = FindNextSibling(mFieldSeparator, NodeType.FieldEnd); // Field code looks something like [ HYPERLINK "http:\\www.myurl.com" ], but it can consist of several runs string fieldCode = GetTextSameParent(mFieldStart.NextSibling, mFieldSeparator); Match match = gRegex.Match(fieldCode.Trim()); mIsLocal = match.Groups[1].Length > 0; //The link is local if \l is present in the field code mTarget = match.Groups[2].Value; }
public static void ExtractContentUsingField() { //ExStart //ExFor:DocumentBuilder.MoveToMergeField(String, Boolean, Boolean) //ExId:ExtractBetweenNodes_UsingField //ExSummary:Shows how to extract content between a specific field and paragraph in the document using the ExtractContent method. // Load in the document Document doc = new Document(mDataDir + "TestFile.doc"); // Use a document builder to retrieve the field start of a merge field. DocumentBuilder builder = new DocumentBuilder(doc); // Pass the first boolean parameter to get the DocumentBuilder to move to the FieldStart of the field. // We could also get FieldStarts of a field using GetChildNode method as in the other examples. builder.MoveToMergeField("Fullname", false, false); // The builder cursor should be positioned at the start of the field. FieldStart startField = (FieldStart)builder.CurrentNode; Paragraph endPara = (Paragraph)doc.FirstSection.GetChild(NodeType.Paragraph, 5, true); // Extract the content between these nodes in the document. Don't include these markers in the extraction. ArrayList extractedNodes = ExtractContent(startField, endPara, false); // Insert the content into a new separate document and save it to disk. Document dstDoc = GenerateDocument(doc, extractedNodes); dstDoc.Save(mDataDir + "TestFile.Fields Out.pdf"); //ExEnd }
public static void Run() { //ExStart:ExtractContentUsingField // The path to the documents directory. string dataDir = RunExamples.GetDataDir_WorkingWithDocument(); string fileName = "TestFile.doc"; Document doc = new Document(dataDir + fileName); // Use a document builder to retrieve the field start of a merge field. DocumentBuilder builder = new DocumentBuilder(doc); // Pass the first boolean parameter to get the DocumentBuilder to move to the FieldStart of the field. // We could also get FieldStarts of a field using GetChildNode method as in the other examples. builder.MoveToMergeField("Fullname", false, false); // The builder cursor should be positioned at the start of the field. FieldStart startField = (FieldStart)builder.CurrentNode; Paragraph endPara = (Paragraph)doc.FirstSection.GetChild(NodeType.Paragraph, 5, true); // Extract the content between these nodes in the document. Don't include these markers in the extraction. ArrayList extractedNodes = Common.ExtractContent(startField, endPara, false); // Insert the content into a new separate document and save it to disk. Document dstDoc = Common.GenerateDocument(doc, extractedNodes); dataDir = dataDir + RunExamples.GetOutputFilePath(fileName); dstDoc.Save(dataDir); //ExEnd:ExtractContentUsingField Console.WriteLine("\nExtracted content using the Field successfully.\nFile saved at " + dataDir); }
internal MergeField(FieldStart fieldStart) { if (fieldStart.Equals(null)) { throw new ArgumentNullException("fieldStart"); } if (!fieldStart.FieldType.Equals(FieldType.FieldMergeField)) { throw new ArgumentException("Field start type must be FieldMergeField."); } mFieldStart = fieldStart; // Find the field separator node. mFieldSeparator = FindNextSibling(mFieldStart, NodeType.FieldSeparator); if (mFieldSeparator == null) { throw new InvalidOperationException("Cannot find field separator."); } // Find the field end node. Normally field end will always be found, but in the example document // there happens to be a paragraph break included in the hyperlink and this puts the field end // in the next paragraph. It will be much more complicated to handle fields which span several // paragraphs correctly, but in this case allowing field end to be null is enough for our purposes. mFieldEnd = FindNextSibling(mFieldSeparator, NodeType.FieldEnd); }
internal Hyperlink(FieldStart fieldStart) { if (fieldStart == null) { throw new ArgumentNullException("fieldStart"); } if (fieldStart.FieldType != FieldType.FieldHyperlink) { throw new ArgumentException("Field start type must be FieldHyperlink."); } mFieldStart = fieldStart; // Find the field separator node. mFieldSeparator = FindNextSibling(mFieldStart, NodeType.FieldSeparator); if (mFieldSeparator == null) { throw new InvalidOperationException("Cannot find field separator."); } // Normally, we can always find the field's end node, but the example document // contains a paragraph break inside a hyperlink, which puts the field end // in the next paragraph. It will be much more complicated to handle fields which span several // paragraphs correctly. In this case allowing field end to be null is enough. mFieldEnd = FindNextSibling(mFieldSeparator, NodeType.FieldEnd); // Field code looks something like "HYPERLINK "http:\\www.myurl.com"", but it can consist of several runs. string fieldCode = GetTextSameParent(mFieldStart.NextSibling, mFieldSeparator); Match match = gRegex.Match(fieldCode.Trim()); // The hyperlink is local if \l is present in the field code. mIsLocal = match.Groups[1].Length > 0; mTarget = match.Groups[2].Value; }
public Hyperlink(FieldStart fieldStart) { if (fieldStart == null) throw new ArgumentNullException("fieldStart"); if (fieldStart.FieldType != FieldType.FieldHyperlink) throw new ArgumentException("Field start type must be FieldHyperlink."); mFieldStart = fieldStart; // Find field separator node. mFieldSeparator = FindNextSibling(mFieldStart, NodeType.FieldSeparator); if (mFieldSeparator == null) throw new Exception("Cannot find field separator."); // Find field end node. Normally field end will always be found, but in the example document // there happens to be a paragraph break included in the hyperlink and this puts the field end // in the next paragraph. It will be much more complicated to handle fields which span several // paragraphs correctly, but in this case allowing field end to be null is enough for our purposes. mFieldEnd = FindNextSibling(mFieldSeparator, NodeType.FieldEnd); // Field code looks something like [ HYPERLINK "http:\\www.myurl.com" ], but it can consist of several runs. string fieldCode = GetTextSameParent(mFieldStart.NextSibling, mFieldSeparator); Match match = gRegex.Match(fieldCode.Trim()); mIsLocal = (match.Groups[1].Length > 0); // The link is local if \l is present in the field code. mTarget = match.Groups[2].Value; }
private static string GetDocumentVariableFromField(FieldStart fieldStart) { var nextSibling = (Run)fieldStart.NextSibling; var runText = nextSibling.Text; int lengthOfDocumentVariablePrefix = DocumentVariablePrefix.Length; return(runText.Substring(lengthOfDocumentVariablePrefix + 1).Trim()); }
/// <summary> /// Called when a FieldStart node is encountered in the document. /// </summary> public override VisitorAction VisitFieldStart(FieldStart fieldStart) { IndentAndAppendLine("[Field start] FieldType: " + fieldStart.FieldType); mDocTraversalDepth++; mVisitorIsInsideField = true; return(VisitorAction.Continue); }
private static string GetText(FieldStart start, string code, FieldSeparator separator = null, string result = null, FieldEnd end = null, bool useRawFieldChars = false) { return string.Format("{0}{1}{2}{3}{4}", start != null ? useRawFieldChars ? FieldStart : "{" : "", code ?? "", separator != null ? useRawFieldChars ? FieldSeparator : "|" : "", result ?? "", end != null ? useRawFieldChars ? FieldEnd : "}" : ""); }
/// <summary> /// Create a hierarchical group of nodes from the start of the field. /// </summary> /// <param name="fieldStart">The node which represents the start of a field.</param> public WordField(FieldStart fieldStart) { // Create the object. this.endDictionary = new Dictionary <Node, WordField>(); this.startDictionary = new Dictionary <Node, WordField>(); this.fieldStart = fieldStart; // This will find the end of the field and acts as a kind of delimiter. Since the DOM structure makes heavy use of linked lists, it can also act // to join the end of a field to the start and back again when navigating through the document. Boolean parsing = true; Node node = this.fieldStart.NextSibling; while (parsing) { // This is the field that is going to be parsed out of the stream. WordField wordField = default(WordField); // Each node is parsed to determine what kind of an object has been discovered here. switch (node.NodeType) { case NodeType.FieldStart: // This will recurse into any of the embedded fields creating a hierarchical order of fields that can be evaluated from the inside out. wordField = CreateField(node as FieldStart); // Any field found in the field field result area is ignored. All other fields are added to a collection that can be indexed sequentially // using an enumerator or directly using the dictionary. this.startDictionary.Add(wordField.FieldStart, wordField); this.endDictionary.Add(wordField.FieldEnd, wordField); // This moves the parser to the position after the field no matter how many levels of embedded fields were included. node = wordField.FieldEnd; break; case NodeType.FieldSeparator: // This indicates where the literal portion of a field starts. this.fieldSeperator = node as FieldSeparator; break; case NodeType.FieldEnd: // The end of the field is part of this obect and used when navigating forward or backward through the nodes of a paragraph. this.fieldEnd = node as FieldEnd; // When the final field is found the parsing of this field is completed. parsing = false; break; } // The parsing continues until the last field node is discovered. node = node.NextSibling; } }
/// <summary> /// Called when a FieldStart node is encountered in the document. /// </summary> public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // If this node is hidden, then remove it. if (this.isHidden(fieldStart)) { fieldStart.Remove(); } return(VisitorAction.Continue); }
/// <summary> /// Called when a FieldStart node is encountered in the document. /// </summary> public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // In Microsoft Word, a field code (such as "MERGEFIELD FieldName") follows // after a field start character. We want to skip field codes and output field. // Result only, therefore we use a flag to suspend the output while inside a field code. // Note this is a very simplistic implementation and will not work very well. // If you have nested fields in a document. mIsSkipText = true; return(VisitorAction.Continue); }
/// <summary> /// Create an inline field for the given field start node. /// </summary> public static InlineField Create(FieldStart start) { switch (start.FieldType) { case FieldType.FieldMergeField: return new MergeField(start); case FieldType.FieldHyperlink: return new HyperlinkField(start); default: return new InlineField(start); } }
/// <summary> /// Called when a FieldStart node is encountered in the document. /// </summary> public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // In Microsoft Word, a field code (such as "MERGEFIELD FieldName") follows // After a field start character. We want to skip field codes and output field // Result only, therefore we use a flag to suspend the output while inside a field code. // // Note this is a very simplistic implementation and will not work very well // If you have nested fields in a document. mIsSkipText = true; return VisitorAction.Continue; }
/// <summary> /// Attempt to create an inline field for the given start field. /// </summary> public static bool TryParse(FieldStart start, out InlineField field) { var end = FindMatchingEnd(start); if (end != null) { field = Create(start); return true; } field = null; return false; }
/// <summary> /// Attempts to parse a field, starting at the given field start node, as a hyperlink field. /// </summary> public static bool TryParse(FieldStart start, out HyperlinkField field) { if (start.FieldType == FieldType.FieldHyperlink) { InlineField inlineField; if (InlineField.TryParse(start, out inlineField)) { field = (HyperlinkField)inlineField; return true; } } field = null; return false; }
private static string GetFieldCode(FieldStart fieldStart) { StringBuilder builder = new StringBuilder(); for (Node node = fieldStart; node != null && node.NodeType != NodeType.FieldSeparator && node.NodeType != NodeType.FieldEnd; node = node.NextPreOrder(node.Document)) { // Use text only of Run nodes to avoid duplication. if (node.NodeType == NodeType.Run) { builder.Append(node.GetText()); } } return(builder.ToString()); }
public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // We must keep track of the starts and ends of fields incase of any nested fields. if (fieldStart.FieldType.Equals(mTargetFieldType)) { mFieldDepth++; fieldStart.Remove(); } else { // This removes the field start if it's inside a field that is being converted. CheckDepthAndRemoveNode(fieldStart); } return VisitorAction.Continue; }
public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // We must keep track of the starts and ends of fields incase of any nested fields. if (fieldStart.FieldType.Equals(mTargetFieldType)) { mFieldDepth++; fieldStart.Remove(); } else { // This removes the field start if it's inside a field that is being converted. CheckDepthAndRemoveNode(fieldStart); } return(VisitorAction.Continue); }
internal MergeField(FieldStart fieldStart) { if (fieldStart.Equals(null)) throw new ArgumentNullException("fieldStart"); if (!fieldStart.FieldType.Equals(FieldType.FieldMergeField)) throw new ArgumentException("Field start type must be FieldMergeField."); mFieldStart = fieldStart; // Find the field separator node. mFieldSeparator = fieldStart.GetField().Separator; if (mFieldSeparator == null) throw new InvalidOperationException("Cannot find field separator."); mFieldEnd = fieldStart.GetField().End; }
private static void RemoveField(FieldStart fieldStart) { Node currentNode = fieldStart; bool isRemoving = true; while (currentNode != null && isRemoving) { if (currentNode.NodeType == NodeType.FieldEnd) { isRemoving = false; } Node nextNode = currentNode.NextPreOrder(currentNode.Document); currentNode.Remove(); currentNode = nextNode; } }
/// <summary> /// Create a MergeField from the Document Object Model. /// </summary> /// <param name="fieldStart">The starting node in the DOM from which the MergeField is constructed.</param> public MergeField(FieldStart fieldStart) : base(fieldStart) { // Extract from the entire text of the field from the 'Run' nodes in the DOM. The field seperator divides the text of the merge field from the // current contents of that merged field, otherwise the end of the field is used as the delimiter. String fieldText = String.Empty; for (Node node = this.FieldStart; node.NodeType != NodeType.FieldSeparator && node.NodeType != NodeType.FieldEnd; node = node.NextSibling) { if (node.NodeType == NodeType.Run) { fieldText += ((Run)node).Text; } } // A regular expression is used to extract the name of the data element referenced by this MergeField. Match match = MergeField.mergeExpression.Match(fieldText); // The reference is the name of the field. This name is cross referenced to the data dictionary when looking up values during a mail merge. Group referenceGroup = match.Groups["reference"]; this.reference = referenceGroup.Success ? referenceGroup.Value : String.Empty; // The default formatting is to use the native format for the data type. This can be overridden with additional codes in the merge field. this.format = "{0}"; // The default formatting for numbers can be overridden with the number format syntax. Group numberFormatGroup = match.Groups["numberFormat"]; if (numberFormatGroup.Success) { this.format = String.Format("{{0:{0}}}", numberFormatGroup.Value); } // The default formatting for dates and times can be overridden with the date format syntax. Group dateFormatGroup = match.Groups["dateFormat"]; if (dateFormatGroup.Success) { this.format = String.Format("{{0:{0}}}", dateFormatGroup.Value); } }
internal MergeField(FieldStart fieldStart) { if (fieldStart.Equals(null)) throw new ArgumentNullException("fieldStart"); if (!fieldStart.FieldType.Equals(FieldType.FieldMergeField)) throw new ArgumentException("Field start type must be FieldMergeField."); this.mFieldStart = fieldStart; // Find the field separator node. this.mFieldSeparator = FindNextSibling(this.mFieldStart, NodeType.FieldSeparator); if (this.mFieldSeparator == null) throw new InvalidOperationException("Cannot find field separator."); // Find the field end node. Normally field end will always be found, but in the example document // there happens to be a paragraph break included in the hyperlink and this puts the field end // in the next paragraph. It will be much more complicated to handle fields which span several // paragraphs correctly, but in this case allowing field end to be null is enough for our purposes. this.mFieldEnd = FindNextSibling(this.mFieldSeparator, NodeType.FieldEnd); }
public void GetFieldFromDocument() { //ExStart //ExFor:FieldChar.GetField //ExId:GetField //ExSummary:Demonstrates how to retrieve the field class from an existing FieldStart node in the document. Aspose.Words.Document doc = new Aspose.Words.Document(MyDir + "Document.TableOfContents.doc"); FieldStart fieldStart = (FieldStart)doc.GetChild(NodeType.FieldStart, 0, true); // Retrieve the facade object which represents the field in the document. Field field = fieldStart.GetField(); Console.WriteLine("Field code:" + field.GetFieldCode()); Console.WriteLine("Field result: " + field.Result); Console.WriteLine("Is locked: " + field.IsLocked); // This updates only this field in the document. field.Update(); //ExEnd }
internal MergeField(FieldStart fieldStart) { if (fieldStart.Equals(null)) { throw new ArgumentNullException("fieldStart"); } if (!fieldStart.FieldType.Equals(FieldType.FieldMergeField)) { throw new ArgumentException("Field start type must be FieldMergeField."); } mFieldStart = fieldStart; // Find the field separator node. mFieldSeparator = fieldStart.GetField().Separator; if (mFieldSeparator == null) { throw new InvalidOperationException("Cannot find field separator."); } mFieldEnd = fieldStart.GetField().End; }
public void ExtractContentUsingField() { //ExStart:ExtractContentUsingField Document doc = new Document(MyDir + "Extract content.docx"); DocumentBuilder builder = new DocumentBuilder(doc); // Pass the first boolean parameter to get the DocumentBuilder to move to the FieldStart of the field. // We could also get FieldStarts of a field using GetChildNode method as in the other examples. builder.MoveToMergeField("Fullname", false, false); // The builder cursor should be positioned at the start of the field. FieldStart startField = (FieldStart)builder.CurrentNode; Paragraph endPara = (Paragraph)doc.FirstSection.GetChild(NodeType.Paragraph, 5, true); // Extract the content between these nodes in the document. Don't include these markers in the extraction. ArrayList extractedNodes = ExtractContentHelper.ExtractContent(startField, endPara, false); Document dstDoc = ExtractContentHelper.GenerateDocument(doc, extractedNodes); dstDoc.Save(ArtifactsDir + "ExtractContent.ExtractContentUsingField.docx"); //ExEnd:ExtractContentUsingField }
public override VisitorAction VisitFieldStart(FieldStart fieldStart) { if (fieldStart.FieldType == FieldType.FieldDocProperty) { this.currentDocumentProperty = GetDocumentPropertyFromField(fieldStart); this.currentFieldTagName = GetXmlTagForDocumentProperty(this.currentDocumentProperty); this.structureBuilder .AppendFormat( "<{0} {1} />", this.currentFieldTagName + "Start", FormatAttributes(new NamedValue("Name", HttpUtility.HtmlEncode(this.currentDocumentProperty)))) .AppendLine(); this.skipRun = true; } else if (fieldStart.FieldType == FieldType.FieldDocVariable) { this.currentDocumentVariable = GetDocumentVariableFromField(fieldStart); this.currentFieldTagName = "DocumentVariable"; this.structureBuilder .AppendFormat( "<{0} {1} />", this.currentFieldTagName + "Start", FormatAttributes(new NamedValue("Name", HttpUtility.HtmlEncode(this.currentDocumentVariable)))) .AppendLine(); this.skipRun = true; } else { this.structureBuilder.AppendLine("<FieldStart />"); } return(VisitorAction.Continue); }
/// <summary> /// Creates a strongly-typed field from the tokens in the WordProcessing DOM. /// </summary> /// <param name="fieldStart">The starting point for the field.</param> /// <returns>A strongly typed field based on the tokens found in the stream.</returns> public static WordField CreateField(FieldStart fieldStart) { // This is what is created if the type isn't recognized. WordField wordField = default(WordField); // The field type indicates what parser is used to evaluate the field. switch (fieldStart.FieldType) { case FieldType.FieldDate: // Create a new 'DATE' field. wordField = new DateField(fieldStart); break; case FieldType.FieldIf: // Create a new 'IF' field. wordField = new IfField(fieldStart); break; case FieldType.FieldMergeField: // Create a new 'MERGEFIELD' field. wordField = new MergeField(fieldStart); break; default: // All other fields are unhandled but parsed. wordField = new UnhandledField(fieldStart); break; } // This is a generic field that can be evaluated. return(wordField); }
internal MergeField(FieldStart start) : base(start) { }
private static string GetDocumentVariableFromField(FieldStart fieldStart) { var nextSibling = fieldStart.NextSibling as Run; var runText = nextSibling.Text; int lengthOfDocumentVariablePrefix = DocumentVariablePrefix.Length; return runText.Substring(lengthOfDocumentVariablePrefix + 1).Trim(); }
/// <summary> /// Creates a field not handled by the field evaluation logic. /// </summary> /// <param name="fieldstart">The start of the field.</param> public UnhandledField(FieldStart fieldstart) : base(fieldstart) { }
private static string ParseInlineResult(FieldStart start, string code, FieldSeparator separator, out Node lastNode) { var resultBuilder = new StringBuilder(); lastNode = separator; foreach (var sibling in separator.GetFollowingSiblings().TakeWhile(n => !(n is FieldEnd))) { var run = sibling as Run; if (run != null) resultBuilder.Append(run.GetText()); else if (!(sibling is BookmarkStart) && !(sibling is BookmarkEnd)) throw new Exception(string.Format("Found unexpected node of type '{0}' after \"{1}\".", sibling.GetType().Name, GetText(start, code, separator, resultBuilder.ToString()))); lastNode = sibling; } return resultBuilder.ToString(); }
private static string GetDocumentPropertyFromField(FieldStart fieldStart) { var nextSibling = (Run)fieldStart.NextSibling; var runText = nextSibling.Text; int lengthOfDocumentPropertyPrefix = DocumentPropertyPrefix.Length; return runText.Substring(lengthOfDocumentPropertyPrefix + 1).Trim(); }
/// <summary> /// Removes the Field from the document /// </summary> /// <param name="fieldStart">The field start node of the field to remove.</param> private static void RemoveField(FieldStart fieldStart) { Node currentNode = fieldStart; bool isRemoving = true; while (currentNode != null && isRemoving) { if (currentNode.NodeType == NodeType.FieldEnd) isRemoving = false; Node nextNode = currentNode.NextPreOrder(currentNode.Document); currentNode.Remove(); currentNode = nextNode; } }
private static string ParseInlineCode(FieldStart start, out Node lastNode) { var codeBuilder = new StringBuilder(); lastNode = start; foreach (var sibling in start.GetFollowingSiblings().TakeWhile(n => !(n is FieldSeparator))) { var run = sibling as Run; if (run != null) codeBuilder.Append(run.GetText()); else throw new Exception(string.Format("Found unexpected node of type '{0}' after \"{1}\".", sibling.GetType().Name, GetText(start, codeBuilder.ToString()))); lastNode = sibling; } return codeBuilder.ToString(); }
public override VisitorAction VisitFieldStart(FieldStart fieldStart) { if (fieldStart.FieldType == FieldType.FieldDocProperty) { this.currentDocumentProperty = GetDocumentPropertyFromField(fieldStart); this.currentFieldTagName = GetXmlTagForDocumentProperty(this.currentDocumentProperty); this.structureBuilder.AppendLine(string.Concat("<", this.currentFieldTagName, "Start Name=\"", this.currentDocumentProperty, "\" />")); this.skipRun = true; } else if (fieldStart.FieldType == FieldType.FieldDocVariable) { this.currentDocumentVariable = GetDocumentVariableFromField(fieldStart); this.currentFieldTagName = "DocumentVariable"; this.structureBuilder.AppendLine(string.Concat("<", this.currentFieldTagName, "Start Name=\"", this.currentDocumentVariable, "\" />")); this.skipRun = true; } else { this.structureBuilder.AppendLine("<Field>"); } return VisitorAction.Continue; }
/// <summary> /// Called when a FieldStart node is encountered in the document. /// </summary> public override VisitorAction VisitFieldStart(FieldStart fieldStart) { // If this node is hidden, then remove it. if (isHidden(fieldStart)) fieldStart.Remove(); return VisitorAction.Continue; }
/// <summary> /// Find the FieldEnd node that matches the field's start node. /// </summary> private static FieldEnd FindMatchingEnd(FieldStart start) { try { FieldEnd end; start.ParseInlineField(out end); return end; } catch { return null; } }
/// <summary> /// Determines if the given field start is the quote reference field and returns its field end if so. /// </summary> public static bool IsQuoteField(FieldStart start, out FieldEnd end) { if (start.FieldType != FieldType.FieldSet) { end = null; return false; } return start.ParseInlineField(out end).Trim() == "SET q \"\\\"\""; }
internal HyperlinkField(FieldStart start) : base(start) { }
public override VisitorAction VisitFieldStart(FieldStart fieldStart) { if (fieldStart.FieldType == FieldType.FieldDocProperty) { this.currentDocumentProperty = GetDocumentPropertyFromField(fieldStart); this.currentFieldTagName = GetXmlTagForDocumentProperty(this.currentDocumentProperty); this.structureBuilder .AppendFormat( "<{0} {1} />", this.currentFieldTagName + "Start", FormatAttributes(new NamedValue("Name", HttpUtility.HtmlEncode(this.currentDocumentProperty)))) .AppendLine(); this.skipRun = true; } else if (fieldStart.FieldType == FieldType.FieldDocVariable) { this.currentDocumentVariable = GetDocumentVariableFromField(fieldStart); this.currentFieldTagName = "DocumentVariable"; this.structureBuilder .AppendFormat( "<{0} {1} />", this.currentFieldTagName + "Start", FormatAttributes(new NamedValue("Name", HttpUtility.HtmlEncode(this.currentDocumentVariable)))) .AppendLine(); this.skipRun = true; } else { this.structureBuilder.AppendLine("<FieldStart />"); } return VisitorAction.Continue; }
internal InlineField(FieldStart start) { Start = start; }
//ExStart //ExFor:FieldStart //ExFor:FieldSeparator //ExFor:FieldEnd //ExId:AppendDocument_HelperFunctions //ExSummary:Provides some helper functions by the methods above /// <summary> /// Retrieves the field code from a field. /// </summary> /// <param name="fieldStart">The field start of the field which to gather the field code from</param> /// <returns></returns> private static string GetFieldCode(FieldStart fieldStart) { StringBuilder builder = new StringBuilder(); for (Node node = fieldStart; node != null && node.NodeType != NodeType.FieldSeparator && node.NodeType != NodeType.FieldEnd; node = node.NextPreOrder(node.Document)) { // Use text only of Run nodes to avoid duplication. if (node.NodeType == NodeType.Run) builder.Append(node.GetText()); } return builder.ToString(); }
/// <summary> /// Create the resources required for this instance. /// </summary> /// <param name="startNode"></param> public IfField(FieldStart startNode) : base(startNode) { // Initialize the object. this.falseExpression = new List <Node>(); this.trueExpression = new List <Node>(); this.expression = String.Empty; // The parser below will pull apart the various parts of the conditional statement. The biggest issue is that the 'true' clause and the 'false' // clause contain a combination of Nodes which must be preserved exactly as they will become part of the document. It is not enought to just extract // the text from these nodes, the fonts, paragraphs and styles and any other fields must be extracted as well. Parsing nodes is difficult as the // various tokens can exist all in a single node, or be spread across several nodes. These variables are used for parsing. Note that parsinge starts // from the end of the statement and works towards the beginning. This is because the 'true' and 'false' clauses are a fixed part of the syntax // and can be deterministically parsed using the quotes as tokens. The rest of the statement -- the conditional expression -- is much more // difficult and requires a professional strength parser and analyzer. The reason a RexEx function doesn't work here is because the values // extracted are in the form of CodeDOM Nodes and can't be tokenized. Int32 lastIndex = default(Int32); Node node = this.FieldEnd.PreviousSibling; Run run = default(Run); Run runClone = default(Run); Int32 startIndex = -1; State state = default(State); // The parsinge is constructed as a simple state machine. There is no pushback or advanced expression evaluation performed in this section. The // primary goal is to remove the nodes that will become the 'true' and 'false' expressions. A full-blown expression evaluator will be called with // the part of the statement that remains after pulling out the clauses. while (state != State.Final) { switch (node.NodeType) { case NodeType.Run: // Each of the run nodes can have a quote character which delineates the 'true' and 'false' clauses. The 'startIndex' acts as a kind of // cursor as the parser moves through each of the Run nodes. run = node as Run; if (startIndex == -1) { startIndex = run.Text.Length - 1; } // Once a Run node is recognized it is examined for tokens that delinate the clauses. The state drives how each Run node is examined and // determines what is trying to be extracted from each node. switch (state) { case State.FieldResult: // Just eat any Run nodes in the Field Result section of the field. This moves the parser past the garbage -- the last evaluated data // for this field -- and into the useful part of the field. startIndex = -1; node = node.PreviousSibling; break; case State.FalseEndQuote: // Look for the quote that finishes the 'False' clause. lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex == -1) { // If the delimiter character can't be found then try the previous node. startIndex = -1; } else { // If the delimiter is found then move the cursor into the body of the clause. state = State.FalseStatement; startIndex = lastIndex - 1; } // move to the previous node when there is nothing left to parse in the current node until the delimiter is found. if (startIndex == -1) { node = node.PreviousSibling; } break; case State.FalseStatement: // This will extract the body of the 'False' condition. The next token that will cause a state change is the quote that opened this // condition (remember we are parsing backwards). lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex == -1) { // If there are no delimiters in the current run then clone it and copy the text up to the end quote (or the entire Run node if the // end quote was in a previously parsed node). There are times when the false clause is empty and an optimization removes any // empty Run nodes. String text = run.Text.Substring(0, startIndex + 1); if (text != String.Empty) { runClone = run.Clone(true) as Run; runClone.Text = text; this.falseExpression.Add(runClone); } // Since this node doesn't contain a delimiter, the state remains the same. Continue parsing the next node for the start quote. node = node.PreviousSibling; startIndex = -1; } else { // When an open quote is found we've come to the start of the false statement. Copy any remaining text in the current Run node // into the resulting clause. The quotes will be removed but the rest of the text will be a literal copy of the text found in the // document complete with all the formatting. String text = run.Text.Substring(lastIndex + 1, startIndex - lastIndex); if (runClone == null || runClone.Text != String.Empty) { runClone = run.Clone(true) as Run; runClone.Text = text; this.falseExpression.Add(runClone); } // After the opening quote is found the parser will move to a state where it looks for the opening quote. While this may seem // unnecessary because we've already found the quote, it made the logic simpler than having to create a 'push back' concept for // the tokens. state = State.FalseStartQuote; } break; case State.FalseStartQuote: // This will search for the start quote of the 'False' clause. Note that there is no check here for an empty Run node. The only way // for the parser to be in this state is if the previous state had found an open quote in the current Run node. lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex != -1) { // If the start quote was the starting character of this Run node then the parsing will continue with the previous sibling when // the next state is entered. Otherwise, the cursor is moved in front of the opening quote as the parsing continues. if (lastIndex == 0) { node = node.PreviousSibling; startIndex = -1; } else { startIndex = lastIndex - 1; } // Transition to a state where the parser searches for the opening quote of the 'True' condition. state = State.TrueEndQuote; } break; case State.TrueEndQuote: // Look for the quote that finishes the 'True' clause. lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex == -1) { // If the delimiter character can't be found then try the previous node. startIndex = -1; } else { // If the delimiter is found then move the cursor into the body of the clause. state = State.TrueStatement; startIndex = lastIndex - 1; } // move to the previous node when there is nothing left to parse in the current node until the delimiter is found. if (startIndex == -1) { node = node.PreviousSibling; } break; case State.TrueStatement: // This will extract the body of the 'True' condition. The next token that will cause a state change is the quote that opened this // condition (remember we are parsing backwards). lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex == -1) { // If there are no delimiters in the current run then clone it and copy the text up to the end quote (or the entire Run node if the // end quote was in a previously parsed node). There are times when the true clause is empty and an optimization removes any // empty Run nodes. String text = run.Text.Substring(0, startIndex + 1); if (text != String.Empty) { runClone = run.Clone(true) as Run; runClone.Text = text; this.trueExpression.Add(runClone); } // Since this node doesn't contain a delimiter, the state remains the same. Continue parsing the next node for the start quote. node = node.PreviousSibling; startIndex = -1; } else { // When an open quote is found we've come to the start of the true statement. Copy any remaining text in the current Run node into // the resulting clause. The quotes will be removed but the rest of the text will be a literal copy of the text found in the // document complete with all the formatting. String text = run.Text.Substring(lastIndex + 1, startIndex - lastIndex); if (runClone.Text != String.Empty) { runClone = run.Clone(true) as Run; runClone.Text = text; this.trueExpression.Add(runClone); } // After the opening quote is found the parser will move to a state where it looks for the opening quote. While this may seem // unnecessary because we've already found the quote, it made the logic simpler than having to create a 'push back' concept for // the tokens. state = State.TrueStartQuote; } break; case State.TrueStartQuote: // This will search for the start quote of the 'True' clause. Note that there is no check here for an empty Run node. The only way // for the parser to be in this state is if the previous state had found an open quote in the current Run node. lastIndex = run.Text.LastIndexOf('\"', startIndex); if (lastIndex != -1) { // If the start quote was the starting character of this Run node then the parsing will continue with the previous sibling when // the next state is entered. Otherwise, the cursor is moved in front of the opening quote as the parsing continues. if (lastIndex == 0) { node = node.PreviousSibling; startIndex = -1; } else { startIndex = lastIndex - 1; } // Transition to a state where the parser searches for condition expression. state = State.Expression; } break; case State.Expression: // Once the 'True' and 'False' clauses have been parsed, everthing between the start quote and the start of the field can be considered // part of the expression. There is no formatting that is required to evaluate the expression so there is no need to pull apart the // Run nodes. this.expression = run.Text.Substring(0, startIndex + 1) + expression; // This moves the cursor up to the previous run node. Remember the parsing runs backwards because it is more deterministic to pull out // the 'True' and 'False' clauses from the end than to parse forward to determine where the condition ended. startIndex = -1; node = node.PreviousSibling; break; } break; case NodeType.FieldSeparator: // The field seperator divides the field into the part you see in the document when working with fields, and the actual text that is // displayed when you are not working with fields. Anything after the field seperator is previously evaluated data which is of no interest // to this parser. node = node.PreviousSibling; state = State.FalseEndQuote; break; case NodeType.FieldEnd: // Remember that the parser works from the end of the field to the start. Therefore, the end is just the beginning (of a field). WordField wordField = this.FindByEndNode(node); // Each field is handled according to the state of the parser. switch (state) { case State.FalseStatement: // A field found during the parsing of a false statement is added in its entirety to the literal collection. while (node != null && node.NextSibling != wordField.FieldStart) { this.falseExpression.Insert(0, node.Clone(true)); node = node.PreviousSibling; } // Evaluate the previous node in the DOM. node = wordField.FieldStart.PreviousSibling; break; case State.TrueStatement: // A field found during the parsing of a true statement is added in its entirety to the literal collection. while (node != null && node.NextSibling != wordField.FieldStart) { this.trueExpression.Insert(0, node.Clone(true)); node = node.PreviousSibling; } // Evaluate the previous node in the DOM. node = wordField.FieldStart.PreviousSibling; break; case State.Expression: // When a field is found during the evaluation of an expression, the name of the merge field is used as a general purpose parameter to // the expression evaluator. if (wordField is MergeField) { MergeField mergeField = wordField as MergeField; this.expression = mergeField.Reference + expression; } // Evaluate the previous node in the DOM. node = wordField.FieldStart.PreviousSibling; break; default: // Ignore anything whiel processing the field results. node = wordField.FieldStart.PreviousSibling; break; } break; case NodeType.FieldStart: // The parser works from the end of the field to the front because the expression can have many items in it but the 'True' and 'False' // clauses are relatively easy to delinate. When the cursor has come to the start of the field, then the parsing of this 'IF' statement is // complete. The last task is to remove the 'IF' keyword from the expression. this.expression = expression.Substring(expression.IndexOf(IfField.ifText) + IfField.ifText.Length); state = State.Final; break; } } }
/// <summary> /// Determines if the given field start is the quote reference field. /// </summary> public static bool IsQuoteField(FieldStart start) { FieldEnd end; return IsQuoteField(start, out end); }