• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

b1f6c1c4 / ProfessionalAccounting / 327

28 Apr 2025 05:37PM UTC coverage: 51.346% (-3.0%) from 54.325%
327

push

appveyor

b1f6c1c4
ci buildx

6620 of 12893 relevant lines covered (51.35%)

126.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.26
/AccountingServer.Shell/Serializer/CSharpSerializer.cs
1
/* Copyright (C) 2020-2025 b1f6c1c4
2
 *
3
 * This file is part of ProfessionalAccounting.
4
 *
5
 * ProfessionalAccounting is free software: you can redistribute it and/or
6
 * modify it under the terms of the GNU Affero General Public License as
7
 * published by the Free Software Foundation, version 3.
8
 *
9
 * ProfessionalAccounting is distributed in the hope that it will be useful, but
10
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
12
 * for more details.
13
 *
14
 * You should have received a copy of the GNU Affero General Public License
15
 * along with ProfessionalAccounting.  If not, see
16
 * <https://www.gnu.org/licenses/>.
17
 */
18

19
using System;
20
using System.Collections;
21
using System.Globalization;
22
using System.IO;
23
using System.Linq;
24
using System.Reflection;
25
using System.Runtime;
26
using System.Runtime.Loader;
27
using System.Text;
28
using AccountingServer.BLL.Util;
29
using AccountingServer.Entities;
30
using Microsoft.CodeAnalysis;
31
using Microsoft.CodeAnalysis.CSharp;
32

33
namespace AccountingServer.Shell.Serializer;
34

35
public class CSharpSerializer : IEntitySerializer
36
{
37
    /// <summary>
38
    ///     转义字符串
39
    /// </summary>
40
    /// <param name="s">待转义的字符串</param>
41
    /// <returns>转义后的字符串</returns>
42
    private static string ProcessString(string s)
43
    {
220✔
44
        if (s == null)
220✔
45
            return "null";
44✔
46

47
        s = s.Replace("\"", "\"\"");
176✔
48
        return $"@\"{s}\"";
176✔
49
    }
220✔
50

51
    /// <summary>
52
    ///     从C#表达式中取得对象
53
    /// </summary>
54
    /// <param name="str">C#表达式</param>
55
    /// <param name="type">对象类型</param>
56
    /// <returns>对象</returns>
57
    private static object ParseCSharp(string str, Type type)
58
    {
24✔
59
        var sb = new StringBuilder();
24✔
60
        sb.Append("using System;\n");
24✔
61
        sb.Append("using System.Collections.Generic;\n");
24✔
62
        sb.Append("using AccountingServer.Entities;\n");
24✔
63
        sb.Append("namespace AccountingServer.Shell.Dynamic\n");
24✔
64
        sb.Append("{\n");
24✔
65
        sb.Append("    public static class ObjectCreator\n");
24✔
66
        sb.Append("    {\n");
24✔
67
        sb.Append("        private static DateTime D(string s)\n");
24✔
68
        sb.Append("        {\n");
24✔
69
        sb.Append("            return DateTime.Parse(s+\"Z\").ToUniversalTime();\n");
24✔
70
        sb.Append("        }\n");
24✔
71
        sb.Append("        private static string G()\n");
24✔
72
        sb.Append("        {\n");
24✔
73
        sb.Append("            return Guid.NewGuid().ToString().ToUpperInvariant();\n");
24✔
74
        sb.Append("        }\n");
24✔
75
        sb.Append($"        public static {type.FullName} GetObject()\n");
24✔
76
        sb.Append("        {\n");
24✔
77
        sb.Append($"            return {str};\n");
24✔
78
        sb.Append("        }\n");
24✔
79
        sb.Append("    }\n");
24✔
80
        sb.Append("}\n");
24✔
81

82
        var syntaxTree = CSharpSyntaxTree.ParseText(sb.ToString());
24✔
83
        var assemblyName = Path.GetRandomFileName();
24✔
84

85
        var refPaths = new[]
24✔
86
            {
87
                typeof(object).GetTypeInfo().Assembly.Location,
88
                typeof(Console).GetTypeInfo().Assembly.Location,
89
                Path.Combine(
90
                    Path.GetDirectoryName(typeof(GCSettings).GetTypeInfo().Assembly.Location)!,
91
                    "System.Runtime.dll"),
92
                Path.Combine(Path.GetDirectoryName(typeof(IList).GetTypeInfo().Assembly.Location)!,
93
                    "System.Collections.dll"),
94
                Path.Combine(Path.GetDirectoryName(typeof(Voucher).GetTypeInfo().Assembly.Location)!,
95
                    "AccountingServer.Entities.dll"),
96
            };
97
        var references = refPaths.Select(static r => MetadataReference.CreateFromFile(r)).ToArray();
120✔
98
        var compilation = CSharpCompilation.Create(
24✔
99
            assemblyName,
100
            new[] { syntaxTree },
101
            references,
102
            new(OutputKind.DynamicallyLinkedLibrary));
103

104
        using var ms = new MemoryStream();
24✔
105

106
        var result = compilation.Emit(ms);
24✔
107
        if (!result.Success)
24✔
108
        {
6✔
109
            var failure = result.Diagnostics.First(static diagnostic =>
6✔
110
                diagnostic.IsWarningAsError ||
6✔
111
                diagnostic.Severity == DiagnosticSeverity.Error);
112
            throw new(failure.GetMessage());
6✔
113
        }
114

115
        ms.Seek(0, SeekOrigin.Begin);
18✔
116

117
        var resultAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);
18✔
118
        return
18✔
119
            resultAssembly.GetType("AccountingServer.Shell.Dynamic.ObjectCreator")!
120
                .GetMethod("GetObject")
121
                ?.Invoke(null, null);
122
    }
