Skip to content

Commit

Permalink
Rich text (#358)
Browse files Browse the repository at this point in the history
Co-authored-by: Luiz Guilherme Alvarez <[email protected]>
  • Loading branch information
TarekTolba1 and luizgpa authored Jul 9, 2024
1 parent e718e46 commit 6880d95
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 8 deletions.
2 changes: 1 addition & 1 deletion lib/src/parser/parse.dart
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ class Parser {
case 's':
final sharedString = _excel._sharedStrings
.value(int.parse(_parseValue(node.findElements('v').first)));
value = TextCellValue(sharedString!.stringValue);
value = TextCellValue.span(sharedString!.textSpan);
break;
// boolean
case 'b':
Expand Down
6 changes: 3 additions & 3 deletions lib/src/save/save_file.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ class Save {
CellValue? value, NumFormat? numberFormat) {
SharedString? sharedString;
if (value is TextCellValue) {
sharedString = _excel._sharedStrings.tryFind(value.value);
sharedString = _excel._sharedStrings.tryFind(value.toString());
if (sharedString != null) {
_excel._sharedStrings.add(sharedString, value.value);
_excel._sharedStrings.add(sharedString, value.toString());
} else {
sharedString = _excel._sharedStrings.addFromString(value.value);
sharedString = _excel._sharedStrings.addFromString(value.toString());
}
}

Expand Down
115 changes: 115 additions & 0 deletions lib/src/sharedStrings/shared_strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,91 @@ class SharedString {
return stringValue;
}

TextSpan get textSpan {
bool getBool(XmlElement element) {
return bool.tryParse(element.getAttribute('val') ?? '') ?? true;
}

int getDouble(XmlElement element) {
// Should be double
return double.parse(element.getAttribute('val')!).toInt();
}

String? text;
List<TextSpan>? children;

/// SharedStringItem
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sharedstringitem?view=openxml-3.0.1
assert(node.localName == 'si'); //18.4.8 si (String Item)

for (final child in node.childElements) {
switch (child.localName) {
/// Text
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.text?view=openxml-3.0.1
case 't': //18.4.12 t (Text)
text = (text ?? '') + child.innerText;
break;

/// Rich Text Run
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.run?view=openxml-3.0.1
case 'r': //18.4.4 r (Rich Text Run)
var style = CellStyle();
for (final runChild in child.childElements) {
switch (runChild.localName) {
/// RunProperties
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.runproperties?view=openxml-3.0.1
case 'rPr':
for (final runProperty in runChild.childElements) {
switch (runProperty.localName) {
case 'b': //18.8.2 b (Bold)
style = style.copyWith(boldVal: getBool(runProperty));
break;
case 'i': //18.8.26 i (Italic)
style = style.copyWith(italicVal: getBool(runProperty));
break;
case 'u': //18.4.13 u (Underline)
style = style.copyWith(
underlineVal:
runProperty.getAttribute('val') == 'double'
? Underline.Double
: Underline.Single);
break;
case 'sz': //18.4.11 sz (Font Size)
style =
style.copyWith(fontSizeVal: getDouble(runProperty));
break;
case 'rFont': //18.4.5 rFont (Font)
style = style.copyWith(
fontFamilyVal: runProperty.getAttribute('val'));
break;
case 'color': //18.3.1.15 color (Data Bar Color)
style = style.copyWith(
fontColorHexVal:
runProperty.getAttribute('rgb')?.excelColor);
break;
}
}
break;

/// Text
case 't': //18.4.12 t (Text)
if (children == null) children = [];
children.add(TextSpan(text: runChild.innerText, style: style));
break;
}
}
break;

/// Phonetic Run
/// https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.phoneticrun?view=openxml-3.0.1
case 'rPh': //18.4.6 rPh (Phonetic Run)
break;
}
}

return TextSpan(text: text, children: children);
}

String get stringValue {
var buffer = StringBuffer();
node.findAllElements('t').forEach((child) {
Expand All @@ -102,3 +187,33 @@ class SharedString {
return value.isNotEmpty && value == stringValue;
}
}

class TextSpan {
final String? text;
final List<TextSpan>? children;
final CellStyle? style;

const TextSpan({this.children, this.text, this.style});

@override
String toString() {
String r = '';
if (text != null) r += text!;
if (children != null) r += children!.join();
return r;
}

@override
operator ==(Object other) {
if (identical(this, other)) return true;
if (other.runtimeType != runtimeType) return false;
return other is TextSpan &&
other.text == text &&
other.style == style &&
ListEquality().equals(other.children, children);
}

@override
int get hashCode =>
Object.hash(text, style, Object.hashAll(children ?? const []));
}
7 changes: 4 additions & 3 deletions lib/src/sheet/data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,14 @@ class DateCellValue extends CellValue {
}

class TextCellValue extends CellValue {
final String value;
final TextSpan value;

const TextCellValue(this.value);
TextCellValue(String text) : value = TextSpan(text: text);
TextCellValue.span(this.value);

@override
String toString() {
return value;
return value.toString();
}

@override
Expand Down
3 changes: 2 additions & 1 deletion lib/src/sheet/sheet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,8 @@ class Sheet {
if (sourceData is! TextCellValue) {
continue;
}
final result = sourceData.value.replaceAllMapped(source, (match) {
final result =
sourceData.value.toString().replaceAllMapped(source, (match) {
if (first == -1 || first != replaceCount) {
++replaceCount;
return match.input.replaceRange(match.start, match.end, target);
Expand Down
30 changes: 30 additions & 0 deletions test/excel_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,36 @@ void main() {
});
});

group('Cell Style', () {
test('read file with rich text', () {
final file = './test/test_resources/richText.xlsx';
final bytes = File(file).readAsBytesSync();
final excel = Excel.decodeBytes(bytes);
final Sheet sheetObject = excel.tables['Sheet1']!;
final redHex = 'FFFF0000';
final blueHex = 'FF2A6099';

final cellA1 = sheetObject.cell(CellIndex.indexByString('A1')).value
as TextCellValue;
expect(cellA1.value.children![0].style!.fontSize, 12);
expect(cellA1.value.children![0].style!.fontColor.colorHex, redHex);
expect(cellA1.value.children![1].style!.fontSize, 10);
expect(cellA1.value.children![1].style!.fontColor.colorHex, blueHex);

final cellA2 = sheetObject.cell(CellIndex.indexByString('A2')).value
as TextCellValue;
expect(cellA2.value.children![0].style!.isBold, true);
expect(cellA2.value.children![0].style!.isItalic, false);
expect(cellA2.value.children![1].style!.isBold, false);
expect(cellA2.value.children![1].style!.isItalic, true);

final cellA3 = sheetObject.cell(CellIndex.indexByString('A3')).value
as TextCellValue;
expect(cellA3.value.children![0].style!.fontFamily, "Skia");
expect(cellA3.value.children![1].style!.fontFamily, "Arial");
});
});

group('rPh tag', () {
test('Read Cell shared text without rPh elements', () {
var file = './test/test_resources/rphSample.xlsx';
Expand Down
Binary file added test/test_resources/richText.xlsx
Binary file not shown.

0 comments on commit 6880d95

Please sign in to comment.