Nucleus(JP)フォーラム

NucleusCMS日本語版ユーザーのためのサポートフォーラムです。疑問が生じたらまずは記事検索をご利用ください。

ログインしていません。

#1 2011-12-31 16:53:00

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

調べていて少々遅くなりました。

対応としては、Reineさんのおっしゃるとおり、html_entity_decodeとhtmlentitiesを文字符号化方式のオプションを渡して実行するのがいいと思います(_CHARSETの代わりに新設のi18n::get_current_charset()を使うのがいいでしょう)。

Reine さんの発言:

get_html_translation_tableはシングルバイトにしか対応していなくて意図しない変換をやらかしてしまっているようです。

原因はおそらくそちらではなくて、strtr()にあるかと思います。理由を説明すると
1. get_html_translation_table()で返される変換テーブル(連想配列)のkeyとvalueに含まれる文字列は、すべてASCII文字集合に含まれるものとなっている
2. 大方の文字符号化方式は、ASCII文字集合に関して、共通のビット列を与えている。これは歴史的な経緯による
3. 共通のビット列を与えている以上、文字符号化方式が異なっても不適切な変換結果にはならない

PHPマニュアルには言及されていませんが、strtrはマルチバイト文字列の扱いに対応していないっぽいです。すなわち

1. 例えばUTF-8において、ある文字は、UTF-8の符号化文字集合であるUCS(ISO/IEC 10646)のどの面に属するかで、1バイト符号化、2バイト符号化、3バイト符号化、4バイト符号化に処理が分かれる。
2. get_html_translation_tableには先述のとおり、ASCII文字集合、これはUCSでは1バイト符号化される文字列になるんですが、これしか含まれない
3. strtrは置換対象の文字列の符号化の「境界」を考慮しないため、2バイトや3バイト文字列もバイト単位で置換してしまう。
3. その結果として、ビット列がおかしくなり「文字化け」として表示されてしまう

対策としてi18nに置換のための関数を用意しようかなとも思いましたが、ビルトイン関数の方が処理が高速なので、HTML_ENTITIESすべてを置換対象とするならhtml_entity_decodeとhtmlentitiesを使うのが妥当と思います。

Reine さんの発言:

日本語版では誤変換の元であるHTML_ENTITIESの利用を見限り、HTML_SPECIALCHARSで最小限の変換だけ行うようにしていると思われます。

履歴を調べてみたら、shortenに関してはリビジョン220で、encode_descに関してはリビジョン163で修正がコミットされています。いずれも私がチームに加わる前のことでした。

リビジョン220
http://sourceforge.jp/projects/nucleus- ... vision=220

リビジョン163
http://sourceforge.jp/projects/nucleus- ... vision=163

HTML_ENTITIESを使わずにHTML_SPECIALCHARSを使った事情を調べてから、本家に反映しようと思います。

オフライン

#2 2012-01-04 23:04:39

Reine
Administrator
From: 大阪
登録日: 2006-06-27
投稿: 80

Re: [4.0の開発 - 3] エンティティの変換に関して

今年もよろしくお願いいたします。

文字コード周りはそこまで突き詰めたことないので細かいところでミスしてるかもしれませんが、調べた結果&考察を。

Mocchi さんの発言:

1. get_html_translation_table()で返される変換テーブル(連想配列)のkeyとvalueに含まれる文字列は、すべてASCII文字集合に含まれるものとなっている

ASCIIでは0x7F(DELコントロールコード)までしか定義されていないと認識しています。
HTML_ENTITIESで返されるコードはASCII文字コード表ではなく、ISO-8859-1文字コード表の0x80~0xFFに含まれるものになるためASCIIでは表現しきれていないのではないかと思われます。

Mocchi さんの発言:

2. 大方の文字符号化方式は、ASCII文字集合に関して、共通のビット列を与えている。これは歴史的な経緯による

Unicodeの経緯として、ASCII文字集合(世界共通のアルファベット&コントロールコード)以外は各国固有の文字として別途テーブルが割り当てられていると認識しています。
HTML_ENTITIESで返されるコードは固有の表記文字(Àとか)なので、UnicodeではISO-8859-1で定義しているアドレスとは別のアドレスに割り当てられている可能性が高いです。

Mocchi さんの発言:

3. 共通のビット列を与えている以上、文字符号化方式が異なっても不適切な変換結果にはならない

