Skip to content

Commit 96a08f8

Browse files
committed
Add NumberNormalizer
1 parent 9ae84bd commit 96a08f8

9 files changed

Lines changed: 588 additions & 88 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ updates:
55
schedule:
66
interval: "daily"
77
open-pull-requests-limit: 10
8+
ignore:
9+
- dependency-name: "Meziantou.Analyzer"
810
groups:
911
tests:
1012
patterns: ["*"]

‎.github/workflows/codeql.yml‎

Lines changed: 19 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,14 @@
1-
# For most projects, this workflow file will not need changing; you simply need
2-
# to commit it to your repository.
3-
#
4-
# You may wish to alter this file to override the set of languages analyzed,
5-
# or to provide custom queries or build logic.
6-
#
7-
# ******** NOTE ********
8-
# We have attempted to detect the languages in your repository. Please check
9-
# the `language` matrix defined below to confirm you have the correct set of
10-
# supported CodeQL languages.
11-
#
121
name: "CodeQL"
132

143
on:
154
push:
165
branches: [ "master" ]
17-
pull_request:
18-
# The branches below must be a subset of the branches above
6+
pull_request:
197
branches: [ "master" ]
208

219
jobs:
2210
analyze:
2311
name: Analyze
24-
# Runner size impacts CodeQL analysis time. To learn more, please see:
25-
# - https://gh.io/recommended-hardware-resources-for-running-codeql
26-
# - https://gh.io/supported-runners-and-hardware-resources
27-
# - https://gh.io/using-larger-runners
28-
# Consider using larger runners for possible analysis time improvements.
2912
runs-on: windows-2022
3013
permissions:
3114
actions: read
@@ -36,51 +19,42 @@ jobs:
3619
fail-fast: false
3720
matrix:
3821
language: [ 'csharp' ]
39-
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ]
40-
# Use only 'java' to analyze code written in Java, Kotlin or both
41-
# Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
42-
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
4322

4423
steps:
4524
- name: Checkout repository
4625
uses: actions/checkout@v6
4726

48-
# Initializes the CodeQL tools for scanning.
4927
- name: Initialize CodeQL
5028
uses: github/codeql-action/init@v4
5129
with:
5230
languages: ${{ matrix.language }}
5331
config-file: ./.github/workflows/codeql/codeql-config.yml
54-
# If you wish to specify custom queries, you can do so here or in a config file.
55-
# By default, queries listed here will override any specified in a config file.
56-
# Prefix the list here with "+" to use these queries and those in the config file.
5732

58-
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
59-
# queries: security-extended,security-and-quality
60-
61-
62-
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
63-
# If this step fails, then you should remove it and run the build manually (see below)
64-
# - name: Autobuild
65-
# uses: github/codeql-action/autobuild@v4
6633
- name: Setup .NET
67-
uses: actions/setup-dotnet@v5
34+
uses: actions/setup-dotnet@v4
6835
with:
6936
dotnet-version: 10.0.x
37+
7038
- name: Build
7139
run: dotnet build --configuration Release
7240

73-
# ℹ️ Command-line programs to run using the OS shell.
74-
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
75-
76-
# If the Autobuild fails above, remove it and uncomment the following three lines.
77-
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
78-
79-
# - run: |
80-
# echo "Run, Build Application using script"
81-
# ./location_of_script_within_repo/buildscript.sh
82-
8341
- name: Perform CodeQL Analysis
8442
uses: github/codeql-action/analyze@v4
8543
with:
8644
category: "/language:${{matrix.language}}"
45+
upload: false
46+
output: sarif-results
47+
48+
- name: Filter SARIF
49+
uses: advanced-security/filter-sarif@v1
50+
with:
51+
patterns: |
52+
-**/*.g.cs
53+
-**/*.g.i.cs
54+
input: sarif-results/${{ matrix.language }}.sarif
55+
output: sarif-results/${{ matrix.language }}.sarif
56+
57+
- name: Upload Filtered Results
58+
uses: github/codeql-action/upload-sarif@v3
59+
with:
60+
sarif_file: sarif-results/${{ matrix.language }}.sarif

‎DNTPersianUtils.Core.sln‎

Lines changed: 0 additions & 41 deletions
This file was deleted.

‎DNTPersianUtils.Core.slnx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Solution>
2+
<Folder Name="/src/">
3+
<Project Path="src/DNTPersianUtils.Core.PerformanceTests/DNTPersianUtils.Core.PerformanceTests.csproj">
4+
<BuildType Project="Debug" />
5+
</Project>
6+
<Project Path="src/DNTPersianUtils.Core.Tests/DNTPersianUtils.Core.Tests.csproj">
7+
<BuildType Project="Debug" />
8+
</Project>
9+
<Project Path="src/DNTPersianUtils.Core/DNTPersianUtils.Core.csproj" />
10+
</Folder>
11+
</Solution>

