Support of rich text in cells
Created originally on Bitbucket by Cilyan (Cilyan Olowen)
as I needed the feature, I invested some little time looking into the support of rich text in cells. This feature allows to set font style to only parts of a cell's text, in comparison with cell style (already supported) that styles the whole content.
I created the pull request in order to discuss with you, it is clearly not ready for integration. I planned on working further on it, but any advise/review comment is welcome as it's my first time hacking into openpyxl.
As cell style is already implemented and the rich text feature is nothing more than
Font inside the shared strings, I tried to reuse as much as possible the existing code. Also, I did not want to break the compatibility of the
Cell.value property since probably a lot of user program do not expect to receive rich text on that interface. So even if cell contains rich text,
Cell.value returns a plain text version of it. At first, I used a separate internal property so that
_value is also not changed. But in the end, I thought that user program that was low level enough to interface with
Cell.internal_value should be able to distinguish between plain text and rich text content. Comments welcome on that decision.
The proposed implementation is based on a type named
RichTextContent implemented in
openpyxl.cell.rich_text. The name was selected so that it doesn't conflict with
openpyxl.cell.text.RichText, although this later class is only used internally to read XML. Maybe it would be more intuitive to rename the original class and use RichText for the new class.
RichTextContent class is responsible for holding the content of the cell when it is rich text instead of plain string. The class implements basic
add functions as well as
+= operators to present a (hopefully) nice interface to the user. The class is also responsible for its serialisation, although I did not inherit
Serialisable to not clutter the namespace too much in a class that is supposed to be exposed to the user code.
write_shared_string to call the RichTextContent function when it recognises that the shared string is not plain text. The compatibility with
IndexedList is ensured by the fact that objects have a hash and as such can be used as dict keys. However, this is limited to recognising the same object and not two objects that would contain the same content. A further improvement can be made by computing a hash from the content instead and implementing comparision functions (
To have access to the rich text content and make modifications, one would use the new
Cell.rich_text propery always return a
RichTextContent object even if the internal value is a plain string. This is done so that manipulation and mostly concatenation is made easy and transparent to the used. The property
Cell.is_rich_text can then be used to query if internal content of a cell is indeed rich text or plain text. In the end, it doesn't matter much to the user, as
RichTextContent is intelligent enough to produce plain text XML when it contains no styles. The only difference would be at the
The most important limitation of the current implementation is that it doesn't write properly the
xml:space="preserve" property when necessary. As such, space between text chunks are not observed. For this, I would need advice, because I could not properly understand how to support that attribute through the
Serialisable interface of
Text. Maybe by having a specialisation of
NestedText? I am not sure.
I did not code yet an implementation for the reader. When the writer is finished and the Cell interface makes everyone happy, it should be straightforward to convert
RichTextContent before placing the value in the cell, instead of calling
Another discrepancy that would need care is the fact that if you save the file once, then change a cell's text and save it again, the new file will also contain strings that are no longer used. This happens the same for plain text in the current implementation. I think clearing the shared strings table before writing the file would be sufficient, but maybe not very efficient. Comments welcome.
#!python >>> from openpyxl import Workbook >>> wb = Workbook() >>> ws = wb.active >>> cell = ws["A1"] >>> cell.rich_text Traceback (most recent call last): File "<pyshell#4>", line 1, in <module> cell.rich_text File "C:\Anaconda3\envs\openpyxl\lib\site-packages\openpyxl\cell\cell.py", line 328, in rich_text self.__class__.__name__ AttributeError: 'Cell' object as no attribute 'rich_text' >>> from openpyxl.styles.fonts import Font >>> bold_red = Font(name='Calibri',size=11,bold=True,italic=False,vertAlign=None,underline='none',strike=False,color='FFFF0000') >>> cell.rich_text = "Hello " >>> cell.rich_text += ("world", bold_red) >>> cell.rich_text <openpyxl.cell.rich_text.RichTextContent object at 0x0000000003C0AF98> >>> cell.value 'Hello world' >>> wb.save(r"W:\RichText.xlsx")