From e8cacb82c47af9f4f71b378b60652d22a5322b42 Mon Sep 17 00:00:00 2001 From: cssho Date: Thu, 5 Nov 2026 21:31:35 +0900 Subject: [PATCH] =?UTF-8?q?=E9=83=B5=E4=BE=BF=E7=95=AA=E5=8F=B7=E3=83=BB?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=82=B3=E3=83=BC=E3=83=89=E3=83=9E=E3=83=8B?= =?UTF-8?q?=E3=83=A5=E3=82=A2=E3=83=AB=E3=81=AE=E6=8A=9C=E3=81=8D=E5=87=BA?= =?UTF-8?q?=E3=81=97=E3=81=AE=E8=A3=9C=E8=B6=B3=E3=83=AB=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 特定文字(丁目、番地、号、ノ など)の前の漢数字を算用数字に変換する(補足ルール1) - 算用数字に続く"F"は抜き出さず、後ろに抜き出す文字があればハイフンにする(補足ルール2、3) - 住所A(町域名まで)と住所B(町域名以降)を分けて受け取る Create を追加し、 住所Bだけから抜き出すようにした - 1.x の出力がマニュアルと異なる5ケースを比較用データから外し、 マニュアルの例を期待値にしたテストを追加 - README と CLAUDE.md を更新 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HNb8H3ydQTR8dWU8d28osE --- CLAUDE.md | 6 +- README.md | 16 ++- .../SupplementaryRuleTests.cs | 134 ++++++++++++++++++ .../TestData/cases.tsv | 5 - .../TestData/v1-10.png | Bin 420 -> 0 bytes .../TestData/v1-11.png | Bin 404 -> 0 bytes .../TestData/v1-3.png | Bin 461 -> 0 bytes .../TestData/v1-8.png | Bin 427 -> 0 bytes .../TestData/v1-9.png | Bin 392 -> 0 bytes ZipConvertCustomerBarCode/BarCode.cs | 84 ++++++++++- 10 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 ZipConvertCustomerBarCode.Tests/SupplementaryRuleTests.cs delete mode 100644 ZipConvertCustomerBarCode.Tests/TestData/v1-10.png delete mode 100644 ZipConvertCustomerBarCode.Tests/TestData/v1-11.png delete mode 100644 ZipConvertCustomerBarCode.Tests/TestData/v1-3.png delete mode 100644 ZipConvertCustomerBarCode.Tests/TestData/v1-8.png delete mode 100644 ZipConvertCustomerBarCode.Tests/TestData/v1-9.png diff --git a/CLAUDE.md b/CLAUDE.md index 44bc5db..66e660f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -19,7 +19,7 @@ ## テスト - `dotnet test` で実行(xUnit v3、net10.0)。`global.json` で Microsoft.Testing.Platform を指定しているため、`Microsoft.NET.Test.Sdk` や `xunit.runner.visualstudio` は追加しない。 -- `TestData/cases.tsv` と `v1-*.png` は 1.x(.NET Framework 版)の出力で、シンボル列と画像がこれとピクセル単位で一致することを確認している。出力が変わる修正をした場合は、意図した変更か確認してから期待値を更新する。 +- `TestData/cases.tsv` と `v1-*.png` は 1.x(.NET Framework 版)の出力で、シンボル列と画像がこれとピクセル単位で一致することを確認している。1.x がマニュアルと異なっていたケース(補足ルール対象など)は除いてあり、それらは `SupplementaryRuleTests` でマニュアルの「バーコード・データ」を期待値にしている。出力が変わる修正をした場合は、マニュアルに照らして意図した変更か確認してから期待値を更新する。 - `cases.tsv` はタブ区切り(郵便番号・住所・シンボル列・画像ファイル名)。住所が空の行があるので、シェルで加工するときは連続するタブがまとめられないよう注意する。 - System.Drawing のテストは Windows 以外ではスキップされる。 @@ -29,8 +29,8 @@ ## バーコード処理 -- 住所の変換(`BarCode.cs`)は日本郵便のバーコードマニュアル(http://www.post.japanpost.jp/zipcode/zipmanual/p19.html)に従う。コード中の番号付きコメント(1.〜6.)はマニュアルの手順番号に対応している。 +- 住所の変換(`BarCode.cs`)は日本郵便のバーコードマニュアル(http://www.post.japanpost.jp/zipcode/zipmanual/p19.html)に従う。コード中の番号付きコメント(1.〜6.、補足ルール1〜5)はマニュアルの手順番号に対応している。 +- マニュアルでは住所を「住所A(町域名まで)」と「住所B(町域名以降)」に分け、住所Bだけから抜き出す。`Create(郵便番号, 住所)` は住所全体を住所Bとして扱うため、町域名に特定文字の前の漢数字を含む住所(一番町、八丁堀など)では誤る。これは既知の制限で、`Create(郵便番号, 住所A, 住所B)` を推奨している。 - バーコードは常に 23 シンボル: スタート + 郵便番号 7 桁 + 住所表示番号 13 枠(不足分は `CC4` で埋める)+ チェックデジット + ストップ。 - バーの形は `CustomerBarCode.cs` の `BarPatternDic` で定義(1 シンボル = 幅 12px に 3 本、x=0/4/8、幅 2px、上・中・下段 各 4px)。この表は旧バージョンの GIF 画像とピクセル単位で一致することを確認済みなので、変更するときは既存出力と比較する。 - 描画パッケージは `GetBars()` の矩形を白地に黒で塗るだけにし、バーコードのロジックはコアに置く。 -- 住所中の漢数字には未対応(README に記載の既知の制限)。 diff --git a/README.md b/README.md index 99d5ea5..bdebc93 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,22 @@ SkiaSharp を Linux で使う場合は、利用側で `SkiaSharp.NativeAssets.Li ```csharp using ZipConvertCustomerBarCode; -// 郵便番号,住所 -CustomerBarCode code = BarCode.Create("0140113", "秋田県大仙市堀見内 南田茂木 添60-1"); +// 郵便番号, 住所A(町域名までの住所), 住所B(町域名以降の住所) +CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室"); ``` 郵便番号が7桁の数値でない場合は `ArgumentException` になります。 +住所表示番号は、[郵便番号・バーコードマニュアル](https://www.post.japanpost.jp/zipcode/zipmanual/p19.html)の抜き出しルール(補足ルールを含む)に従って住所Bから抜き出します。 +「丁目・番地・号」などの前の漢数字は算用数字に変換し(例:十一丁目 → 11)、「2F」のような階数の F は抜き出しません。 + +住所を分けずに渡すこともできます。 + +```csharp +CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関1丁目3番2号 郵便プラザ503室"); +``` + +ただしこの場合は住所全体を住所Bとして扱うため、町域名に「丁目・番・ノ」などの前の漢数字を含む住所(一番町、八丁堀、三ノ輪 など)では、町域名の漢数字も抜き出されて誤ったバーコードになります。住所A・住所Bに分けて渡すことをおすすめします。 + ### SVG(依存ライブラリなし) ```csharp string svg = code.ToSvg(); @@ -69,5 +80,4 @@ foreach (var bar in code.GetBars()) | `BarCode.Export(zip, address, path)` | `BarCode.Create(zip, address).Save(path, ImageFormat.Gif)`(Drawing) | ## 備考 -* 漢数字未対応 * 住所→郵便番号変換辞書検討中(郵便番号の入力が不要に) diff --git a/ZipConvertCustomerBarCode.Tests/SupplementaryRuleTests.cs b/ZipConvertCustomerBarCode.Tests/SupplementaryRuleTests.cs new file mode 100644 index 0000000..2bdc7f2 --- /dev/null +++ b/ZipConvertCustomerBarCode.Tests/SupplementaryRuleTests.cs @@ -0,0 +1,134 @@ +using System.Text; + +namespace ZipConvertCustomerBarCode.Tests +{ + // 抜き出しの基本ルール・補足ルール + // 期待値はマニュアルの「バーコード・データ」 + // http://www.post.japanpost.jp/zipcode/zipmanual/p19.html + public class SupplementaryRuleTests + { + [Theory] + // 基本ルール(p17、p19) + [InlineData("1000013", "東京都千代田区霞が関1丁目3番2号 郵便プラザ503室", "1-3-2-503")] + [InlineData("2630023", "千葉市稲毛区緑町3丁目30-8 郵便ビル403号", "3-30-8-403")] + [InlineData("2730102", "千葉県鎌ヶ谷市右京塚 東3丁目-20-5 郵便・A&bコーポB604号", "3-20-5B604")] + // 補足ルール1: 特定文字の前の漢数字は算用数字に変換する + [InlineData("1980036", "東京都青梅市河辺町十一丁目六番地一号 郵便タワー601", "11-6-1-601")] + [InlineData("0800831", "北海道帯広市稲田町南七線 西28", "7-28")] + // 補足ルール2: 算用数字に続く"F"は抜き出さない + [InlineData("3170055", "茨城県日立市宮田町6丁目7-14 ABCビル2F", "6-7-14-2")] + // 補足ルール3: "F"以降に抜き出す文字があれば"F"はハイフンにする + [InlineData("3170055", "茨城県日立市宮田町6丁目7-14 ABCビル2F201号室", "6-7-14-2-201")] + // 補足ルール4: アルファベット前後のハイフンは取り除く + [InlineData("6500046", "神戸市中央区港島中町9丁目7-6 郵便シティA棟1F1号", "9-7-6A1-1")] + // 補足ルール5: その結果2文字以上連続したアルファベットはそのまま + [InlineData("6230011", "京都府綾部市青野町綾部6-7 LプラザB106", "6-7LB106")] + // 13けたで切り捨て(p20) + [InlineData("0640804", "札幌市中央区南四条西29丁目1524-23 第2郵便ハウス501", "29-1524-23-2-")] + public void マニュアルの例どおりに抜き出す(string zipCode, string address, string expected) + { + var code = BarCode.Create(zipCode, address); + + Assert.Equal(expected, ToBarCodeData(code)); + AssertCheckDigit(code); + } + + [Theory] + [InlineData("二〇五号", "205")] + [InlineData("十番地", "10")] + [InlineData("十一丁目", "11")] + [InlineData("二十三番", "23")] + [InlineData("百二番地", "102")] + [InlineData("千二百三十四番", "1234")] + [InlineData("三ノ二番", "3-2")] + [InlineData("一の三号", "1-3")] + [InlineData("第二地割", "2")] + [InlineData("四丁", "4")] + // 特定文字の前でない漢数字は抜き出さない + [InlineData("北六条東4丁目", "4")] + public void 漢数字を算用数字に変換する(string address, string expected) + { + var code = BarCode.Create("1234567", address); + + Assert.Equal(expected, ToBarCodeData(code)); + } + + [Theory] + [InlineData("2F", "2")] + [InlineData("2F201", "2-201")] + [InlineData("2F-3", "2-3")] + // 数字に続かない"F"、2文字以上連続する"F"は対象外 + [InlineData("F棟2", "F2")] + [InlineData("2FG棟3", "2-3")] + public void 数字に続くFの扱い(string address, string expected) + { + var code = BarCode.Create("1234567", address); + + Assert.Equal(expected, ToBarCodeData(code)); + } + + [Fact] + public void 住所Bだけから抜き出す() + { + // 町域名に特定文字の前の漢数字を含む + Assert.Equal("5-3", ToBarCodeData(BarCode.Create("1020082", "東京都千代田区一番町", "5-3"))); + Assert.Equal("2-1-1", ToBarCodeData(BarCode.Create("1040032", "東京都中央区八丁堀", "2丁目1-1"))); + Assert.Equal("1-2", ToBarCodeData(BarCode.Create("1100011", "東京都台東区三ノ輪", "1丁目2"))); + } + + [Fact] + public void 住所Bが住所Aで始まる場合は住所Aを取り除く() + { + var code = BarCode.Create("1040032", "東京都中央区八丁堀", "東京都中央区八丁堀2丁目1-1"); + + Assert.Equal("2-1-1", ToBarCodeData(code)); + } + + [Fact] + public void 住所全体を渡すと町域名の漢数字も抜き出される() + { + // 2引数版の既知の制限(XMLコメントとREADMEに記載) + var code = BarCode.Create("1020082", "東京都千代田区一番町5-3"); + + Assert.Equal("1-5-3", ToBarCodeData(code)); + } + + [Fact] + public void 住所Aと住所Bがnullなら例外() + { + Assert.Throws(() => BarCode.Create("1020082", null!, "5-3")); + Assert.Throws(() => BarCode.Create("1020082", "東京都千代田区一番町", null!)); + } + + // 住所表示番号のシンボルをバーコード・データの文字列に戻す(CC4 の埋め草は除く) + private static string ToBarCodeData(CustomerBarCode code) + { + var symbols = code.Symbols.Skip(8).Take(13).ToList(); + var sb = new StringBuilder(); + for (int i = 0; i < symbols.Count && symbols[i] != "CC4"; i++) + { + var s = symbols[i]; + if (s.StartsWith("CC")) + { + // 英字用制御コード + 数字でアルファベット1文字 + var control = s[2] - '1'; + var digit = symbols[++i][0] - '0'; + sb.Append((char)('A' + control * 10 + digit)); + } + else + { + sb.Append(s); + } + } + return sb.ToString(); + } + + // 郵便番号・住所表示番号・チェックデジットの合計が19の倍数 + private static void AssertCheckDigit(CustomerBarCode code) + { + var sum = code.Symbols.Skip(1).Take(21).Sum(s => + s == "-" ? 10 : s.StartsWith("CC") ? 10 + (s[2] - '0') : s[0] - '0'); + Assert.Equal(0, sum % 19); + } + } +} diff --git a/ZipConvertCustomerBarCode.Tests/TestData/cases.tsv b/ZipConvertCustomerBarCode.Tests/TestData/cases.tsv index 918ab88..de64856 100644 --- a/ZipConvertCustomerBarCode.Tests/TestData/cases.tsv +++ b/ZipConvertCustomerBarCode.Tests/TestData/cases.tsv @@ -1,15 +1,10 @@ 0140113 秋田県大仙市堀見内 南田茂木 添60-1 STC 0 1 4 0 1 1 3 6 0 - 1 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC8 SPC v1-0.png 2630023 千葉市稲毛区緑町3丁目30-8 郵便ビル403号 STC 2 6 3 0 0 2 3 3 - 3 0 - 8 - 4 0 3 CC4 CC4 CC4 5 SPC v1-1.png 0140113 秋田県大仙市堀見内南田茂木添60-1 STC 0 1 4 0 1 1 3 6 0 - 1 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC8 SPC v1-2.png -1100016 東京都台東区台東5-6-3 ABCビル10F STC 1 1 0 0 0 1 6 5 - 6 - 3 - 1 0 CC1 5 CC4 CC4 CC4 2 SPC v1-3.png 0600906 北海道札幌市東区北六条東4丁目 郵便センター6号館 STC 0 6 0 0 9 0 6 4 - 6 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 9 SPC v1-4.png 0650006 北海道札幌市東区北六条東8丁目 郵便センター10号館 STC 0 6 5 0 0 0 6 8 - 1 0 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 9 SPC v1-5.png 4070033 山梨県韮崎市龍岡町下條南割 韮崎400 STC 4 0 7 0 0 3 3 4 0 0 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 - SPC v1-6.png 2730102 千葉県鎌ケ谷市右京塚 東3丁目-20-5 郵便・A&bコーポB604号 STC 2 7 3 0 1 0 2 3 - 2 0 - 5 CC1 1 6 0 4 CC4 CC4 0 SPC v1-7.png -1980036 東京都青梅市河辺町十一丁目六番地一号 郵便タワー601 STC 1 9 8 0 0 3 6 6 0 1 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC6 SPC v1-8.png -0270203 岩手県宮古市大字津軽石第二地割原 郵便番号CD-1 STC 0 2 7 0 2 0 3 1 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 7 SPC v1-9.png -5900016 大阪府堺市堺区中田出井町四丁 AB 101-1 STC 5 9 0 0 0 1 6 1 0 1 - 1 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 6 SPC v1-10.png -0800831 北海道帯広市稲田町南七線 西28 STC 0 8 0 0 8 3 1 2 8 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 CC4 6 SPC v1-11.png 3170055 茨城県日立市宮田町6丁目7-14 ABC建設A棟 STC 3 1 7 0 0 5 5 6 - 7 - 1 4 CC1 0 CC4 CC4 CC4 CC4 CC4 CC2 SPC v1-12.png 6500046 神戸市中央区港島中町9丁目7-6 郵便シティ A-1101 STC 6 5 0 0 0 4 6 9 - 7 - 6 CC1 0 1 1 0 1 CC4 CC4 9 SPC v1-13.png 6230011 京都府綾部市青野町綾部6-7 LプラザB106 STC 6 2 3 0 0 1 1 6 - 7 CC2 1 CC1 1 1 0 6 CC4 CC4 CC4 4 SPC v1-14.png diff --git a/ZipConvertCustomerBarCode.Tests/TestData/v1-10.png b/ZipConvertCustomerBarCode.Tests/TestData/v1-10.png deleted file mode 100644 index 62513d87b0cc6c637aac28a4198f9cf68ffa9f39..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 420 zcmeAS@N?(olHy`uVBq!ia0y~yU=#tec{tdBFMGalHvTe*RhMmkmty+|MTy)TAn(x zNa0CHlbwdv<@@qK_Pzf;cgNkcz1ORE9jtwI#OmR^yc_f8&YN_ve1CFvnbk!*xi=4r z%+2H8zhwKqQuuXC^{&L~vMR5t`+xi1-hAouyJAE0>AQC-_AZycv)pfu@Vl37Kw-_7 zwco9`r7stL`}iQAUU~83uk-Q>=38y_+p%04D7<{h_q*?bw)k!7XaDKAH*WoWiB|Tn zReS4-jJWt;eXaWYBt=B-%ImMcPM*}TF;LG0Td(r=8O(KgCHsr3=a&@&RiE*^1#-KU z=Wa`w>C$)K-?_K^?wp=O%*B(+Kz93G%7Z)2?Ys5%@0sT6i$5Hgvjpt$$y;W_{I%=i h-*azkZ=F49f44R0()91v1;CJH@O1TaS?83{1ORe3&gB3A diff --git a/ZipConvertCustomerBarCode.Tests/TestData/v1-11.png b/ZipConvertCustomerBarCode.Tests/TestData/v1-11.png deleted file mode 100644 index 9130cad35129c48da89925ad0f1226d474a04874..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 404 zcmeAS@N?(olHy`uVBq!ia0y~yU=#tec{tdBFMGalHvUJtYcTJg8)n5`}&uXKTe0= z@NAM^$`s?`E*4i`5PSXoZsk(X>fQ4ys_uWgw)E|;C3?H07qH?p*#oH$V2)I`3DTejCT%zg^l{YqO;J zg6iA1RgYfQ-j1HW?e(NxZ~mRR{=F z|E5Z7+2y*(Ld c7TH?ul~tM+Sx5@}1BN7nr>mdKI;Vst07AsaZvX%Q diff --git a/ZipConvertCustomerBarCode.Tests/TestData/v1-3.png b/ZipConvertCustomerBarCode.Tests/TestData/v1-3.png deleted file mode 100644 index cdd7f29edd0e58ab76e07a6abaff4ab702692a00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 461 zcmeAS@N?(olHy`uVBq!ia0y~yU=#tec{tdBFuL?^;ULQ$F7Q%Di&+h210+_-BAP>1b9yPYMwcU)I31=_gv z^`t7LyXCum-j`;6f9zgy;1>``yMIimt*!lg_Nj*4n)U0~@82nNwD~I#m``6^5m37E z^`u=u^()I__g(wya_ctGNqO&g1Myp+e=NJ$L&bN4T@3Wf)9hIPC3}GmdHQYN?Wf=F ft?Q1Zh7t848$K&v3yPfti~c@NS|@%vZqN0jvEtu99g3T8wD+4b7DL4P+sIe3~sD_-wimEqf?)vUHGStY5$W{!W>0_SZmAu6O*=!Cycy_w>e!4g2=(t9xFgBj>Ys1IXlx+pmr80xjXL z-h1QU%=O%Re+dhgpVAD{=m9*Wc7R{!kv-immTQCGNumY(k3zE|&Ztav%l zm~NoORr>d)1C2KTss(!JQSSQFB|yIc-Ieq^ec!$A?@vF;UlnXx^nGqaAuxa$JYD@< J);T3K0RZH!+Ohxu diff --git a/ZipConvertCustomerBarCode.Tests/TestData/v1-9.png b/ZipConvertCustomerBarCode.Tests/TestData/v1-9.png deleted file mode 100644 index 4d0c6171753dcf0bd16a3bcdb7e8a355d3c9f90d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 392 zcmeAS@N?(olHy`uVBq!ia0y~yU=#tec{tdB*?YclHvUJv}4y12cDxB?)<-GeBpHX zl9ufUne>lLpKo6=?|FIt6}_vwr+dE*+PmBC=>L`eGymn+KhLhM4E($CiSwJFz12HG z3SI~Qng46b%m3fX_g$MVepTMv?g%;By7^xNLB8JcM~8m_!R*r;e{9&dZ{NRXMLKqC z)~{c`f2T}0|I^mJaW|%mf7^9M@7t~&uaAB!-?l&5zSL4o{uFoh-VOTtZ%S9jz9}ud z-u?D{PW|z^S3u2E#J^P~#>d@w9klz#>qqOvZ{NT9ukY`brx7(8`ft7-+P7!J^`&JS nuOE%o-?o4AU)kR)Puov@vZ?6!c7`Ni=rMS@`njxgN@xNAuRX}V diff --git a/ZipConvertCustomerBarCode/BarCode.cs b/ZipConvertCustomerBarCode/BarCode.cs index 4c64ea7..7aede81 100644 --- a/ZipConvertCustomerBarCode/BarCode.cs +++ b/ZipConvertCustomerBarCode/BarCode.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text.RegularExpressions; @@ -62,9 +63,21 @@ public static class BarCode // アルファベット private static readonly string Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + // 漢数字(位取りなし) + private static readonly string KanjiDigits = "〇一二三四五六七八九"; + + // 特定文字の前にある漢数字 + private static readonly Regex KanjiNumberRegex = new Regex("[〇一二三四五六七八九十百千]+(?=丁目|丁|番地|番|号|地割|線|の|ノ)"); + /// /// カスタマバーコードを生成する + /// 住所Aと住所Bに分けて渡すオーバーロードの使用を推奨 /// + /// + /// 住所全体を町域名以降の住所(住所B)として扱う。 + /// 町域名に「特定文字(丁目、番、ノ など)の前の漢数字」を含む住所(一番町、八丁堀、三ノ輪 など)では、 + /// 町域名の漢数字も住所表示番号として抜き出されるため、誤ったバーコードになる。 + /// /// 郵便番号(半角・全角数字7桁、ハイフンは可) /// 住所 /// カスタマバーコード @@ -78,6 +91,32 @@ public static CustomerBarCode Create(string zipCode, string address) return new CustomerBarCode(code); } + /// + /// カスタマバーコードを生成する + /// + /// + /// 住所表示番号は住所Bからのみ抜き出す。 + /// 住所Bが住所Aで始まる場合(住所全体が渡された場合)は、住所Aの部分を取り除いてから抜き出す。 + /// http://www.post.japanpost.jp/zipcode/zipmanual/p17.html + /// + /// 郵便番号(半角・全角数字7桁、ハイフンは可) + /// 住所A: 町域名までの住所(例: 東京都千代田区霞が関) + /// 住所B: 町域名以降の住所(例: 1丁目3番2号 郵便プラザ503室) + /// カスタマバーコード + /// 郵便番号が7桁の数値でない場合 + public static CustomerBarCode Create(string zipCode, string townAddress, string blockAddress) + { + if (townAddress == null) throw new ArgumentNullException(nameof(townAddress)); + if (blockAddress == null) throw new ArgumentNullException(nameof(blockAddress)); + + var town = townAddress.Trim(); + if (town.Length > 0 && blockAddress.StartsWith(town, StringComparison.Ordinal)) + { + blockAddress = blockAddress.Substring(town.Length); + } + return Create(zipCode, blockAddress); + } + /// /// 郵便番号チェック /// ハイフンを除いて数値7桁であること @@ -126,6 +165,16 @@ private static IEnumerable ConvertCustomerBarCode(string address, IEnume //「&」(アンパサンド)、「/」(スラッシュ)、「・」(中グロ)、「.」(ピリオド) address = string.Join("", address.Split('&', '&', '/', '/', '・', '・', '.', '.')); + // 補足ルール + // 1. 漢数字が下記の特定文字の前にある場合は抜き出し対象とし、算用数字に変換して抜き出します。 + // "丁目" "丁" "番地" "番" "号" "地割" "線" "の" "ノ" + address = KanjiNumberRegex.Replace(address, m => KanjiToNumber(m.Value)); + + // 2. 連続していないアルファベット1文字は抜き出し対象となりますが、算用数字に続くアルファベット1文字"F"に限っては抜き出し対象としません。 + // 3. "F"以降のデータに抜き出し対象となる文字がある場合、"F"はハイフン1文字に置き換えます。 + // (抜き出し対象外の文字として、4の処理でハイフンに置き換わる) + address = Regex.Replace(address, "(?<=[0-9])F(?![A-Z])", "-"); + // 3. 1および2で整理したデータから、算用数字、ハイフンおよび連続していないアルファベット1文字を必要な文字情報として抜き出します。 // 4. 次に抜き出された文字の前にある下記の文字等は、ハイフン1文字に置き換えます。 // 「漢字」、「かな文字」、「カタカナ文字」、「漢数字」、「ブランク」、「2文字以上連続したアルファベット文字」 @@ -142,9 +191,42 @@ private static IEnumerable ConvertCustomerBarCode(string address, IEnume yield return endCode; } + /// + /// 漢数字を算用数字に変換する + /// 「十一」のような位取りありと、「二〇五」のような位取りなしの両方に対応 + /// + /// 漢数字 + /// 算用数字 + private static string KanjiToNumber(string kanji) + { + if (kanji.IndexOfAny(new[] { '十', '百', '千' }) < 0) + { + return string.Concat(kanji.Select(c => KanjiDigits.IndexOf(c))); + } + + int total = 0, current = 0; + foreach (var c in kanji) + { + var digit = KanjiDigits.IndexOf(c); + if (digit >= 0) + { + current = digit; + continue; + } + var unit = c == '十' ? 10 : c == '百' ? 100 : 1000; + total += (current == 0 ? 1 : current) * unit; + current = 0; + } + return (total + current).ToString(CultureInfo.InvariantCulture); + } + /// /// ハイフン処理 /// + /// + /// 補足ルール5: アルファベット前後のハイフンを取り除いた結果、 + /// 2文字以上の連続したアルファベット文字が残った場合はそのままにする(例: 6-7-L-B106 → 6-7LB106) + /// /// 処理対象文字列 /// 処理後文字列 private static string TrimHyphen(string target) @@ -152,7 +234,7 @@ private static string TrimHyphen(string target) // 5. 4の置き換えで、ハイフンが連続する場合は1つにまとめます。 target = Regex.Replace(target, "\\-{2,}", "-"); - //アルファベット前後のハイフンを除去 + // 補足ルール4: アルファベット前後のハイフンを除去 var tmp = target.ToCharArray(); for (int i = 0; i < target.Length; i++) {