Skip to content

Commit 8ef9cde

Browse files
authored
Merge pull request #821 from FastReports/sync_branch_2026.2.0
FastReport.OpenSource 2026.2.0
2 parents 812146f + 9bf2dda commit 8ef9cde

66 files changed

Lines changed: 3985 additions & 3167 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Demos/Reports/QR-Codes.frx

Lines changed: 70 additions & 10 deletions
Large diffs are not rendered by default.

FastReport.Base/Data/JsonConnection/JsonTableDataSource.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ internal static void InitSchema(Column table, JsonSchema schema, bool simpleStru
272272
else if (kv.Value.Type == "array")
273273
{
274274
Column c = new JsonTableDataSource();
275-
c.Name = kv.Key;
275+
c.Name = table.Report?.Dictionary?.CreateUniqueName(kv.Key) ?? kv.Key;
276276
c.Alias = kv.Key;
277277
c.PropName = kv.Key;
278278
c.DataType = kv.Value.DataType;

FastReport.Base/Export/Html/HTMLExport.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,18 @@ public bool Layers
215215
set { layers = value; }
216216
}
217217

218+
/// <summary>
219+
/// For internal use only.
220+
/// </summary>
221+
[EditorBrowsable(EditorBrowsableState.Never)]
222+
public string PrintScriptSrc { get; set; }
223+
224+
/// <summary>
225+
/// For internal use only.
226+
/// </summary>
227+
[EditorBrowsable(EditorBrowsableState.Never)]
228+
public bool DisableInlineScript { get; set; }
229+
218230
/// <summary>
219231
/// For internal use only.
220232
/// </summary>
@@ -618,7 +630,13 @@ private void DoPageStart(Stream stream, string documentTitle)
618630
private void DoPageEnd(Stream stream, bool print)
619631
{
620632
if (print)
621-
ExportUtils.WriteLn(stream, PRINT_JS);
633+
{
634+
if (string.IsNullOrEmpty(PrintScriptSrc))
635+
ExportUtils.WriteLn(stream, PRINT_JS);
636+
else
637+
ExportUtils.WriteLn(stream, $@"<script type=""module"" src=""{PrintScriptSrc}""></script>");
638+
}
639+
622640
ExportUtils.WriteLn(stream, BODY_END);
623641
ExportUtils.Write(stream, templates.PageTemplateFooter);
624642
}

FastReport.Base/Export/Html/HTMLExportLayers.cs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ private void Layer(FastString Page, ReportComponentBase obj,
9393
obj.AbsLeft.ToString("#0"),
9494
obj.AbsTop.ToString("#0"));
9595

96-
Page.Append(" onclick=\"")
97-
.AppendFormat(OnClickTemplate, ReportID, onclick, eventParam)
98-
.Append("\"");
96+
Page.Append(" " + ClickEvent(onclick, eventParam));
9997
}
10098

