private static object WmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement, Func <XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor, ReplaceInternalInfo replInfo, bool coalesceContent) { XElement element = node as XElement; if (element != null) { if (element.Name == W.p) { var paragraph = element; string contents = element .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.t) .Select(t => (string)t) .StringConcatenate(); if (regex.IsMatch(contents)) { contents = element .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.t) .Select(t => (string)t) .StringConcatenate(); if (regex.IsMatch(contents)) { XElement paragraphWithSplitRuns = new XElement(W.p, paragraph.Attributes(), paragraph.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent))); var runsTrimmed = paragraphWithSplitRuns .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.r) .ToList(); var charsAndRuns = runsTrimmed .Select(r => { if (r.Element(W.t) != null) { return new { Ch = r.Element(W.t).Value, r, } } ; else { return new { Ch = "\x01", r, } }; }) .ToList(); var content = charsAndRuns.Select(t => t.Ch).StringConcatenate(); var alignedRuns = charsAndRuns.Select(t => t.r).ToArray(); var matchCollection = regex.Matches(content); replInfo.Count += matchCollection.Count; if (replacement == null) { if (callback != null) { foreach (var match in matchCollection.Cast <Match>()) { callback(paragraph, match); } } } else { foreach (var match in matchCollection.Cast <Match>()) { if (match.Length == 0) { continue; } if (callback == null || callback(paragraph, match)) { var runCollection = alignedRuns .Skip(match.Index) .Take(match.Length) .ToList(); // uses the Skip / Take special semantics of array to implement efficient finding of sub array var firstRun = runCollection.First(); var firstRunProperties = firstRun.Elements(W.rPr).FirstOrDefault(); // save away first run properties if (trackRevisions) { if (replacement != null && replacement != "") { var newIns = new XElement(W.ins, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), new XElement(W.r, firstRunProperties, new XElement(W.t, replacement))); if (firstRun.Parent.Name == W.ins) { firstRun.Parent.AddBeforeSelf(newIns); } else { firstRun.AddBeforeSelf(newIns); } } foreach (var run in runCollection) { var isInIns = run.Parent.Name == W.ins; if (isInIns) { var parentIns = run.Parent; if ((string)parentIns.Attributes(W.author).FirstOrDefault() == revisionTrackingAuthor) { var parentInsSiblings = parentIns .Parent .Elements() .Where(c => c != parentIns) .ToList(); parentIns.Parent.ReplaceNodes(parentInsSiblings); } else { var parentInsSiblings = parentIns .Parent .Elements() .Select(c => { if (c == parentIns) { var newIns = new XElement(W.ins, parentIns.Attributes(), new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), parentIns.Elements().Select(r => TransformToDelText(r)))); return(newIns); } else { return(c); } }) .ToList(); parentIns.Parent.ReplaceNodes(parentInsSiblings); } } else { var delRun = new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), TransformToDelText(run)); run.ReplaceWith(delRun); } } } else // not tracked revisions { foreach (var runToDelete in runCollection.Skip(1).ToList()) { if (runToDelete.Parent.Name == W.ins) { runToDelete.Parent.Remove(); } else { runToDelete.Remove(); } } XAttribute xs = null; if (replacement.Length > 0 && (replacement[0] == ' ' || replacement[replacement.Length - 1] == ' ')) { xs = new XAttribute(XNamespace.Xml + "space", "preserve"); } var newFirstRun = new XElement(W.r, firstRun.Element(W.rPr), new XElement(W.t, xs, replacement)); // creates a new run with proper run properties if (firstRun.Parent.Name == W.ins) { firstRun.Parent.ReplaceWith(newFirstRun); } else { firstRun.ReplaceWith(newFirstRun); // finds firstRun in its parent's list of children, unparents firstRun, } // sets newFirstRun's parent to firstRuns old parent, and inserts in the list // of children at the right place. } } } if (coalesceContent) { paragraph = WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(paragraphWithSplitRuns); } else { paragraph = paragraphWithSplitRuns; } } } return(paragraph); } var newEle = new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => { var e = n as XElement; if (e != null) { if (e.Name == W.pPr || e.Name == W.rPr) { return(e); } if (e.Name == W.r && (e.Element(W.t) != null) || e.Element(W.tab) != null) { return(e); } if (e.Name == W.ins && e.Element(W.r) != null && e.Element(W.r).Element(W.t) != null) { return(e); } var newContent = WmlSearchAndReplaceTransform(e, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent); return(newContent); } return(n); })); if (newEle.Name == W.p) { //if (newEle.Descendants(W.ins).Any()) // Console.WriteLine(); if (coalesceContent) { var newPara = CoalesceContent(newEle); return(newPara); } else { return(newEle); } } else { return(newEle); } } if (element.Name == W.ins && element.Elements(W.r).Any()) { var collectionOfCollections = element .Elements() .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)) .ToList(); var collectionOfIns = collectionOfCollections .Select(c => { if (c is IEnumerable <XElement> ) { var ix = (IEnumerable <XElement>)c; var collectionOfNewIns = ix .Select(ixc => { var newIns = new XElement(W.ins, element.Attributes(), ixc); return(newIns); }); return(collectionOfNewIns); } return(c); }) .ToList(); return(collectionOfIns); } if (element.Name == W.r && element.Elements(W.t).Any()) { var collectionOfCollections = element.Elements() .Where(e => e.Name != W.rPr) .Select(e => { if (e.Name == W.t) { string s = (string)e; IEnumerable <XElement> collectionOfSubRuns = s.Select(c => { XElement newRun = new XElement(W.r, element.Elements(W.rPr), new XElement(W.t, c == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null, c)); return(newRun); }); return(collectionOfSubRuns); } else { XElement newRun = new XElement(W.r, element.Elements(W.rPr), e); return(new [] { newRun }); } }) .ToList(); var collectionOfRuns = collectionOfCollections.SelectMany(t => t); return(collectionOfRuns); } return(new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)))); } return(node); }
private static object PmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement, Func <XElement, Match, bool> callback, ReplaceInternalInfo counter) { XElement element = node as XElement; if (element != null) { if (element.Name == A.p) { var paragraph = element; string contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate(); if (regex.IsMatch(contents)) { contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate(); if (regex.IsMatch(contents)) { XElement paragraphWithSplitRuns = new XElement(A.p, paragraph.Attributes(), paragraph.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter))); var runsTrimmed = paragraphWithSplitRuns .Descendants(A.r) .ToList(); var charsAndRuns = runsTrimmed .Select(r => { if (r.Element(A.t) != null) { return new { Ch = r.Element(A.t).Value, r, } } ; else { return new { Ch = "\x01", r, } }; }) .ToList(); var content = charsAndRuns.Select(t => t.Ch).StringConcatenate(); var alignedRuns = charsAndRuns.Select(t => t.r).ToArray(); var matchCollection = regex.Matches(content); counter.Count += matchCollection.Count; if (replacement == null) { foreach (var match in matchCollection.Cast <Match>()) { callback(paragraph, match); } } else { foreach (var match in matchCollection.Cast <Match>()) { if (callback == null || callback(paragraph, match)) { var runCollection = alignedRuns .Skip(match.Index) .Take(match.Length) .ToList(); // uses the Skip / Take special semantics of array to implement efficient finding of sub array var firstRun = runCollection.First(); // save away first run because we want the run properties runCollection.Skip(1).Remove(); // binds to Remove(this IEnumerable<XElement> elements), which is an extension // in LINQ to XML that uses snapshot semantics and removes every element from // its parent. var newFirstRun = new XElement(A.r, firstRun.Element(A.rPr), new XElement(A.t, replacement)); // creates a new run with proper run properties firstRun.ReplaceWith(newFirstRun); // finds firstRun in its parent's list of children, unparents firstRun, // sets newFirstRun's parent to firstRuns old parent, and inserts in the list // of children at the right place. } } var paragraphWithReplacedRuns = paragraphWithSplitRuns; var groupedAdjacentRunsWithIdenticalFormatting = paragraphWithReplacedRuns .Elements() .GroupAdjacent(ce => { if (ce.Name != A.r) { return("DontConsolidate"); } if (ce.Elements().Where(e => e.Name != A.rPr).Count() != 1 || ce.Element(A.t) == null) { return("DontConsolidate"); } if (ce.Element(A.rPr) == null) { return(""); } return(ce.Element(A.rPr).ToString(SaveOptions.None)); }); XElement paragraphWithConsolidatedRuns = new XElement(A.p, groupedAdjacentRunsWithIdenticalFormatting.Select(g => { if (g.Key == "DontConsolidate") { return((object)g); } string textValue = g.Select(r => r.Element(A.t).Value).StringConcatenate(); XAttribute xs = null; if (textValue.Length > 0 && (textValue[0] == ' ' || textValue[textValue.Length - 1] == ' ')) { xs = new XAttribute(XNamespace.Xml + "space", "preserve"); } return(new XElement(A.r, g.First().Elements(A.rPr), new XElement(A.t, xs, textValue))); })); paragraph = paragraphWithConsolidatedRuns; } } return(paragraph); } return(new XElement(element.Name, element.Attributes(), element.Nodes())); } if (element.Name == A.r && element.Elements(A.t).Any()) { var collectionOfRuns = element.Elements() .Where(e => e.Name != A.rPr) .Select(e => { if (e.Name == A.t) { string s = (string)e; IEnumerable <XElement> collectionOfSubRuns = s.Select(c => { XElement newRun = new XElement(A.r, element.Elements(A.rPr), new XElement(A.t, c == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null, c)); return(newRun); }); return((object)collectionOfSubRuns); } else { XElement newRun = new XElement(A.r, element.Elements(A.rPr), e); return(newRun); } }); return(collectionOfRuns); } return(new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter)))); } return(node); }
private static int ReplaceInternal(IEnumerable <XElement> content, Regex regex, string replacement, Func <XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor, bool coalesceContent) { var first = content.FirstOrDefault(); if (first == null) { return(0); } if (first.Name.Namespace == W.w) { if (!content.Any()) { return(0); } var replInfo = new ReplaceInternalInfo() { Count = 0, }; foreach (var c in content) { var newC = (XElement)WmlSearchAndReplaceTransform(c, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent); c.ReplaceNodes(newC.Nodes()); } var root = content.First().AncestorsAndSelf().Last(); var nextId = (new[] { 0 }) .Concat(root.Descendants() .Where(d => RevTrackMarkupWithId.Contains(d.Name)) .Attributes(W.id) .Select(a => (int)a) ).Max() + 1; var revTrackingWithoutId = root .DescendantsAndSelf() .Where(d => RevTrackMarkupWithId.Contains(d.Name) && d.Attribute(W.id) == null); foreach (var item in revTrackingWithoutId) { item.Add(new XAttribute(W.id, nextId++)); } var revTrackingWithDuplicateIds = root .DescendantsAndSelf() .Where(d => RevTrackMarkupWithId.Contains(d.Name)) .GroupBy(d => (int)d.Attribute(W.id)) .Where(g => g.Count() > 1) .ToList(); foreach (var group in revTrackingWithDuplicateIds) { foreach (var gc in group.Skip(1)) { gc.Attribute(W.id).Value = nextId.ToString(); nextId++; } } return(replInfo.Count); } else if (first.Name.Namespace == P.p || first.Name.Namespace == A.a) { if (trackRevisions) { throw new OpenXmlPowerToolsException("PPTX does not support revision tracking"); } var counter = new ReplaceInternalInfo() { Count = 0 }; foreach (var c in content) { var newC = (XElement)PmlSearchAndReplaceTransform(c, regex, replacement, callback, counter); c.ReplaceNodes(newC.Nodes()); } return(counter.Count); } return(0); }
private static object WmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement, Func <XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor, ReplaceInternalInfo replInfo, bool coalesceContent) { var element = node as XElement; if (element == null) { return(node); } if (element.Name == W.p) { XElement paragraph = element; string preliminaryContent = paragraph .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.r && (d.Parent == null || d.Parent.Name != W.del)) .Select(UnicodeMapper.RunToString) .StringConcatenate(); if (regex.IsMatch(preliminaryContent)) { var paragraphWithSplitRuns = new XElement(W.p, paragraph.Attributes(), paragraph.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent))); IEnumerable <XElement> runsTrimmed = paragraphWithSplitRuns .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.r && (d.Parent == null || d.Parent.Name != W.del)); var charsAndRuns = runsTrimmed .Select(r => new { Ch = UnicodeMapper.RunToString(r), r }) .ToList(); string content = charsAndRuns.Select(t => t.Ch).StringConcatenate(); XElement[] alignedRuns = charsAndRuns.Select(t => t.r).ToArray(); MatchCollection matchCollection = regex.Matches(content); replInfo.Count += matchCollection.Count; // Process Match if (replacement == null) { if (callback == null) { return(paragraph); } foreach (Match match in matchCollection.Cast <Match>()) { callback(paragraph, match); } return(paragraph); } // Process Replace foreach (Match match in matchCollection.Cast <Match>()) { if (match.Length == 0) { continue; } if ((callback != null) && !callback(paragraph, match)) { continue; } List <XElement> runCollection = alignedRuns .Skip(match.Index) .Take(match.Length) .ToList(); // uses the Skip / Take special semantics of array to implement efficient finding of sub array XElement firstRun = runCollection.First(); XElement firstRunProperties = firstRun.Elements(W.rPr).FirstOrDefault(); // save away first run properties if (trackRevisions) { if (replacement != "") { // We coalesce runs as some methods, e.g., in DocumentAssembler, // will try to find the replacement string even though they // set coalesceContent to false. string newTextValue = match.Result(replacement); List <XElement> newRuns = UnicodeMapper.StringToCoalescedRunList(newTextValue, firstRunProperties); var newIns = new XElement(W.ins, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"), newRuns); if (firstRun.Parent != null && firstRun.Parent.Name == W.ins) { firstRun.Parent.AddBeforeSelf(newIns); } else { firstRun.AddBeforeSelf(newIns); } } foreach (XElement run in runCollection) { bool isInIns = run.Parent != null && run.Parent.Name == W.ins; if (isInIns) { XElement parentIns = run.Parent; XElement grandParentParagraph = parentIns.Parent; if (grandParentParagraph != null) { if ((string)parentIns.Attributes(W.author).FirstOrDefault() == revisionTrackingAuthor) { List <XElement> parentInsSiblings = grandParentParagraph .Elements() .Where(c => c != parentIns) .ToList(); grandParentParagraph.ReplaceNodes(parentInsSiblings); } else { List <XElement> parentInsSiblings = grandParentParagraph .Elements() .Select(c => c == parentIns ? new XElement(W.ins, parentIns.Attributes(), new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"), parentIns.Elements().Select(TransformToDelText))) : c) .ToList(); grandParentParagraph.ReplaceNodes(parentInsSiblings); } } } else { var delRun = new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.UtcNow.ToString("s") + "Z"), TransformToDelText(run)); run.ReplaceWith(delRun); } } } else // not tracked revisions { foreach (XElement runToDelete in runCollection.Skip(1).ToList()) { if (runToDelete.Parent != null && runToDelete.Parent.Name == W.ins) { runToDelete.Parent.Remove(); } else { runToDelete.Remove(); } } // We coalesce runs as some methods, e.g., in DocumentAssembler, // will try to find the replacement string even though they // set coalesceContent to false. string newTextValue = match.Result(replacement); List <XElement> newRuns = UnicodeMapper.StringToCoalescedRunList(newTextValue, firstRunProperties); if (firstRun.Parent != null && firstRun.Parent.Name == W.ins) { firstRun.Parent.ReplaceWith(newRuns); } else { firstRun.ReplaceWith(newRuns); } } } return(coalesceContent ? WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(paragraphWithSplitRuns) : paragraphWithSplitRuns); } var newParagraph = new XElement(W.p, paragraph.Attributes(), paragraph.Nodes().Select(n => { var e = n as XElement; if (e == null) { return(n); } if (e.Name == W.pPr) { return(e); } if (((e.Name == W.r) && e.Elements(W.t).Any()) || e.Elements(W.tab).Any()) { return(e); } if ((e.Name == W.ins) && e.Elements(W.r).Elements(W.t).Any()) { return(e); } return(WmlSearchAndReplaceTransform(e, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)); })); return(coalesceContent ? WordprocessingMLUtil.CoalesceAdjacentRunsWithIdenticalFormatting(newParagraph) // CoalesceContent(newParagraph) : newParagraph); } if (element.Name == W.ins && element.Elements(W.r).Any()) { List <object> collectionOfCollections = element .Elements() .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)) .ToList(); List <object> collectionOfIns = collectionOfCollections .Select(c => { var elements = c as IEnumerable <XElement>; return(elements != null ? elements.Select(ixc => new XElement(W.ins, element.Attributes(), ixc)) : c); }) .ToList(); return(collectionOfIns); } if (element.Name == W.r) { return(element.Elements() .Where(e => e.Name != W.rPr) .Select(e => e.Name == W.t ? ((string)e).Select(c => new XElement(W.r, element.Elements(W.rPr), new XElement(W.t, XmlUtil.GetXmlSpaceAttribute(c), c))) : new[] { new XElement(W.r, element.Elements(W.rPr), e) }) .SelectMany(t => t)); } return(new XElement(element.Name, element.Attributes(), element.Nodes() .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo, coalesceContent)))); }
private static object PmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement, Func<XElement, Match, bool> callback, ReplaceInternalInfo counter) { XElement element = node as XElement; if (element != null) { if (element.Name == A.p) { var paragraph = element; string contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate(); if (regex.IsMatch(contents)) { contents = element.Descendants(A.t).Select(t => (string)t).StringConcatenate(); if (regex.IsMatch(contents)) { XElement paragraphWithSplitRuns = new XElement(A.p, paragraph.Attributes(), paragraph.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter))); var runsTrimmed = paragraphWithSplitRuns .Descendants(A.r) .ToList(); var charsAndRuns = runsTrimmed .Select(r => { if (r.Element(A.t) != null) return new { Ch = r.Element(A.t).Value, r, }; else return new { Ch = "\x01", r, }; }) .ToList(); var content = charsAndRuns.Select(t => t.Ch).StringConcatenate(); var alignedRuns = charsAndRuns.Select(t => t.r).ToArray(); var matchCollection = regex.Matches(content); counter.Count += matchCollection.Count; if (replacement == null) { foreach (var match in matchCollection.Cast<Match>()) callback(paragraph, match); } else { foreach (var match in matchCollection.Cast<Match>()) { if (callback == null || callback(paragraph, match)) { var runCollection = alignedRuns .Skip(match.Index) .Take(match.Length) .ToList(); // uses the Skip / Take special semantics of array to implement efficient finding of sub array var firstRun = runCollection.First(); // save away first run because we want the run properties runCollection.Skip(1).Remove(); // binds to Remove(this IEnumerable<XElement> elements), which is an extension // in LINQ to XML that uses snapshot semantics and removes every element from // its parent. var newFirstRun = new XElement(A.r, firstRun.Element(A.rPr), new XElement(A.t, replacement)); // creates a new run with proper run properties firstRun.ReplaceWith(newFirstRun); // finds firstRun in its parent's list of children, unparents firstRun, // sets newFirstRun's parent to firstRuns old parent, and inserts in the list // of children at the right place. } } var paragraphWithReplacedRuns = paragraphWithSplitRuns; var groupedAdjacentRunsWithIdenticalFormatting = paragraphWithReplacedRuns .Elements() .GroupAdjacent(ce => { if (ce.Name != A.r) return "DontConsolidate"; if (ce.Elements().Where(e => e.Name != A.rPr).Count() != 1 || ce.Element(A.t) == null) return "DontConsolidate"; if (ce.Element(A.rPr) == null) return ""; return ce.Element(A.rPr).ToString(SaveOptions.None); }); XElement paragraphWithConsolidatedRuns = new XElement(A.p, groupedAdjacentRunsWithIdenticalFormatting.Select(g => { if (g.Key == "DontConsolidate") return (object)g; string textValue = g.Select(r => r.Element(A.t).Value).StringConcatenate(); XAttribute xs = null; if (textValue.Length > 0 && (textValue[0] == ' ' || textValue[textValue.Length - 1] == ' ')) xs = new XAttribute(XNamespace.Xml + "space", "preserve"); return new XElement(A.r, g.First().Elements(A.rPr), new XElement(A.t, xs, textValue)); })); paragraph = paragraphWithConsolidatedRuns; } } return paragraph; } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter))); } if (element.Name == A.r && element.Elements(A.t).Any()) { var collectionOfRuns = element.Elements() .Where(e => e.Name != A.rPr) .Select(e => { if (e.Name == A.t) { string s = (string)e; IEnumerable<XElement> collectionOfSubRuns = s.Select(c => { XElement newRun = new XElement(A.r, element.Elements(A.rPr), new XElement(A.t, c == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null, c)); return newRun; }); return (object)collectionOfSubRuns; } else { XElement newRun = new XElement(A.r, element.Elements(A.rPr), e); return newRun; } }); return collectionOfRuns; } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => PmlSearchAndReplaceTransform(n, regex, replacement, callback, counter))); } return node; }
private static object WmlSearchAndReplaceTransform(XNode node, Regex regex, string replacement, Func<XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor, ReplaceInternalInfo replInfo) { XElement element = node as XElement; if (element != null) { if (element.Name == W.p) { var paragraph = element; string contents = element .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.t) .Select(t => (string)t) .StringConcatenate(); if (regex.IsMatch(contents)) { contents = element .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.t) .Select(t => (string)t) .StringConcatenate(); if (regex.IsMatch(contents)) { XElement paragraphWithSplitRuns = new XElement(W.p, paragraph.Attributes(), paragraph.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo))); var runsTrimmed = paragraphWithSplitRuns .DescendantsTrimmed(W.txbxContent) .Where(d => d.Name == W.r) .ToList(); var charsAndRuns = runsTrimmed .Select(r => { if (r.Element(W.t) != null) return new { Ch = r.Element(W.t).Value, r, }; else return new { Ch = "\x01", r, }; }) .ToList(); var content = charsAndRuns.Select(t => t.Ch).StringConcatenate(); var alignedRuns = charsAndRuns.Select(t => t.r).ToArray(); var matchCollection = regex.Matches(content); replInfo.Count += matchCollection.Count; if (replacement == null) { if (callback != null) { foreach (var match in matchCollection.Cast<Match>()) callback(paragraph, match); } } else { foreach (var match in matchCollection.Cast<Match>()) { if (match.Length == 0) continue; if (callback == null || callback(paragraph, match)) { var runCollection = alignedRuns .Skip(match.Index) .Take(match.Length) .ToList(); // uses the Skip / Take special semantics of array to implement efficient finding of sub array var firstRun = runCollection.First(); var firstRunProperties = firstRun.Elements(W.rPr).FirstOrDefault(); // save away first run properties if (trackRevisions) { if (replacement != null && replacement != "") { var newIns = new XElement(W.ins, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), new XElement(W.r, firstRunProperties, new XElement(W.t, replacement))); if (firstRun.Parent.Name == W.ins) firstRun.Parent.AddBeforeSelf(newIns); else firstRun.AddBeforeSelf(newIns); } foreach (var run in runCollection) { var isInIns = run.Parent.Name == W.ins; if (isInIns) { var parentIns = run.Parent; if ((string)parentIns.Attributes(W.author).FirstOrDefault() == revisionTrackingAuthor) { var parentInsSiblings = parentIns .Parent .Elements() .Where(c => c != parentIns) .ToList(); parentIns.Parent.ReplaceNodes(parentInsSiblings); } else { var parentInsSiblings = parentIns .Parent .Elements() .Select(c => { if (c == parentIns) { var newIns = new XElement(W.ins, parentIns.Attributes(), new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), parentIns.Elements().Select(r => TransformToDelText(r)))); return newIns; } else { return c; } }) .ToList(); parentIns.Parent.ReplaceNodes(parentInsSiblings); } } else { var delRun = new XElement(W.del, new XAttribute(W.author, revisionTrackingAuthor), new XAttribute(W.date, DateTime.Now.ToString("s") + "Z"), TransformToDelText(run)); run.ReplaceWith(delRun); } } } else // not tracked revisions { foreach (var runToDelete in runCollection.Skip(1).ToList()) { if (runToDelete.Parent.Name == W.ins) runToDelete.Parent.Remove(); else runToDelete.Remove(); } XAttribute xs = null; if (replacement.Length > 0 && (replacement[0] == ' ' || replacement[replacement.Length - 1] == ' ')) xs = new XAttribute(XNamespace.Xml + "space", "preserve"); var newFirstRun = new XElement(W.r, firstRun.Element(W.rPr), new XElement(W.t, xs, replacement)); // creates a new run with proper run properties if (firstRun.Parent.Name == W.ins) firstRun.Parent.ReplaceWith(newFirstRun); else firstRun.ReplaceWith(newFirstRun); // finds firstRun in its parent's list of children, unparents firstRun, // sets newFirstRun's parent to firstRuns old parent, and inserts in the list // of children at the right place. } } } paragraph = CoalesceContent(paragraphWithSplitRuns); } } return paragraph; } var newEle = new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => { var e = n as XElement; if (e != null) { if (e.Name == W.pPr || e.Name == W.rPr) return e; if (e.Name == W.r && (e.Element(W.t) != null) || e.Element(W.tab) != null) return e; if (e.Name == W.ins && e.Element(W.r) != null && e.Element(W.r).Element(W.t) != null) return e; var newContent = WmlSearchAndReplaceTransform(e, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo); return newContent; } return n; })); if (newEle.Name == W.p) { //if (newEle.Descendants(W.ins).Any()) // Console.WriteLine(); var newPara = CoalesceContent(newEle); return newPara; } else { return newEle; } } if (element.Name == W.ins && element.Elements(W.r).Any()) { var collectionOfCollections = element .Elements() .Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo)) .ToList(); var collectionOfIns = collectionOfCollections .Select(c => { if (c is IEnumerable<XElement>) { var ix = (IEnumerable<XElement>)c; var collectionOfNewIns = ix .Select(ixc => { var newIns = new XElement(W.ins, element.Attributes(), ixc); return newIns; }); return collectionOfNewIns; } return c; }) .ToList(); return collectionOfIns; } if (element.Name == W.r && element.Elements(W.t).Any()) { var collectionOfCollections = element.Elements() .Where(e => e.Name != W.rPr) .Select(e => { if (e.Name == W.t) { string s = (string)e; IEnumerable<XElement> collectionOfSubRuns = s.Select(c => { XElement newRun = new XElement(W.r, element.Elements(W.rPr), new XElement(W.t, c == ' ' ? new XAttribute(XNamespace.Xml + "space", "preserve") : null, c)); return newRun; }); return collectionOfSubRuns; } else { XElement newRun = new XElement(W.r, element.Elements(W.rPr), e); return new [] { newRun }; } }) .ToList(); var collectionOfRuns = collectionOfCollections.SelectMany(t => t); return collectionOfRuns; } return new XElement(element.Name, element.Attributes(), element.Nodes().Select(n => WmlSearchAndReplaceTransform(n, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo))); } return node; }
private static int ReplaceInternal(IEnumerable<XElement> content, Regex regex, string replacement, Func<XElement, Match, bool> callback, bool trackRevisions, string revisionTrackingAuthor) { var first = content.FirstOrDefault(); if (first == null) return 0; if (first.Name.Namespace == W.w) { if (! content.Any()) return 0; var replInfo = new ReplaceInternalInfo() { Count = 0, }; foreach (var c in content) { var newC = (XElement)WmlSearchAndReplaceTransform(c, regex, replacement, callback, trackRevisions, revisionTrackingAuthor, replInfo); c.ReplaceNodes(newC.Nodes()); } var root = content.First().AncestorsAndSelf().Last(); var nextId = (new[] { 0 }) .Concat(root.Descendants() .Where(d => RevTrackMarkupWithId.Contains(d.Name)) .Attributes(W.id) .Select(a => (int)a) ).Max() + 1; var revTrackingWithoutId = root .DescendantsAndSelf() .Where(d => RevTrackMarkupWithId.Contains(d.Name) && d.Attribute(W.id) == null); foreach (var item in revTrackingWithoutId) item.Add(new XAttribute(W.id, nextId++)); var revTrackingWithDuplicateIds = root .DescendantsAndSelf() .Where(d => RevTrackMarkupWithId.Contains(d.Name)) .GroupBy(d => (int)d.Attribute(W.id)) .Where(g => g.Count() > 1) .ToList(); foreach (var group in revTrackingWithDuplicateIds) { foreach (var gc in group.Skip(1)) { gc.Attribute(W.id).Value = nextId.ToString(); nextId++; } } return replInfo.Count; } else if (first.Name.Namespace == P.p || first.Name.Namespace == A.a) { if (trackRevisions) throw new OpenXmlPowerToolsException("PPTX does not support revision tracking"); var counter = new ReplaceInternalInfo() { Count = 0 }; foreach (var c in content) { var newC = (XElement)PmlSearchAndReplaceTransform(c, regex, replacement, callback, counter); c.ReplaceNodes(newC.Nodes()); } return counter.Count; } return 0; }