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);
        }
Esempio n. 4
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;
 }