コンテンツにスキップ

利用者:Hatukanezumi/AnansiRules.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/* <source lang="javascript"><pre> */

/*
{{Main|User:Hatukanezumi/Anansi.js}}

AnansiRules.js - Rule definitions for Anansi.

Version: 0.00-alpha.

== Code ==
*/

anansi.config.namespaces.push(100, 101); // Portal and its talk page on jaWP.

/*****
***** Utilities
*****/

anansi.util.ANANSI_HYPHENIC             = 1;
anansi.util.ANANSI_PROLONGED_SOUND_MARK = 1 << 1;
anansi.util.ANANSI_SPACES               = 1 << 2;

/***
*** correct fullwidth variants.
***/

anansi.util.CanonifyFullWidth = function(s, ambig)
{
  var ret = s;
  ret = ret.replace(/[\uFF01-\uFF5D]/g,
                    function(c)
                    {
                      return String.fromCharCode(c.charCodeAt(0) - 0xFEE0);
                    });
  ret = ret.replace(/[\uFF5F\uFF60\uFFE0-\uFFE6]/g,
                    function(c)
                    {
                      return {
                        "\uFF5F": "\u2985", // left white parenthesis
                        "\uFF60": "\u2986", // right white parenthesis
                        "\uFFE0": "\u00A2", // cent sign
                        "\uFFE1": "\u00A3", // poung sign
                        "\uFFE2": "\u00AC", // not sign
                        "\uFFE3": "\u00AF", // macron FIXME: sometimes FW overline
                        "\uFFE4": "\u00A6", // broken bar
                        "\uFFE5": "\u00A5", // yen sign
                        "\uFFE6": "\u20A9"  // won sign
                      }[c] || c;
                    });
  if (ambig & anansi.util.ANANSI_HYPHENIC)
    // HYPHEN, NON-BREAKING HYPHEN, FIGURE DASH, EN DASH, EM DASH,
    // HORIZONTAL BAR, MINUS SIGN
    ret = ret.replace(/[\u2010-\u2015\u2212]/g, "-");
  if (ambig & anansi.util.ANANSI_PROLONGED_SOUND_MARK)
    ret = ret.replace(/[\u30FC]/g, "-");
  if (ambig & anansi.util.ANANSI_SPACES)
    ret = ret.replace(/[\u00A0\u2000-\u200B\u202F\u205F\u3000]/g, " ");
  
  return ret;
};

/***
*** correct halfwidth variants (not including CJK punctuations).
***/
/* TODO: HW hangul jamo. */

anansi.util.CanonifyHalfWidth = function(s, ambig) {
  var halfWidthKana =
    "\uFF67\uFF71\uFF68\uFF72\uFF69\uFF73\uFF6A\uFF74\uFF6B\uFF75" +
    "\uFF76.\uFF77.\uFF78.\uFF79.\uFF7A." +
    "\uFF7B.\uFF7C.\uFF7D.\uFF7E.\uFF7F." +
    "\uFF80.\uFF81.\uFF6F\uFF82.\uFF83.\uFF84." +
    "\uFF85\uFF86\uFF87\uFF88\uFF89" +
    "\uFF8A.,\uFF8B.,\uFF8C.,\uFF8D.,\uFF8E.," +
    "\uFF8F\uFF90\uFF91\uFF92\uFF93" +
    "\uFF6C\uFF94\uFF6D\uFF95\uFF6E\uFF96" +
    "\uFF97\uFF98\uFF99\uFF9A\uFF9B" +
    "?\uFF9C??\uFF66\uFF9D";
  var voicedWvoices = {
    "\uFF73": "\u30F4", // U-voiced -> VU
    "\uFF9C": "\u30F7", // WA-voiced -> VA
    "\uFF66": "\u30FA"  // WO-voiced -> VO
  };
  var kanaMarks = {
    "\uFF9E": "\u309B", // voiced sound mark
    "\uFF9F": "\u309C", // semi-voiced sound mark
    "\uFF70": "\u30FC"  // prolonged sign
  };
  
  var ret = "";
  for (var i = 0; i < s.length; i++) {
    var c = s.slice(i, i+1);

    var idx = halfWidthKana.indexOf(c);
    if (0 <= idx) {
      if (i < s.length - 1 &&
          /[\u3099\u309B\uFF9E]/.test(s.slice(i + 1, i + 2))) { // voiced
        if (halfWidthKana.slice(idx + 1, idx + 2) == ".")
          ret += String.fromCharCode(idx + 0x30A1 + 1);
        else // search voiced letter or add combining mark.
          ret += voicedWvoices[c] || String.fromCharCode(idx + 0x30A1) + "\u3099";
        i++;
      }
      else if (i < s.length - 1 &&
               /[\u3099\u309B\uFF9F]/.test(s.slice(i + 1, i + 2))) { // semi-voiced
        if (halfWidthKana.slice(idx + 2, idx + 3) == ",")
          ret += String.fromCharCode(idx + 0x30A1 + 2);
        else // semi-voiced kana was not found; add combining mark
          ret += String.fromCharCode(idx + 0x30A1) + "\u309A";
        i++;
      }
      else // others.
        ret += String.fromCharCode(idx + 0x30A1);
    }
    else // maybe a voiced mark, a prolonged sound mark.
      ret += kanaMarks[c] || c;          // otherwise, not a kana.
  }

  return ret;
};