上記で述べたとおり、ISO-8859-1での0x80~0xFFはUTF-8(Unicode)では別のアドレスに振られている可能性があり、ISO-8859-1で表現される『À』のアドレスなのか、UTF-8で表現される『À』のアドレスなのかというところで影響がありそうに思えます。


と、知ってそうに書きましたが、最初から上記の事を認識していたのではなく、get_html_translation_tableが返す文字列を確認しようと、encode_descに"print_r($to_entities);"を埋め込んだら化けたテーブルが表示されたのでget_html_translation_tableの返してくる文字コードに問題があったのだと認識し、上記の考察に至りました。 :oops:
言われる通り、ASCII文字集合に含まれるのであれば、新設されたcharset_hint引数は何のためにあるのか理解に苦しむ所です。(PHPはよく理解に苦しむ拡張を行なっている印象もありますけど lol
UTF-8という文字符号化方式(どのコードにどの文字が当てられるか)に対して、実際に表示を行う場合は文字コード表から対応する文字データを取り出して表示する必要があったと認識しています。(文字コード関係の話は難しくて理解できてるか自信がもてません)
HTML_ENTITIESで返されるコードはASCIIではなく、ISO-8859-1文字コード表の0x80~0xFFに含まれるものになるため、ISO-8859-1で取得したエンティティテーブルを、UTF-8で表示してしまうとASCII文字テーブル外の文字となるため、意図しない文字データが返ってきているのではないでしょうか。

Mocchi さんの発言:

PHPマニュアルには言及されていませんが、strtrはマルチバイト文字列の扱いに対応していないっぽいです。

さらに、strtrがシングルバイトで文字列を置換していくことで、完璧な文字化け文字列を生成しているものと思われます。

Mocchi さんの発言:

(_CHARSETの代わりに新設のi18n::get_current_charset()を使うのがいいでしょう)

_CHARSETも後方互換で残っているという訳ですね。
この文を見たときに_CHARSETはあちこちで使われていたような、、とハッとしましたが、私が提示したコード以外では以下9カ所で済んでいるようです。(カッコ内は行番号)

  • createaccount.php(30)

  • xml-rss2.php(57)

  • nucleus\libs\ACTIONS.php(653)

  • nucleus\libs\ADMIN.php(5303)

  • nucleus\libs\globalfunctions.php(767,2189,2208)

  • nucleus\libs\SKIN.php(232)

  • nucleus\libs\include\bookmarklet-add.template(4)

  • nucleus\libs\include\bookmarklet-edit.template(4)

  • nucleus\xmlrpc\server.php(76)

Mocchi さんの発言:

HTML_ENTITIESを使わずにHTML_SPECIALCHARSを使った事情を調べてから、本家に反映しようと思います。

お手数をおかけしておりますが、宜しくお願い致します。

オフライン

#3 2012-01-06 01:31:20

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

Reineさん、ご指摘どうもありがとうございます。

まず先にお詫びを。UTF-8による符号化で文字がどんなビット列に変換されるのか、私が把握できていませんでした。具体的には、UCS-2のコードポイントをUTF-8符号化後のビット列と混同してしまっていました。さらにget_html_translation_table()に含まれる非ASCII文字集合文字列まで考慮できていなかったなど、混乱させてしまい本当に申し訳ありませんでした。

加えていろいろ試した結果、strtrによる置換処理は文字化けの原因とはなりませんでした。文字化けはReineさんがおっしゃるとおり、get_html_translation_table()がISO-8859-1に基づいた連想配列を返すことが原因です。

PHP5のソース(C言語)を読んでget_html_translation_table()でどんな処理をしているか見てみたところ、確かに、PHP5.3リリース時点ではISO-8859-1に基づく変換テーブルが連想配列として返されています。

http://svn.php.net/viewvc/php/php-src/t ... iew=markup

なお、現在リリースキャンディデートの段階にある5.4では、確かにdetermine_charset()の引数にcharset_hintを渡しています。処理内容もCHARSET_UNICODE_COMPATマクロで処理を分けるなどして、いくつかの文字符号化方式を考慮したものとなっています。

http://svn.php.net/viewvc/php/php-src/t ... iew=markup

Nucleus CMSはPHP5以上をターゲットとしているので、html_entity_decode()とhtmlentities()を文字符号化方式のオプションを渡して実行するか、get_html_translation_table()の各要素をi18n::convert()で変換するといった対応ができそうです。

今回の件、理解不足が根強くて情けなくなった次第です。これからもご指南いただけると嬉しいです。


== 以下、php5.3のコード ==

determine_charset()で返ってきたcharsetに該当するマップをentity_mapから見つけて、entity_map.endcharからentity_map.basecharまでを順繰りに、entity_map.tableで参照できるエントリーと共に配列として出力しているようです。

PHP5.3ではdetermine_charsetの引数がNULLなので、iso-8859-1決め打ちとなっています。コードはいくつかの文字符号化方式にも対応できるように書かれているにも関わらず。何らかの事情があったのでしょう。

1410 	/* {{{ proto array get_html_translation_table([int table [, int quote_style]])
1411 	Returns the internal translation table used by htmlspecialchars and htmlentities */
1412 	PHP_FUNCTION(get_html_translation_table)
1413 	{
1414 	long which = HTML_SPECIALCHARS, quote_style = ENT_COMPAT;
1415 	int i, j;
1416 	char ind[2];
1417 	enum entity_charset charset = determine_charset(NULL TSRMLS_CC);
1418 	
1419 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|ll", &which, &quote_style) == FAILURE) {
1420 	return;
1421 	}
1422 	
1423 	array_init(return_value);
1424 	
1425 	ind[1] = 0;
1426 	
1427 	switch (which) {
1428 	case HTML_ENTITIES:
1429 	for (j=0; entity_map[j].charset != cs_terminator; j++) {
1430 	if (entity_map[j].charset != charset)
1431 	continue;
1432 	for (i = 0; i <= entity_map[j].endchar - entity_map[j].basechar; i++) {
1433 	char buffer[16];
1434 	
1435 	if (entity_map[j].table[i] == NULL)
1436 	continue;
1437 	/* what about wide chars here ?? */
1438 	ind[0] = i + entity_map[j].basechar;
1439 	snprintf(buffer, sizeof(buffer), "&%s;", entity_map[j].table[i]);
1440 	add_assoc_string(return_value, ind, buffer, 1);
1441 	
1442 	}
1443 	}
1445 	
1446 	case HTML_SPECIALCHARS:
1447 	for (j = 0; basic_entities[j].charcode != 0; j++) {
1448 	
1449 	if (basic_entities[j].flags && (quote_style & basic_entities[j].flags) == 0)
1450 	continue;
1451 	
1452 	ind[0] = (unsigned char)basic_entities[j].charcode;
1453 	add_assoc_stringl(return_value, ind, basic_entities[j].entity, basic_entities[j].entitylen, 1);
1454 	}
1455 	add_assoc_stringl(return_value, "&", "&", sizeof("&") - 1, 1);
1456 	
1457 	break;
1458 	}
1459 	}
1460 	/* }}} */

