Browse our Products

Aspose.Words for Python via .NET 23.5 Release Notes

Major Features

There are 174 improvements and fixes in this regular monthly release. The most notable are:

  • Provided the feature to get and modify chart series data.
  • Implemented support for text wrapping in headers/footers.
  • Fixed rendering of MathML formula with embedded images.
  • Added an ability to remove digital signatures from ODT documents.
  • Added public properties to obtain base and ruby text of phonetic guide Run.

Full List of Issues Covering all Changes in this Release

Public API and Backward Incompatible Changes

This section lists public API changes that were introduced in Aspose.Words for Python via .NET 23.5. It includes not only new and obsoleted public methods, but also a description of any changes in the behavior behind the scenes in Aspose.Words for Python via .NET which may affect existing code. Any behavior introduced that could be seen as regression and modifies the existing behavior is especially important and is documented here.

Added ability to remove digital signatures from ODT

Implemented removing digital signatures from ODT using aspose.words.digitalsignatures.DigitalSignatureUtil.remove_all_signatures method.

DigitalSignatureUtil.remove_all_signatures("in.odt", "out.odt");

Added new public property FindReplaceOptions.IgnoreShapes

The following public property was added to aspose.words.replacing.FindReplaceOptions class:

@property
def ignore_shapes(self) -> bool:
    '''Gets or sets a boolean value indicating either to ignore shapes within a text.
    
    The default value is ``False``.'''
    ...

@ignore_shapes.setter
def ignore_shapes(self, value: bool):
    ...
from aspose.words import DocumentBuilder
from aspose.words.drawing import ShapeType
from aspose.words.replacing import FindReplaceOptions

builder = DocumentBuilder()
builder.write("123")
builder.insert_shape(ShapeType.BALLOON, 200, 200)
builder.write("456")

options = FindReplaceOptions()
options.ignore_shapes = True

builder.document.range.replace("123456", "789", options=options)
print(builder.document.get_text().encode('utf-8', 'ignore'))

# This code produces the following output:
# b'789\r\x0c'

Added new public property Forms2OleControl.group_name

The following public property was added to aspose.words.drawing.ole.Forms2OleControl class:

@property
def group_name(self) -> str:
    '''Gets or sets a string that specifies a group of mutually exclusive controls.
    The default value is an empty string.'''
    ...

@group_name.setter
def group_name(self, value: str):
    ...
# Assume there is a shape with Forms2OleControl in document.
from aspose.words import Document, NodeType

doc = Document("input.docx")
shape = doc.get_child_nodes(NodeType.SHAPE, True)[0].as_shape()

control = shape.ole_format.ole_control.as_forms2_ole_control()

if control is not None:
    print(f"Control group name is: {control.group_name}")
    control.group_name = "newGroup"

Added new public property PdfSaveOptions.export_paragraph_graphics_to_artifact

The following public property was added to aspose.words.saving.PdfSaveOptions class:

@property
def export_paragraph_graphics_to_artifact(self) -> bool:
    '''Gets or sets a value determining whether a paragraph graphic should be marked as an artifact.
    
    Default value is ``False`` and paragraph graphics (underlines, text emphasis, etc.)
    will be marked as "Span" in the logical structure of the document.
    
    When the value is ``True`` the paragraph graphics will be marked as "Artifact".
    
    This value is ignored when :attr:`PdfSaveOptions.export_document_structure` is ``False``.'''
    ...

@export_paragraph_graphics_to_artifact.setter
def export_paragraph_graphics_to_artifact(self, value: bool):
    ...
doc = Document(file_name)

save_options = PdfSaveOptions()
save_options.export_paragraph_graphics_to_artifact = True
doc.save(output_file_name, save_options)

Added public properties to obtain base and ruby text of phonetic guide Run

The following public property was added to aspose.words.Run class:

@property
def phonetic_guide(self) -> aspose.words.PhoneticGuide:
    '''Gets a :attr:`Run.phonetic_guide` object.'''
    ...

Added the following public class into aspose.words namespace:

class PhoneticGuide:
    '''Represents Phonetic Guide.'''
    
    @property
    def base_text(self) -> str:
        '''Gets base text of the phonetic guide.'''
        ...
    
    @property
    def ruby_text(self) -> str:
        '''Gets ruby text of the phonetic guide.'''
        ...
