| /*************************************************************************** |
| |
| Copyright (c) Microsoft Corporation 2012-2015. |
| |
| This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here: |
| |
| http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx |
| |
| Published at http://OpenXmlDeveloper.org |
| Resource Center and Documentation: http://openxmldeveloper.org/wiki/w/wiki/powertools-for-open-xml.aspx |
| |
| Developer: Eric White |
| Blog: http://www.ericwhite.com |
| Twitter: @EricWhiteDev |
| Email: eric@ericwhite.com |
| |
| ***************************************************************************/ |
| |
| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using System.Text; |
| using System.Xml.Linq; |
| using DocumentFormat.OpenXml.Packaging; |
| |
| namespace OpenXmlPowerTools |
| { |
| public class FieldRetriever |
| { |
| public static string InstrText(XElement root, int id) |
| { |
| |
| XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; |
| |
| #if false |
| // This is the old code. Both versions work - the caching version is significantly faster. |
| var relevantElements = root.Descendants() |
| .Where(e => |
| { |
| Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>(); |
| if (s != null) |
| return s.Any(z => z.Id == id && |
| z.FieldElementType == FieldElementTypeEnum.InstrText); |
| return false; |
| }) |
| .ToList(); |
| #else |
| var cachedAnnotationInformation = root.Annotation<Dictionary<int, List<XElement>>>(); |
| if (cachedAnnotationInformation == null) |
| throw new OpenXmlPowerToolsException("Internal error"); |
| |
| // it is possible that a field code contains no instr text |
| if (!cachedAnnotationInformation.ContainsKey(id)) |
| return ""; |
| var relevantElements = cachedAnnotationInformation[id]; |
| #endif |
| |
| var groupedSubFields = relevantElements |
| .GroupAdjacent(e => |
| { |
| Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>(); |
| var stackElement = s.FirstOrDefault(z => z.Id == id); |
| var elementsBefore = s.TakeWhile(z => z != stackElement); |
| return elementsBefore.Any(); |
| }) |
| .ToList(); |
| |
| var instrText = groupedSubFields |
| .Select(g => |
| { |
| if (g.Key == false) |
| { |
| return g.Select(e => |
| { |
| Stack<FieldElementTypeInfo> s = e.Annotation<Stack<FieldElementTypeInfo>>(); |
| var stackElement = s.FirstOrDefault(z => z.Id == id); |
| if (stackElement.FieldElementType == FieldElementTypeEnum.InstrText && |
| e.Name == w + "instrText") |
| return e.Value; |
| return ""; |
| }) |
| .StringConcatenate(); |
| } |
| else |
| { |
| Stack<FieldElementTypeInfo> s = g.First().Annotation<Stack<FieldElementTypeInfo>>(); |
| var stackElement = s.FirstOrDefault(z => z.Id == id); |
| var elementBefore = s.TakeWhile(z => z != stackElement).Last(); |
| var subFieldId = elementBefore.Id; |
| return InstrText(root, subFieldId); |
| } |
| }) |
| .StringConcatenate(); |
| |
| return "{" + instrText + "}"; |
| } |
| |
| public static void AnnotateWithFieldInfo(OpenXmlPart part) |
| { |
| XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; |
| |
| XElement root = part.GetXDocument().Root; |
| var r = root.DescendantsAndSelf() |
| .Rollup( |
| new FieldElementTypeStack |
| { |
| Id = 0, |
| FiStack = null, |
| }, |
| (e, s) => |
| { |
| if (e.Name == w + "fldChar") |
| { |
| if (e.Attribute(w + "fldCharType").Value == "begin") |
| { |
| Stack<FieldElementTypeInfo> fis; |
| if (s.FiStack == null) |
| fis = new Stack<FieldElementTypeInfo>(); |
| else |
| fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| fis.Push( |
| new FieldElementTypeInfo |
| { |
| Id = s.Id + 1, |
| FieldElementType = FieldElementTypeEnum.Begin, |
| }); |
| return new FieldElementTypeStack |
| { |
| Id = s.Id + 1, |
| FiStack = fis, |
| }; |
| }; |
| if (e.Attribute(w + "fldCharType").Value == "separate") |
| { |
| Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| FieldElementTypeInfo wfi = fis.Pop(); |
| fis.Push( |
| new FieldElementTypeInfo |
| { |
| Id = wfi.Id, |
| FieldElementType = FieldElementTypeEnum.Separate, |
| }); |
| return new FieldElementTypeStack |
| { |
| Id = s.Id, |
| FiStack = fis, |
| }; |
| } |
| if (e.Attribute(w + "fldCharType").Value == "end") |
| { |
| Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| FieldElementTypeInfo wfi = fis.Pop(); |
| return new FieldElementTypeStack |
| { |
| Id = s.Id, |
| FiStack = fis, |
| }; |
| } |
| } |
| if (s.FiStack == null || s.FiStack.Count() == 0) |
| return s; |
| FieldElementTypeInfo wfi3 = s.FiStack.Peek(); |
| if (wfi3.FieldElementType == FieldElementTypeEnum.Begin) |
| { |
| Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| FieldElementTypeInfo wfi2 = fis.Pop(); |
| fis.Push( |
| new FieldElementTypeInfo |
| { |
| Id = wfi2.Id, |
| FieldElementType = FieldElementTypeEnum.InstrText, |
| }); |
| return new FieldElementTypeStack |
| { |
| Id = s.Id, |
| FiStack = fis, |
| }; |
| } |
| if (wfi3.FieldElementType == FieldElementTypeEnum.Separate) |
| { |
| Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| FieldElementTypeInfo wfi2 = fis.Pop(); |
| fis.Push( |
| new FieldElementTypeInfo |
| { |
| Id = wfi2.Id, |
| FieldElementType = FieldElementTypeEnum.Result, |
| }); |
| return new FieldElementTypeStack |
| { |
| Id = s.Id, |
| FiStack = fis, |
| }; |
| } |
| if (wfi3.FieldElementType == FieldElementTypeEnum.End) |
| { |
| Stack<FieldElementTypeInfo> fis = new Stack<FieldElementTypeInfo>(s.FiStack.Reverse()); |
| fis.Pop(); |
| if (!fis.Any()) |
| fis = null; |
| return new FieldElementTypeStack |
| { |
| Id = s.Id, |
| FiStack = fis, |
| }; |
| } |
| return s; |
| }); |
| var elementPlusInfo = root.DescendantsAndSelf().PtZip(r, (t1, t2) => |
| { |
| return new |
| { |
| Element = t1, |
| Id = t2.Id, |
| WmlFieldInfoStack = t2.FiStack, |
| }; |
| }); |
| foreach (var item in elementPlusInfo) |
| { |
| if (item.WmlFieldInfoStack != null) |
| item.Element.AddAnnotation(item.WmlFieldInfoStack); |
| } |
| |
| //This code is useful when you want to take a look at the annotations, making sure that they are made correctly. |
| // |
| //foreach (var desc in root.DescendantsAndSelf()) |
| //{ |
| // Stack<FieldElementTypeInfo> s = desc.Annotation<Stack<FieldElementTypeInfo>>(); |
| // if (s != null) |
| // { |
| // Console.WriteLine(desc.Name.LocalName.PadRight(20)); |
| // foreach (var item in s) |
| // { |
| // Console.WriteLine(" {0:0000} {1}", item.Id, item.FieldElementType.ToString()); |
| // Console.ReadKey(); |
| // } |
| // } |
| //} |
| |
| var cachedAnnotationInformation = new Dictionary<int, List<XElement>>(); |
| foreach (var desc in root.DescendantsTrimmed(d => d.Name == W.rPr || d.Name == W.pPr)) |
| { |
| Stack<FieldElementTypeInfo> s = desc.Annotation<Stack<FieldElementTypeInfo>>(); |
| |
| if (s != null ) |
| { |
| foreach (var item in s) |
| { |
| if (item.FieldElementType == FieldElementTypeEnum.InstrText) |
| { |
| if (cachedAnnotationInformation.ContainsKey(item.Id)) |
| { |
| cachedAnnotationInformation[item.Id].Add(desc); |
| } |
| else |
| { |
| cachedAnnotationInformation.Add(item.Id, new List<XElement>() { desc }); |
| } |
| } |
| } |
| } |
| } |
| root.AddAnnotation(cachedAnnotationInformation); |
| } |
| |
| private enum State |
| { |
| InToken, |
| InWhiteSpace, |
| InQuotedToken, |
| OnOpeningQuote, |
| OnClosingQuote, |
| OnBackslash, |
| } |
| |
| private static string[] GetTokens(string field) |
| { |
| State state = State.InWhiteSpace; |
| int tokenStart = 0; |
| char quoteStart = char.MinValue; |
| List<string> tokens = new List<string>(); |
| for (int c = 0; c < field.Length; c++) |
| { |
| if (Char.IsWhiteSpace(field[c])) |
| { |
| if (state == State.InToken) |
| { |
| tokens.Add(field.Substring(tokenStart, c - tokenStart)); |
| state = State.InWhiteSpace; |
| continue; |
| } |
| if (state == State.OnOpeningQuote) |
| { |
| tokenStart = c; |
| state = State.InQuotedToken; |
| } |
| if (state == State.OnClosingQuote) |
| state = State.InWhiteSpace; |
| continue; |
| } |
| if (field[c] == '\\') |
| { |
| if (state == State.InQuotedToken) |
| { |
| state = State.OnBackslash; |
| continue; |
| } |
| } |
| if (state == State.OnBackslash) |
| { |
| state = State.InQuotedToken; |
| continue; |
| } |
| if (field[c] == '"' || field[c] == '\'' || field[c] == 0x201d) |
| { |
| if (state == State.InWhiteSpace) |
| { |
| quoteStart = field[c]; |
| state = State.OnOpeningQuote; |
| continue; |
| } |
| if (state == State.InQuotedToken) |
| { |
| if (field[c] == quoteStart) |
| { |
| tokens.Add(field.Substring(tokenStart, c - tokenStart)); |
| state = State.OnClosingQuote; |
| continue; |
| } |
| continue; |
| } |
| if (state == State.OnOpeningQuote) |
| { |
| if (field[c] == quoteStart) |
| { |
| state = State.OnClosingQuote; |
| continue; |
| } |
| else |
| { |
| tokenStart = c; |
| state = State.InQuotedToken; |
| continue; |
| } |
| } |
| continue; |
| } |
| if (state == State.InWhiteSpace) |
| { |
| tokenStart = c; |
| state = State.InToken; |
| continue; |
| } |
| if (state == State.OnOpeningQuote) |
| { |
| tokenStart = c; |
| state = State.InQuotedToken; |
| continue; |
| } |
| if (state == State.OnClosingQuote) |
| { |
| tokenStart = c; |
| state = State.InToken; |
| continue; |
| } |
| } |
| if (state == State.InToken) |
| tokens.Add(field.Substring(tokenStart, field.Length - tokenStart)); |
| return tokens.ToArray(); |
| } |
| |
| public static FieldInfo ParseField(string field) |
| { |
| FieldInfo emptyField = new FieldInfo |
| { |
| FieldType = "", |
| Arguments = new string[] { }, |
| Switches = new string[] { }, |
| }; |
| |
| if (field.Length == 0) |
| return emptyField; |
| string fieldType = field.TrimStart().Split(' ').FirstOrDefault(); |
| if (fieldType == null) |
| return emptyField; |
| if (fieldType.ToUpper() != "HYPERLINK" && |
| fieldType.ToUpper() != "REF" && |
| fieldType.ToUpper() != "SEQ" && |
| fieldType.ToUpper() != "STYLEREF") |
| return emptyField; |
| string[] tokens = GetTokens(field); |
| if (tokens.Length == 0) |
| return emptyField; |
| FieldInfo fieldInfo = new FieldInfo() |
| { |
| FieldType = tokens[0], |
| Switches = tokens.Where(t => t[0] == '\\').ToArray(), |
| Arguments = tokens.Skip(1).Where(t => t[0] != '\\').ToArray(), |
| }; |
| return fieldInfo; |
| } |
| |
| public class FieldInfo |
| { |
| public string FieldType; |
| public string[] Switches; |
| public string[] Arguments; |
| } |
| |
| public enum FieldElementTypeEnum |
| { |
| Begin, |
| InstrText, |
| Separate, |
| Result, |
| End, |
| }; |
| |
| public class FieldElementTypeInfo |
| { |
| public int Id; |
| public FieldElementTypeEnum FieldElementType; |
| } |
| |
| public class FieldElementTypeStack |
| { |
| public int Id; |
| public Stack<FieldElementTypeInfo> FiStack; |
| } |
| } |
| } |