/*****************************************************************************/
/***** Rules ****************************************************************/
/*****************************************************************************/

/***
*** ISBNコードの校正。
***
* - 全角形英数字や、ハイフンマイナスでない区切り文字がある場合は訂正。
* - 桁数が合わない場合は指摘。
* - チェックサムが合致しない場合は指摘。
*/

anansi.registerRule(function(){
  this.id = "ISBN";
  this.description = "WP:CITE#出典の示し方";
  
  this.pattern = ["",
                  // ISBNコードのように見えるもの。
                  // 区切り記号としてつぎのものを許容する:
                  // ハイフンマイナス、ハイフン、ノンブレーキングハイフン、
                  // FIGURE DASH、エンダッシュ、エムダッシュ、横線、
                  // マイナス記号、オンビキ、半角形オンビキ
                  "[II][SS][BB][NN][ \u00A0\u2000-\u200B\u202F\u205F\u3000]?" +
                  "[0-90-9]+" +
                  "([-\u2010-\u2015\u2212\u30FC\uFF70]?[0-90-9]){7,}" +
                  "[-\u2010-\u2015\u2212\u30FC\uFF70]?[0-90-9XX]",
                  ""];
  
  function verifyISBN10(numbers)
  {
    var cd = 0;
    for (var i = 0; i < 9; i++)
      cd += eval(numbers.slice(i, i + 1)) * (10 - i);
    cd = 11 - (cd % 11);
    if (cd == 11)
      cd = "0";
    else if (cd == 10)
      cd = "X";
    else
      cd += "";
      return (cd == numbers.slice(9));
  }
  
  function verifyISBN13(numbers)
  {
    var cd = 0;
    for (var i = 0; i < 12; i++)
      cd += eval(numbers.slice(i, i + 1)) * ((i % 2) * 2 + 1);
    cd = 10 - (cd % 10);
    if (cd == 10)
      cd = "0";
    else
      cd += "";
      return (cd == numbers.slice(12));
  }
  
  this.replace = function() {
    if (this.subseq)
      return;

    var canon = anansi.util.CanonifyFullWidth(this.string,
                                              anansi.util.ANANSI_HYPHENIC +
                                              anansi.util.ANANSI_PROLONGED_SOUND_MARK +
                                              anansi.util.ANANSI_SPACES
                                             ).replace(/[\uFF70]/g, "-");
    var numbers = canon.slice(4).replace(/ +/, "");
    canon = "ISBN " + numbers;
    numbers = numbers.replace(/-/g, "");
    var ok;
    
    if ((numbers.slice(0, 3) == "978" || numbers.slice(0, 3) == "979") &&
        numbers.length == 13 && verifyISBN13(numbers)) // 13桁ISBNの検査
      ok = true;
    else if (numbers.length == 10 && verifyISBN10(numbers)) // 10桁ISBNの検査
      ok = true;
    else // いずれでもなければ失敗。
      ok = false;
    
    if (ok) { // 検査成功の場合は、文字幅の訂正が必要なら指摘。
      if (canon == this.string)
        return;
      else
        this.candidate.push(canon);
    }
    else // 検査失敗。確認作業を促す。
      this.instruction.push("ISBNコードの転記ミス。訂正が必要です (除去しないでください)。");
  };
});

