NDW

アプリ開発やトラブルシューティング等のノウハウ、キャンプや登山の紹介や体験談など。

.NET Core 1. システムエンジニアリング html/jquery 実装技術

HTML5文字実体参照をタブ区切りファイルに変換

投稿日:

概要

サンプルコード

前提とする入出力ファイル仕様

  • 入力ファイル仕様
    想定しているHTML5の文字実体参照JSONファイルのイメージを次に示します。
    {
      "&AElig": { "codepoints": [198], "characters": "\u00C6" },
      "Æ": { "codepoints": [198], "characters": "\u00C6" },
    ...
      "⊁": { "codepoints": [8833], "characters": "\u2281" },
      "⪰̸": { "codepoints": [10928, 824], "characters": "\u2AB0\u0338" },
      "⋡": { "codepoints": [8929], "characters": "\u22E1" },
      "≿̸": { "codepoints": [8831, 824], "characters": "\u227F\u0338" },
      "⊃⃒": { "codepoints": [8835, 8402], "characters": "\u2283\u20D2" },
      "⊉": { "codepoints": [8841], "characters": "\u2289" },
    ...
    
  • 出力ファイル仕様

    ここで説明するサンプルコードでは、変換した内容をタブ区切りファイルに出力します。
    このファイルに出力する項目は次の通りです。

    項番 項目名 説明 サンプル値
    1 No. 1から始まる連番 376
    2 文字実体参照名 特定文字を表現するための名称 ⊃⃒
    3 コードポイント(1/2) 文字実体参照に対応するコードポイント値。
    文字実体参照が2文字に対応する場合、2文字目のコードポイント値を「コードポイント(1/2)」に出力する。
    8835
    4 コードポイント(2/2) 8402
    5 数値文字参照(10進数)(1/2) 文字実体参照に対応する10進数形式の数値文字参照。
    文字実体参照が2文字に対応する場合、2文字目の数値文字参照を「数値文字参照(10進数)(2/2)」に出力する。
    ⊃
    6 数値文字参照(10進数)(2/2) ⃒
    7 数値文字参照(16進数)(1/2) 文字実体参照に対応する16進数形式の数値文字参照。
    文字実体参照が2文字に対応する場合、2文字目の数値文字参照を「数値文字参照(16進数)(2/2)」に出力する。
    ⊃
    8 数値文字参照(16進数)(2/2) ⃒

    出力イメージを次に示します。
    (ここでは見やすいようタブではなく任意長の空白を使用しています。前述のサンプル値をハイライトで表示しています。)

    1    &AElig                      198             Æ             Æ    
    2    Æ                     198             Æ             Æ    
    ...
    372  ⊁              8833            ⊁             ⊁    
    373  ⪰̸        10928     824   ⪰    ̸   ⪰    ̸
    374  ⋡    8929            ⋡             ⋡    
    375  ≿̸         8831     824    ≿    ̸   ≿    ̸
    376  ⊃⃒              8835    8402    ⊃   ⃒   ⊃    ⃒
    377  ⊉         8841            ⊉             ⊉    
    ...
    

変換サンプルコード(1:N)

文字実体参照JSONファイルを単純にタブ区切りファイルに変換するサンプルコードです。
文字実体参照JSONファイルは、コードポイントに対して複数の文字実体参照が対応(1:N)する場合がありますが、そのままファイルに出力します。

private static void OutputAsTsv(string inputFilename, string outputFilename)
{
    using var inputStream = File.OpenRead(inputFilename);
    using var outputWriter = new StreamWriter(outputFilename, false);

    int no = 1;
    var document = JsonDocument.Parse(inputStream);
    foreach (var jsonProperty in document.RootElement.EnumerateObject())
    {
        // 文字実体参照名とコードポイント配列の取得
        var cer = jsonProperty.Name;
        var value = jsonProperty.Value;
        var codepoints = value.GetProperty("codepoints")
            .EnumerateArray().Select(e => e.GetInt32()).ToArray();

        // 出力データの編集
        WriteRecord(outputWriter, no++, codepoints, cer);
    }
}

private static void WriteRecord(StreamWriter writer, int no, int[] codepoints, string cer)
{
    int cp1 = codepoints[0];
    int? cp2 = codepoints.Length > 1 ? codepoints[1] : (int?)null;

    var cp1str = cp1.ToString();
    var cp2str = cp2?.ToString() ?? string.Empty;
    (var ncrDec1, var ncrHex1) = ToNcr(cp1);
    (var ncrDec2, var ncrHex2) = ToNcr(cp2);

    writer.WriteLine($"{no}\t{cer}\t{cp1str}\t{cp2str}" +
        $"\t{ncrDec1}\t{ncrDec2}\t{ncrHex1}\t{ncrHex2}");
}

private static (string dec, string hex) ToNcr(int? codepoint)
{
    if (!codepoint.HasValue) return (string.Empty, string.Empty);
    return ($"&#{codepoint.Value};" , $"&#x{codepoint.Value:X5};");
}

変換サンプルコード(1:1)

前述の「変換サンプルコード(1:N)」と同様にタブ区切りファイルに変換するサンプルコードです。
こちらのサンプルでは、次のルールに従ってコードポイントに対する文字実体参照を1:1に対応させています。

条件 候補例 優先候補
同じコードポイントで”;”の有無が異なる文字実体参照がある場合、”;”がある文字実体参照を優先する。 &LT“, “< <
同じコードポイントで大小文字が異なる文字実体参照がある場合、小文字の文字実体参照を優先する。 <“, “< <
同じコードポイントで異なる文字実体参照がある場合、短い方を優先する。  “, “   
private static void OutputUniqueCodepointAsTsv(string inputFilename, string outputFilename)
{
    // コードポイント配列・文字実体参照ディクショナリ
    var dic = new Dictionary<int[], string>(new IntArrayEqualityComparer());

    // 重複するコードポイントを除外
    using var inputStream = File.OpenRead(inputFilename);
    var document = JsonDocument.Parse(inputStream);
    foreach (var jsonProperty in document.RootElement.EnumerateObject())
    {
        // 文字実体参照名とコードポイント配列の取得
        var newCer = jsonProperty.Name;
        var value = jsonProperty.Value;
        var codepoints = value.GetProperty("codepoints")
            .EnumerateArray().Select(e => e.GetInt32()).ToArray();

        if (!dic.ContainsKey(codepoints))
        {
            dic[codepoints] = newCer;
            continue;
        }

        // 同一のコードポイントが存在する場合、ルールに基づいて更新
        var oldCer = dic[codepoints];
        var oldCerLower = oldCer.ToLower();
        var newCerLower = newCer.ToLower();

        bool priorNew;
        // "&Xyz;" vs "&xyz" → 左辺を優先
        if (oldCerLower == newCerLower + ";") priorNew = false;
        // "&Xyz" vs "&xyz;" → 右辺を優先
        else if (oldCerLower + ";" == newCerLower) priorNew = true;
        // 大小文字無視で一致の場合、小文字側を優先
        else if (oldCerLower == newCerLower) priorNew = (newCer == newCerLower);
        // 大小文字無視で不一致の場合、桁数が少ない側を優先
        else priorNew = oldCer.Length > newCer.Length;

        // debug
        //Console.WriteLine($"{string.Join(",", codepoints)}: " +
        //    (priorNew ? newCer : oldCer ) + $" <- [ {oldCer} / {newCer} ]");

        if (priorNew) dic[codepoints] = newCer;
    }

    // データ出力
    using var outputWriter = new StreamWriter(outputFilename, false);
    var kvlist = dic.ToList();
    for (var i = 0; i < kvlist.Count; i++)
    {
        var codepoints = kvlist[i].Key;
        var cer = kvlist[i].Value;
        WriteRecord(outputWriter, i + 1, codepoints, cer);
    }
}

class IntArrayEqualityComparer : IEqualityComparer<int[]>
{
    public bool Equals([AllowNull] int[] x, [AllowNull] int[] y)
        => Enumerable.SequenceEqual(x, y);
    public int GetHashCode([DisallowNull] int[] obj)
    {
        var hash = new HashCode();
        foreach (var v in obj) hash.Add(obj[0]);
        return hash.ToHashCode();
    }
}

private static void WriteRecord(StreamWriter writer, int no, int[] codepoints, string cer)
{
    int cp1 = codepoints[0];
    int? cp2 = codepoints.Length > 1 ? codepoints[1] : (int?)null;

    var cp1str = cp1.ToString();
    var cp2str = cp2?.ToString() ?? string.Empty;
    (var ncrDec1, var ncrHex1) = ToNcr(cp1);
    (var ncrDec2, var ncrHex2) = ToNcr(cp2);

    writer.WriteLine($"{no}\t{cer}\t{cp1str}\t{cp2str}" +
        $"\t{ncrDec1}\t{ncrDec2}\t{ncrHex1}\t{ncrHex2}");
}

private static (string dec, string hex) ToNcr(int? codepoint)
{
    if (!codepoint.HasValue) return (string.Empty, string.Empty);
    return ($"&#{codepoint.Value};" , $"&#x{codepoint.Value:X5};");
}






-.NET Core, 1. システムエンジニアリング, html/jquery, 実装技術

関連記事

疎通確認用pingツール

新人君たちと本番環境の構築作業でデータセンターに入り。 構築したサーバから、既存の重要なサーバへの疎通確認を行うために、pingを何度も入力する予定とのこと。 作業時間の短縮や間違いの低減のために、こ …

ASP.NET Core: タグヘルパーでのHTML編集方法

ASP.NET Coreで独自のタグを生成するためにTagHelperを使用します。 TagHelperでどのようにHTMLを生成できるかを説明します。 概要 ここではタグヘルパーで出力するHTMLの …

パック10進数の変換方法とC#のサンプル

なお、ゾーン10進数はこちらで紹介しています。 パック10進数 ゾーン10進数の変換方法 10進数の各桁を「上位4ビットで1桁、下位4ビットで1桁」のバイトに変換して生成します。 生成したバイト列の最 …

開発環境のJBoss EAP7にリモートアクセス

開発中のものを他者に見せたり、問題が発生している開発者の開発物を参照するために、eclipse上で起動しているEAP7のWebアプリに別のPCからアクセスしたい場合があります。 このための手順を記載し …

パスワード情報の保管方式の比較

Webアプリの開発でパスワードを使ったユーザ認証を設計・実装する機会がよくある。 後輩への説明や勉強会ネタとして、この辺の話を纏めてみようと思う。 概要 オンラインバンキングやネットショッピングのサイ …