18✔
123

124
    #region Voucher
125

126
    /// <inheritdoc />
127
    public string PresentVoucherDetail(VoucherDetail detail)
128
    {
28✔
129
        if (detail == null)
28✔
130
            return string.Empty;
×
131

132
        var sb = new StringBuilder();
28✔
133
        sb.Append("        new VoucherDetail { ");
28✔
134
        sb.Append($"User = {ProcessString(detail.User)}, ");
28✔
135
        sb.Append($"Currency = {ProcessString(detail.Currency)},\n");
28✔
136
        sb.Append("                            ");
28✔
137
        sb.Append($"Title = {detail.Title:0}, ");
28✔
138
        sb.Append(
28✔
139
            detail.SubTitle.HasValue
140
                ? $"SubTitle = {detail.SubTitle:00},    // {TitleManager.GetTitleName(detail)}"
141
                : $"                  // {TitleManager.GetTitleName(detail)}");
142
        sb.Append("\n");
28✔
143
        sb.Append("                            ");
28✔
144
        if (detail.Content != null)
28✔
145
            sb.Append($"Content = {ProcessString(detail.Content)}, ");
7✔
146
        sb.Append(detail.Fund.HasValue ? $"Fund = {detail.Fund}" : "Fund = null");
28✔
147
        if (detail.Remark != null)
28✔
148
            sb.Append($", Remark = {ProcessString(detail.Remark)}");
21✔
149
        sb.Append(" },\n");
28✔
150
        sb.Append("\n");
28✔
151
        return sb.ToString();
28✔
152
    }
28✔
153

154
    /// <inheritdoc />
155
    public string PresentVoucherDetail(VoucherDetailR detail)
156
        => PresentVoucherDetail((VoucherDetail)detail);
×
157

158
    /// <inheritdoc />
159
    public string PresentVoucher(Voucher voucher)
160
    {
15✔
161
        if (voucher == null)
15✔
162
            return "new Voucher {\n\n}";
1✔
163

164
        var sb = new StringBuilder();
14✔
165
        sb.Append("new Voucher {");
14✔
166
        sb.Append($"  ID = {ProcessString(voucher.ID)},");
14✔
167
        sb.Append("\n");
14✔
168
        sb.Append(voucher.Date.HasValue ? $"    Date = D(\"{voucher.Date:yyyy-MM-dd}\"),\n" : "    Date = null,\n");
14✔
169
        if ((voucher.Type ?? VoucherType.Ordinary) != VoucherType.Ordinary)
14✔
170
            sb.Append($"    Type = VoucherType.{voucher.Type},\n");
13✔
171
        if (voucher.Remark != null)
14✔
172
            sb.Append($"    Remark = {ProcessString(voucher.Remark)},\n");
14✔
173
        if (voucher.Redacted)
14✔
174
            sb.Append("    Details = new() { // [[REDACTED]]\n");
×
175
        else
176
            sb.Append("    Details = new() {\n");
14✔
177
        foreach (var detail in voucher.Details)
42✔
178
            sb.Append(PresentVoucherDetail(detail));
28✔
179

180
        sb.Append("} }");
14✔
181
        return sb.ToString();
14✔
182
    }