/***
*** 半角形片仮名の校正 (約物以外)。
***
* - 通常の片仮名に訂正。
* - ただし、訂正結果がつぎのいずれかの文字単独である場合、小書き仮名
*   (または仮名類似の文字) の代替表記を意図している可能性があるので、それも候補に加える。
*   o クシストヌハフプヘホムラリルレロ - アイヌ語用表音拡張の小書き仮名。
*   o カケ - 「箇」の異表記 ([[WP:JPE]]では非推奨)。
*   o ワ - 小書きワ。
*/

anansi.registerRule(function(){
  this.id = "HWKana";
  this.description = "WP:JPE#使用可能な文字";
  this.pattern = ["",
                  // 半角形の片仮名 (濁点、半濁点含む。オンビキ含まず)
                  // で始まる。
                  // 半角形の片仮名、半角形または通常の仮名符号 (濁点、
                  // 半濁点、それらの結合文字形、オンビキ) のいずれかが
                  // (あれば) 続く。
                  /[\uFF66-\uFF6F\uFF71-\uFF9F][\u3099-\u309C\u30FC\uFF66-\uFF9F]*/,
                  ""];

  // 代替表記とおもわれるもの
  var alts = {
    "\u30AB": "\u30F5",       // カ → 小書きカ (仮名ではない)
    "\u30AF": "\u31F0",       // ク → 小書きク
    "\u30B1": "\u30F6",       // ケ → 小書きケ (仮名ではない)
    "\u30B7": "\u31F1",       // シ → 小書きシ
    "\u30B9": "\u31F2",       // ス → 小書きス
    "\u30C8": "\u31F3",       // ト → 小書きト
    "\u30CC": "\u31F4",       // ヌ → 小書きヌ
    "\u30CF": "\u31F5",       // ハ → 小書きハ
    "\u30D2": "\u31F6",       // ヒ → 小書きヒ
    "\u30D5": "\u31F7",       // フ → 小書きフ
    "\u30D7": "\u31F7\u309A", // プ → 小書きフ + 結合文字半濁点
    "\u30D8": "\u31F8",       // ヘ → 小書きヘ
    "\u30DB": "\u31F9",       // ホ → 小書きホ
    "\u30E0": "\u31FA",       // ム → 小書きム
    "\u30E9": "\u31FB",       // ラ → 小書きラ
    "\u30EA": "\u31FC",       // リ → 小書きリ
    "\u30EB": "\u31FD",       // ル → 小書きル
    "\u30EC": "\u31FE",       // レ → 小書きレ
    "\u30ED": "\u31FF",       // ロ → 小書きロ
    "\u30EF": "\u30EE"        // ワ → 小書きワ
  };
  
  this.replace = function() {
    if (this.subseq)
      return;

    // 半角形片仮名を訂正。
    var c = anansi.util.CanonifyHalfWidth(this.string);
    this.candidate.push(c);
    // 代替表記として半角形を用いている可能性のあるものは、候補に追加。
    var alt = alts[c];
    if (alt)
      this.candidate.push(alt);
  };
});

/***
*** 全角形ラテン文字、数字、ASCII記号、通貨記号の校正 (文脈依存がないため単純に置き換えられるもののみ)。
***
* 作成中。まだ完全ではありません。
*/

anansi.registerRule(function(){
  this.id = "FWASCII";
  this.description = "WP:JPE#使用可能な文字";
  this.pattern = ["",
                  // DOLLAR SIGN ($), PERCENT SIGN (%), AMPERSAND (&), ASTERISK (*), PLUS SIGN (+),
                  // SOLIDUS (/), COMMERCIAL AT (@), CIRCUMFLEX ACCENT (^), LOW LINE (_),
                  // CENT SIGN (¢; U+00A2), POUND SIGN (£; 00A3), NOT SIGN (¬; U+00AC),
                  // YEN SIGN (¥; U+00A5), WON SIGN (₩; U+20A9), 数字, ラテン文字
                  // --- 以上の全角形。
                  /[$%&*+/0-9@A-Z^_a-z\uFF3E\uFFE0-\uFFE2\uFFE5\uFFE6]+/,
                  ""];
  
  this.replace = function() {
    if (this.subseq)
      return;

    // 全角形文字を訂正。
    this.candidate.push(anansi.util.CanonifyFullWidth(this.string));
  };
});

/***
 *** 約物の前後の空きの校正。
 ***
 *
 * テスト中。
 * 詳細は[[User:Hatukanezumi/JIS X 4051の字間空き量]]参照。
 *
 * バグまたは未実装:
 * * (1x) 始め括弧 (両方) と (2x) 終わり括弧 (両方) への対応は不十分である。
 *
 */

anansi.registerRule(function(){
  // 文字分類。上記ページの'''表5'''参照。

  // (1w) 始め括弧 (広)
  // 〈 《 「 『 【 〔 〖 〘 〚 〝
  // ( [ { ⦅
  var klass1w = "\u3008\u300A\u300C\u300E\u3010\u3014\u3016\u3018\u301A\u301D" +
                "\uFF08\uFF3B\uFF5B\uFF5F";

  // (1x) 始め括弧 (両方)
  // ‘ “
  var klass1x = "\u2018\u201C";

  //  (1n) 始め括弧 (狭)
  // ( [ {
  // «
  // ‚ ‛ „ ‟ ‹ ⁅
  var klass1n = "\u0028\\\u005B\u007B" +
                "\u00AB" +
                "\u201A\u201B\u201E\u201F\u2039\u2045";

  // (2w) 終わり括弧・読点 (広) および (7w) 句点 (広)
  // 、 〉 》 」 』 】 〕 〗 〙 〛 〞 〟
  // ) ] } ⦆
  // 。
  var klass2w = "\u3001\u3009\u300B\u300D\u300F\u3011\u3015\u3017\u3019\u301B\u301E\u301F" +
                "\uFF09\uFF3D\uFF5D\uFF60" +
                "\u3002";

  // (2x) 終わり括弧 (両方)
  // ’ ”
  var klass2x = "\u2019\u201D";

  // (2n) 終わり括弧・読点類 (狭)
  // ) , : ; ] }
  // »
  // › ⁆
  var klass2n = "\u0029\u002C\u003A\u003B\\\u005D\u007D" +
                "\u00BB" +
                "\u203A\u2046";

  // (6w) 中点類
  // ・:
  var klass6w = "\u30FB\uFF1A";

  // (7n) ピリオド・疑問符・感嘆符
  // ! . ?
  // ‼ ⁇ ⁈ ⁉
  // ! ?
  var klass7n = "\u0021\u002E\u003F" +
                "\u203C\u2047\u2048\u2049" +
                "\uFF01\uFF1F";

  // (X) その他の約物
  // # $ %
  // ¢ £ ¥ °
  // ‐ – — ‗ ‣ ‥ … ‰-‷
  // U+20A0-U+20FF
  // ℃ ℉ ℓ №
  // 〜 〳 〴 〵 ゠
  // ㏋
  // =
  var klassX =  "\u0023\u0024\u0025" +
                "\u00A2\u00A3\u00A5\u00B0" +
                "\u2010\u2013\u2014\u2017\u2023\u2025\u2026\u2030-\u2037" +
                "\u20A0-\u20FF" +
                "\u2103\u2109\u2113\u2116" +
                "\u301C\u3033\u3034\u3035\u30A0" +
                "\u33CB" +
                "\uFF1D";

  // JIS (18) 連数字中の文字。UAX #14のISにあるものを一部追加している。
  // 0-9
  // , . : ; ⁄
  var klassNU = "\u0030-\u0039";
  var klassIS = "\u002C\u002E\u003A\u003B\u2044";

  // 改行文字、スペース、クワタ等
  var nl = "\u000A\u000D";
  var ispc = "\u00A0\u2000-\u200B\u202F\u205F\u3000";
  var spaces = "[ " + ispc + "]";
  var quads = "[" + ispc + "]";
  var nonspaces = "[^ " + ispc + "]";
  var nonspacesRe = new RegExp(nonspaces);

  // ファイル名の拡張子のパターン
  var fileExtPat = "(aiff?|asc|au|avi|awk|bas|bat|bin|bmp|bz2?|" +
                   "c|cc|cfg|class|conf|cpio|cpp|com|css|csv|" +
                   "dll|doc|dvi|el|eml|eps|exe|gif|t?gz|" +
                   "h|hdml?|hqx|[mpswx]?html?|ico|inc|ini|" +
                   "java|jar|jpeg?|jpg|js|lha|lzh|" +
                   "midi?|mk|mp[23g]|od[abcfgimps]|ogg|" +
                   "pdf|ph|phps?|pl|png|ppt|ps|py|rb|rpm|rtf|" +
                   "sed|sh|sig|snd|so(\\.[0-9]+)*|sty|sys|" +
                   "g?tar|tex|texi|tiff?|torrent|te?xt|" +
                   "vbs?|vml|wav|wmf|xls|xml|xsl|z|zip)";

  this.id = "Spacing";
  this.description = "WP:JPE#約物の使い方";
  
  this.pattern = [
    // 行頭と行末のアキは不可。
    "^", spaces + "*" + quads + "+" + spaces + "*", "",
    "", spaces + "*" + quads + "+" + spaces + "*", "[" + nl + "]" + "|$",

    // 除外: URL。HTTP(S)、FTP(S)に対応。ASCIIの印字文字のうち " < > [ ] を除く。
    "", /([uU][rR][lL]:)?(https?|ftps?):\/\/[!#-/0-9:;=?@A-Z\\^-`a-z{-~]+/, "",

    // 除外: ファイル名。当面、英数字と : - _ . + / \ を含むもののみ。識別は拡張子で行う。
    // FIXME: JavaScriptの実装によっては \b を理解しないかもしれない。要確認。
    "\\b", "[-.+:/0-9A-Za-z\\_]+\\." + fileExtPat, "\\b",
    "\\b", "[-.+:/0-9A-Za-z\\_]+\\." + fileExtPat.toUpperCase(), "\\b",

    // 除外: 連数字。
    "", "[" + klassNU + "][" + klassNU + klassIS + "]*[" + klassIS + "][" + klassNU + klassIS + "]*[" + klassNU + "]", "",

    // 以下は'''表6'''に基づくパターン。
    // 詰めるべきもの。
    "",                                                spaces + "+",  "[" + klass2w + klass2x + klass2n + klass7n + "]",
    "[" + klass1w + klass1x + klass1n + klass2w + "]", spaces + "+", "",
    "[^" + klass7n + "]",                              spaces + "+",  "[" + klass1w + klass6w + "]",
    "[" + klassX + "]",                                spaces + "+",  "[^ " + ispc + klass1n + "]",
    "[^ " + ispc + klass2n + klass7n + "]",            spaces + "+",  "[" + klassX + "]",
    // 空けるべきもの。(7n) - (1w) は空ける (「維持」としない)。
    "[^ " + ispc + klass1w + klass1x + klass1n + klass2w + klass6w + "]", "", "[" + klass1n + "]",
    "[" + klass2n + klass7n + "]",                     "",            "[^ " + nl + ispc + klass1w + klass2w + klass2x + klass2n + klass6w + klass7n + "]",
    "[" + klass7n + "]",                               "",            "[" + klass6w + "]",
    "[" + klass7n + "]",                               "",            "[" + klass1w + "]"
  ];
  
  this.replace = function() {
    if (this.string) {
      if (!nonspacesRe.exec(this.string))
        this.candidate.push("");
    } else
      this.candidate.push(" ");
  };
});

/* </pre></source> */