from aspose.words import Document, NodeType

doc = Document("DocWithRuby.docx")
run = doc.first_section.body.first_paragraph.get_child(NodeType.RUN, 0, False).as_run();

if run is not None:
    print(run.phonetic_guide.base_text)
    print(run.phonetic_guide.ruby_text)

Added public property ChartSeries.series_type of new ChartSeriesType enum type

The series_type property has been added to the ChartSeries class:

@property
    def series_type(self) -> aspose.words.drawing.charts.ChartSeriesType:
        '''Gets the type of this chart series.'''

The definition of the added ChartSeriesType enum type:

class ChartSeriesType:
    '''Specifies a type of a chart series.'''
    
    AREA: int
    AREA_STACKED: int
    AREA_PERCENT_STACKED: int
    AREA_3D: int
    AREA_3D_STACKED: int
    AREA_3D_PERCENT_STACKED: int
    BAR: int
    BAR_STACKED: int
    BAR_PERCENT_STACKED: int
    BAR_3D: int
    BAR_3D_STACKED: int
    BAR_3D_PERCENT_STACKED: int
    BUBBLE: int
    BUBBLE_3D: int
    COLUMN: int
    COLUMN_STACKED: int
    COLUMN_PERCENT_STACKED: int
    COLUMN_3D: int
    COLUMN_3D_STACKED: int
    COLUMN_3D_PERCENT_STACKED: int
    COLUMN_3D_CLUSTERED: int
    DOUGHNUT: int
    LINE: int
    LINE_STACKED: int
    LINE_PERCENT_STACKED: int
    LINE_3D: int
    PIE: int
    PIE_3D: int
    PIE_OF_BAR: int
    PIE_OF_PIE: int
    RADAR: int
    SCATTER: int
    STOCK: int
    SURFACE: int
    SURFACE_3D: int
    TREEMAP: int
    SUNBURST: int
    HISTOGRAM: int
    PARETO: int
    PARETO_LINE: int
    BOX_AND_WHISKER: int
    WATERFALL: int
    FUNNEL: int
    REGION_MAP: int
from aspose.words import Document, NodeType
from aspose.words.drawing.charts import ChartSeriesType

doc = Document("ComboChart.docx")

shape = doc.get_child_nodes(NodeType.SHAPE, True)[0].as_shape()
chart = shape.chart

# Remove all series of the Column type.
for i in range(chart.series.count,0, -1):
    if chart.series[i].series_type == ChartSeriesType.COLUMN:
        chart.series.remove_at(i)

# Add a new series.
chart.series.add("New Series",["Category 1", "Category 2", "Category 3", "Category 4"], [5.6, 7.1, 2.9, 8.9])
doc.save("out.docx")

Implemented ability to get and modify chart series data

The following changes have been implemented:

Added new classes: ChartXValue, ChartYValue, ChartXValueCollection, ChartYValueCollection, BubbleSizeCollection, ChartMultilevelValue, and new enum types: ChartXValueType, ChartYValueType.

Added new properties and methods to the ChartSeries class.

