Text Manipulation Keeping or Replacing the Underlying Formatting in C#

evaluating Aspose for a migration from NetOfficeFW, so far mostly going well.
the one issue we’re coming across is text manipulation.

we have a number of text boxes which contains some text which we want to replace, either keeping or replacing the underlying formatting - annoyingly the text doesn’t always fit within an Aspose ‘portion’ so simply working through the portions and doing .ReplaceFirst is sometimes missing substitutions.

in NetOfficeFW it was possible to access a TextRange and manipulate that - changing the content/formatting independent of the existing portions. This makes text substitutions (including creating a second textframe and inserting that into the middle of an existing one) easy - are there similar capabilities - either baked into Apsode.Slides oras existing helper code/sample?

@jcath,
Thank you for describing the issue. In order to better understand the issue and help you accordingly, we need some additional data. Please share the following:

  • sample source presentation
  • code example that does not give the expected result
  • presentation with the expected result

You can zip the presentation files and upload them here.

As we are missing the ability to select a TextRange, and this example has no code (feel free to steal mine to flesh it out!), the following is my attempt to solve the problem.

This allows me to take an existing AutoShape and replace some text with some other text, and apply some formatting over-rides (this would need to be more fully fleshed out, but for the PoC is enough).

In the code below I’m taking the text “[this block]” from somewhere in the original object and replacing it with “This Is Nicer”, making it italic, and changing the color.

The problem occurs if, for instance the “[” and “]” are normal font weight and the “this block” are italic in the source object… because the manipulation occurs at a portion (aka “run” in the Office terminology) the text matching doesn’t work.

In NetOfficeFW (for instance) being able to select an arbitrary range of text based on the character positions and manipulate it (and the underlying font characteristics on a character by character basis) to change/insert text makes this less challenging.

I’m hoping I’m missing something obvious in the documentation and there’s an easier way to do this (especially as I now need to go and extend this to allow me to replace the from_str with the entire contents of a different text shape)

IAutoShape tshape = (IAutoShape)shape;
TextReplace(tshape, "[this block]", "This Is Nicer", italic: NullableBool.True, clr: Color.FromArgb(0xff, 0xD0, 0x15, 0x60));
                               

public static void TextReplace(IAutoShape tshape, string from_str, string to_str, NullableBool bold = NullableBool.NotDefined, NullableBool italic = NullableBool.NotDefined, Nullable<Color> clr = null)
    {
        int portion_index = -1;
        while (portion_index++ < tshape.TextFrame.Paragraphs[0].Portions.Count - 1)
        {
            IPortion port = tshape.TextFrame.Paragraphs[0].Portions[portion_index];
            if (port.Text.IndexOf(from_str) > -1)
            {
                Portion newPort = ClonePortion((Portion)port);
                Portion newPortAfter = ClonePortion((Portion)port);

                newPort.Text = to_str;
                newPort.PortionFormat.FontBold = (bold == NullableBool.NotDefined ? newPort.PortionFormat.FontBold : bold);
                newPort.PortionFormat.FontItalic = (italic == NullableBool.NotDefined ? newPort.PortionFormat.FontItalic : italic);
                newPort.PortionFormat.FillFormat.SolidFillColor.Color = (clr == null ? newPort.PortionFormat.FillFormat.SolidFillColor.Color : (Color)clr);
                newPortAfter.Text = port.Text.Substring(port.Text.IndexOf(from_str) + from_str.Length);
                port.Text = port.Text.Left(port.Text.IndexOf(from_str));
                tshape.TextFrame.Paragraphs[0].Portions.Insert(portion_index + 1, newPort);
                tshape.TextFrame.Paragraphs[0].Portions.Insert(portion_index + 2, newPortAfter);
                portion_index = portion_index + 2;  // skip the insertions
            }
        }
    }
public static Portion ClonePortion(Portion port)
    {

        Portion tempPortion = new Portion();
        PortionFormat tempPF = (PortionFormat)tempPortion.PortionFormat;
        PortionFormat portData = (PortionFormat)port.PortionFormat;

        // use PortionFormat to set values
        // get top level properties
        var properties = from p in typeof(PortionFormat).GetProperties()
                         where p.CanRead && p.CanWrite
                         select p;

        foreach (var property in properties)
        {
            property.SetValue(tempPF, property.GetValue(portData));
        }

        // backfill the nested properties
        tempPF.FillFormat.FillType = portData.FillFormat.FillType;
        tempPF.FillFormat.SolidFillColor.Color = portData.FillFormat.SolidFillColor.Color;
        tempPF.FontUnderline = portData.FontUnderline;
        tempPF.UnderlineFillFormat.FillType = portData.UnderlineFillFormat.FillType;
        tempPF.UnderlineFillFormat.SolidFillColor.Color = portData.UnderlineFillFormat.SolidFillColor.Color;

        return tempPortion;
    }

@jcath,
I think I understand the problem you have encountered and added a ticket with ID SLIDESNET-43547 to our issue-tracking system. Our development team will consider implementing such a feature or suggest you a solution. We will inform you of any progress.

thanks!
I do find the TextRange in NetOfficeFW (and the Office OLE API) to be very useful, so hopefully there’s a way to bring that to Aspose Slides as well. Meantime the code above should let me continue to evaluate.
and if the sample is useful for the PortionCopy feel free to grab it :wink:

@jcath,
I will be waiting for any update/solution from our developers and inform you.

The issues you found earlier (filed as SLIDESNET-43547) have been fixed in Aspose.Slides for .NET 23.2 (ZIP, MSI).
You can check all fixes on the Release Notes page.
You can also find the latest version of our library on the Product Download page.