15✔
183

184
    /// <inheritdoc />
185
    public string PresentVoucher(Voucher voucher, string inject) => throw new NotImplementedException();
×
186

187
    /// <inheritdoc />
188
    public Voucher ParseVoucher(string str)
189
    {
9✔
190
        try
191
        {
9✔
192
            return (Voucher)ParseCSharp(str, typeof(Voucher));
9✔
193
        }
194
        catch (Exception e)
2✔
195
        {
2✔
196
            throw new FormatException("格式错误", e);
2✔
197
        }
198
    }
7✔
199

200
    /// <inheritdoc />
201
    public VoucherDetail ParseVoucherDetail(string str)
202
    {
×
203
        try
204
        {
×
205
            return (VoucherDetail)ParseCSharp(str, typeof(VoucherDetail));
×
206
        }
207
        catch (Exception e)
×
208
        {
×
209
            throw new FormatException("格式错误", e);
×
210
        }
211
    }
×
212

213
    #endregion
214

215
    #region Asset
216

217
    /// <inheritdoc />
218
    public string PresentAsset(Asset asset)
219
    {
5✔
220
        if (asset == null)
5✔
221
            return "null";
1✔
222

223
        var sb = new StringBuilder();
4✔
224
        sb.Append("new Asset {");
4✔
225
        sb.Append($"  StringID = {ProcessString(asset.StringID)},\n");
4✔
226
        sb.Append($"    User = {ProcessString(asset.User)}, \n");
4✔
227
        sb.Append($"    Name = {ProcessString(asset.Name)},\n");
4✔
228
        sb.Append(asset.Date.HasValue ? $"    Date = D(\"{asset.Date:yyyy-MM-dd}\"),\n" : "    Date = null,\n");
4✔
229
        sb.Append($"    Currency = {ProcessString(asset.Currency)},\n");
4✔
230
        sb.Append($"    Value = {asset.Value}, Salvage = {asset.Salvage}, Life = {asset.Life},\n");
4✔
231
        sb.Append($"    Title = {asset.Title}, Method = DepreciationMethod.{asset.Method},\n");
4✔
232
        sb.Append(
4✔
233
            $"    DepreciationTitle = {asset.DepreciationTitle}, DepreciationExpenseTitle = {asset.DepreciationExpenseTitle}, DepreciationExpenseSubTitle = {asset.DepreciationExpenseSubTitle},\n");
234
        sb.Append(
4✔
235
            $"    DevaluationTitle = {asset.DevaluationTitle}, DevaluationExpenseTitle = {asset.DevaluationExpenseTitle}, DevaluationExpenseSubTitle = {asset.DevaluationExpenseSubTitle},\n");
236
        if (asset.Remark != null)
4✔
237
            sb.Append($"    Remark = {ProcessString(asset.Remark)},\n");
4✔
238
        sb.Append("    Schedule = new() {\n");
4✔
239
        if (asset.Schedule != null)
4✔
240
        {
4✔
241
            void Present(AssetItem item, string str)
242
            {
16✔
243
                sb.Append($"        new {item.GetType().Name.CPadRight(16)} {{ ");
16✔
244
                sb.Append(item.Date.HasValue ? $"Date = D(\"{item.Date:yyyy-MM-dd}\"), " : "Date = null, ");
16✔
245
                sb.Append($"VoucherID = {(ProcessString(item.VoucherID) + ",").CPadRight(28)}");
16✔
246
                sb.Append(str.CPadRight(30));
16✔
247
                sb.Append($"Value = {item.Value.ToString(CultureInfo.InvariantCulture).CPadRight(16)} ");
16✔
248
                if (item.Remark != null)
16✔
249
                {
16✔
250
                    sb.Append("".CPadLeft(30));
16✔
251
                    sb.Append($", Remark = {ProcessString(item.Remark)} ");
16✔
252
                }
16✔
253

254
                sb.Append("},\n");
16✔
255
            }
16✔
256

257
            foreach (var item in asset.Schedule)
20✔
258
                Present(item, item switch
×
259
                    {
260
                        AcquisitionItem acq => $"OrigValue = {acq.OrigValue},",
4✔
261
                        DepreciateItem dep => $"Amount    = {dep.Amount},",
4✔
262
                        DevalueItem dev => $"FairValue = {dev.FairValue}, Amount = {dev.Amount},",
4✔
263
                        DispositionItem => "",
4✔
264
                        _ => throw new InvalidOperationException(),
×
265
                    });
266

267
            sb.Append("} }");
4✔
268
        }
4✔
269
        else
270
            sb.Append('}');
×
271

272
        return sb.ToString();
4✔
273
    }