class ChartSeries:
    '''Represents chart series properties.
    To learn more, visit the `Working with Charts <https://docs.aspose.com/words/net/working-with-charts/>` documentation article.'''
    
    @overload
    def add(self, x_value: aspose.words.drawing.charts.ChartXValue) -> None:
        '''Adds the specified X value to the chart series. If the series supports Y values and bubble sizes, they will
        be empty for the X value.'''
        ...
    
    @overload
    def add(self, x_value: aspose.words.drawing.charts.ChartXValue, y_value: aspose.words.drawing.charts.ChartYValue) -> None:
        '''Adds the specified X and Y values to the chart series.'''
        ...
    
    @overload
    def add(self, x_value: aspose.words.drawing.charts.ChartXValue, y_value: aspose.words.drawing.charts.ChartYValue, bubble_size: float) -> None:
        '''Adds the specified X value, Y value and bubble size to the chart series.'''
        ...
    
    @overload
    def insert(self, index: int, x_value: aspose.words.drawing.charts.ChartXValue) -> None:
        '''Inserts the specified X value into the chart series at the specified index. If the series supports Y values
        and bubble sizes, they will be empty for the X value.
        
        The corresponding data point with default formatting will be inserted into the data point collection. And,
        if data labels are displayed, the corresponding data label with default formatting will be inserted too.'''
        ...
    
    @overload
    def insert(self, index: int, x_value: aspose.words.drawing.charts.ChartXValue, y_value: aspose.words.drawing.charts.ChartYValue) -> None:
        '''Inserts the specified X and Y values into the chart series at the specified index.
        
        The corresponding data point with default formatting will be inserted into the data point collection. And,
        if data labels are displayed, the corresponding data label with default formatting will be inserted too.'''
        ...
    
    @overload
    def insert(self, index: int, x_value: aspose.words.drawing.charts.ChartXValue, y_value: aspose.words.drawing.charts.ChartYValue, bubble_size: float) -> None:
        '''Inserts the specified X value, Y value and bubble size into the chart series at the specified index.
        
        The corresponding data point with default formatting will be inserted into the data point collection. And,
        if data labels are displayed, the corresponding data label with default formatting will be inserted too.'''
        ...
    
    def remove(self, index: int) -> None:
        '''Removes the X value, Y value, and bubble size, if supported, from the chart series at the specified index.
        The corresponding data point and data label are also removed.'''
        ...
    
    def clear(self) -> None:
        '''Removes all data values from the chart series. Format of all individual data points and data labels is cleared.'''
        ...
    
    def clear_values(self) -> None:
        '''Removes all data values from the chart series with preserving the format of the data points and data labels.'''
        ...
    
    @property
    def explosion(self) -> int:
        '''Specifies the amount the data point shall be moved from the center of the pie.
        Can be negative, negative means that property is not set and no explosion should be applied.
        Applies only to Pie charts.'''
        ...
    
    @explosion.setter
    def explosion(self, value: int):
        ...
    
    @property
    def x_values(self) -> aspose.words.drawing.charts.ChartXValueCollection:
        '''Gets a collection of X values for this chart series.'''
        ...
    
    @property
    def y_values(self) -> aspose.words.drawing.charts.ChartYValueCollection:
        '''Gets a collection of Y values for this chart series.'''
        ...
    
    @property
    def bubble_sizes(self) -> aspose.words.drawing.charts.BubbleSizeCollection:
        '''Gets a collection of bubble sizes for this chart series.'''
        ...

class ChartXValueType:
    '''Allows to specify type of an X value of a chart series.'''
    
    STRING: int
    DOUBLE: int
    DATE_TIME: int
    TIME: int
    MULTILEVEL: int

class ChartYValueType:
    '''Allows to specify type of an Y value of a chart series.'''
    
    DOUBLE: int
    DATE_TIME: int
    TIME: int

class ChartXValue:
    '''Represents an X value for a chart series.
    
    This class contains a number of static methods for creating an X value of a particular type. The
    :attr:`ChartXValue.value_type` property allows you to determine the type of an existing X value.
    
    All non-null X values of a chart series must be of the same :class:`ChartXValueType` type.'''
    
    @staticmethod
    def from_string(self, value: str) -> aspose.words.drawing.charts.ChartXValue:
        '''Creates a :class:`ChartXValue` instance of the :attr:`ChartXValueType.STRING` type.'''
        ...
    
    @staticmethod
    def from_double(self, value: float) -> aspose.words.drawing.charts.ChartXValue:
        '''Creates a :class:`ChartXValue` instance of the :attr:`ChartXValueType.DOUBLE` type.'''
        ...
    
    @staticmethod
    def from_date_time(self, value: datetime.datetime) -> aspose.words.drawing.charts.ChartXValue:
        '''Creates a :class:`ChartXValue` instance of the :attr:`ChartXValueType.DATE_TIME` type.'''
        ...
    
    @staticmethod
    def from_time_span(self, value: datetime.timespan) -> aspose.words.drawing.charts.ChartXValue:
        '''Creates a :class:`ChartXValue` instance of the :attr:`ChartXValueType.TIME` type.'''
        ...
    
    @staticmethod
    def from_multilevel_value(self, value: aspose.words.drawing.charts.ChartMultilevelValue) -> aspose.words.drawing.charts.ChartXValue:
        '''Creates a :class:`ChartXValue` instance of the :attr:`ChartXValueType.MULTILEVEL` type.'''
        ...
    
    @property
    def value_type(self) -> aspose.words.drawing.charts.ChartXValueType:
        '''Gets the type of the X value stored in the object.'''
        ...
    
    @property
    def string_value(self) -> str:
        '''Gets the stored string value.'''
        ...
    
    @property
    def double_value(self) -> float:
        '''Gets the stored numeric value.'''
        ...
    
    @property
    def date_time_value(self) -> datetime.datetime:
        '''Gets the stored datetime value.'''
        ...
    
    @property
    def time_value(self) -> datetime.timespan:
        '''Gets the stored time value.'''
        ...
    
    @property
    def multilevel_value(self) -> aspose.words.drawing.charts.ChartMultilevelValue:
        '''Gets the stored multilevel value.'''
        ...