entity_mapのデータ構造

381 	struct html_entity_map {
382 	enum entity_charset charset; /* charset identifier */
383 	unsigned short basechar; /* char code at start of table */
384 	unsigned short endchar; /* last char code in the table */
385 	entity_table_t *table; /* the table of mappings */
386 	};
387 	
388 	static const struct html_entity_map entity_map[] = {
389 	{ cs_cp1252, 0x80, 0x9f, ent_cp_1252 },
390 	{ cs_cp1252, 0xa0, 0xff, ent_iso_8859_1 },
391 	{ cs_8859_1, 0xa0, 0xff, ent_iso_8859_1 },
392 	{ cs_8859_15, 0xa0, 0xff, ent_iso_8859_15 },
393 	{ cs_utf_8, 0xa0, 0xff, ent_iso_8859_1 },
394 	{ cs_utf_8, 338, 402, ent_uni_338_402 },
395 	{ cs_utf_8, 710, 732, ent_uni_spacing },
396 	{ cs_utf_8, 913, 982, ent_uni_greek },
397 	{ cs_utf_8, 8194, 8260, ent_uni_punct },
398 	{ cs_utf_8, 8364, 8364, ent_uni_euro },
399 	{ cs_utf_8, 8465, 8501, ent_uni_8465_8501 },
400 	{ cs_utf_8, 8592, 9002, ent_uni_8592_9002 },
401 	{ cs_utf_8, 9674, 9674, ent_uni_9674 },
402 	{ cs_utf_8, 9824, 9830, ent_uni_9824_9830 },
403 	{ cs_big5, 0xa0, 0xff, ent_iso_8859_1 },
404 	{ cs_gb2312, 0xa0, 0xff, ent_iso_8859_1 },
405 	{ cs_big5hkscs, 0xa0, 0xff, ent_iso_8859_1 },
406 	{ cs_sjis, 0xa0, 0xff, ent_iso_8859_1 },
407 	{ cs_eucjp, 0xa0, 0xff, ent_iso_8859_1 },
408 	{ cs_koi8r, 0xa3, 0xff, ent_koi8r },
409 	{ cs_cp1251, 0x80, 0xff, ent_cp_1251 },
410 	{ cs_8859_5, 0xc0, 0xff, ent_iso_8859_5 },
411 	{ cs_cp866, 0xc0, 0xff, ent_cp_866 },
412 	{ cs_macroman, 0x0b, 0xff, ent_macroman },
413 	{ cs_terminator }
414 	};