5✔
274

275
    /// <inheritdoc />
276
    public Asset ParseAsset(string str)
277
    {
6✔
278
        try
279
        {
6✔
280
            return (Asset)ParseCSharp(str, typeof(Asset));
6✔
281
        }
282
        catch (Exception e)
2✔
283
        {
2✔
284
            throw new FormatException("格式错误", e);
2✔
285
        }
286
    }
4✔
287

288
    #endregion
289

290
    #region Amort
291

292
    /// <inheritdoc />
293
    public string PresentAmort(Amortization amort)
294
    {
8✔
295
        if (amort == null)
8✔
296
            return "null";
1✔
297

298
        var sb = new StringBuilder();
7✔
299
        sb.Append("new Amortization {");
7✔
300
        sb.Append($"  StringID = {ProcessString(amort.StringID)},\n");
7✔
301
        sb.Append($"    User = {ProcessString(amort.User)}, \n");
7✔
302
        sb.Append($"    Name = {ProcessString(amort.Name)},\n");
7✔
303
        if (amort.Date.HasValue)
7✔
304
        {
3✔
305
            sb.Append($"    Date = D(\"{amort.Date:yyyy-MM-dd}\"),");
3✔
306
            sb.Append("\n");
3✔
307
        }
3✔
308
        else
309
            sb.Append("    Date = null,\n");
4✔
310

311
        sb.Append($"    Value = {amort.Value}, \n");
7✔
312
        sb.Append($"    TotalDays = {amort.TotalDays}, Interval = AmortizeInterval.{amort.Interval},\n");
7✔
313
        sb.Append("Template = ");
7✔
314
        sb.Append(PresentVoucher(amort.Template));
7✔
315
        sb.Append(",\n");
7✔
316
        if (amort.Remark != null)
7✔
317
            sb.Append($"    Remark = {ProcessString(amort.Remark)},\n");
7✔
318

319
        if (amort.Schedule != null)
7✔
320
        {
7✔
321
            sb.Append("    Schedule = new() {\n");
7✔
322
            foreach (var item in amort.Schedule)
21✔
323
            {
14✔
324
                sb.Append("        new AmortItem { ");
14✔
325
                sb.Append(item.Date.HasValue ? $"Date = D(\"{item.Date:yyyy-MM-dd}\"), " : "Date = null, ");
14✔
326
                sb.Append($"VoucherID = {(ProcessString(item.VoucherID) + ",").CPadRight(28)}");
14✔
327
                sb.Append($"Amount = {(item.Amount.ToString(CultureInfo.InvariantCulture) + ",").CPadRight(19)}");
14✔
328
                sb.Append($"Value = {item.Value.ToString(CultureInfo.InvariantCulture).CPadRight(16)} ");
14✔
329
                if (item.Remark != null)
14✔
330
                {
14✔
331
                    sb.Append("".CPadLeft(30));
14✔
332
                    sb.Append($", Remark = {ProcessString(item.Remark)} ");
14✔
333
                }
14✔
334

335
                sb.Append("},\n");
14✔
336
            }
14✔
337

338
            sb.Append("} }");
7✔
339
        }
7✔
340
        else
341
        {
×
342
            sb.Append("    Schedule = null\n");
×
343
            sb.Append('}');
×
344
        }
×
345

346
        return sb.ToString();
7✔
347
    }
8✔
348

349
    /// <inheritdoc />
350
    public Amortization ParseAmort(string str)
351
    {
9✔
352
        try
353
        {
9✔
354
            return (Amortization)ParseCSharp(str, typeof(Amortization));
9✔
355
        }
356
        catch (Exception e)
2✔
357
        {
2✔
358
            throw new FormatException("格式错误", e);
2✔
359
        }
360
    }
7✔
361

362
    #endregion
363
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc