Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ updates:
patterns:
- SkiaSharp
- SkiaSharp.*
# デモサイト(Blazor WebAssembly)のパッケージも、同じバージョンに揃える必要がある
blazor-webassembly:
patterns:
- Microsoft.AspNetCore.Components.WebAssembly*
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@
- CI の `verify-lockfile.yml` が PR・push・週1回でロックファイルを検証する。アクションの更新は Dependabot(`.github/dependabot.yml`、既定の cooldown でリリースから3日待つ)が、ロックファイルの作り直しも含めて PR にする。
- パッケージ作成時の検証(`EnablePackageValidation`)で、net8.0 版から netstandard2.0 版の公開 API が欠けているとエラー(CP0002)になる(net8.0 版にだけ API を足すのは検出しない)。
- あわせて、公開済みのバージョン(`Directory.Build.props` の `PackageValidationBaselineVersion`)と比べて公開 API を削除・変更していないかも確認する。破壊的変更をするメジャーバージョンアップでは、この基準を外すか更新する。
- NuGet パッケージの更新も Dependabot が PR にする。`SkiaSharp` とテスト用の `SkiaSharp.NativeAssets.Linux.NoDependencies` はバージョンを揃える必要があるので、グループにまとめて一緒に更新する。
- NuGet パッケージの更新も Dependabot が PR にする。`SkiaSharp` とテスト用の `SkiaSharp.NativeAssets.Linux.NoDependencies` はバージョンを揃える必要があるので、グループにまとめて一緒に更新する。デモの `Microsoft.AspNetCore.Components.WebAssembly` と `...DevServer` も同様にグループにしている。
- `build.yml` は、ドキュメントなどビルドに関係しないファイル(`*.md`・`LICENSE`・`docs/`・`.claude/`・`.github/dependabot.yml`)だけの変更では test と pack をスキップする(`changes` ジョブで判定)。`paths-ignore` を使うと必須チェックが報告されず PR をマージできなくなるので使わない。
- master はルールセットで保護している(PR 経由のみ、`build-result`(test・pack の結果をまとめるジョブ)と verify-lockfile の `verify` の成功が必須)。CI のジョブ名を変えるとマージできなくなるので、ルールセットの必須チェックも合わせて変更する。

## バーコード処理