entity_map.tableの例(ent_iso_8859-1)

60 	typedef const char *const entity_table_t;
61 	
(中略)
70 	
71 	static entity_table_t ent_iso_8859_1[] = {
72 	"nbsp", "iexcl", "cent", "pound", "curren", "yen", "brvbar",
73 	"sect", "uml", "copy", "ordf", "laquo", "not", "shy", "reg",
74 	"macr", "deg", "plusmn", "sup2", "sup3", "acute", "micro",
75 	"para", "middot", "cedil", "sup1", "ordm", "raquo", "frac14",
76 	"frac12", "frac34", "iquest", "Agrave", "Aacute", "Acirc",
77 	"Atilde", "Auml", "Aring", "AElig", "Ccedil", "Egrave",
78 	"Eacute", "Ecirc", "Euml", "Igrave", "Iacute", "Icirc",
79 	"Iuml", "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde",
80 	"Ouml", "times", "Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml",
81 	"Yacute", "THORN", "szlig", "agrave", "aacute", "acirc",
82 	"atilde", "auml", "aring", "aelig", "ccedil", "egrave",
83 	"eacute", "ecirc", "euml", "igrave", "iacute", "icirc",
84 	"iuml", "eth", "ntilde", "ograve", "oacute", "ocirc", "otilde",
85 	"ouml", "divide", "oslash", "ugrave", "uacute", "ucirc",
86 	"uuml", "yacute", "thorn", "yuml"
87 	};

determine_charset()の一部分