‎global.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"sdk": {
3-
"version": "10.0.102",
3+
"version": "10.0.301",
44
"rollForward": "latestMajor",
55
"allowPrerelease": false
66
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
using Microsoft.VisualStudio.TestTools.UnitTesting;
2+
3+
namespace DNTPersianUtils.Core.Tests;
4+
5+
[TestClass]
6+
public class NumberNormalizerTests
7+
{
8+
[TestMethod]
9+
10+
// حذف کاراکتر غیرمجاز $
11+
[DataRow("$123.45", 0, false)]
12+
[DataRow("12%34", 0, false)]
13+
[DataRow("(123)", 0, false)]
14+
[DataRow("50%", 0, false)]
15+
16+
// Leading decimal point
17+
[DataRow(".5", 0.5, true)]
18+
[DataRow("-.5", -0.5, true)]
19+
[DataRow("+.5", +0.5, true)]
20+
21+
// Trailing decimal point
22+
[DataRow("5.", 5.0, true)]
23+
[DataRow("-5.", -5.0, true)]
24+
[DataRow("+5.", +5.0, true)]
25+
26+
// Persian digits
27+
[DataRow("۱۲۳", 123, true)]
28+
[DataRow("۱۲۳٫۴۵", 123.45, true)]
29+
[DataRow("۰٫۷۵", 0.75, true)]
30+
[DataRow("۱۲.۵", 12.5, true)]
31+
[DataRow("١٢,٥", 12.5, true)]
32+
33+
// Arabic digits
34+
[DataRow("٣٫١٤", 3.14, true)]
35+
[DataRow("١٢٣٤", 1234, true)]
36+
37+
// Decimal separator normalization
38+
[DataRow("1,25", 1.25, true)]
39+
[DataRow("12,0", 12.0, true)]
40+
[DataRow("123,45", 123.45, true)]
41+
[DataRow("،123،45", 12345, true)]
42+
43+
// اصلاح ممیزهای فارسی (/) به نقطه
44+
[DataRow("1/25", 1.25, true)]
45+
[DataRow("45/", 45.0, true)]
46+
[DataRow("1,250.75", 1250.75, true)]
47+
48+
// Signs
49+
[DataRow("−12", -12, true)]
50+
[DataRow("-15", -15, true)]
51+
[DataRow("+7", +7, true)]
52+
[DataRow("+-123", 0, false)]
53+
[DataRow("-+123", 0, false)]
54+
[DataRow("123-", 0, false)]
55+
[DataRow("123+", 0, false)]
56+
[DataRow(" - 456.78 ", -456.78, true)]
57+
58+
// Remove thousands separators
59+
[DataRow("1_234.567", 1234.567, true)]
60+
[DataRow("1'234.561", 1234.561, true)]
61+
[DataRow("۱،۲۳۴٫۵۶", 1234.56, true)]
62+
[DataRow("12,345,678", 12345678, true)]
63+
[DataRow("12345,678", 12345.678, true)]
64+
[DataRow("1,234,567.89", 1234567.89, true)]
65+
[DataRow("1,000", 1000, true)]
66+
[DataRow("11,000", 11000, true)]
67+
[DataRow("111,000", 111000, true)]
68+
[DataRow("1,111,000", 1111000, true)]
69+
[DataRow("1,000.5", 1000.5, true)]
70+
[DataRow("1.234,56", 1234.56, true)]
71+
[DataRow("1,234.56", 1234.56, true)]
72+
[DataRow(" 1 2 3 . 4 ", 123.4, true)]
73+
[DataRow("12..5", 0, false)]
74+
[DataRow("1234,56", 1234.56, true)]
75+
[DataRow("1234.56", 1234.56, true)]
76+
[DataRow("1234", 1234, true)]
77+
[DataRow("12.345.678", 12345678, true)]
78+
[DataRow("12,345,678.90", 12345678.90, true)]
79+
[DataRow("12.345.678,90", 12345678.90, true)]
80+
[DataRow("1,2", 1.2, true)]
81+
[DataRow("100,5", 100.5, true)]
82+
[DataRow("1,23,456", 0, false)]
83+
84+
// Remove whitespace
85+
[DataRow(" 123 ", 123, true)]
86+
[DataRow("123 456", 123456, true)]
87+
[DataRow("1 234", 1234, true)]
88+
[DataRow("۱ ۲۳۴٫۵", 1234.5, true)]
89+
90+
// Mixed Persian & English
91+
[DataRow("۱۲3.۴۵", 123.45, true)]
92+
93+
// Already normalized
94+
[DataRow("123.45", 123.45, true)]
95+
[DataRow("-123.45", -123.45, true)]
96+
97+
// صفرهای ابتدایی حذف نمی‌شوند (طبق متد فعلی)
98+
[DataRow("000123", 123, true)]
99+
100+
// Empty
101+
[DataRow("", 0, false)] // ورودی خالی
102+
[DataRow(" ", 0, false)] // ورودی فقط فاصله
103+
[DataRow("-", 0, false)] // فقط علامت منفی
104+
[DataRow("+", 0, false)] // فقط علامت مثبت
105+
106+
//Bad inputs
107+
[DataRow("12..3", 0, false)]
108+
[DataRow("1.2.3", 0, false)]
109+
[DataRow("--5", 0, false)]
110+
[DataRow("++5", 0, false)]
111+
[DataRow("12a", 0, false)]
112+
[DataRow("abc", 0, false)]
113+
[DataRow("-.+5", 0, false)]
114+
[DataRow("1-2", 0, false)]
115+
[DataRow("1+2", 0, false)]
116+
[DataRow("1..2", 0, false)]
117+
[DataRow("1....2", 0, false)]
118+
[DataRow("1.23.456", 0, false)] // هزارگان اشتباه
119+
[DataRow("12,,5", 0, false)] // دو ممیز
120+
[DataRow("1,234.5.6", 0, false)] // دو ممیز اعشاری
121+
[DataRow("1.234,5,6", 0, false)] // دو ممیز اعشاری
122+
[DataRow("123,45,67", 0, false)] // الگوی نامعتبر
123+
124+
// some tests
125+
[DataRow("1_234.56", 1234.56, true)]
126+
[DataRow("+.75", 0.75, true)]
127+
public void ToNormalizedNumberShouldProduceParsableDecimal(string input, double expected, bool success)
128+
{
129+
var result = input.TryNormalizeAndParseNumberString<decimal>(out var actual);
130+
Assert.AreEqual(success, result);
131+
Assert.AreEqual((decimal)expected, actual);
132+
}
133+
}

‎src/DNTPersianUtils.Core/DNTPersianUtils.Core.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<Description>DNTPersianUtils.Core is a collection of Persian helper extension methods.</Description>
4-
<VersionPrefix>6.9.0</VersionPrefix>
4+
<VersionPrefix>7.0.0</VersionPrefix>
55
<Authors>Vahid Nasiri</Authors>
66
<TargetFrameworks>net10.0;net9.0;net8.0;net7.0;net6.0;net5.0;netstandard2.0;netstandard1.3;netstandard2.1;net46;</TargetFrameworks>
77
<TargetFrameworks Condition="'$(OS)' != 'Windows_NT'">net5.0;netstandard2.1;netstandard1.3;netstandard2.0</TargetFrameworks>
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
namespace DNTPersianUtils.Core;
2+
3+
/// <summary>
4+
/// </summary>
5+
public static class CharExtensions
6+
{
7+
extension(char ch)
8+
{
9+
/// <summary>
10+
/// آيا يك كاراكتر فاصله متداول است؟
11+
/// </summary>
12+
/// <returns></returns>
13+
public bool IsSpace()
14+
=> ch is ' ' or '\t' or '\r' or '\n' or '\u00A0' or '\u2009' or '\u202F' or '\u200B' or '\u200C' or
15+
'\u200D' or '\uFEFF';
16+
17+
/// <summary>
18+
/// آيا يك كاراكتر مميز متداول است؟
19+
/// </summary>
20+
/// <returns></returns>
21+
public bool IsDecimalSeparator() => ch is '.' or ',' or '٫' or '/';
22+
23+
/// <summary>
24+
/// آيا يك كاراكتر علامت متداول است؟
25+
/// </summary>
26+
/// <returns></returns>
27+
public bool IsSign() => ch is '+' or '+' or '-' or '−' or '-' or '–' or '—';
28+
29+
/// <summary>
30+
/// آيا يك كاراكتر مثبت متداول است؟
31+
/// </summary>
32+
/// <returns></returns>
33+
public bool IsPlusSign() => ch is '+' or '+';
34+
35+
/// <summary>
36+
/// آيا يك كاراكتر جداكننده هزارگان متداول است؟
37+
/// </summary>
38+
/// <returns></returns>
39+
public bool IsThousandsSeparator() => ch is '،' or '\'' or '_';
40+
41+
/// <summary>
42+
/// تبديل يك كاراكتر عددي ايراني به نمونه انگليسي
43+
/// </summary>
44+
/// <param name="digit"></param>
45+
/// <returns></returns>
46+
public bool TryConvertPersianDigit(out char digit)
47+
{
48+
if (ch is >= '\u06F0' and <= '\u06F9')
49+
{
50+
digit = (char)('0' + (ch - '\u06F0'));
51+
52+
return true;
53+
}
54+
55+
digit = '\0';
56+
57+
return false;
58+
}
59+
60+
/// <summary>
61+
/// تبديل يك كاراكتر عددي عربي به نمونه انگليسي
62+
/// </summary>
63+
/// <param name="digit"></param>
64+
/// <returns></returns>
65+
public bool TryConvertArabicDigit(out char digit)
66+
{
67+
if (ch is >= '\u0660' and <= '\u0669')
68+
{
69+
digit = (char)('0' + (ch - '\u0660'));
70+
71+
return true;
72+
}
73+
74+
digit = '\0';
75+
76+
return false;
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)