class ChartYValue:
    '''Represents an Y value for a chart series.
    
    This class contains a number of static methods for creating an Y value of a particular type. The
    :attr:`ChartYValue.value_type` property allows you to determine the type of an existing Y value.
    
    All non-null Y values of a chart series must be of the same :class:`ChartYValueType` type.'''
    
    @staticmethod
    def from_double(self, value: float) -> aspose.words.drawing.charts.ChartYValue:
        '''Creates a :class:`ChartYValue` instance of the :attr:`ChartYValueType.DOUBLE` type.'''
        ...
    
    @staticmethod
    def from_date_time(self, value: datetime.datetime) -> aspose.words.drawing.charts.ChartYValue:
        '''Creates a :class:`ChartYValue` instance of the :attr:`ChartYValueType.DATE_TIME` type.'''
        ...
    
    @staticmethod
    def from_time_span(self, value: datetime.timespan) -> aspose.words.drawing.charts.ChartYValue:
        '''Creates a :class:`ChartYValue` instance of the :attr:`ChartYValueType.TIME` type.'''
        ...
    
    @property
    def value_type(self) -> aspose.words.drawing.charts.ChartYValueType:
        '''Gets the type of the Y value stored in the object.'''
        ...
    
    @property
    def double_value(self) -> float:
        '''Gets the stored numeric value.'''
        ...
    
    @property
    def date_time_value(self) -> datetime.datetime:
        '''Gets the stored datetime value.'''
        ...
    
    @property
    def time_value(self) -> datetime.timespan:
        '''Gets the stored time value.'''
        ...

class ChartXValueCollection:
    '''Represents a collection of X values for a chart series.
    
    All items of the collection other than **null** must have the same :attr:`ChartXValue.value_type`.
    
    The collection allows only changing X values. To add or insert new values to a chart series, or remove values,
    the appropriate methods of the :class:`ChartSeries` class can be used.'''
    
    def __getitem__(self, index: int) -> aspose.words.drawing.charts.ChartXValue:
        '''Gets or sets the X value at the specified index.
        
        Empty values are represented as **null**.'''
        ...
    
    def __setitem__(self, index: int, value: aspose.words.drawing.charts.ChartXValue):
        ...
    
    @property
    def count(self) -> int:
        '''Gets the number of items in this collection.'''
        ...

class ChartYValueCollection:
    '''Represents a collection of Y values for a chart series.
    
    All items of the collection other than **null** must have the same :attr:`ChartYValue.value_type`.
    
    The collection allows only changing Y values. To add or insert new values to a chart series, or remove values,
    the appropriate methods of the :class:`ChartSeries` class can be used.'''
    
    def __getitem__(self, index: int) -> aspose.words.drawing.charts.ChartYValue:
        '''Gets or sets the Y value at the specified index.
        
        Empty values are represented as **null**.'''
        ...
    
    def __setitem__(self, index: int, value: aspose.words.drawing.charts.ChartYValue):
        ...
    
    @property
    def count(self) -> int:
        '''Gets the number of items in this collection.'''
        ...