712 	/* {{{ entity_charset determine_charset
713 	* returns the charset identifier based on current locale or a hint.
714 	* defaults to iso-8859-1 */
715 	static enum entity_charset determine_charset(char *charset_hint TSRMLS_DC)
716 	{
717 	int i;
718 	enum entity_charset charset = cs_8859_1;
719 	int len = 0;
720 	zval *uf_result = NULL;
721 	
722 	/* Guarantee default behaviour for backwards compatibility */
723 	if (charset_hint == NULL)
724 	return cs_8859_1;
725 	
726 	if ((len = strlen(charset_hint)) != 0) {
727 	goto det_charset;
728 	}

オフライン

#4 2012-01-06 12:18:50

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

Mocchi さんの発言:

加えていろいろ試した結果、strtrによる置換処理は文字化けの原因とはなりませんでした。

PHP5のソースを読んでみたところ、これも間違いなことに気づきました。やはりバイト単位でビットを変えていくので、例えば2バイト符号化の後半1バイトだけを置換してしまうということが起こりうるかと思います。

以下、strtrの主要な処理部分。PHP5.3から5.4rcの間に修正はありません。

http://svn.php.net/viewvc/php/php-src/t ... rkup#l2742

/* {{{ php_strtr
 */
PHPAPI char *php_strtr(char *str, int len, char *str_from, char *str_to, int trlen)
{
	int i;
	unsigned char xlat[256];

	if ((trlen < 1) || (len < 1)) {
		return str;
	}

	for (i = 0; i < 256; xlat[i] = i, i++);

	for (i = 0; i < trlen; i++) {
		xlat[(unsigned char) str_from[i]] = str_to[i];
	}

	for (i = 0; i < len; i++) {
		str[i] = xlat[(unsigned char) str[i]];
	}

	return str;
}
/* }}} */

オフライン

#5 2012-01-07 00:11:58

Reine
Administrator
From: 大阪
登録日: 2006-06-27
投稿: 80

Re: [4.0の開発 - 3] エンティティの変換に関して

PHPのソースコードまで確認していただいて申し訳ありません。

Mocchi さんの発言:

PHP5のソースを読んでみたところ、これも間違いなことに気づきました。やはりバイト単位でビットを変えていくので、例えば2バイト符号化の後半1バイトだけを置換してしまうということが起こりうるかと思います。

上記について、
http://php.net/manual/ja/function.strtr.php
マニュアルにも記載の通り、strtrは引数2個と3個で挙動が大きく変わり、Cのコードも引数の数で分かれていると思われます。
2742行のphp_strtrの関数を掲示いただいてますが、encode_descで用いられているのは引数2個の方で、2767行のphp_strtr_arrayの方が呼ばれているように思われます。

私はC言語の理解に相当時間が掛かりそうなので雰囲気でしか読んでないですが、php_strtr_arrayも結局バイト置換しているようなのでダメっぽいですね。(文字列単位で比較するので誤認識する可能性は低いかもしれませんが、今回は1文字の置換なので影響ありまくりです) lol

オフライン

#6 2012-01-07 10:07:46

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

件名に関して[Nucleus CMS 4.0の開発 - 2] 言語ファイル命名の新ルールとロケール情報の実装で議論が分岐しましたので、新しくトピックを立てて分離しました。

問題としては、PHP5.3以前のget_html_translation_table()がISO-8859-1に基づいて符号化された連想配列を返すため、UTF-8やその他で符号化された文字列の誤変換の原因となるというものです。

オフライン

#7 2012-01-08 19:45:02

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

件名に関して、本家に修正コードをコミットしました。リビジョン1617です。

http://nucleuscms.svn.sourceforge.net/v ... ision=1617

i18nクラスにHTMLエンティティを置換する2つのメソッド、i18n::hsc()、i18n::hen()を設け、そちらで処理をするようにしました。このメソッドはパブリックに宣言してあるので、プラグインからも利用可能です。

PHP5のソースのphp_strtr_arrayの解読は、おそらくPHPのZendエンジンに対する理解がないと難しいと思えましたので、このような対策としました。もうちょっとスキルが欲しいところです、残念。。。

Reineさん、どうもありがとうございました!!

オフライン

#8 2012-01-09 01:16:12

Reine
Administrator
From: 大阪
登録日: 2006-06-27
投稿: 80

Re: [4.0の開発 - 3] エンティティの変換に関して

Mocchiさん、対応有難うございます。

もしかしたら対応箇所が記憶から漏れているのかもと思って書いています。
対応中でしたら申し訳ありません。

ここまでのやり取りが長くなってしまったので話の発端がぼけてしまいましたが、
改めるとglobalfunctions.phpで修正すべき関数は2ヶ所ありました。
shorten(1540行)
文字を切り詰める関数であちこちから呼ばれている。
一度HTMLエンティティ化した文字を元に戻した上で切り詰め処理を行い、HTMLエンティティ化せずに返す。

encode_desc(2380行)
プラグインリストの概要文に対してHTMLエンティティへの変換を行なう。
showlist.php(190行)からしか呼ばれていない。
HTMLエンティティが中途半端に含まれている事を想定していて、一旦HTMLエンティティを文字に戻した後に、再度HTMLエンティティ化している。

これに対して、今回追加していただいたhenとhscの位置づけがいまいち理解できませんでした。

当初の目的は、上記2関数で行われているHTMLエンティティの文字化と、文字のHTMLエンティティ化をマルチバイト対応させることだったと認識しています。
それに対して、henとhscは『HTMLエンティティが中途半端に含まれている事を想定していて、一旦HTMLエンティティを文字に戻した後に、再度HTMLエンティティ化する』という目的に特化して作成されているため、shorten関数の修正には使えないものとなっています。

もう一つ、encode_desc関数の処理をhenで置き換えていただいてますが、showlist.phpから呼び出しているencode_desc関数もhenに置き換えたために、encode_descはどこからも呼ばれない関数となってしまっています。
encode_desc関数のような処理が他に無いとは考えられないですが、他ではget_html_translation_tableが見つかりません。
と、言うことはencode_descは初期の頃に気まぐれで書いただけで他ではそのような処理をそもそも必要としていなかったのではないか(単にhtmlentitiesすれば十分だった?)と考えられます。

今後はちゃんと、henとhscみたいな処理をしないといけないよね。という話の方向性も想定されるので、残しておくとして、追加で修正が必要そうな箇所を以下に記載します。

修正コード書いたような気がする・・・と思ったら言語ファイルとロケールに埋もれてしまってました。 big_smile
http://japan.nucleuscms.org/bb/viewtopi … 193#p27193

globalfunctions.php shorten(1540行)

// shortens a text string to maxlength ($toadd) is what needs to be added
// at the end (end length is <= $maxlength)
function shorten($text, $maxlength, $toadd)
{
	// 1. remove entities...
	$text = html_entity_decode($text, ENT_QUOTES, i18n::get_current_charset());
	
	// 2. the actual shortening
	if (i18n::strlen($text) > $maxlength)
	{
		$text = i18n::substr($text, 0, $maxlength - i18n::strlen($toadd) ) . $toadd;
	
	}
	
	return $text;
}

globalfunctions.php encode_desc(2380行)
(henで代替できるので削除)


確認してたら、アイテム一覧でshortenが反映されないケースが出てきました。
本文の途中に"<"を含み、本文末尾まで">"が現れない場合です。
確認したところ、strip_tagsが"<"が現れてから文字列末尾まで">"が存在しない場合、"<"以降をタグとして切り捨ててしまう事が原因のようです。
つまり、shortenの結果をstrip_tagsが台無しにした感じですね。

諦めるか、閉じない"<"以降を残すstrip_tags的な処理とhtmlentitiesを組み合わせた処理を行う関数を作成するかの2択ですが、国際化対応とは違う領域になっちゃいますね。 sad

オフライン

#9 2012-01-09 16:30:22

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

Reineさんどうもありがとうございます。

shortenのこと、後回しにしたまますっかり忘れてました、すみません。。。

Reine さんの発言:

これに対して、今回追加していただいたhenとhscの位置づけがいまいち理解できませんでした。

i18n::hen()とi18n::hsc()はこの問題とは切り離して考えて下さい。エンティティ変換は文字列、ビットに対する変換処理でかつ頻繁に行うと思われますが、引数に必ず文字符号化方式を与えないと文字化けの原因となる可能性があるため、簡便のためにこうしています。元々はi18n::hsc()だけで足りるかなと思っていたのですが、やっぱり全エンティティに対するものも必要でしたのでi18n::hen()を設けました。

# そもそも、htmlspecialchars(16文字)、htmlentities(11文字)長いってのがありまして。
# 使用頻度がかなり高いだけにを引数込みで打ち込むのがメンドイ。。。
# i18n::hsc/i18n::hen(いずれも9文字)

Reine さんの発言:

もう一つ、encode_desc関数の処理をhenで置き換えていただいてますが、showlist.phpから呼び出しているencode_desc関数もhenに置き換えたために、encode_descはどこからも呼ばれない関数となってしまっています。
encode_desc関数のような処理が他に無いとは考えられないですが、他ではget_html_translation_tableが見つかりません。
と、言うことはencode_descは初期の頃に気まぐれで書いただけで他ではそのような処理をそもそも必要としていなかったのではないか(単にhtmlentitiesすれば十分だった?)と考えられます。

過去の経緯はよくわかりませんが、これはグローバル関数なのでとりあえずプラグインからコールされている可能性を考えて残しておきました。deprecatedなので将来的には削除の方向に持って行きたいと思います。

さて、shorten()に関してです。この関数、エンティティを戻した文字列を返してしまうので、関数の目的を満たすには少々やりすぎ感があります。エンティティを再度変換して返すのがいいのかな、と。

[list=]
[*]エンティティを戻して[/*]
[*]文字列をカウントして必要なら切り詰め[/*]
[*]エンティティを変換して返す[/*][/list]

/**
 * shortens a text string to maxlength
 * $suffix is what needs to be added at the end (end length is <= $maxlength)
 * 
 * The purpose is to limit the width of string for rendered screen in web browser.
 * So it depends on style sheet, browser's rendering scheme, client's system font.
 * 
 * NOTE: In general, non-Latin font such as Japanese, Chinese, Cyrillic have two times as long as Latin fonts,
 *  but this is not always correct, for example, rendered by proportional font.
 * 
 * @param string $escaped_string target string
 * @param integer $maxlength maximum length of return string which includes suffix
 * @param string $suffix added in the end of shortened-string
 * @return string
 */
function shorten($string, $maxlength, $suffix)
{
	static $flag;
	
	$decoded_entities_pcre = array();
	$encoded_entities = array();
	
	/* 1. store html entities */
	preg_match('#&[^&]+?;#', $string, $encoded_entities);
	if ( !$encoded_entities )
	{
		$flag = FALSE;
	}
	else
	{
		$flag = TRUE;
	}
	if ( $flag )
	{
		foreach ( $encoded_entities as $encoded_entity )
		{
			$decoded_entities_pcre[] = '#' . html_entity_decode($encoded_entity, ENT_QUOTES, i18n::get_current_charset()) . '#';
		}
	}
	
	/* 2. decode string */
	$string = html_entity_decode($string, ENT_QUOTES, i18n::get_current_charset());
	
	/* 3. shorten string and add suffix if string length is longer */
	if ( i18n::strlen($string) > $maxlength - i18n::strlen($suffix) )
	{
		$string = i18n::substr($string, 0, $maxlength - i18n::strlen($suffix) );
		$string .= $suffix;
	}
	
	/* 4. recover entities */
	if ( $flag )
	{
		$string = preg_replace($decoded_entities_pcre, $encoded_entities, $string);
	}
	
	return $string;
}

過去のコードと互換性のない処理なのでコアや各プラグインとの兼ね合いが問題となりますが、とりあえず。

str_tag()は、うーん、どうしましょ。。。

オフライン

#10 2012-01-09 19:21:54

Reine
Administrator
From: 大阪
登録日: 2006-06-27
投稿: 80

Re: [4.0の開発 - 3] エンティティの変換に関して

Mocchi さんの発言:

shortenのこと、後回しにしたまますっかり忘れてました、すみません。。。

よかったです、言い出そうかどうしようか結構悩みました。

i18n::hen()とi18n::hsc()については了解しました。
頻繁に使うための便利関数ということで良いのですね。
ただ、私の経験上、便利関数というものはコードを書く人数が多いほど周知徹底が必要で、
存在に気づかずにhtmlentities()を使ったようなベタ書きが行われます。
後々どちらかに統一する作業を行うことになる事が多いので、
後方互換などの理由がない限りは記述量に劇的な差が出ないラッパ関数は避けたいというのが心情です。


Mocchi さんの発言:

過去の経緯はよくわかりませんが、これはグローバル関数なのでとりあえずプラグインからコールされている可能性を考えて残しておきました。deprecatedなので将来的には削除の方向に持って行きたいと思います。

そうでしたね、追加はともかく削除はその点留意する必要がありましたね。
でも影響ほとんどなさそうだし消したい・・・というのは個人的な感情なのでdeprecatedがついただけでも十分じゃないかと自分をなだめておきます。 tongue

Mocchi さんの発言:

さて、shorten()に関してです。この関数、エンティティを戻した文字列を返してしまうので、関数の目的を満たすには少々やりすぎ感があります。エンティティを再度変換して返すのがいいのかな、と。

思ったよりコードが長くなってて焦りました。
記載のコードからすると、「エンティティを再度変換」というのは元々含まれていたエンティティだけ元に戻すということでしょうか。

shortenを利用する立場から考えると、入力が中途半端にエンティティ変換された文字列(もしくは、中途半端にエンティティ変換すべき文字が残っている文字列)の場合、帰ってくる文字列はやはり中途半端に変換されている文字列なので、表示するときにはi18n::hen()やhtmlentities()を通さないといけないのでは?と考えます。
さらにややこしいのは、もし、入力に"&"が含まれていた場合、返り値にhtmlentities()を通すと"&amp;"になってしまうことです。
i18n::hen()の存在を知っていればいいのですが、知らない場合は返り値に対してどのように処理するかしばし悩むことになりそうです。
(結果的にhtml_entity_decode()してhtmlentities()すると思いますが)

shorten()を利用している所では、受け取った文字列をもれなくhtmlentities()して表示しているので、それらもあわせて修正する必要が出てきます。
これも、他のプラグインなどで使用されていることを考えると、仕様は変えない方が良いかなと考えています。


strip_tags()は脆弱性の面で問題とならないと思うので、言語仕様ということで一旦放置しましょうか。
Nucleus内では大抵
文字列 → shorten() → strip_tags() → htmlentities()
という処理を経て表示を行なっているようなので、
見直す場合はその辺りの処理をどう置き換えるかなども含めて検討する必要があるかと思います。

オフライン

#11 2012-01-10 01:31:20

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

以下でコアのソースコード全体に対するi18n::hsc()/hen()の実装をコミットしました。なお、このコミットはもともと私の中で予定していたことです。

CHANGE: replace htmlentities() to i18n::hen()
http://nucleuscms.svn.sourceforge.net/v ... ision=1623

FIX: modify i18n::hsc() in point of third argument for htmlspecialchars_decode()
CHANGE: htmlspecialchars() to i18n::hsc() and htmlentities() to i18n::hen() in whole script
http://nucleuscms.svn.sourceforge.net/v ... ision=1624

add comments for i18n::hsc()
http://nucleuscms.svn.sourceforge.net/v ... ision=1625

REMOVE: 'ENT_QUOTES' argument from i18n::hsc() in whole usage.
http://nucleuscms.svn.sourceforge.net/v ... ision=1626

Reine さんの発言:

ただ、私の経験上、便利関数というものはコードを書く人数が多いほど周知徹底が必要で、存在に気づかずにhtmlentities()を使ったようなベタ書きが行われます。

Nucleus CMSではrequestVar()とかintGetVar()あたりでしょうね。まぁ、それはそれでいいんじゃないでしょうか?コアに関しては私は私のresponsibilityの及ぶ範囲で最適の実装を施しているだけですし、プラグイン開発者及びプラグイン利用者もそれぞれのresponsibilityでやっていただければな、と。

とは言えコミットログやドキュメント、更新履歴などで情報は残しておきますし、コアのコード変更に対するツッコミはするつもりではあります。

Reine さんの発言:

記載のコードからすると、「エンティティを再度変換」というのは元々含まれていたエンティティだけ元に戻すということでしょうか。

そのとおりです。

Reine さんの発言:

shortenを利用する立場から考えると、入力が中途半端にエンティティ変換された文字列(もしくは、中途半端にエンティティ変換すべき文字が残っている文字列)の場合、帰ってくる文字列はやはり中途半端に変換されている文字列なので、表示するときにはi18n::hen()やhtmlentities()を通さないといけないのでは?と考えます。
さらにややこしいのは、もし、入力に"&"が含まれていた場合、返り値にhtmlentities()を通すと"&amp;"になってしまうことです。
i18n::hen()の存在を知っていればいいのですが、知らない場合は返り値に対してどのように処理するかしばし悩むことになりそうです。

そもそも、すべてのエンティティが変換されているわけではない文字列を入力しているわけなので、すべてのエンティティが変換されているわけではない文字列が返ってきたほうが、処理を管理する上で簡便だと考えています。加えて、この類の簡便さは、後方互換性やユーザーの利便性を犠牲にしてもいいと考えています。

shorten()のこの実装に関しては本家の開発者メーリングリストに投げているところなのでしばしお待ち下さい。

オフライン

#12 2012-01-10 20:09:22

Reine
Administrator
From: 大阪
登録日: 2006-06-27
投稿: 80

Re: [4.0の開発 - 3] エンティティの変換に関して

Mocchi さんの発言:

Nucleus CMSではrequestVar()とかintGetVar()あたりでしょうね。まぁ、それはそれでいいんじゃないでしょうか?

そんな関数ありましたね。
だとすると、そんなに気にすることじゃなかったのかもしれません。
単なる私の一意見なので軽く流してしまってください。

Mocchi さんの発言:

shorten()のこの実装に関しては本家の開発者メーリングリストに投げているところなのでしばしお待ち下さい。

お手数おかけします。


これで私の出せるものはひと通り出しきりました。

だいぶMocchiさんのお手を煩わせてしまったと感じています。申し訳ありません。 sad
また、PHPもNucleusのコアコードについても初心者の私に懇切丁寧に解説していただき本当にありがとうございました。m(_ _)m

オフライン

#13 2012-01-14 16:58:29

Mocchi
メンバー
登録日: 2006-11-19
投稿: 438

Re: [4.0の開発 - 3] エンティティの変換に関して

Reine さんの発言:

これで私の出せるものはひと通り出しきりました。

どうもありがとうございました。Reineさんのやりとりによって、UTF-8とISO-8859-1に共通している符号化文字列の符号化ビット列を、私が誤って同じものと理解していたことについて確認することができ、感謝しています。

Reine さんの発言:

だいぶMocchiさんのお手を煩わせてしまったと感じています。申し訳ありません。 sad
また、PHPもNucleusのコアコードについても初心者の私に懇切丁寧に解説していただき本当にありがとうございました。m(_ _)m

それが私の役割ですのでお気遣いなく wink
むしろ、これからもどんどん私の手を煩わせていただければと思います。その都度調べて、本家に反映することになりますから!!

オフライン

Board footer