- 住所の変換(`BarCode.cs`)は日本郵便のバーコードマニュアル(http://www.post.japanpost.jp/zipcode/zipmanual/p19.html)に従う。コード中の番号付きコメント(1.〜6.、補足ルール1〜5)はマニュアルの手順番号に対応している。
- 住所の変換(`BarCode.cs`)は日本郵便のバーコードマニュアル(https://www.post.japanpost.jp/zipcode/zipmanual/p19.html)に従う。コード中の番号付きコメント(1.〜6.、補足ルール1〜5)はマニュアルの手順番号に対応している。
- マニュアルでは住所を「住所A(町域名まで)」と「住所B(町域名以降)」に分け、住所Bだけから抜き出す。`Create(郵便番号, 住所)` は住所全体を住所Bとして扱うため、町域名に特定文字の前の漢数字を含む住所(一番町、八丁堀など)では誤る。これは既知の制限で、`Create(郵便番号, 住所A, 住所B)` か `ZipCodeDictionary` を渡す `Create` を推奨している。
- バーコードは常に 23 シンボル: スタート + 郵便番号 7 桁 + 住所表示番号 13 枠(不足分は `CC4` で埋める)+ チェックデジット + ストップ。
- バーの形は `CustomerBarCode.cs` の `BarPatternDic` で定義(1 シンボル = 幅 12px に 3 本、x=0/4/8、幅 2px、上・中・下段 各 4px)。この表は旧バージョンの GIF 画像とピクセル単位で一致することを確認済みなので、変更するときは既存出力と比較する。
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
それ以外は -p:VersionSuffix=ci.N でプレリリース版(2.0.0-ci.N)にする。
アセンブリのバージョンがここから決まり、PackageValidationBaselineVersion 以上である必要がある
-->
<VersionPrefix>2.0.0</VersionPrefix>
<VersionPrefix>2.1.0</VersionPrefix>
<Authors>cssho</Authors>
<Product>ZipConvertCustomerBarCode</Product>
<Copyright>Copyright (c) 2015 cssho</Copyright>
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## カスタマバーコード
> お客さまがあらかじめ郵便物に印字する場合のバーコードがカスタマバーコードです。カスタマバーコードは、原則として差し出しの必要条件ではなく、料金割引を受けようとする場合に印字するもので、あて名印字と同時に印字できるような仕様としています。

[郵便局 - 郵便番号・バーコードマニュアル](http://www.post.japanpost.jp/zipcode/zipmanual/index.html)
[郵便局 - 郵便番号・バーコードマニュアル](https://www.post.japanpost.jp/zipcode/zipmanual/index.html)

## デモ
**https://cssho.github.io/ZipConvertCustomerBarCode/**
Expand Down Expand Up @@ -53,7 +53,7 @@ using ZipConvertCustomerBarCode;
// 郵便番号, 住所A(町域名までの住所), 住所B(町域名以降の住所)
CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
```
郵便番号が7桁の数値でない場合は `ArgumentException` になります。
郵便番号は全角数字でもよく、ハイフン・長音(ー)などの区切り、空白、先頭の「〒」は読み飛ばします。7桁の数値でない場合は `ArgumentException` になります。

住所表示番号は、[郵便番号・バーコードマニュアル](https://www.post.japanpost.jp/zipcode/zipmanual/p19.html)の抜き出しルール(補足ルールを含む)に従って住所Bから抜き出します。
「丁目・番地・号」などの前の漢数字は算用数字に変換し(例:十一丁目 → 11)、「2F」のような階数の F は抜き出しません。
Expand Down Expand Up @@ -86,6 +86,7 @@ CustomerBarCode code = BarCode.Create("1020082", "東京都千代田区一番町
- 代表の郵便番号(町域名が「以下に掲載がない場合」)は町域名がわからないため、分割しません。
- `IsRepresentativeZipCode` は郵便番号データに基づいて判定します。郵便番号データにない番号(郵便番号データより新しい番号など)は、下2けたで判定します。
- 従来の Shift_JIS 形式(KEN_ALL.CSV)は、長い町域名が複数行に分かれているため対象外です。

### 事業所の個別郵便番号データを使う
日本郵便の[事業所の個別郵便番号データ](https://www.post.japanpost.jp/zipcode/dl/jigyosyo/index-zip.html)(`jigyosyo.zip`、Shift_JIS)も一緒に読み込むと、大口事業所・私書箱の個別郵便番号を扱えます。

Expand Down
11 changes: 7 additions & 4 deletions ZipConvertCustomerBarCode.Demo/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

<label class="field">
<span>郵便番号</span>
<input @bind="zipCode" @bind:event="oninput" inputmode="numeric" autocomplete="postal-code" placeholder="263-0023" maxlength="10" />
<input @bind="zipCode" @bind:event="oninput" inputmode="numeric" autocomplete="postal-code" placeholder="263-0023" maxlength="16" />
</label>

<fieldset class="mode">
Expand Down Expand Up @@ -63,12 +63,14 @@
}
</section>

<section class="card" aria-live="polite">
<section class="card">
<h2>結果</h2>
@{
// 入力のたびに作り直す(BarCode.Create は軽いので、ボタンを押さなくても即時に反映する)
var (barcode, error) = Generate();
}
@* 結果全体を読み上げると入力のたびにシンボル列まで読まれるため、状態の短い文だけを読み上げる *@
<p class="visually-hidden" aria-live="polite">@(error ?? (barcode == null ? "" : $"郵便番号 {barcode.ZipCode} のバーコードを作りました。"))</p>
@if (error != null)
{
<p class="error">@error</p>
Expand Down Expand Up @@ -211,11 +213,12 @@
var create = split
? $"BarCode.Create({Literal(zipCode)}, {Literal(townAddress)}, {Literal(blockAddress)})"
: $"BarCode.Create({Literal(zipCode)}, {Literal(address)})";
return "using ZipConvertCustomerBarCode;\n\n"
return "using ZipConvertCustomerBarCode;\n"
+ "using ZipConvertCustomerBarCode.Skia; // SavePng を使う場合(ZipConvertCustomerBarCode.SkiaSharp パッケージ)\n\n"
+ $"var code = {create};\n"
+ "string svg = code.ToSvg(scale: 3); // 画像用(依存ライブラリなし)\n"
+ "string printSvg = code.ToPrintSvg(point: 10); // 印刷用(mm 単位の実寸)\n"
+ "// SkiaSharp で PNG にする場合: code.SavePng(\"barcode.png\", scale: 3);";
+ "code.SavePng(\"barcode.png\", scale: 3); // PNG(SkiaSharp)";
}
}
}
13 changes: 13 additions & 0 deletions ZipConvertCustomerBarCode.Demo/wwwroot/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,19 @@ code, pre {
overflow-wrap: anywhere;
}

/* 画面には表示せず、スクリーンリーダーにだけ読ませる */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}

.footer {
color: var(--muted);
font-size: 0.85rem;
Expand Down
16 changes: 13 additions & 3 deletions ZipConvertCustomerBarCode.Tests/BarCodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public void シンボル列が1x版と一致する(string zipCode, string addres
}

// 住所表示番号は13けたまで。13けた目がアルファベットの場合は英字用制御コードのみ残す
// http://www.post.japanpost.jp/zipcode/zipmanual/p20.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p20.html
[Theory]
// マニュアル p20 の例
[InlineData("9100067", "福井県福井市新田塚3丁目80-25 J1ビル2-B", "STC 9 1 0 0 0 6 7 3 - 8 0 - 2 5 CC1 9 1 - 2 CC1 9 SPC")]
Expand All @@ -36,7 +36,15 @@ public void 住所表示番号は13けたで切り捨てる(string zipCode, stri
[InlineData("0140113")]
[InlineData("014‐0113")]
[InlineData("014-0113")]
public void 郵便番号は全角とハイフンを受け付ける(string zipCode)
// 日本語入力で打ちやすい区切り(長音・マイナス)、空白、先頭の〒
[InlineData("014ー0113")]
[InlineData("014ー0113")]
[InlineData("014−0113")]
[InlineData("014 0113")]
[InlineData(" 014-0113 ")]
[InlineData("〒014-0113")]
[InlineData("〒 014-0113")]
public void 郵便番号は全角と区切りと先頭の郵便マークを受け付ける(string zipCode)
{
var code = BarCode.Create(zipCode, "添60-1");

Expand All @@ -48,8 +56,10 @@ public void 郵便番号は全角とハイフンを受け付ける(string zipCod
[InlineData("014011")]
[InlineData("01401130")]
[InlineData("014011A")]
[InlineData("014 0113")]
[InlineData("0140113\n")]
[InlineData("014\t0113")]
[InlineData("014〒0113")]
[InlineData("〒")]
// アラビア・インド数字
[InlineData("٠١٤٠١١٣")]
public void 郵便番号が7桁の数値でなければ例外(string zipCode)
Expand Down
2 changes: 1 addition & 1 deletion ZipConvertCustomerBarCode.Tests/PrintTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace ZipConvertCustomerBarCode.Tests
{
// 印刷用の実寸
// http://www.post.japanpost.jp/zipcode/zipmanual/p12.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p12.html
public class PrintTests
{
private readonly CustomerBarCode code = BarCode.Create("1000013", "東京都千代田区霞が関", "1丁目3番2号 郵便プラザ503室");
Expand Down
2 changes: 1 addition & 1 deletion ZipConvertCustomerBarCode.Tests/SupplementaryRuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace ZipConvertCustomerBarCode.Tests
{
// 抜き出しの基本ルール・補足ルール
// 期待値はマニュアルの「バーコード・データ」
// http://www.post.japanpost.jp/zipcode/zipmanual/p19.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p19.html
public class SupplementaryRuleTests
{
[Theory]
Expand Down
24 changes: 14 additions & 10 deletions ZipConvertCustomerBarCode/BarCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static class BarCode
/// 町域名に「特定文字(丁目、番、ノ など)の前の漢数字」を含む住所(一番町、八丁堀、三ノ輪 など)では、
/// 町域名の漢数字も住所表示番号として抜き出されるため、誤ったバーコードになる。
/// </remarks>
/// <param name="zipCode">郵便番号(半角・全角数字7桁、ハイフンは可)</param>
/// <param name="zipCode">郵便番号(半角・全角数字7桁。ハイフン・空白・先頭の〒は可)</param>
/// <param name="address">住所</param>
/// <returns>カスタマバーコード</returns>
/// <exception cref="ArgumentException">郵便番号が7桁の数値でない場合</exception>
Expand All @@ -104,7 +104,7 @@ public static CustomerBarCode Create(string zipCode, string address)
/// 大口事業所・私書箱の個別郵便番号(事業所の個別郵便番号データを読み込んだ場合)は、マニュアルに従い
/// 個別番号のみをバーコード化し、住所表示番号は抜き出さない(13けたすべて CC4)。
/// </remarks>
/// <param name="zipCode">郵便番号(半角・全角数字7桁、ハイフンは可)</param>
/// <param name="zipCode">郵便番号(半角・全角数字7桁。ハイフン・空白・先頭の〒は可)</param>
/// <param name="address">住所</param>
/// <param name="dictionary">郵便番号データ</param>
/// <returns>カスタマバーコード</returns>
Expand All @@ -125,7 +125,7 @@ public static CustomerBarCode Create(string zipCode, string address, ZipCodeDict
}

// 大口事業所・私書箱の個別郵便番号は、個別番号のみをバーコード化し、住所表示番号の13けたは CC4 で埋める
// http://www.post.japanpost.jp/zipcode/zipmanual/p22.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p22.html
if (entries.Any(x => x.IsIndividual))
{
return CreateCore(zip, "", false);
Expand Down Expand Up @@ -174,9 +174,9 @@ private static CustomerBarCode CreateCore(string zip, string address, bool isRep
/// 住所表示番号は住所Bからのみ抜き出す。
/// 住所Bが住所Aで始まる場合(住所全体が渡された場合)は、住所Aの部分を取り除いてから抜き出す。
/// 住所Aとの比較では、空白、全角・半角、「ヶ」と「ケ」、漢数字と算用数字の違いは区別しない。
/// http://www.post.japanpost.jp/zipcode/zipmanual/p17.html
/// https://www.post.japanpost.jp/zipcode/zipmanual/p17.html
/// </remarks>
/// <param name="zipCode">郵便番号(半角・全角数字7桁、ハイフンは可)</param>
/// <param name="zipCode">郵便番号(半角・全角数字7桁。ハイフン・空白・先頭の〒は可)</param>
/// <param name="townAddress">住所A: 町域名までの住所(例: 東京都千代田区霞が関)</param>
/// <param name="blockAddress">住所B: 町域名以降の住所(例: 1丁目3番2号 郵便プラザ503室)</param>
/// <returns>カスタマバーコード</returns>
Expand All @@ -194,21 +194,25 @@ public static CustomerBarCode Create(string zipCode, string townAddress, string
return Create(zipCode, blockAddress);
}

// 郵便番号の区切りとして読み飛ばす文字(ハイフンのほか、日本語入力で打ちやすい長音・マイナス・ダッシュと空白)
private static readonly string PostCodeSeparators = "-‐‑–—―−-ーー  ";

/// <summary>
/// 郵便番号チェック
/// ハイフンを除いて数値7桁であること
/// 区切り(ハイフン・空白など)と先頭の「〒」を除いて数値7桁であること
/// </summary>
/// <param name="zipCode">郵便番号</param>
/// <returns>郵便番号(半角数字7桁)</returns>
internal static string CheckPostCode(string zipCode)
{
// 全角→半角にし、ハイフンを除いて半角数字(0~9 のみ。全角以外の Unicode 数字や改行は不可)が7桁であること
// 全角→半角にし、区切りと先頭の「〒」を除いて半角数字(0~9 のみ。全角以外の Unicode 数字や改行は不可)が7桁であること
var digits = new char[7];
var count = 0;
foreach (var c in zipCode)
{
var hankaku = ToHankaku(c);
if (hankaku == '-') continue;
if (PostCodeSeparators.IndexOf(hankaku) >= 0) continue;
if (hankaku == '〒' && count == 0) continue;
if (hankaku < '0' || hankaku > '9' || count == digits.Length) throw InvalidPostCode();
digits[count++] = hankaku;
}
Expand Down Expand Up @@ -242,7 +246,7 @@ private static string ExtractAddressNumber(string address)
// 全角→半角
address = string.Join("", address.Select(ToHankaku));

// http://www.post.japanpost.jp/zipcode/zipmanual/p19.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p19.html
// 1. まず、データ内にあるアルファベットの小文字は大文字に置き換えます。
// 実行環境のカルチャによらず変換する(tr-TR では i が İ になるため)
address = address.ToUpperInvariant();
Expand Down Expand Up @@ -400,7 +404,7 @@ private static string[] ConvertCCode(string address)
{
// 数値、ハイフンは1けた、アルファベットは英字用制御コードと数字の2けたに変換し、
// 13けたに満たない場合は CC4 で埋める
// http://www.post.japanpost.jp/zipcode/zipmanual/p20.html
// https://www.post.japanpost.jp/zipcode/zipmanual/p20.html
// 合計けた数が20けた(住所表示番号は13けた)を超えた場合、以降の文字については切り捨てます。
// その際、20~21けた目がアルファベット文字となった場合、20けた目の英字用制御(CC1、CC2、CC3)は残して以降は切り捨てます。
return address.SelectMany(c => BarcodeCharDic[c.ToString()])
Expand Down
4 changes: 2 additions & 2 deletions ZipConvertCustomerBarCode/CustomerBarCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ internal CustomerBarCode(string zipCode, string addressNumber, bool isRepresenta
/// 代表の郵便番号(郵便番号簿の「以下に掲載がない場合」の番号)の郵便物には、カスタマバーコードを付けない。
/// 郵便番号データ(<see cref="ZipCodeDictionary"/>)を渡して生成した場合は、郵便番号データに基づいて判定する。
/// 渡さずに生成した場合は、マニュアルの「原則として下2けたが"00"」に従って判定する目安。
/// http://www.post.japanpost.jp/zipcode/zipmanual/p18.html
/// https://www.post.japanpost.jp/zipcode/zipmanual/p18.html
/// </remarks>
public bool IsRepresentativeZipCode { get; }

Expand Down Expand Up @@ -157,7 +157,7 @@ public double GetPrintHeight(double point = DefaultPoint)
/// </summary>
/// <remarks>
/// 寸法は郵便番号・バーコードマニュアルに従う(10ポイントのときバー幅0.6mm、バーピッチ1.2mm、ロングバー3.6mm)。
/// http://www.post.japanpost.jp/zipcode/zipmanual/p12.html
/// https://www.post.japanpost.jp/zipcode/zipmanual/p12.html
/// </remarks>
/// <param name="point">大きさ(8~11.5ポイント)</param>
/// <returns>バー配置</returns>
Expand Down
Loading
Loading