class BubbleSizeCollection:
    '''Represents a collection of bubble sizes for a chart series.
    
    The collection allows only changing bubble sizes. To add or insert new values to a chart series, or remove
    values, the appropriate methods of the :class:`ChartSeries` class can be used.
    
    Empty bubble size values are represented as System.Double.NaN.'''
    
    def __getitem__(self, index: int) -> float:
        '''Gets or sets the bubble size value at the specified index.'''
        ...
    
    def __setitem__(self, index: int, value: float):
        ...
    
    @property
    def count(self) -> int:
        '''Gets the number of items in this collection.'''
        ...

class ChartMultilevelValue:
    '''Represents a value for charts that display multilevel data.'''
    
    @overload
    def __init__(self, level1: str, level2: str, level3: str):
        '''Initializes a new instance of this class that represents a three-level value.'''
        ...
    
    @overload
    def __init__(self, level1: str, level2: str):
        '''Initializes a new instance of this class that represents a two-level value.'''
        ...
    
    @overload
    def __init__(self, level1: str):
        '''Initializes a new instance of this class that represents a single-level value.'''
        ...
    
    @property
    def level1(self) -> str:
        '''Gets the name of the chart top level that this value refers to.'''
        ...
    
    @property
    def level2(self) -> str:
        '''Gets the name of the chart intermediate level that this value refers to.'''
        ...
    
    @property
    def level3(self) -> str:
        '''Gets the name of the chart bottom level that this value refers to.'''
        ...
from aspose.words import Document, NodeType
from aspose.words.drawing.charts import ChartXValue, ChartYValue

doc = Document("ScatterChart.docx")

shape = doc.get_child(NodeType.SHAPE, 0, True).as_shape()

chart = shape.chart
series1 = chart.series[0]

# Clear X and Y values of the first series.
series1.clear_values()

# Populate the series with data.
series1.add(x_value=ChartXValue.from_double(3.0), y_value=ChartYValue.from_double(10.0))
series1.add(ChartXValue.from_double(5.0), ChartYValue.from_double(5.0))
series1.add(ChartXValue.from_double(7.0), ChartYValue.from_double(11.0))
series1.add(ChartXValue.from_double(9.0), ChartYValue.from_double(17.0))

series2 = chart.series[1]

# Clear X and Y values of the second series.
series2.clear_values()

# Populate the series with data.
series2.Add(ChartXValue.from_double(2.0), ChartYValue.from_double(4.0))
series2.Add(ChartXValue.from_double(4.0), ChartYValue.from_double(7.0))
series2.Add(ChartXValue.from_double(6.0), ChartYValue.from_double(14.0))
series2.Add(ChartXValue.from_double(8.0), ChartYValue.from_double(7.0))

doc.save("out.docx")
from aspose.words import Document, NodeType
from aspose.pydrawing import Color

doc = Document("Chart.docx")

shape = doc.get_child(NodeType.SHAPE, 0, True).as_shape()

chart = shape.chart
series = chart.series[0]

minValue = sys.float_info.max
minValueIndex = 0
maxValue = sys.float_info.min
maxValueIndex = 0

for i in range(series.y_values.count):
    # Clear individual format of all data points.Data points and data values are one - to - one in column charts.
    series.data_points[i].clear_format()

    # Get Y value.
    yValue = series.y_values[i].double_value
    
    if yValue < minValue:
        minValue = yValue
        minValueIndex = i
        
    if yValue > maxValue:
        maxValue = yValue
        maxValueIndex = i

# Change colors of the max and min values.
series.data_points[minValueIndex].format.fill.fore_color = Color.red
series.DataPoints[maxValueIndex].Format.Fill.ForeColor = Color.green

doc.save("out.docx");
from aspose.words import Document, NodeType
from aspose.words.drawing.charts import ChartXValue, ChartYValue

doc = Document("SalesChart.docx")

shape = doc.get_child(NodeType.SHAPE, 0, True).as_shape()

chart = shape.chart
department1Series = chart.series[0]
department2Series = chart.series[1]

# Remove the first value in the both series.
department1Series.remove(0)
department2Series.remove(0)

# Add new values to the both series.

newXCategory = ChartXValue.from_string("Q1, 2023")
department1Series.add(newXCategory, ChartYValue.from_double(10.3))
department2Series.add(newXCategory, ChartYValue.from_double(5.7))

doc.save("out.docx")