サロゲートペアを含むような文字列を,JavaScriptで正しく処理するためのライブラリと動作サンプル。
サンプルページ
サロゲート安全なJavaScript文字ライブラリの開発
http://tagengo-gakushuu.study-tips.info/2019/06_12_js_surrogate/test.html
サンプルページのソースコード
<!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>サロゲート安全なJavaScriptの文字列処理ライブラリの開発</title> <script src="./jquery-3.3.1.min.js"></script> <script src="./string_with_surrogate_pairs.js"></script> </head> <body> <h2 >サロゲート安全なJavaScriptの文字列処理ライブラリの開発 </h2> <div id="div_head_author"> 2019.6.13. </div> <br> <script> function log(){ // argumentsを配列に変換 var arr_args = Array.prototype.slice.call( arguments ); http://phiary.me/javascript-arguments-to-array/ // つなげる //var out = arr_args.join(", "); // これだとRTLの文字が含まれるときに出力がバグって確認不能 // これでもだめ。前半のspanの中に後半のspanが重なってしまう。 //var out = "<span>" + arr_args.join("</span>, <span>") + "</span>"; // tableとして無理やり分ける var out = "<tr><td>" + arr_args.join("</td><td>") + "</td></tr>"; // 出力 $("#tbody_result").html( $("#tbody_result").html() + out + "<br>" ); /* $("#div_result").html( $("#div_result").html() + out + "<br>" ); */ } $(function(){ log( "~~~~~ 素のlengthのテスト ~~~~~" ); log( "'叱られる'.length", "叱られる".length ); log( "'𠮟られる'.length", "𠮟られる".length ); log( "'𠮟𩸽𐱅'.length", "𠮟𩸽𐱅".length ); log( "~~~~~ サロゲートペアを含むか ~~~~~" ); log( "'叱られる'.hasSurrogatePair()", "叱られる".hasSurrogatePair() ); log( "'𠮟られる'.hasSurrogatePair()", "𠮟られる".hasSurrogatePair() ); log( "'𠮟𩸽𐱅'.hasSurrogatePair()", "𠮟𩸽𐱅".hasSurrogatePair() ); log( "~~~~~ 1文字ずつを配列化 ~~~~~" ); log( "'叱られる'.toCharArray()", "叱られる".toCharArray() ); log( "'𠮟られる'.toCharArray()", "𠮟られる".toCharArray() ); log( "'𠮟𩸽𐱅'.toCharArray()", "𠮟𩸽𐱅".toCharArray() ); log( "~~~~~ サロゲートペアを加味した文字長 ~~~~~" ); log( "'叱られる'.surrogatedLength()", "叱られる".surrogatedLength() ); log( "'𠮟られる'.surrogatedLength()", "𠮟られる".surrogatedLength() ); log( "'𠮟𩸽𐱅'.surrogatedLength()", "𠮟𩸽𐱅".surrogatedLength() ); log( "~~~~~ 素のcharAtのテスト ~~~~~" ); log( "'叱られる'.charAt(0)", "叱られる".charAt(0) ); log( "'叱られる'.charAt(1)", "叱られる".charAt(1) ); log( "'叱られる'.charAt(2)", "叱られる".charAt(2) ); log( "'𠮟られる'.charAt(0)", "𠮟られる".charAt(0) ); log( "'𠮟られる'.charAt(1)", "𠮟られる".charAt(1) ); log( "'𠮟られる'.charAt(2)", "𠮟られる".charAt(2) ); log( "'𠮟𩸽𐱅'.charAt(0)", "𠮟𩸽𐱅".charAt(0) ); log( "'𠮟𩸽𐱅'.charAt(1)", "𠮟𩸽𐱅".charAt(1) ); log( "'𠮟𩸽𐱅'.charAt(2)", "𠮟𩸽𐱅".charAt(2) ); log( "'𠮟𩸽𐱅'.charAt(3)", "𠮟𩸽𐱅".charAt(3) ); log( "'𠮟𩸽𐱅'.charAt(4)", "𠮟𩸽𐱅".charAt(4) ); log( "'𠮟𩸽𐱅'.charAt(5)", "𠮟𩸽𐱅".charAt(5) ); log( "'𠮟𩸽𐱅'.charAt(6)", "𠮟𩸽𐱅".charAt(6) ); log( "~~~~~ サロゲート安全なcharAtのテスト ~~~~~" ); log( "'叱られる'.setSurrogateSafe().charAt(0)", "叱られる".setSurrogateSafe().charAt(0) ); log( "'叱られる'.setSurrogateSafe().charAt(1)", "叱られる".setSurrogateSafe().charAt(1) ); log( "'叱られる'.setSurrogateSafe().charAt(2)", "叱られる".setSurrogateSafe().charAt(2) ); log( "'𠮟られる'.setSurrogateSafe().charAt(0)", "𠮟られる".setSurrogateSafe().charAt(0) ); log( "'𠮟られる'.setSurrogateSafe().charAt(1)", "𠮟られる".setSurrogateSafe().charAt(1) ); log( "'𠮟られる'.setSurrogateSafe().charAt(2)", "𠮟られる".setSurrogateSafe().charAt(2) ); log( "'𠮟られる'.setSurrogateSafe().charAt(3)", "𠮟られる".setSurrogateSafe().charAt(3) ); log( "'𠮟られる'.setSurrogateSafe().charAt(-1)", "𠮟られる".setSurrogateSafe().charAt(-1) ); log( "'𠮟られる'.setSurrogateSafe().charAt(4)", "𠮟られる".setSurrogateSafe().charAt(4) ); log( "'𠮟られる'.setSurrogateSafe().charAt(5)", "𠮟られる".setSurrogateSafe().charAt(5) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(0)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(0) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(1)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(2)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(3)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(4)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(4) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(5)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(5) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().charAt(6)", "𠮟𩸽𐱅".setSurrogateSafe().charAt(6) ); log( "~~~~~ 素のsliceのテスト(第1引数のみ) ~~~~~" ); log( "'叱られる'.slice(0)", "叱られる".slice(0) ); log( "'叱られる'.slice(1)", "叱られる".slice(1) ); log( "'叱られる'.slice(2)", "叱られる".slice(2) ); log( "'𠮟られる'.slice(0)", "𠮟られる".slice(0) ); log( "'𠮟られる'.slice(1)", "𠮟られる".slice(1) ); log( "'𠮟られる'.slice(2)", "𠮟られる".slice(2) ); log( "~~~~~ 素のsliceのテスト(第2引数が0以上) ~~~~~" ); log( "'123'.slice(0, 0)", "123".slice(0, 0) ); log( "'123'.slice(0, 1)", "123".slice(0, 1) ); log( "'123'.slice(0, 2)", "123".slice(0, 2) ); log( "'123'.slice(0, 3)", "123".slice(0, 3) ); log( "'123'.slice(1, 0)", "123".slice(1, 0) ); log( "'123'.slice(1, 1)", "123".slice(1, 1) ); log( "'123'.slice(1, 2)", "123".slice(1, 2) ); log( "'123'.slice(1, 3)", "123".slice(1, 3) ); log( "'123'.slice(2, 0)", "123".slice(2, 0) ); log( "'123'.slice(2, 1)", "123".slice(2, 1) ); log( "'123'.slice(2, 2)", "123".slice(2, 2) ); log( "'123'.slice(2, 3)", "123".slice(2, 3) ); log( "'123'.slice(3, 0)", "123".slice(3, 0) ); log( "'123'.slice(3, 1)", "123".slice(3, 1) ); log( "'123'.slice(3, 2)", "123".slice(3, 2) ); log( "'123'.slice(3, 3)", "123".slice(3, 3) ); log( "'𠮟𩸽𐱅'.slice(0, 0)", "𠮟𩸽𐱅".slice(0, 0) ); log( "'𠮟𩸽𐱅'.slice(0, 1)", "𠮟𩸽𐱅".slice(0, 1) ); log( "'𠮟𩸽𐱅'.slice(0, 2)", "𠮟𩸽𐱅".slice(0, 2) ); log( "'𠮟𩸽𐱅'.slice(0, 3)", "𠮟𩸽𐱅".slice(0, 3) ); log( "'𠮟𩸽𐱅'.slice(1, 0)", "𠮟𩸽𐱅".slice(1, 0) ); log( "'𠮟𩸽𐱅'.slice(1, 1)", "𠮟𩸽𐱅".slice(1, 1) ); log( "'𠮟𩸽𐱅'.slice(1, 2)", "𠮟𩸽𐱅".slice(1, 2) ); log( "'𠮟𩸽𐱅'.slice(1, 3)", "𠮟𩸽𐱅".slice(1, 3) ); log( "'𠮟𩸽𐱅'.slice(2, 0)", "𠮟𩸽𐱅".slice(2, 0) ); log( "'𠮟𩸽𐱅'.slice(2, 1)", "𠮟𩸽𐱅".slice(2, 1) ); log( "'𠮟𩸽𐱅'.slice(2, 2)", "𠮟𩸽𐱅".slice(2, 2) ); log( "'𠮟𩸽𐱅'.slice(2, 3)", "𠮟𩸽𐱅".slice(2, 3) ); log( "'𠮟𩸽𐱅'.slice(3, 0)", "𠮟𩸽𐱅".slice(3, 0) ); log( "'𠮟𩸽𐱅'.slice(3, 1)", "𠮟𩸽𐱅".slice(3, 1) ); log( "'𠮟𩸽𐱅'.slice(3, 2)", "𠮟𩸽𐱅".slice(3, 2) ); log( "'𠮟𩸽𐱅'.slice(3, 3)", "𠮟𩸽𐱅".slice(3, 3) ); log( "~~~~~ 素のsliceのテスト(第2引数が負) ~~~~~" ); log( "'123'.slice(0, -1)", "123".slice(0, -1) ); log( "'123'.slice(0, -2)", "123".slice(0, -2) ); log( "'123'.slice(0, -3)", "123".slice(0, -3) ); log( "'123'.slice(0, -4)", "123".slice(0, -4) ); log( "'123'.slice(1, -1)", "123".slice(1, -1) ); log( "'123'.slice(1, -2)", "123".slice(1, -2) ); log( "'123'.slice(1, -3)", "123".slice(1, -3) ); log( "'123'.slice(1, -4)", "123".slice(1, -4) ); log( "'123'.slice(2, -1)", "123".slice(2, -1) ); log( "'123'.slice(2, -2)", "123".slice(2, -2) ); log( "'123'.slice(2, -3)", "123".slice(2, -3) ); log( "'123'.slice(2, -4)", "123".slice(2, -4) ); log( "'123'.slice(3, -1)", "123".slice(3, -1) ); log( "'123'.slice(3, -2)", "123".slice(3, -2) ); log( "'123'.slice(3, -3)", "123".slice(3, -3) ); log( "'123'.slice(3, -4)", "123".slice(3, -4) ); log( "'𠮟𩸽𐱅'.slice(0, -1)", "𠮟𩸽𐱅".slice(0, -1) ); log( "'𠮟𩸽𐱅'.slice(0, -2)", "𠮟𩸽𐱅".slice(0, -2) ); log( "'𠮟𩸽𐱅'.slice(0, -3)", "𠮟𩸽𐱅".slice(0, -3) ); log( "'𠮟𩸽𐱅'.slice(0, -4)", "𠮟𩸽𐱅".slice(0, -4) ); log( "'𠮟𩸽𐱅'.slice(1, -1)", "𠮟𩸽𐱅".slice(1, -1) ); log( "'𠮟𩸽𐱅'.slice(1, -2)", "𠮟𩸽𐱅".slice(1, -2) ); log( "'𠮟𩸽𐱅'.slice(1, -3)", "𠮟𩸽𐱅".slice(1, -3) ); log( "'𠮟𩸽𐱅'.slice(1, -4)", "𠮟𩸽𐱅".slice(1, -4) ); log( "'𠮟𩸽𐱅'.slice(2, -1)", "𠮟𩸽𐱅".slice(2, -1) ); log( "'𠮟𩸽𐱅'.slice(2, -2)", "𠮟𩸽𐱅".slice(2, -2) ); log( "'𠮟𩸽𐱅'.slice(2, -3)", "𠮟𩸽𐱅".slice(2, -3) ); log( "'𠮟𩸽𐱅'.slice(2, -4)", "𠮟𩸽𐱅".slice(2, -4) ); log( "'𠮟𩸽𐱅'.slice(3, -1)", "𠮟𩸽𐱅".slice(3, -1) ); log( "'𠮟𩸽𐱅'.slice(3, -2)", "𠮟𩸽𐱅".slice(3, -2) ); log( "'𠮟𩸽𐱅'.slice(3, -3)", "𠮟𩸽𐱅".slice(3, -3) ); log( "'𠮟𩸽𐱅'.slice(3, -4)", "𠮟𩸽𐱅".slice(3, -4) ); log( "~~~~~ サロゲート安全なsliceのテスト(第1引数のみ) ~~~~~" ); log( "'叱られる'.setSurrogateSafe().slice(0)", "叱られる".setSurrogateSafe().slice(0) ); log( "'叱られる'.setSurrogateSafe().slice(1)", "叱られる".setSurrogateSafe().slice(1) ); log( "'叱られる'.setSurrogateSafe().slice(2)", "叱られる".setSurrogateSafe().slice(2) ); log( "'𠮟られる'.setSurrogateSafe().slice(0)", "𠮟られる".setSurrogateSafe().slice(0) ); log( "'𠮟られる'.setSurrogateSafe().slice(1)", "𠮟られる".setSurrogateSafe().slice(1) ); log( "'𠮟られる'.setSurrogateSafe().slice(2)", "𠮟られる".setSurrogateSafe().slice(2) ); log( "~~~~~ サロゲート安全なsliceのテスト(第2引数が0以上) ~~~~~" ); log( "'123'.setSurrogateSafe().slice(0, 0)", "123".setSurrogateSafe().slice(0, 0) ); log( "'123'.setSurrogateSafe().slice(0, 1)", "123".setSurrogateSafe().slice(0, 1) ); log( "'123'.setSurrogateSafe().slice(0, 2)", "123".setSurrogateSafe().slice(0, 2) ); log( "'123'.setSurrogateSafe().slice(0, 3)", "123".setSurrogateSafe().slice(0, 3) ); log( "'123'.setSurrogateSafe().slice(1, 0)", "123".setSurrogateSafe().slice(1, 0) ); log( "'123'.setSurrogateSafe().slice(1, 1)", "123".setSurrogateSafe().slice(1, 1) ); log( "'123'.setSurrogateSafe().slice(1, 2)", "123".setSurrogateSafe().slice(1, 2) ); log( "'123'.setSurrogateSafe().slice(1, 3)", "123".setSurrogateSafe().slice(1, 3) ); log( "'123'.setSurrogateSafe().slice(2, 0)", "123".setSurrogateSafe().slice(2, 0) ); log( "'123'.setSurrogateSafe().slice(2, 1)", "123".setSurrogateSafe().slice(2, 1) ); log( "'123'.setSurrogateSafe().slice(2, 2)", "123".setSurrogateSafe().slice(2, 2) ); log( "'123'.setSurrogateSafe().slice(2, 3)", "123".setSurrogateSafe().slice(2, 3) ); log( "'123'.setSurrogateSafe().slice(3, 0)", "123".setSurrogateSafe().slice(3, 0) ); log( "'123'.setSurrogateSafe().slice(3, 1)", "123".setSurrogateSafe().slice(3, 1) ); log( "'123'.setSurrogateSafe().slice(3, 2)", "123".setSurrogateSafe().slice(3, 2) ); log( "'123'.setSurrogateSafe().slice(3, 3)", "123".setSurrogateSafe().slice(3, 3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, 0)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, 0) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, 1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, 1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, 2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, 2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, 3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, 3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, 0)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, 0) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, 1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, 1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, 2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, 2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, 3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, 3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, 0)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, 0) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, 1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, 1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, 2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, 2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, 3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, 3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, 0)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, 0) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, 1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, 1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, 2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, 2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, 3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, 3) ); log( "~~~~~ サロゲート安全なsliceのテスト(第2引数が負) ~~~~~" ); log( "'123'.setSurrogateSafe().slice(0, -1)", "123".setSurrogateSafe().slice(0, -1) ); log( "'123'.setSurrogateSafe().slice(0, -2)", "123".setSurrogateSafe().slice(0, -2) ); log( "'123'.setSurrogateSafe().slice(0, -3)", "123".setSurrogateSafe().slice(0, -3) ); log( "'123'.setSurrogateSafe().slice(0, -4)", "123".setSurrogateSafe().slice(0, -4) ); log( "'123'.setSurrogateSafe().slice(1, -1)", "123".setSurrogateSafe().slice(1, -1) ); log( "'123'.setSurrogateSafe().slice(1, -2)", "123".setSurrogateSafe().slice(1, -2) ); log( "'123'.setSurrogateSafe().slice(1, -3)", "123".setSurrogateSafe().slice(1, -3) ); log( "'123'.setSurrogateSafe().slice(1, -4)", "123".setSurrogateSafe().slice(1, -4) ); log( "'123'.setSurrogateSafe().slice(2, -1)", "123".setSurrogateSafe().slice(2, -1) ); log( "'123'.setSurrogateSafe().slice(2, -2)", "123".setSurrogateSafe().slice(2, -2) ); log( "'123'.setSurrogateSafe().slice(2, -3)", "123".setSurrogateSafe().slice(2, -3) ); log( "'123'.setSurrogateSafe().slice(2, -4)", "123".setSurrogateSafe().slice(2, -4) ); log( "'123'.setSurrogateSafe().slice(3, -1)", "123".setSurrogateSafe().slice(3, -1) ); log( "'123'.setSurrogateSafe().slice(3, -2)", "123".setSurrogateSafe().slice(3, -2) ); log( "'123'.setSurrogateSafe().slice(3, -3)", "123".setSurrogateSafe().slice(3, -3) ); log( "'123'.setSurrogateSafe().slice(3, -4)", "123".setSurrogateSafe().slice(3, -4) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, -1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, -1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, -2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, -2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, -3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, -3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(0, -4)", "𠮟𩸽𐱅".setSurrogateSafe().slice(0, -4) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, -1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, -1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, -2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, -2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, -3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, -3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(1, -4)", "𠮟𩸽𐱅".setSurrogateSafe().slice(1, -4) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, -1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, -1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, -2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, -2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, -3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, -3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(2, -4)", "𠮟𩸽𐱅".setSurrogateSafe().slice(2, -4) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, -1)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, -1) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, -2)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, -2) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, -3)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, -3) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().slice(3, -4)", "𠮟𩸽𐱅".setSurrogateSafe().slice(3, -4) ); log( "~~~~~ 素のindexOfのテスト ~~~~~" ); log( "'叱られる'.indexOf('叱')", "叱られる".indexOf('叱') ); log( "'叱られる'.indexOf('ら')", "叱られる".indexOf('ら') ); log( "'叱られる'.indexOf('れ')", "叱られる".indexOf('れ') ); log( "'叱られる'.indexOf('る')", "叱られる".indexOf('る') ); log( "'叱られる'.indexOf('叱ら')", "叱られる".indexOf('叱ら') ); log( "'叱られる'.indexOf('られ')", "叱られる".indexOf('られ') ); log( "'叱られる'.indexOf('れる')", "叱られる".indexOf('れる') ); log( "'叱られる'.indexOf('叱られ')", "叱られる".indexOf('叱られ') ); log( "'叱られる'.indexOf('られる')", "叱られる".indexOf('られる') ); log( "'叱られる'.indexOf('叱られる')", "叱られる".indexOf('叱られる') ); log( "'叱られる'.indexOf('X')", "叱られる".indexOf('X') ); log( "'叱られる'.indexOf('叱られるX')", "叱られる".indexOf('叱られるX') ); log( "'叱られる叱'.indexOf('叱')", "叱られる叱".indexOf('叱られる叱') ); log( "'叱られる'.indexOf('')", "叱られる".indexOf('') ); log( "'叱られる'.indexOf()", "叱られる".indexOf() ); log( "'叱られる'.indexOf(null)", "叱られる".indexOf(null) ); log( "'叱られる'.indexOf(undefined)", "叱られる".indexOf(undefined) ); log( "'𠮟𩸽𐱅'.indexOf('𠮟')", "𠮟𩸽𐱅".indexOf('𠮟') ); log( "'𠮟𩸽𐱅'.indexOf('𩸽')", "𠮟𩸽𐱅".indexOf('𩸽') ); log( "'𠮟𩸽𐱅'.indexOf('𐱅')", "𠮟𩸽𐱅".indexOf('𐱅') ); log( "'𠮟𩸽𐱅'.indexOf('𠮟𩸽')", "𠮟𩸽𐱅".indexOf('𠮟𩸽') ); log( "'𠮟𩸽𐱅'.indexOf('𩸽𐱅')", "𠮟𩸽𐱅".indexOf('𩸽𐱅') ); log( "'𠮟𩸽𐱅'.indexOf('𠮟𩸽𐱅')", "𠮟𩸽𐱅".indexOf('𠮟𩸽𐱅') ); log( "'𠮟𩸽𐱅'.indexOf('X')", "𠮟𩸽𐱅".indexOf('X') ); log( "'𠮟𩸽𐱅'.indexOf('𠮟𩸽𐱅X')", "𠮟𩸽𐱅".indexOf('𠮟𩸽𐱅X') ); log( "'𠮟𩸽𐱅'.indexOf('')", "𠮟𩸽𐱅".indexOf('') ); log( "'𠮟𩸽𐱅'.indexOf()", "𠮟𩸽𐱅".indexOf() ); log( "'𠮟𩸽𐱅'.indexOf(null)", "𠮟𩸽𐱅".indexOf(null) ); log( "'𠮟𩸽𐱅'.indexOf(undefined)", "𠮟𩸽𐱅".indexOf(undefined) ); log( "~~~~~ サロゲート安全なindexOfのテスト ~~~~~" ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𠮟')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𠮟') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𩸽')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𩸽') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𐱅')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𐱅') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𠮟𩸽')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𠮟𩸽') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𩸽𐱅')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𩸽𐱅') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𠮟𩸽𐱅')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𠮟𩸽𐱅') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('X')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('X') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('𠮟𩸽𐱅X')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('𠮟𩸽𐱅X') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf('')", "𠮟𩸽𐱅".setSurrogateSafe().indexOf('') ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf()", "𠮟𩸽𐱅".setSurrogateSafe().indexOf() ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf(null)", "𠮟𩸽𐱅".setSurrogateSafe().indexOf(null) ); log( "'𠮟𩸽𐱅'.setSurrogateSafe().indexOf(undefined)", "𠮟𩸽𐱅".setSurrogateSafe().indexOf(undefined) ); // TODO: 単体テスト化 }); </script> <br> <br> 結果表示部: <table id="table_result"> <thead> <tr> <th>実行内容</th> <th>実行値</th> </tr> </thead> <tbody id="tbody_result"> </tbody> </table> <!-- divだと,RTLの文字列を正しく結果表示できない <div id="div_result"></div> --> <style> table#table_result, table#table_result tr, table#table_result th, table#table_result td { padding : 5px; border-collapse : collapse; border : solid 1px black; } table#table_result { margin :10px; } </style> <br> <br> <br> <br> <br> <br> <br> <br> </body> </html>
ライブラリ
string_with_surrogate_pairs.js
/* サロゲートペアを加味した文字列の処理ライブラリ string_with_surrogate_pairs.js @author : id:SourceCode-Student MIT License ver 0.1.1 2019.06.12. ・サロゲート安全な処理として,下記のメソッドを実装した。 toCharArray:1文字ずつの配列化 surrogatedLength:文字数の算出 charAt:特定の位置の文字を1文字抽出 slice:開始位置から終了位置までを切り出し indexOf:ある文字列が何文字目に存在するかのカウント URL: http://source-code-student.hatenablog.jp/entry/2019/06/12/%E3%82%B5%E3%83%AD%E3%82%B2%E3%83%BC%E3%83%88%E5%AE%89%E5%85%A8%E3%81%AAJavaScript%E3%81%AE%E6%96%87%E5%AD%97%E5%88%97%E5%87%A6%E7%90%86%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%80%8Cs */ // デバッグ用のコンソールログ function clog(s){ //console.log(s) } // ------------------------ サロゲート安全モードの使用有無 ------------------------ // 全文字列をサロゲート安全に使用することの宣言 String.setSurrogateSafeGlobally = function( b ){ // 内部フラグをON/OFF this._globallySurrogateSafe = b; }; // 全文字列をサロゲート安全に使用するかどうか String.getSurrogateSafeGlobally = function(){ // 内部フラグ return this._globallySurrogateSafe; }; // ある文字列をサロゲート安全に使用することの宣言 String.prototype.setSurrogateSafe = function(){ // 内部フラグをON this._surrogateSafe = true; // OFFにする機能は提供不要 // メソッドチェイン可能 return this; }; // ある文字列をサロゲート安全に使用するかどうか String.prototype.getSurrogateSafe = function(){ // 内部フラグ return this._surrogateSafe; }; // 現在の処理でサロゲート安全な動作が求められているかどうか String.prototype.mustActSurrogateSafe = function(){ // 内部フラグか,グローバルフラグのどちらかがONか return this._surrogateSafe || String.getSurrogateSafeGlobally(); }; // ------------------------ サロゲートペアの検出 ------------------------ // 文字列がサロゲートペアを含むかどうか String.prototype.hasSurrogatePair = function(){ // 参考:hasSurrogate // http://liosk.blog103.fc2.com/blog-entry-162.html if( this.match( /[\uD800-\uDBFF][\uDC00-\uDFFF]/g ) ) { return true; } else { return false; } }; // ------------------------ よくある処理のサロゲート安全化 ------------------------ // サロゲートペアを加味しながら,文字列を1文字ずつの配列に変換 String.prototype.toCharArray = function(){ return ( this.match( /[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g ) || [] // 空文字にmatchメソッドを適用するとnullが返るので,その時用に // 参考:文字列を1文字ずつ配列化(サロゲートペアを考慮) // https://qiita.com/sounisi5011/items/aa2d747322aad4850fe7 ); }; // サロゲートペアを加味した文字長 String.prototype.surrogatedLength = function(){ // サロゲートを加味した配列にしてから,配列長を返す return this.toCharArray().length; }; // ------------------------ 組み込みメソッドのサロゲート安全化 ------------------------ // NOTE: // サロゲートペアを含むとバグるのは,文字列の長さ,切り出し,抽出 // charAtの再定義 String.prototype._charAt_orig = String.prototype.charAt; // 組み込みメソッドの退避 String.prototype.charAt = function(){ // サロゲート安全な動作を期待? if( this.mustActSurrogateSafe() ) { // サロゲート安全なcharAt // charAtの仕様 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/charAt var index = arguments[0]; var arr = this.toCharArray(); // 引数が無ければ0にする if( ! index ){ index = 0; } // インデックスが範囲内なら,その文字を返す if( ( index >= 0 ) && ( index < arr.length ) ){ return arr[ index ]; } else // インデックスが範囲外なら,空文字を返す { return ""; } } else { // 組み込みメソッドを呼び出し return String.prototype._charAt_orig.apply( this, arguments ); } }; // sliceの再定義 String.prototype._slice_orig = String.prototype.slice; // 組み込みメソッドの退避 String.prototype.slice = function(){ // サロゲート安全な動作を期待? if( this.mustActSurrogateSafe() ) { // サロゲート安全なslice // この文字列をサロゲート安全な配列に var arr = this.toCharArray(); // 引数 var arg1 = arguments[0]; var arg2 = arguments[1]; // 配列をsliceする var sliced_arr = arr.slice( arg1, arg2 ); // つなげて文字列にして返す return sliced_arr.join(""); // String.prototype.sliceの仕様 // http://catprogram.hatenablog.com/entry/2013/05/13/231457 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/slice // Array.prototype.sliceについて // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/slice } else { // 組み込みメソッドを呼び出し return String.prototype._slice_orig.apply( this, arguments ); } }; // indexOfの再定義 String.prototype._indexOf_orig = String.prototype.indexOf; // 組み込みメソッドの退避 String.prototype.indexOf = function( kwd_str ){ // サロゲート安全な動作を期待? if( this.mustActSurrogateSafe() ) { // サロゲート安全なindexOf clog("サロゲート安全なindexOfを実行します。:" + this + ", kwd_str = " + kwd_str ); // 検索キーワードが無効な値だったら if( ( kwd_str == null ) || ( kwd_str == undefined ) // NOTE: // JavaScriptでは空文字はfalseとなるので,if( ! "" ) はtrueとなる。 ) { // null や undefined だったら -1 が返る仕様 return -1; } // 空文字列は0を返す仕様 clog("kwd_str.length=" + kwd_str.length); if( kwd_str == "" ){ clog("''が来たので0を返却:kwd_str.length=" + kwd_str.length); return 0; } // この文字列をサロゲート安全な配列に var arr_base = this.toCharArray(); clog("arr_base = " + arr_base); // 検索文字列もサロゲート安全な配列に var arr_kwd = kwd_str.toCharArray(); clog("arr_kwd = " + arr_kwd); // ずらしかたを変えながらマッチを検査 for( var i = 0; i < arr_base.length - arr_kwd.length + 1; i ++ ){ // i だけずらした場合の検査 clog("i だけずらした場合の検査:i = " + i); // 単語全体でマッチしたかというフラグ var word_match_flag = true; // arr_baseのi番目の要素から順に,arr_kwdの要素が並んでいないか? SCAN_IN_KWD : for( var j = 0; j < arr_kwd.length; j ++ ){ clog("j 文字目の比較:j = " + j); // 比較対象の文字 var ch_base = arr_base[ i + j ]; var ch_kwd = arr_kwd[ j ]; clog("比較対象の文字:ch_base = " + ch_base + ", ch_kwd = " + ch_kwd); // 文字単位でマッチしたかというフラグ var ch_match_flag = ( ch_base == ch_kwd ); clog("文字単位のマッチフラグ:ch_match_flag = " + ch_match_flag ); // 単語単位のマッチフラグも書き換える word_match_flag = word_match_flag && ch_match_flag; // 文字単位でマッチしなかったら if( ! ch_match_flag ){ break SCAN_IN_KWD; } } // 単語単位でマッチした? if( word_match_flag ){ clog("語単位のマッチが発生:i = " + i ); // マッチが発生した時のずれ方を報告する return i; } } clog("語単位のマッチが発生しなかったため,-1を返します。" ); // マッチしなかった場合は -1 を返す return -1; // String.prototype.indexOfの仕様 // https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf } else { // 組み込みメソッドを呼び出し return String.prototype._indexOf_orig.apply( this, arguments ); } };
参考資料
JavaScriptの動かないコード (中級編) 文字数のカウントに失敗する
https://language-and-engineering.hatenablog.jp/entry/20120523/LengthOfUnicodeStringWithSurrogatePairs
- 「叱られる」は4文字だが, 「𠮟られる」は,なぜか5文字になる。 JavaScriptが,文字長のカウントに失敗している。 lengthプロパティの挙動がおかしい。
JavaScriptでのサロゲートペア文字列のメモ - Qiita
https://qiita.com/YusukeHirao/items/2f0fb8d5bbb981101be0
- 10000以降のUnicode番号の文字はすべてサロゲートペアでないと表現できない
[JavaScript] サロゲート・ペアに対応した文字列操作関数を書いてみた / LiosK-free Blog
http://liosk.blog103.fc2.com/blog-entry-162.html
- 「文字列中にサロゲートペアを含む場合はtrue」という関数
- 正規表現でサロゲートペアを含むか否かを判断している
- サロゲートペアの /[\uD800-\uDBFF][\uDC00-\uDFFF]/ っていうのはすぐ忘れそうだから関数化しておくといい
文字列を1文字ずつ配列化(サロゲートペアを考慮) - Qiita
https://qiita.com/sounisi5011/items/aa2d747322aad4850fe7
- 空文字列が指定された場合にnullが返されてしまう問題があります。
- そこで、||演算子をエルビス演算子のように使用してこう書きます。
- "".match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S]/g) || []
- 生成されるデータ []
JavaScript における文字コードと「文字数」の数え方 | blog.jxck.io
https://blog.jxck.io/entries/2017-03-02/unicode-in-javascript.html
- length は文字数ではなく、単にこの UTF-16 配列の長さだ。 だから、 1 文字に 16bit が 2 つ必要なサロゲートペアは length が 2 となってしまう。
補足
このコードの作成の発端:
突厥文字という古テュルク諸語のアルファベットが,サロゲートペアで表現されていたために
正しく文字列処理ができなかったので。
https://twitter.com/rwanda_go_tan/status/1138458118059417600
- 文字化けの原因,はっきりわかったなあ。 突厥文字が, もともとのUnicodeに収まりきらず 「サロゲートペア」という拡張仕様で表現されているので 突厥語の文章を,うまく分解しきれず 文字化けしていたのね。 全部のコードを見直して, サロゲート安全なロジックに作り替えないといけませんね。