Skip to content

Commit 654dbb7

Browse files
committed
Replace ExcelNumberFormat library (stale) with custom version
1 parent 63f52cf commit 654dbb7

File tree

20 files changed

+2264
-10
lines changed

20 files changed

+2264
-10
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,12 @@ Forked:
7171
- [b2xtranslator](https://github.com/EvolutionJobs/b2xtranslator)
7272
- [markdig.docx](https://github.com/morincer/markdig.docx)
7373

74-
Others:
74+
Others (credits for parts of the logic):
7575
- [Html2OpenXml](https://github.com/onizet/html2openxml) for images header decoding and unit conversions.
7676
- [dwml_cs](https://github.com/m-x-d/dwml_cs) for Office Math (OMML) to LaTex conversion
7777
- [addFormula2docx](https://github.com/Sun-ZhenXing/addFormula2docx) for Office Math (OMML) to MathML conversion
7878
- [RtfPipe](https://github.com/erdomke/RtfPipe), [FridaysForks.RtfPipe](https://github.com/cezarypiatek/FridaysForks.RtfPipe), [RtfConverter](https://github.com/jokecamp/RtfConverter) for part of the RTF parsing logic.
79+
- [ExcelNumberFormat](https://github.com/andersnm/ExcelNumberFormat) for Excel format strings parsing logic.
7980

8081
<a id="recommended_libraries"></a>
8182
Other recommended libraries (some of these are used in the sample app, *not* dependencies when installing packages):

src/DocSharp.Binary/DocSharp.Binary.Xls/XlsFileFormat/Records/Date1904.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class Date1904 : BiffRecord
1818
///
1919
/// Value Meaning
2020
/// 0x0000 The workbook uses the 1900 date system. The first date of the 1900 date
21-
/// system is 00:00:00 on January 1, 1900, specified by a serial value of 1.
21+
/// system is 00:00:00 on January 1, 1900, specified by a serial value of 1 (0 is 1899-Dec-31).
2222
///
2323
/// 0x0001 The workbook uses the 1904 date system. The first date of the 1904 date
2424
/// system is 00:00:00 on January 1, 1904, specified by a serial value of 0.

src/WIP/DocSharp.Ebook/EpubToDocxConverter.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,16 @@ public async Task BuildDocxAsync(Stream input, WordprocessingDocument targetDocu
160160
]));
161161

162162
// Parse the HTML body, convert to Open XML and append to the DOCX.
163-
await converter.ParseBody(htmlWithInlinedCss);
163+
try
164+
{
165+
await converter.ParseBody(htmlWithInlinedCss);
166+
}
167+
catch (Exception internalEx)
168+
{
169+
#if DEBUG
170+
Debug.Write($"ParseBody exception (Html2OpenXml): {internalEx.Message}");
171+
#endif
172+
}
164173

165174
if (PageBreakAfterChapters)
166175
{

src/WIP/DocSharp.Renderer/DocSharp.Renderer.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222

2323
<PackageReference Include="CSharpMath.Rendering" Version="0.5.1" />
2424
<!-- <PackageReference Include="CSharpMath.SkiaSharp" Version="0.5.1" /> -->
25-
26-
<PackageReference Include="ExcelNumberFormat" Version="1.1.0" />
2725
</ItemGroup>
2826

2927
<ItemGroup>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace ExcelNumberFormat
2+
{
3+
internal class Color
4+
{
5+
public string Value { get; set; }
6+
}
7+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
3+
namespace ExcelNumberFormat
4+
{
5+
internal static class CompatibleConvert
6+
{
7+
/// <summary>
8+
/// A backward-compatible version of <see cref="Convert.ToString(object, IFormatProvider)"/>.
9+
/// Starting from .net Core 3.0 the default precision used for formatting floating point number has changed.
10+
/// To always format numbers the same way, no matter what version of runtime is used, we specify the precision explicitly.
11+
/// </summary>
12+
public static string ToString(object value, IFormatProvider provider)
13+
{
14+
switch (value)
15+
{
16+
case double d:
17+
return d.ToString("G15", provider);
18+
case float f:
19+
return f.ToString("G7", provider);
20+
default:
21+
return Convert.ToString(value, provider);
22+
}
23+
}
24+
}
25+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace ExcelNumberFormat
2+
{
3+
internal class Condition
4+
{
5+
public string Operator { get; set; }
6+
public double Value { get; set; }
7+
8+
public bool Evaluate(double lhs)
9+
{
10+
switch (Operator)
11+
{
12+
case "<":
13+
return lhs < Value;
14+
case "<=":
15+
return lhs <= Value;
16+
case ">":
17+
return lhs > Value;
18+
case ">=":
19+
return lhs >= Value;
20+
case "<>":
21+
return lhs != Value;
22+
case "=":
23+
return lhs == Value;
24+
}
25+
26+
return false;
27+
}
28+
}
29+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
using System.Collections.Generic;
2+
3+
namespace ExcelNumberFormat
4+
{
5+
internal class DecimalSection
6+
{
7+
public bool ThousandSeparator { get; set; }
8+
9+
public double ThousandDivisor { get; set; }
10+
11+
public double PercentMultiplier { get; set; }
12+
13+
public List<string> BeforeDecimal { get; set; }
14+
15+
public bool DecimalSeparator { get; set; }
16+
17+
public List<string> AfterDecimal { get; set; }
18+
19+
public static bool TryParse(List<string> tokens, out DecimalSection format)
20+
{
21+
if (Parser.ParseNumberTokens(tokens, 0, out var beforeDecimal, out var decimalSeparator, out var afterDecimal) == tokens.Count)
22+
{
23+
bool thousandSeparator;
24+
var divisor = GetTrailingCommasDivisor(tokens, out thousandSeparator);
25+
var multiplier = GetPercentMultiplier(tokens);
26+
27+
format = new DecimalSection()
28+
{
29+
BeforeDecimal = beforeDecimal,
30+
DecimalSeparator = decimalSeparator,
31+
AfterDecimal = afterDecimal,
32+
PercentMultiplier = multiplier,
33+
ThousandDivisor = divisor,
34+
ThousandSeparator = thousandSeparator
35+
};
36+
37+
return true;
38+
}
39+
40+
format = null;
41+
return false;
42+
}
43+
44+
static double GetPercentMultiplier(List<string> tokens)
45+
{
46+
// If there is a percentage literal in the part list, multiply the result by 100
47+
foreach (var token in tokens)
48+
{
49+
if (token == "%")
50+
return 100;
51+
}
52+
53+
return 1;
54+
}
55+
56+
static double GetTrailingCommasDivisor(List<string> tokens, out bool thousandSeparator)
57+
{
58+
// This parses all comma literals in the part list:
59+
// Each comma after the last digit placeholder divides the result by 1000.
60+
// If there are any other commas, display the result with thousand separators.
61+
bool hasLastPlaceholder = false;
62+
var divisor = 1.0;
63+
64+
for (var j = 0; j < tokens.Count; j++)
65+
{
66+
var tokenIndex = tokens.Count - 1 - j;
67+
var token = tokens[tokenIndex];
68+
69+
if (!hasLastPlaceholder)
70+
{
71+
if (Token.IsPlaceholder(token))
72+
{
73+
// Each trailing comma multiplies the divisor by 1000
74+
for (var k = tokenIndex + 1; k < tokens.Count; k++)
75+
{
76+
token = tokens[k];
77+
if (token == ",")
78+
divisor *= 1000.0;
79+
else
80+
break;
81+
}
82+
83+
// Continue scanning backwards from the last digit placeholder,
84+
// but now look for a thousand separator comma
85+
hasLastPlaceholder = true;
86+
}
87+
}
88+
else
89+
{
90+
if (token == ",")
91+
{
92+
thousandSeparator = true;
93+
return divisor;
94+
}
95+
}
96+
}
97+
98+
thousandSeparator = false;
99+
return divisor;
100+
}
101+
}
102+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace ExcelNumberFormat
6+
{
7+
internal static class Evaluator
8+
{
9+
public static Section GetSection(List<Section> sections, object value)
10+
{
11+
// Standard format has up to 4 sections:
12+
// Positive;Negative;Zero;Text
13+
switch (value)
14+
{
15+
case string s:
16+
if (sections.Count >= 4)
17+
return sections[3];
18+
19+
return null;
20+
21+
case DateTime dt:
22+
// TODO: Check date conditions need date helpers and Date1904 knowledge
23+
return GetFirstSection(sections, SectionType.Date);
24+
25+
case TimeSpan ts:
26+
return GetNumericSection(sections, ts.TotalDays);
27+
28+
case double d:
29+
return GetNumericSection(sections, d);
30+
31+
case int i:
32+
return GetNumericSection(sections, i);
33+
34+
case short s:
35+
return GetNumericSection(sections, s);
36+
37+
default:
38+
return null;
39+
}
40+
}
41+
42+
public static Section GetFirstSection(List<Section> sections, SectionType type)
43+
{
44+
foreach (var section in sections)
45+
if (section.Type == type)
46+
return section;
47+
return null;
48+
}
49+
50+
private static Section GetNumericSection(List<Section> sections, double value)
51+
{
52+
// First section applies if
53+
// - Has a condition:
54+
// - There is 1 section, or
55+
// - There are 2 sections, and the value is 0 or positive, or
56+
// - There are >2 sections, and the value is positive
57+
if (sections.Count < 1)
58+
{
59+
return null;
60+
}
61+
62+
var section0 = sections[0];
63+
64+
if (section0.Condition != null)
65+
{
66+
if (section0.Condition.Evaluate(value))
67+
{
68+
return section0;
69+
}
70+
}
71+
else if (sections.Count == 1 || (sections.Count == 2 && value >= 0) || (sections.Count >= 2 && value > 0))
72+
{
73+
return section0;
74+
}
75+
76+
if (sections.Count < 2)
77+
{
78+
return null;
79+
}
80+
81+
var section1 = sections[1];
82+
83+
// First condition didnt match, or was a negative number. Second condition applies if:
84+
// - Has a condition, or
85+
// - Value is negative, or
86+
// - There are two sections, and the first section had a non-matching condition
87+
if (section1.Condition != null)
88+
{
89+
if (section1.Condition.Evaluate(value))
90+
{
91+
return section1;
92+
}
93+
}
94+
else if (value < 0 || (sections.Count == 2 && section0.Condition != null))
95+
{
96+
return section1;
97+
}
98+
99+
// Second condition didnt match, or was positive. The following
100+
// sections cannot have conditions, always fall back to the third
101+
// section (for zero formatting) if specified.
102+
if (sections.Count < 3)
103+
{
104+
return null;
105+
}
106+
107+
return sections[2];
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)