I've been using some code I found here, to try and change the case of text in a flow document. It changes the text properly however all the formatting is lost (bold, italics, etc) and when I save the document to a XML file all the text ends up in the first Run of the document and all the other Run's are empty.
private void ChangeCase()
{
TextPointer start = mergedDocument.ContentStart;
TextPointer end = mergedDocument.ContentEnd;
List<TextRange> textToChange = SplitToTextRanges(start, end);
ChangeCaseToAllRanges(textToChange);
}
private List<TextRange> SplitToTextRanges(TextPointer start, TextPointer end)
{
List<TextRange> textToChange = new List<TextRange>();
var previousPointer = start;
for (var pointer = start; (pointer != null && pointer.CompareTo(end) <= 0); pointer = pointer.GetPositionAtOffset(1, LogicalDirection.Forward))
{
var contextAfter = pointer.GetPointerContext(LogicalDirection.Forward);
var contextBefore = pointer.GetPointerContext(LogicalDirection.Backward);
if (contextBefore != TextPointerContext.Text && contextAfter == TextPointerContext.Text)
{
previousPointer = pointer;
}
if (contextBefore == TextPointerContext.Text && contextAfter != TextPointerContext.Text && previousPointer != pointer)
{
textToChange.Add(new TextRange(previousPointer, pointer));
previousPointer = null;
}
}
textToChange.Add(new TextRange(previousPointer ?? end, end));
return textToChange;
}
private void ChangeCaseToAllRanges(List<TextRange> textToChange)
{
Func<string, string> caseChanger;
ComboBoxItem cbi = cb_Case.SelectedItem as ComboBoxItem;
var textInfo = CultureInfo.CurrentUICulture.TextInfo;
if (cbi == null || (string)cbi.Tag == "none")
{
return;
}
else if((string)cbi.Tag == "title")
{
caseChanger = (text) => textInfo.ToTitleCase(text);
}
else if ((string)cbi.Tag == "upper")
{
caseChanger = (text) => textInfo.ToUpper(text);
}
else if ((string)cbi.Tag == "lower")
{
caseChanger = (text) => textInfo.ToLower(text);
}
else
return;
foreach (var range in textToChange)
{
if (!range.IsEmpty && !string.IsNullOrWhiteSpace(range.Text))
{
System.Diagnostics.Debug.WriteLine("Casing: " + range.Text);
System.Diagnostics.Debug.WriteLine("\tat: " +
range.Start.GetOffsetToPosition(mergedDocument.ContentStart) +
" ," +
range.End.GetOffsetToPosition(mergedDocument.ContentStart));
range.Text = caseChanger(range.Text);
}
}
}
I can't see any reason why this code isn't working properly. It looks like the textpointers in the textrange object are being redirected to the beginning of the document.
When setting TextRange.Text, it first deletes the selection by telling the TextContainer (the FlowDocument) to delete that content. If that content happened to be an entire Inline with your styling dependency properties then goodbye! So, not only does it get unstyled text, but it sets it
Since you want to keep your existing inline objects you can iterate through the entire FlowDocument to find them and set their text property.
Here is a helper method that only supports Paragraphs and finds all inlines in the entire selection (this logic is a lot simpler if you are always doing Document.ContentStart and Document.ContentEnd). You can expand this to include the inlines inside of Lists, ListItems, and Hyperlinks if you need to (by following a similar pattern).
You should then be able to set the Text property on each of these inlines.
List<Inline> GetInlines(TextRange selection)
{
var inlines = new List<Inline>();
foreach (var block in Document.Blocks.Where(x => selection.Start.CompareTo(x.ContentEnd) < 0 && selection.End.CompareTo(x.ContentStart) > 0))
{
var paragraph = block as Paragraph;
if (paragraph != null)
{
inlines.AddRange(paragraph.Inlines.Where(x => selection.Start.CompareTo(x.ContentEnd) < 0 && selection.End.CompareTo(x.ContentStart) > 0));
}
}
return inlines;
Edit: You will want to cast these to Run or Span to access the text property. You can even just remove Inline and get these types (probably just Run).