private void ReplaceRuns(OpenXmlCompositeElement par, INodeProvider nodeProvider) { FixNakedText(par, nodeProvider); string text = par.ChildElements.Where(a => nodeProvider.IsRun(a)).ToString(r => nodeProvider.GetText(r), ""); var matches = TemplateUtils.KeywordsRegex.Matches(text).Cast <Match>().ToList(); if (matches.Any()) { List <ElementInfo> infos = GetElementInfos(par.ChildElements, nodeProvider); par.RemoveAllChildren(); var stack = new Stack <ElementInfo>(infos.AsEnumerable().Reverse()); foreach (var m in matches) { var interval = new Interval <int>(m.Index, m.Index + m.Length); // [Before][Start][Ignore][Ignore][End]...[Remaining] // [ Match ] ElementInfo start = stack.Pop(); //Start while (start.Interval.Max <= interval.Min) //Before { par.Append(start.Element); start = stack.Pop(); } var startRun = (OpenXmlCompositeElement)nodeProvider.CastRun(start.Element); if (start.Interval.Min < interval.Min) { var firstRunPart = nodeProvider.NewRun( (OpenXmlCompositeElement?)nodeProvider.GetRunProperties(startRun)?.CloneNode(true), start.Text !.Substring(0, m.Index - start.Interval.Min), SpaceProcessingModeValues.Preserve ); par.Append(firstRunPart); } par.Append(new MatchNode(nodeProvider, m) { RunProperties = (OpenXmlCompositeElement?)nodeProvider.GetRunProperties(startRun)?.CloneNode(true) }); ElementInfo end = start; while (end.Interval.Max < interval.Max) //Ignore { end = stack.Pop(); } if (interval.Max < end.Interval.Max) //End { var endRun = (OpenXmlCompositeElement)end.Element; var textPart = end.Text !.Substring(interval.Max - end.Interval.Min); var endRunPart = nodeProvider.NewRun( nodeProvider.GetRunProperties(startRun)?.Let(r => (OpenXmlCompositeElement)r.CloneNode(true)), textPart, SpaceProcessingModeValues.Preserve ); stack.Push(new ElementInfo(endRunPart, textPart) { Interval = new Interval <int>(interval.Max, end.Interval.Max) }); } } while (!stack.IsEmpty()) //Remaining { var pop = stack.Pop(); par.Append(pop.Element); } } }