サロゲート安全なJavaScriptの文字列処理ライブラリ「string_with_surrogate_pairs.js」の公開とサンプルコード

サロゲートペアを含むような文字列を,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に収まりきらず 「サロゲートペア」という拡張仕様で表現されているので 突厥語の文章を,うまく分解しきれず 文字化けしていたのね。 全部のコードを見直して, サロゲート安全なロジックに作り替えないといけませんね。