10199
Page.Append(">");
@@ -148,17 +146,17 @@ private string GetHref(ReportComponentBase obj)
148146
EncodeURL(obj.Name), // object name for security reasons
149147
EncodeURL(obj.Hyperlink.ReportParameter),
150148
EncodeURL(obj.Hyperlink.Value));
151-
string onClick = String.Format(OnClickTemplate, ReportID, "detailed_report", url);
152-
href = String.Format("<a {0} onclick=\"{1}\">", hrefStyle, onClick);
149+
string onClick = ClickEvent("detailed_report", url);
150+
href = String.Format("<a {0} {1}>", hrefStyle, onClick);
153151
}
154152
else if (obj.Hyperlink.Kind == HyperlinkKind.DetailPage)
155153
{
156154
url = String.Format("{0},{1},{2}",
157155
EncodeURL(obj.Name),
158156
EncodeURL(obj.Hyperlink.ReportParameter),
159157
EncodeURL(obj.Hyperlink.Value));
160-
string onClick = String.Format(OnClickTemplate, ReportID, "detailed_page", url);
161-
href = String.Format("<a {0} onclick=\"{1}\">", hrefStyle, onClick);
158+
string onClick = ClickEvent("detailed_page", url);
159+
href = String.Format("<a {0} {1}>", hrefStyle, onClick);
162160
}
163161
else if (SinglePage)
164162
{
@@ -171,12 +169,12 @@ private string GetHref(ReportComponentBase obj)
171169
{
172170
string onClick = String.Empty;
173171
if (obj.Hyperlink.Kind == HyperlinkKind.Bookmark)
174-
onClick = String.Format(OnClickTemplate, ReportID, "bookmark", url);
172+
onClick = ClickEvent("bookmark", url);
175173
else if (obj.Hyperlink.Kind == HyperlinkKind.PageNumber)
176-
onClick = String.Format(OnClickTemplate, ReportID, "goto", url);
174+
onClick = ClickEvent("goto", url);
177175

178176
if (onClick != String.Empty)
179-
href = String.Format("<a {0} onclick=\"{1}\">", hrefStyle, onClick);
177+
href = String.Format("<a {0} {1}>", hrefStyle, onClick);
180178
}
181179
}
182180
return href;

FastReport.Base/Export/Html/HTMLExportUtils.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ private void WriteMimePart(Stream stream, string mimetype, string charset, strin
114114
sb.Clear();
115115
}
116116

117+
private string ClickEvent(string func, string parametrs)
118+
{
119+
if (DisableInlineScript)
120+
return OnClickTemplate.Replace("{0}", func).Replace("{1}", parametrs);
121+
return $"onclick=\"{String.Format(OnClickTemplate, ReportID, func, parametrs)}\"";
122+
}
123+
117124
private void WriteMHTHeader(Stream Stream, string FileName)
118125
{
119126
FastString sb = new FastString(256);
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
using System.Text;
2+
using System.Collections.Generic;
3+
4+
namespace FastReport.Functions
5+
{
6+
/// <summary>
7+
/// Converts numbers to Chinese financial notation (大写) for currencies CNY, USD, EUR.
8+
/// </summary>
9+
internal class NumToWordsCn : NumToWordsBase
10+
{
11+
private static Dictionary<string, CurrencyInfo> currencyList;
12+
13+
private static readonly string Zero = "零";
14+
15+
private static readonly string[] FixedWords =
16+
{
17+
Zero, "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"
18+
};
19+
20+
private static readonly string Ten = "拾"; // 10
21+
private static readonly string Hundred = "佰"; // 100
22+
private static readonly string Thousand = "仟"; // 1 000
23+
private static readonly string TenThousand = "万"; // 10 000
24+
private static readonly string HundredMillion = "亿"; // 100 000 000
25+
26+
private static readonly string Yuan = "元"; // Chinese Yuan (CNY)
27+
private static readonly string Jiao = "角"; // 0.1 Yuan (1/10 of Yuan)
28+
private static readonly string Fen = "分"; // 0.01 Yuan (1/100 of Yuan)
29+
private static readonly string USD = "美元"; // US Dollar
30+
private static readonly string USCent = "美分"; // US Cents
31+
private static readonly string EUR = "欧元"; // Euro
32+
private static readonly string EuroCent = "欧分"; // Euro Cents
33+
34+
private static readonly string WholeAmount = "整"; // "Exact amount" (used for whole numbers without fractional part)
35+
private static readonly string Minus = "负";
36+
37+
private static readonly WordInfo unused = new WordInfo("");
38+
39+
private bool _hasNonZeroIntegerPart = false;
40+
41+
protected override void Str(long value, WordInfo senior, StringBuilder result)
42+
{
43+
if (IsFractionalUnit(senior))
44+
{
45+
HandleFractionalPart(value, senior, result);
46+
}
47+
else
48+
{
49+
HandleIntegerPart(value, senior, result);
50+
}
51+
}
52+
53+
private void HandleFractionalPart(long value, WordInfo senior, StringBuilder result)
54+
{
55+
if (value == 0)
56+
{
57+
result.Append(WholeAmount);
58+
return;
59+
}
60+
61+
if (senior.one == Fen)
62+
{
63+
HandleCnyFractional(value, result);
64+
}
65+
else
66+
{
67+
HandleForeignCurrencyFractional(value, senior, result);
68+
}
69+
}
70+
71+
/// <summary>
72+
/// Handles USD/EUR fractional part as cent amounts.
73+
/// Adds "零" before single-digit cents when integer part exists.
74+
/// </summary>
75+
private void HandleForeignCurrencyFractional(long value, WordInfo senior, StringBuilder result)
76+
{
77+
string fractionalPart = ConvertInteger(value);
78+
// Add "零" before single-digit cents if integer part is non-zero
79+
if (_hasNonZeroIntegerPart && value < 10)
80+
{
81+
result.Append(Zero);
82+
}
83+
result.Append(fractionalPart);
84+
85+
if (value != 0)
86+
{
87+
if (senior.one == USCent) result.Append(USCent);
88+
else if (senior.one == EuroCent) result.Append(EuroCent);
89+
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Handles CNY fractional part with 角(jiao)/分(fen) structure.
95+
/// Adds "零" (zero) between integer part and 分 when 角 is missing.
96+
/// </summary>
97+
private void HandleCnyFractional(long value, StringBuilder result)
98+
{
99+
int jiao = (int)(value / 10);
100+
int fen = (int)(value % 10);
101+
102+
if (jiao > 0)
103+
result.Append(FixedWords[jiao]).Append(Jiao);
104+
105+
if (fen > 0)
106+
{
107+
// Add "零" only if there's no 角 but integer part exists
108+
if (jiao == 0 && _hasNonZeroIntegerPart)
109+
result.Append(Zero);
110+
result.Append(FixedWords[fen]).Append(Fen);
111+
}
112+
}
113+
114+
private void HandleIntegerPart(long value, WordInfo senior, StringBuilder result)
115+
{
116+
_hasNonZeroIntegerPart = (value != 0);
117+
118+
string integerPart = ConvertInteger(value);
119+
if (value == 0 && string.IsNullOrEmpty(integerPart))
120+
{
121+
integerPart = Zero;
122+
}
123+
result.Append(integerPart);
124+
125+
// Add currency symbol
126+
if (senior.one == Yuan) result.Append(Yuan);
127+
else if (senior.one == USD) result.Append(USD);
128+
else if (senior.one == EUR) result.Append(EUR);
129+
}
130+
131+
private bool IsFractionalUnit(WordInfo unit)
132+
{
133+
return (unit != null) &&
134+
(unit.one == Fen || unit.one == USCent || unit.one == EuroCent);
135+
}
136+
137+
/// <summary>
138+
/// Converts integer number to Chinese financial notation using 亿/万 blocks.
139+
/// </summary>
140+
private string ConvertInteger(long num)
141+
{
142+
if (num == 0) return "";
143+
144+
long units = num % 10000;
145+
long tenThousand = (num / 10000) % 10000;
146+
long hundredMillion = num / 100000000;
147+
148+
StringBuilder sb = new StringBuilder();
149+
150+
// 亿
151+
if (hundredMillion > 0)
152+
{
153+
sb.Append(ConvertFourDigits((int)hundredMillion));
154+
sb.Append(HundredMillion);
155+
}
156+
157+
// 万
158+
if (tenThousand > 0)
159+
{
160+
// Missing digits between 亿 and 万 blocks
161+
if (hundredMillion > 0 && units > 0 && tenThousand < 1000)
162+
{
163+
if (!sb.ToString().EndsWith(Zero))
164+
sb.Append(Zero);
165+
}
166+
sb.Append(ConvertFourDigits((int)tenThousand));
167+
sb.Append(TenThousand);
168+
}
169+
else if (hundredMillion > 0 && units > 0)
170+
{
171+
sb.Append(Zero);
172+
}
173+
174+
if (units > 0)
175+
{
176+
string unitStr = ConvertFourDigits((int)units);
177+
// Missing thousands after 万 block
178+
if (tenThousand > 0 && units < 1000 && !unitStr.StartsWith(Zero))
179+
{
180+
unitStr = Zero + unitStr;
181+
}
182+
sb.Append(unitStr);
183+
}
184+
185+
return sb.ToString();
186+
}
187+
188+
/// <summary>
189+
/// Converts 4-digit number to Chinese financial notation.
190+
/// </summary>
191+
private string ConvertFourDigits(int num)
192+
{
193+
if (num == 0) return "";
194+
StringBuilder sb = new StringBuilder();
195+
196+
bool hasThousands = false;
197+
bool hasHundreds = false;
198+
199+
if (num >= 1000)
200+
{
201+
sb.Append(FixedWords[num / 1000]).Append(Thousand);
202+
num %= 1000;
203+
hasThousands = true;
204+
}
205+
206+
if (num >= 100)
207+
{
208+
sb.Append(FixedWords[num / 100]).Append(Hundred);
209+
num %= 100;
210+
hasHundreds = true;
211+
}
212+
else if ((hasThousands) && num > 0)
213+
{
214+
// Missing hundreds between thousands and lower digits
215+
sb.Append(Zero);
216+
}
217+
218+
if (num >= 10)
219+
{
220+
sb.Append(FixedWords[num / 10]).Append(Ten);
221+
num %= 10;
222+
}
223+
else if ((hasThousands || hasHundreds) && num > 0)
224+
{
225+
// Missing tens between higher digits and units
226+
if (!sb.ToString().EndsWith(Zero))
227+
sb.Append(Zero);
228+
}
229+
230+
if (num > 0)
231+
{
232+
sb.Append(FixedWords[num]);
233+
}
234+
235+
return sb.ToString();
236+
}
237+
238+
protected override int GetFixedWordsCount() => 10;
239+
protected override string GetFixedWords(bool male, long value) =>
240+
(value >= 0 && value < FixedWords.Length) ? FixedWords[value] : string.Empty;
241+
protected override string GetTen(bool male, long value) => string.Empty;
242+
protected override string GetHund(bool male, long value) => string.Empty;
243+
244+
protected override WordInfo GetThousands() => unused;
245+
protected override WordInfo GetMillions() => unused;
246+
protected override WordInfo GetMilliards() => unused;
247+
protected override WordInfo GetTrillions() => unused;
248+
249+
protected override CurrencyInfo GetCurrency(string currencyName)
250+
{
251+
currencyName = currencyName.ToUpper();
252+
if (currencyList.TryGetValue(currencyName, out var info))
253+
return info;
254+
return currencyList["CNY"];
255+
}
256+
257+
protected override string GetZero() => Zero;
258+
protected override string GetMinus() => Minus;
259+
protected override string GetDecimalSeparator() => string.Empty;
260+
protected override string Get10_1Separator() => string.Empty;
261+
protected override string Get100_10Separator() => string.Empty;
262+
protected override string Case(long value, WordInfo info) => info.one;
263+
264+
static NumToWordsCn()
265+
{
266+
currencyList = new Dictionary<string, CurrencyInfo>
267+
{
268+
["CNY"] = new CurrencyInfo(new WordInfo(Yuan), new WordInfo(Fen)),
269+
["USD"] = new CurrencyInfo(new WordInfo(USD), new WordInfo(USCent)),
270+
["EUR"] = new CurrencyInfo(new WordInfo(EUR), new WordInfo(EuroCent))
271+
};
272+
}
273+
}
274+
}

0 commit comments

Comments
 (0)