/ 最近 .rdf 追記 設定 本棚

脳log[正規表現: 2013-12-09~]



2013年12月09日 (月)

最終更新: 2014-09-04T19:38+0900

[tDiary][正規表現] tDiary用MathMLプラグイン(x-math.rb)の置換用正規表現

 問題のある現象と急場しのぎの*解決

SCRIPTタグの外側に書いた HTMLコメント(<--から-->までの、二連のハイフンに挟まれた文字)が CDATAセクションに置き換えられたり、STYLEタグを挿入する位置によって無関係の HTMLコメントがやはり CDATAセクションに置き換えられたりられなかったりする現象が発生していて、以下の replace_bodyメソッド内の2行をコメントアウトすることで解消した。正確にいうとこの2行の直前の行末のドットを削除する必要もある。

gsub(/(<style [^>]*?>.*?)<!--(.*?)-->(.*?<\/style>)/m){"#{$1}\n<![CDATA[\n#{$2}\n]]>\n#{$3}"}.
gsub(/(<script [^>]*?>.*?)<!--(.*?)-->(.*?<\/script>)/m){"#{$1}\n<![CDATA[\n#{$2}\n]]>\n#{$3}"}

これは以前にちらりと書いた(自分の好みを正当化する理由としてでっちあげた)指摘の実例ではないかと思う。

つづくパターン次第ではマッチを成功させるために .*? は何にだってマッチする。

避ける方法は例えばドットの直前に (?!</script>) とか (?!</style>) とか (?!-->) と書くなど(だけではなく否定先読みとドットをグループ化したものでドットを置き換えるようにしないといけないかも)。空白を閉じタグのどこに挿入できるかできないかは調べられなかった。

 @2014-09-04 具体的にはこう。(先読みとドットの前後を入れ替えると意味が変わることを覚えられずいつも引っ掛かる)
gsub(/(<style [^>]*?>(?:(?!<\/style>).)*?)<!--((?:(?!<\/style>).)*?)-->(.*?<\/style>)/m){"#{$1}\n<![CDATA[\n#{$2}\n]]>\n#{$3}"}.
gsub(/(<script [^>]*?>(?:(?!<\/script>).)*?)<!--((?:(?!<\/script>).)*?)-->(.*?<\/script>)/m){"#{$1}\n<![CDATA[\n#{$2}\n]]>\n#{$3}"}

ちょーめんどくせー。しかもこれでも閉じタグが <!-- --> でコメントアウトされている場合にその閉じタグを無視できない。思うに、開きタグと <!--の間、-->と閉じタグの間に空白だけを許可すれば単純になるのでは?

# 最終形。置換後に改行が増えてるのが(※元々の仕様)気に入らないけど……。
gsub(/(<(style|script)(?: [^>]*)?>\s*)<!--((?:(?!--|<\/\2>).)*)-->(\s*<\/\2>)/m){"#{$1}\n<![CDATA[\n#{$3}\n]]>\n#{$4}"}

 問題2: XHTMLInfo#unsupported_body (@2014-01-06)

def unsupported_body
	date = @cgi.params['date'][0]
	url = @conf.index + @plugin.anchor(date, false)
	force_url = @conf.index + self.anchor(date, true)

85行目の @plugin.anchor が引数1つのところに2つ渡したエラーになるので、86行目にならって self.anchorに書き換えた。

url = @conf.index + self.anchor(date, false)

 問題3: XHTMLInfo#supported_agent? (2014-01-06)

def supported_agent?
	is_gecko? || is_mathplayer?
end

Blink Operaなんかも XHTMLをサポートしてるらしい(少なくとも HTTPでそう宣言している)ので下のように書きかえた。

def supported_agent?
	@cgi.accept.to_s.index("application/xhtml+xml") || is_gecko? || is_mathplayer?
end
 @2014-01-08 XHTMLでなく MathML

MathML対応の方が問題になるのだった。

対応しているのは、Gecko を採用している Mozilla Firefox などのブラウザ[4]、Safari 5.1 以降。また、Presto を採用している、Opera 9.50[5]〜12.1 においても単体でほとんどの数式の表示が可能となったが、Opera 14 より WebKit そして Blink になり非対応。Google Chrome は対応していない。非対応ブラウザでも MathJax[6] で表示可能。Internet Explorer では、例えばプラグインの Design Science の MathPlayer をインストールすることで MathML を表示できるようになる。

やる気のないブラウザのことなんて知らない。

* 本来の意味の姑息を使うべき場面ですよ。


2013年11月01日 (金) Kindleストアから大手アダルトコミックが大量削除 - 電パブログ」■ちんちんかもかもって辞書に載ってる単語だったりする。知らねー。■紙の本でも独善によって一部をなき物にするアマゾンだけど Kindleではそれよりさらに厳しい基準でのぞんでいると。TSUTAYAと一緒。消えろ(※強勢はなし。付けると願い事みたいになってしまう。平板に、興味のないゴミカスに退場を許可するみたいに発音する)。■ところで、Reader Storeでも Kinoppyでも最初から取り扱いがなかったりするわけですね。終わってるやん。(直販してる)フランス書院が正解。ISBNのついた本を100%カバーすることを目標に掲げた電書書店はないのか?■こんなんのまま紙が廃れたら悲劇だなあ。分を弁えへん私企業に所有させていいもんやないで。こんなところに利益をくれてやるのは同時に人質を差し出すようなもの。あとで後悔することになる。■■■@2013-11-18「あとで後悔」が二重表現の例として挙げられていて、そうかと思って書き直そうとしたがどうもしっくりしない。これは、未来のある時点において後悔することを動詞(の付属部)だけでなく副詞も使って示しているのであって、つまり、後悔の対象が必ずその時点における過去の事象であることと、後悔するのがいつであるかは別物なので二重表現にはならないのではないだろうか。■「今後悔してる」に対する「あとで後悔する(ことになる)」は不自然じゃないと思う。

最終更新: 2016-11-18T12:59+0900

[tDiary][Firefox][正規表現][javascript] Firefoxが句点を行頭に送ってしまうのがあまりに目障りでもう耐えられないので~正規表現(Rubyに劣るECMAScript仕様)~禁則処理(IE完璧。Firefox/Safariに指導)~両端揃え(IE完璧。Firefox満足*。Safariを補完)~画像とCSSのDPI~字詰め

tDiaryのプラグインとして今回追加したのは auto_nobrの部分だけ。

add_title_proc {|date, title|
  auto_nobr = lambda{|src|
    return src.gsub(/[^{}\[\]()*#"!'`=:|][、。」』!?!?)]+|[「『(]+[^{}\[\]()*#"!'`=:|]/u){ %{{{'<nobr>'}}#{$&}{{'</nobr>'}}} }
  }
  inline_or_nil = lambda{|src|
    lines = src.split(/\r?\n/)
    return nil if 1 < lines.length
    html = WikiSection.new(auto_nobr.call lines.first).body_to_html
    return html.gsub!(/\A\s*<p>/, '') && html.gsub!(/<\/p>\s*\z/, '')
  }
  if title.index('<')
    title.sub(/<span class="title">([^<>]+)<\/span>/){|_0|
      html = inline_or_nil.call(CGI.unescapeHTML $1)
      html ? %/<span class="title">#{html}<\/span>/ : _0
    }
  else
    inline_or_nil.call(CGI.unescapeHTML title) or title
  end rescue title
}

一応。Firefoxの挙動は word-break: break-allが指定された結果である。であるが、だからといって行頭の句点、句点だけの行(ぽつーん)はありえないだろうと思うのです。

 やっつけ仕様

 1.標準に存在しない<nobr>タグの使用

かといって空白が関係するわけではないから white-space:nowrap とか使えないし、使えたとして FONTタグを <span style="font:~"></span>に置き換えるようなことに意味を見いだせたのはそれが 2000年頃のことだったからだし、段落全体を一行で表示したいわけでもないので Pタグに対してルールを追加することもできず、代替案が見つからない。

 2.テキトーなテキスト置換

HikiDocで意味を持ってそうな記号を避けつつ句点+1文字を<nobr>で囲ってる。整形式でないというエラーが出るパターンがまだ残ってるかもしれない。footnoteプラグインとかわりと文章を渡すから危険だ。

 3.対象がタイトル欄だけ

全体を対象にするなら JavaScriptでやる。でもテキストを対象にしつつタグをインジェクトする方法がわからなかった。

マッチしたテキストノードをドキュメントフラグメント(テキスト+NOBRエレメント+テキスト)と置き換えればよかったのか?具体的方法が見えたところだが、レイアウトが変わってしまう変更を読み込み完了間際にスクリプトで行うというのはやっぱりよくないかも。フラグメント付きの URLでアクセスしたときターゲットが画面外に逃げてしまいかねない。

 4.表示を確認したのは Firefox(23.0.1)と IE9だけ

Operaは独自のレンダリングエンジンを放棄したし、Windows版Safariはアップデートが止まったし、Google Chromeはインストール場所がキモいから(今でもそうかは知らない。当然やめるだろうと予想するほどに、そして一瞬でアンインストールしてしまうほどにキモかったということだ)。

 CSS 3におけるテキストの自動改行と禁則処理の定義 - builder by ZDNet Japan

break-all

任意の位置で自動改行を行うが、日本語のテキストでは「line-break:normal」と指定したときと同じようにゆるい禁則処理を行う。

これを期待して待ってたんだけど。

 アポラボログ: Firefox 15 の禁則処理を修正

この「word-break」というスタイルシートは文の改行の仕方を指定するもので、もともと Internet Explorer 独自の物だったようなのですが、最近になって Firefox もこれを採用したらしいのです。そのため、以前の Firefox では無効化されることを想定してこのスタイルシートが指定されていたと考えられます。

サイト制作者の想定としては IE のみにこのスタイルシートを指定したつもりが、意図せず Firefox でも有効化されてしまい、禁則処理のされない読みづらい記事が発生してしまったということだと思います。おそらく、IE の場合はこのスタイルシートが指定されていても禁則処理には影響がなかったのではないでしょうか。

先行する IEはまとも。後追いの Firefoxはバカ。日本人の貢献が足りないのか?

 Gecko と Webkit の word-break:break-all; ってこれでいいの? « やおよろグッ!

良くないと思います。

 word-break: break-all; がW3CでOKになってるし|ぼくんちのバックステージ

余談:Googleの検索仕様変更について

1ヶ月ほど前から、Google検索の左メニューが無くなっちゃいましたねえ。

スクリプトを無効にしてると今でも出てくるんですよ。畳まれたメニューをクリックしなくても大体の選択肢が羅列されてるんですよ。フォールバックが機能してるのをほめる前に、スクリプトを使って使いにくくしてることにあきれる。

 @2013-11-02 正規表現パターンについて

auto_nobr処理の中の

/[^{}\[\]()*#"!'`=:|][、。」』!?!?)]+|[「『(]+[^{}\[\]()*#"!'`=:|]/u

というパターンは要するに

HikiDocで特別な意味を持たない1文字+行頭禁則文字、または、行末禁則文字+HikiDocで特別な意味を持たない1文字

という意味なんだけど、HikiDocで~という部分が重複(ちょうふく)してる。パターンの中にパターンを展開する方法を持たない JavaScriptでこの重複をどう取り除けるか?

文字列を組み合わせて RegExpのコンストラクタに渡すのは、文字列を Functionコンストラクタに渡すこと(evalと同じ)や、文字列から SQLを組み立てるのに似て好きではない。一面でわかりにくくなるのを承知で書き直すとこうなる。

/(?=([「『(]+)?([^{}\[\]()*#"!'`=:|])([、。」』!?!?)]+)?)(?:\1\2|\2\3)/u

前半の先読み部分は次のような意味の1~3個のキャプチャを含む。

(行末禁則文字)?(HikiDocで~)(行頭禁則文字)?

後半の、対象文字列と実際にマッチする部分はキャプチャを参照するだけの単純な二択。

(?:\1\2|\2\3)

かっこによるグループ化がないと先読みが最初の選択肢にしかかからないことに注意。| の結合は一番弱い。

キャプチャや分岐が増えてるがパターンの繰り返しはないし、トリッキーに見えてもひとたび構造がわかると簡単だと思うがどうだろう。

最終的にこの日記で使用するパターンは \3? を付け加えてこうなった。

/(?=([「『(]+)?([^{}\[\]()*#"!'`=:|])([、。」』!?!?)]+)?)(?:\1\2\3?|\2\3)/u

これで、かっこで1文字だけを囲った 『目』みたいなテキストがひとかたまりとして扱われて折り返されることがなくなる。

ひとつ心配なのは、?によって存在しないことにされたキャプチャを参照することが必ずマッチの失敗を意味するのかどうか。参照が空の文字列に展開されるなら必ず成功すると判断されてもしかたがない。たとえそれが NULLと空文字列の混同だとしてもありそうな話ではある。

 禁則処理がおかしい - Ronten

ここまで書いてから見つけたのがこれ。

 word-break: break-all;

から、

 word-break: normal;
 word-wrap: break-word;

に変更。元々、英数だけの文字がdivをはみ出す現象の防止の為にword-break: break-all; を指定していたが、それだと日本語の句読点が行頭に来てしまうっぽい。

word-break: normal;だけだと、英数だけの文字がはみ出すが、上のように二つ指定すると、日本語も英語も両方うまくいった。

ええええええ。くやしいから本文の方だけ CSSで対応する。

/* chiffon_leafgreen.css 183行目 */
div.day {
  word-break: break-all;
  word-wrap : break-word;
}

/* に追加して */
div.section {
  word-break: normal;
}

思い出した。word-break:break-allと word-break:normal(+word-wrap:break-word)での英字折り返しの違い。normalだとスペースでの折り返しが優先される結果、word-break:break-allしたかった目的

#箱の端から端まで文字を満たしたい。
#句読点など1文字程度の段差なら許容できる。
#やりすぎた字間調整(text-justify)は見苦しいからやらない。

が実現できないのだった。はみだすのも嫌だが右端がガタガタでカタマリ感がないのも嫌なのだった。これは word-wrap:break-wordでは補えない。

a ab abc abcd abcde abcdef abcdefg abcdefgh abcdefghi abcdefghij abcdefghijk abcdefghijkl abcdefghijklm abcdefghijklmn abcdefghijklmno abcdefghijklmnop abcdefghijklmnopq abcdefghijklmnopqr abcdefghijklmnopqrs abcdefghijklmnopqrst abcdefghijklmnopqrstu abcdefghijklmnopqrstuv abcdefghijklmnopqrstuvw abcdefghijklmnopqrstuvwx abcdefghijklmnopqrstuvwxy abcdefghijklmnopqrstuvwxyz

方針は変更せず、タイトルではきっちり右端での折り返しを優先し、本文では禁則処理を優先しよう。そもそもは word-break:break-allで禁則処理が行われるのが本当で、それこそが望みの結果なのに。

 @2013-11-04 朝令暮改

2.テキトーなテキスト置換」が予想以上にプロブレマティックだった。二重ブラケットで囲った URLの中の ? を NOBRタグで囲おうとして URLを破壊し XHTMLを破壊していた。既に書いたように footnoteプラグインに引数として渡す文章に NOBRタグを挿入する問題もある。タイトル欄で footnoteプラグインは使わない(セクション末尾に脚注を挿入しようとしても無理だから)が、同じような問題が続出するということだ。

というわけで、目途をつけておいた JavaScriptでの実装に切り替えた。

// Firefox(ver.15-23現在まで)が word-break:break-allで禁則処理を
// してくれないので NOBRタグで強制的に特定の折り返しを禁止する。
function auto_nobr(textNode)
{
	var create_nobr = function(text){
		var d = textNode.ownerDocument;
		var nobr = d.createElement("nobr");
		if (text) {
			nobr.appendChild(d.createTextNode(text));
		}
		return nobr;
	};
	var m, re = /[「『((]+.[))』」、。!!??]*|.[))』」、。!!??]+/;
	while (m = re.exec(textNode.nodeValue)) {
		/* assert 0 < match.length in case of infinite loop. */
		nobrText = textNode.splitText(m.index);
		textNode = nobrText.splitText(m[0].length);
		nobrText.parentNode.replaceChild(create_nobr(m[0]), nobrText);
	}
	return textNode; // the last Node of splitted textNodes.
}

function apply_auto_nobr_recursively(node)
{
	var except_tags = {
		"textarea":"タグの包含が許可されていないのか<nobr>で囲ったテキストが消えてしまう。",
		"nobr"    :"二重適用防止"
	};
	for (var child = node.firstChild; child; child = child.nextSibling){
		if ((child.tagName||"").toLowerCase() in except_tags) {
			// skip blacklist-ed elements.
		} else if (child.nodeType == Node.TEXT_NODE) {
			child = auto_nobr(child);
		} else if (child.firstChild) {
			apply_auto_nobr_recursively(child);
		}
	}
}

var h2 = document.getElementsByTagName("h2");
for (var i = 0; i < h2.length; ++i) {
	var root = h2[i].parentElement;
	if (! root || root.tagName.toLowerCase() != "div" || -1 == (" "+root.className+" ").indexOf(" day ")) {
		continue;
	}
	// now root is a div.day.
	apply_auto_nobr_recursively(root);
}

それから、

ひとつ心配なのは、?によって存在しないことにされたキャプチャを参照することが必ずマッチの失敗を意味するのかどうか。参照が空の文字列に展開されるなら必ず成功すると判断されてもしかたがない。たとえそれが NULLと空文字列の混同だとしてもありそうな話ではある。

と書いておいた懸念は現実のものだった。Firefox(23.0.1)でも IE9でも次のスクリプトは true(マッチした)を表示する。(ちなみに ruby1.8は nil(マッチ無し)を返す)

alert(/(?=(A)?)\1/.test("B"));

これと区別できた方が応用が広がるのに。

alert(/(?=(A?))\1/.test("B"));

 @2013-11-05 Safari(5.1.7)の微妙に異なる振る舞いとやり残し。

Safariでは <nobr>で囲った直前の文字までがひとかたまりになるのか、句点+2文字が次行に送られている。読みやすくはあるがなんでこうなる? Safariのユーザー・エージェント・スタイルシートに nobr{white-space:nowrap}がある。これを normalで上書きすると NOBRタグによる禁則が無効になる。空白による分かち書きを行わない日本語文章において white-space指定は空白だけを対象にしたものではなかったか。そういえば全角文字だけの文章と全半角混在の文章で Safariの処理が異なるという報告もあった。ま、<NOBR>でも white-space:nowrapでも、Safariが対象要素の外側にある1文字を余分に巻き込む理由にはならないとおもうけど。パターンから行頭禁則文字手前の1文字にマッチするドットを取り除くと Safariの挙動に対応した禁則処理が行えるが、そうすると Firefoxで行頭禁則が効かなくなる。おかしな Safariに合わせたりはしない。

既にタグで囲まれているテキストと交差するように <nobr>で囲うことはできない。ということは、<a>テキスト</a>とか <a>テキスト</a>みたいなよくあるマークアップ済みテキストに禁則処理を適用できないということだ(むしろこれに対処したくて Safariは直前の文字(要素)を巻き込んでるのか?という疑いもわいてきたが、直前の文字ならぬ直前の要素にくっついたりはしなかった。残念Safari残念)。これは後付けスクリプトでなんとかできるとは思えない。Firefoxの word-break:break-all完全対応待ち。

 @2013-11-06 両端揃え

1.箱の端から端まで文字を満たしたい。

を徹底するためにスタイル指定を足して完成。

.day .section, .day .title > * {
  text-align: justify;           /* 両端揃え */
  text-justify: inter-ideograph; /* 日本語両端揃え(IE向けに字間の調整方法を指定する。CSS3?) */
}

Firefox(23.0.1)と IE9は期待通り。Safari(5.1.7)は英字手前のスペースだけを使って両端揃えをしようとして英字混じり文が不自然に分断される。これは

3.やりすぎた字間調整(text-justify)は見苦しいからやらない。

に反するけど、Safariだけの問題なので知らない。時間が解決するでしょう。

<追記@2013-11-11>WebKit向けに面白いことをしてる人がいますね。「【目指せePub出版】Webkitでtext-align:justifyに挑戦する | 高橋文樹.com」俺だったら表示されない空白SPAN要素を挿入するんでなく Unicodeの幅0スペースのどれかを挿入するかな。結果が期待通りになるかは確かめる必要があるし、いずれにしろコピペがひどいことになりそうでコンテンツを改変しない方策が望まれるが。</追記>

<追記@2013-11-12>こういう指摘「iOS5のMobile Safariでは、日本語でも両端揃えができるようになりました | BALLOG」もあるが、提示された例から判断する限り、iOS4で行われていた「ィ」「ー」の禁則処理が iOS5で行われなくなった結果行末が揃っただけに見える。字間調整が行われた結果ではなく、むしろ退化してないか?それから、全角文字だけで文章を書くならスペースに頼らない字間調整が行われるとの報告もある>「webkit系ブラウザ(Chrome/Safari)で両端揃えはできないの?jQueryで検証 | 株式会社LIG」。</追記>

 @2013-11-07「CSS Text Module Level 3 (www.w3.org)

 text-justify

W3C Last Call Working Draft 10 October 2013

The following features are at risk and may be cut from the spec during its CR period if there are no (correct) implementations:

  • the ‘text-justify’ property

Value: auto | none | inter-word | distribute

先月出た文書。text-justify(とその他いくつか)が名指しで消滅の危機。後がない。inter-ideographに代えて distributeを指定しといた方が先々有効かも(属性自体が消えてなければ)。

 word-break, line-break

word-breakに line-breakを統合するのはなくなったんだろうか。line-breakの項目がある。うまくないとは思ってたので歓迎。でも句読点の禁則が line-breakの3つのレベル:strict-normal-looseのどこかわからない。

句点は U+3002;CL # IDEOGRAPHIC FULL STOP。かぎかっこ(開)は U+300C;OP # LEFT CORNER BRACKET。UAX #14 (www.unicode.org)では CLも OPも言及されてるけど、CSSの 5.1. Line Breaking Detailsではそれらを含まないクラスに限定して UAX #14を参照してるから CLも OPも位置づけが明確にならない。要は、line-break属性で具体的に挙げられてるのは最低限の要件であって、その他の線引きは User-Agentまかせということだった。書いてありました。

CSS distinguishes between three levels of strictness in the rules for text wrapping. The precise set of rules in effect for each level is up to the UA and should follow language conventions. However, this specification does require that:

将来組み版向けに細かい制御が必要だろうとも(別の場所で)言ってるが、当面満足できないということは、line-breakを緩くしておいて必要な部分に<nobr>というアプローチもなくはないのか、な?

KADOKAWAが EPUBの取り扱いに関する文書を公開していたな、とダウンロードしてみた。→「KADOKAWA-EPUB PORTAL

■3点リーダや2倍ダーシがつづく際の禁則の抑制について

3点リーダや2倍ダーシ、ナカグロなどは、前後の文字と分離禁止禁則が行われるため、あまり長いと直前の文字から改行されて、意図せぬ表示になることがある。そのようなときは、4文字以上連続する場合を目安に、以下のように「word-break-break-all」を該当箇所にのみ指定すること。未対応の RS もあるが、指定があって表示が崩れることはないので挿入しておく。

【参考】分離禁止される可能性の高い文字について

CSS3 の「line-break」の項には、行頭・行末で前後の文字との分離が禁止になる文字についての記載がある。詳細は「http://www.w3.org/TR/2012/WD-css3-text-20121113/」の「line-break」の項を参照。

実際には、まだ禁則処理については RS 次第であり、line-break の指定が反映されることも期待できないので、これらに依存するような記述は避ける。

基本は word-break:normalで、必要に応じて一部を word-break:break-allで包む(くるむ)と。どっかで見たような文書(だがちょっと古い)へのリンクもあるぞ。

この PDFでも見たし、数日前に「自炊ePubのためのあれこれ覚え書き - 道具眼日誌:古田-私的記録」でも見たのだけど、tcyというクラス名を。やっぱりアレをタテチュウヨコと読むんだってことだよね。検索したら例によって Wikipediaが一番。「読みは「たてちゅうよこ」であり、「たてなかよこ」ではない(JIS X 4051で規定)」。腹腔といい縦中横といい、ローカルルールでもこうと決めたからには正解!みたいなのってどうなん?

 @2013-11-09 ECMAScript(3rd&5th ed.)の、Rubyとは異なる、残念正規表現仕様

20131101p01.08の最後の方に書いてたこと。Firefoxでも IEでも Safariでも同じ挙動――?をキャプチャの中に付けても外に付けても trueを返すこと――を示すので ECMAScriptとして規定されてるんだろうと探してみた。以下該当部引用。太字強調は失われたり付け足したりしてます。

The form (?! Disjunction ) specifies a zero-width negative lookahead. In order for it to succeed, the pattern inside Disjunction must fail to match at the current position. The current position is not advanced before matching the sequel. Disjunction can contain capturing parentheses, but backreferences to them only make sense from within Disjunction itself. Backreferences to these capturing parentheses from elsewhere in the pattern always return undefined because the negative lookahead must fail for the pattern to succeed. For example,

 /(.*?)a(?!(a+)b\2c)\2(.*)/.exec("baaabaac")

looks for an a not immediately followed by some positive number n of a's, a b, another n a's (specified by the first \2) and a c. The second \2 is outside the negative lookahead, so it matches against undefined and therefore always succeeds. The whole expression returns the array:

 ["baaabaac", "ba", undefined, "abaac"]

こちらでも "always succeeds"と書いてある。

Informative comments: An escape sequence of the form \ followed by a nonzero decimal number n matches the result of the nth set of capturing parentheses (see 15.10.2.11). It is an error if the regularexpression has fewer than n capturing parentheses. If the regular expression has n or more capturing parentheses but the nth one is undefined because it hasn't captured anything, then the backreference always succeeds.

残念だ。thereforeとかあっさり書いちまいやがって。それはまったく自明ではないぞ。

空パターン(※JavaScriptは Perlと違ってダブルスラッシュがコメントになってしまうので作るのに小細工が必要)が必ず成功するのはわかる。でもそれに対応するのはキャプチャが空文字列を保存していてそれを後から(その文字列そのものにマッチする)パターンとして参照した場合であって、参照すべきキャプチャ・参照すべきパターンが存在しない(=undefinedである。空文字列ではない)ときは必ず失敗して欲しかった。理由はすでに書いたように、この二つを区別できなくなるのが困るからだ。

/(?=(A)?)\1/.test("B"))
/(?=(A?))\1/.test("B"))

もっと実際的な不利益は 20131101p01.06に書いた書き換えが通用しないことだ。これって C++11にも影響する(してる)んでしょ? *SIGH*

 @2013-11-12 Safari(Win版5.1.7)で満遍なく字間調整

Safariでは全半角混在文章に対しては自動的に text-justify:inter-wordに相当する字間調整が選択されるらしく、日本語文章にわずかに含まれる空白が過剰に引き延ばされた見苦しい表示になってしまうことをもう書いた>20131101p01.10

実は word-break:break-allを指定しているとほとんど任意の場所で折り返しができるので(Safariの場合禁則処理も行わないので)わずかなスペースに調整のしわ寄せがいっても問題にならない(といっても切り落としたような右端のラインは得られないが)。でも禁則処理を施した場合 Safariはなぜか3文字を次行に送ってしまうので(20131101p01.09)、全角1文字分以上の空白が文章を分断してしまうのが問題になる。

どうするか。これに対処して「<span style="width:0; font-size:0; overflow:hidden"> </span>」を挿入した人が唯一見つけられる。visibility:hiddenを指定した空白なら挿入してもコピペに影響しないのを Safariと IEと Firefoxで確認した(※)ので自分はこうした。※.textContentには影響するかも。.innerTextには影響しないかも。

// Windows版Safari(5.1.7)の字間調整は全半角混在文章でスペースに対してしか働かず文が不自然に分断されてしまう。
// 不可視の空白を挿入することでさらなる字間調整ポイントを Safariに対して教える。
// 相当うざい結果になる(なにせほぼ任意の2文字の間に SPAN要素が挿入される)ので、Safariでだけ実行するように。
function textfunc2_safari_whitespace_distribution_inter_ideograph(textNode)
{
	var _xp, new_xp = function() { // XP = expansion point
		if (! _xp) {
			var d = textNode.ownerDocument;
			_xp = d.createElement("span");
			_xp.style.fontSize      =
			_xp.style.letterSpacing = "0px";
			_xp.style.visibility    = "hidden";
			_xp.appendChild(d.createTextNode(" "));
		}
		return _xp.cloneNode(true);
	};
	var p = textNode.parentNode;
	var m, re = /\S(?=\S)/;
	while (m = re.exec(textNode.nodeValue)) {
		/* assert 0 < match.length in case of infinite loop. */
		textNode = textNode.splitText(m.index + m[0].length);
		p.insertBefore(new_xp(), textNode);
	}
	// ついでに、Safariが <nobr>の直前直後の2文字を接着して字間調整も折り返しも行わないのを矯正する。
	try { if (textNode.nextSibling.tagName.toLowerCase() == "nobr") {
		p.insertBefore(new_xp(), textNode.nextSibling);
	} } catch(e) {}
	return textNode; // the last Node of splitted textNodes.
}

スクリプトの全体はページのソースを参照のこと。結構重い処理なのでページの表示後しばらくして文字が移動するのが見えてしまうかも。でも Safariだけの問題なので(略)。

Safariで表示したこのページでテキストを選択してみると、文字と文字の間に白い縦線が入ってるのが見えるんじゃないだろうか。それが visibility:hiddenなスペースだと思われる。行末から行頭に向かって1文字1文字の間に1ピクセルの字間を配ってるかんじ。行頭に着いても分配する字間が余ってる場合は2ピクセル目をまた行末から配る、と。たまに2文字くっついたままになってるのは禁則処理のために挿入した NOBR(開き)タグの直前と直後の文字。Safariはなんでこの2文字を接着してしまって字間調整も折り返しも行わないんだろう。<NOBR>の直前に字間調整用の SPANを挿入するとめでたく接着が解けたが、SPAN挿入の二重適用を避けつつ NOBRに限ったアドホックな処理を追加せずに済む方法は……。<追記@2013-11-14>上のスクリプトは Safariを特定したものなのだし、NOBR要素狙い打ちで字間調整用の SPANを注入することにした。 </追記>

<追記@2013-11-21>見えない、クリップボードにコピーされないとはいえ、スペースを挿入してるのは間違いないので、スタイルシートを切ると空白入りのテキストが表示されるし、ページ内検索も空白を入れないとヒットしない。タグの意味的にもこれらの副作用を避ける目的でも WBRタグを使いたいんだけど、字間を挿入する効果がないんよね。</追記>

<追記@2013-11-25>分離禁止と分割禁止の2つの概念。分離禁止は分割禁止を含む、より強い拘束。―と―の間に改行(折り返し)はもちろん空白も挿入してはいけないというのが分離禁止の例。WBRは分割(改行(折り返し)の挿入)を許可する要素だから自動的に字間の挿入も許可しているはず。期待して待っていていいかな?</追記>

 W3C日本語組版ノートとCSS3 - JAGAT

WebKit以外は禁則に対応している。CSSには禁則ルールを「通常」から「厳しくする」と3段階の設定がある。両端揃えについては実装依存、「どういう実装をしてもいい。ただ日本語ではJLREQを参照するといい」としている。EPUB仕様も同様である。

Firefoxで word-break:break-allを指定すると禁則処理が行われなくなるのが問題。(対応してるが適用外)

Safariが全半角混在文章に対して日本語向けの両端揃えを選ばないのが問題。(xml:lang="ja-JP"なのに JLREQを参照すべき場面だと思われていない)

 public-html-ig-jp@w3.org Mail Archives

 @2013-11-14 PS3・泥ケー・Opera

PS3のインターネットブラウザも今では WebKitベースだとか(実際そう名乗っていた。診断くん)。なかなかきれいなフォントレンダリングで(でもズームするともやっとする)、禁則処理、字間調整の方法まで含めて Safariによく似た結果。スクリプトを有効にすることでこのページも期待通りの表示になった。

もうひとつブラウザ。ドロケー(URBANO L01)で表示すると中心線近くでせせこましく折り返したり折り返さなかったりする現象(※)が見られたんだけど、google/京セラのせいにして放置していいのか、DPIの高さにこのページが対応できていなかったりするのか。

※スクリプト実行中は画面に見えてる3日分のタイトルがせせこましく折り返してた。実行後は先頭の1日だけがせせこましく折り返したままだった。

また、ナビゲーションリンクをクリックすると再現性なく HTTPステータスコード 505 HTTP Version not supportedが返ってくることがわずかな時間に何度もあった。やっぱりブラウザ(GCかどうか知らないが)が信用ならんのかな。LTE経由やったから auがいらん茶々入れてたんかな。

Opera17も試してみたんだけど、フォントの設定がないのんな。開発者プレビューにはあったみたいだけどその場所にもなかった。閲覧者が読みやすいと感じるフォントは閲覧者が知っている(そして設定している)だろうと思って HTML/CSSではフォントファミリを指定してないんだけど、その結果がMS Pゴシックのビットマップフォント。3段階ほど文字を大きくするとベクタデータに切り替わるのか見られる表示になるけど、MS Pゴシックをあえて指定してすら文字が美しい Safariとは雲泥の差。見るに堪えない。

 @2013-11-15 テーマ画像。DPI

URBANOのブラウザは Google Chromeではなかった。バージョンが4.XXXだということがかろうじてわかったがまったく素性が知れない。これは不自由ゆえか不慣れゆえか。

スマホの Google Chromeで表示してみると PCで見るより一行の文字数が少ない。固定サイズの背景画像を基準にして横幅をピクセルで指定しているために、ひと文字ひと文字をより多くのピクセルを使って精細に描画する(高DPIってこと)スマホでは文字数が減るんだろう。こういうのってベクタ画像をスケーリングしたり、左上角+辺の繰り返し+右上角の画像3枚構成で横幅を可変にしたりするのだろうか。そういうことができるのかどうかも知らないけど。それかこういうときにこそ画像の DPI値(JPEGにそういう値がなかった?)が役目を果たしてブラウザが勝手にリサイズしてくれたりするんじゃないんだろうか。DPIとピクセル数で現実世界の長さがわかるわけで、それを共通の尺度にしてブラウザが再度スマホにおいて画像を表示するのに使うべきピクセル数を求められるはずでは?でも現実世界の長さを共通の尺度にするとスマホの画面サイズは PCのモニタと比べて小さすぎるのか。でもそれはスマホが DPI設定をそれなりの高さに抑えておいてズームを駆使すればいいだけのことじゃないか。Windowsの DPI設定がモニタのピクセル密度を全然反映していないのだから何を考えても机上の空論か。そもそも、日記のテーマ(chiffon_leafgreen)で使われてる画像が DPI値をセットできない GIF(ジフ)だった。画像にだけ DPIが指定してあっても、というのもある。画像のピクセル数を基準にして div.day要素の横幅が 510pxと指定してあるから、こちらも、画像が何ピクセルを使って表示されるのかに合わせて変換されないとつじつまが合わない。そのへんはまた CSSの px単位が論理的/相対的な単位であるとかが関係してきて……。これかな?「‘px’ pixels; 1px is equal to 1/96th of 1in」DPIを固定して定義されてるから pxと現実世界の長さがいつでも1:1対応する、その結果 CSSの 1pxとモニタの画素とは1:1対応しない、と。テーマ(※ライセンスはGPL)で使われてる画像を 96×96DPI指定した PNG(pingと同じピンではないの?)に変換するだけで解決するといいなあ(儚い希望)。

 PCでなんちゃってRetina。Firefoxと Safariにつく差。(@2013-11-22)

Windowsの DPI設定を2倍(192dpi)にするとなんちゃってRetina(300overにはまだまだ届かない)を体験できる。贅沢なピクセルの使い方の代償は画面の仮想的な広がりが面積比4分の1に狭くなること。24.1インチ・WUXGA(1920×1200)モニタは今となってはでかいばっかりで粗いもんだ。4Kなんかより QUXGA Wide(3840×2400)が 27インチまでで出ないかなー。

Firefoxは高DPI設定に応じて滑らかな、質感すら感じさせる文字を表示する。Safariは、自分の管理外なのであろうウィンドウのタイトルバーの文字だけがきれいで、メニューから文字から他の全てが滲んでぼやけている(もちろんズームはしていない)。Safariの普段の美しさは最適化(切り捨て)の結果なのだった。<< .exeのプロパティで「高 DPI設定では画面のスケーリングを無効にする」にチェックを入れるとまた結果が違うかも。(@2015-11-24)

仮想的に DPIを上げたからといってスマホの Google Chromeで見たようにこの日記の1行が16文字になったりはしないんだよね。ズームでなく文字サイズだけを拡大すると行あたりの文字数が減っていくから、これかな? 対策は、画面が小さくて文字が読めないときは文字サイズそのままでズームしろ、と。<<改めてスマホのGCで確認した。既に述べた状態は文字サイズ100%でのもの。60%で PCより少し1行の文字が多いくらいになる。それでタップしてズームすると左右の余白がほぼゼロになって精細で美しい文字の文章が難なく読める。物理的には PCモニタより小さいんだろうけど、カンマとピリオドを見分けるのにも苦労しない。文字サイズ100%で PCと同じレイアウトになって欲しいんだけど……。

 追記@2014-02-16 ドロケーのブラウザで「60%で PCより少し1行の文字が多いくらいになる」ことに関係するかもしれない話。

Androidの密度非依存ピクセル「dp」

Density-independent Pixel / dipともいう

このdpという単位は、Androidアプリを作る際に使われる単位で、Androidの開発者向けウェブサイトではDevice-independent Pixel(密度非依存ピクセル)という単位を定義しています。これは160dpiのピクセル密度を持つディスプレイで表示される1pxを1dpとしたもので、たとえば、320dpiの場合1dp = 2pxに、480dpiの場合1dp = 3pxになります。

Androidでは Windowsや CSS3に準拠した 96dpiではなく、160dpiに固定して論理ピクセル「dp」を定義したと。160:96 = 1:0.6だから、文字の描画に使用する画素数を 60%(縦)×60%(横)に減らしたときに PCとレイアウトが同じになることに納得できる。

 訂正@2014-03-17 CSS3の 1pxが1対1対応するのは CSS3の 1inであった。

そして、1inが現実の 1インチに対応するパターンの他に、1pxが "reference pixel"に結びつけられるパターンが認められている。Androidの Google Chromeでは reference pixelを媒介にして CSS3 px と dpが一致してるってことなのかも。

 @2013-11-19「HTMLで文字詰めするタイポグラフィー用JS | fladdict

これは字詰め。禁則とも字間調整(広げる方)ともかち合うので、これを適用しようとするとタグの交差に対処する方法を真剣に考えないといけない。でもやりたいなあ。読点とかぎ括弧、中黒は全角だと間延びしすぎだし、だからって半角文字を使うのも違う気がするし。ああでも、フォントを知らないと盲撃ち(めくらうち)になってしまうのか……。Webフォントが救世主なんだろうけど、文字サイズを決めないのと同様にフォントもこちらで決めたくはないのだな。といいつつ……「リニューアルした(www.hsbt.org)」 www.ruby-lang.org/en/ の文字が見なれないきれいさだなと思ってインスペクトすると Noto Sunsという Googleが提供する(?)フォントが使われていたりして結局は受けとり手が満足できるかどうかが問題なのだった。

<link href='https://fonts.googleapis.com/css?family=Noto+Sans:400,700,400italic,700italic&amp;subset=latin,cyrillic,greek,vietnamese' rel='stylesheet' type='text/css'>

 @2013-11-20 字詰め。

やった。ほとんど昨日貼ったリンク先のスクリプト(FLAutoKerning.js)の移植。行頭の約物の処理だけ省いた。折り返しでの天付きをせずにそれだけするのもどうかと思って。いい加減長いので詳細はソースの textfunc4_jizumeへ。

 問題点
  • さらに重くなった。重たい字間処理と重なる WebKit系が悲惨。
  • MS Pゴシックだと詰まりすぎる。つまり、ユーザーがフォントを指定できない Opera17で詰まりすぎる。今日のこの日記を長々と書き継いでいるうちにリリースされた Opera18ではフォントの指定ができました。めでたいOperaすばらしい。(もちろん皮肉)
  • Safariでは一部の記号の一部(※)が元々詰まっていて、そこに字詰め処理が入ると詰まりすぎる。※CODEタグによってプロポーショナルと等幅が切り替わる所だった。これは特殊だし難しいケースだ。(ある程度はタグを乗り越えて)隣の文字を見て詰める量を決めるけど、隣の文字からフォントが変わってるとは思わないもんなあ。タグを乗り越える理由は禁則のための NOBRタグを無視したいからなので、要素の終端で打ち切る選択肢はない。
  • 適用先の要素に 0以外の letter-spacingが指定されていた場合は、それに加算した letter-spacingを設定すべきではないか?

まとめると、フォントにメイリオを指定した Firefox(25.0)、Internet Explorer(9)以外お断り仕様になってしまいました。

Firefox25での表示(ベンチマーク)> 20131101p01.pdf(610KiB)

スクリーンショット追加@2013-12-05

  • Firefox19 on Mountain Lionのスクリーンショット(5.2MiB)>macml_firefox_19.0.png
    • Fx23 on Vistaとの違い。

      1. text-align:justifyにも関わらず右端が揃っていない(その割に文字間が調整されたような疎密の波がある)。
      2. PRE内の文字が font-size:100%指定にも関わらず 90%ほどになってる(IEとか他のブラウザでなら見たことあるけど)。
  • iPhone5でのスクリーンショット(5.0MiB)>ios_iPhone-5_6.0_landscape.jpg
    • Fx23 on Vistaとの違い。

      1. PRE内の文字が font-size:100%指定にも関わらず 90%ほどになってる。
      2. 他は完璧、というか、感覚的な読みやすさで凌駕してるのでは?

プロポーショナルフォントにはもともと余分な空白はないし(※)、等幅フォントが指定してあるなら等幅であることを尊重すべきだし、実質、ほとんど等幅のプロポーショナルフォントであるメイリオでしか見るべき効果がない。

※参考にすること(メトリクスカーニングとオプティカルカーニング)@2013-12-05「デザイナーは文字詰めに命をかけよう ~和文と欧文のフォーマットの違いから考える~ | 企業ソーシャルメディア運用・ソーシャルデータ分析・YouTubeチャンネル運用の株式会社アクトゼロ

SVGの方に measureTextとか text-renderingがあるらしい(DOMインスペクタを覗いていたら見つかった)。CSS Fonts Module Level 3にも font-kerningとか font-variant-east-asian:proportional-width(胡散臭い……)とかあった。追い追いどうにかなるでしょう。


といいつつ、せっかく書いたコードを捨てきれないのだなあ。簡易に等幅とプロポーショナルフォントを見分けて、プロポーショナルの場合は字詰めの対象を絞ることにした。メイリオ、MS Pゴシック、SH G30-M、SH G30-Pでまあまあ破綻せずに表示できてると思う。テストするときは、フォントを切り替えた後にリロードしないとフォントにあった処理が行われないことに注意。

長いけど貼りますよ。バージョン管理されてませんからログを残さないとね。

// 全角の約物・句読点などの連続すると過剰になる余白を詰める。プロポーショナルフォントはもともと
// 余白が調整されているから、主にメイリオと等幅フォントが対象になる。
// textfunc2/textfunc3で字間調整のための見えないスペースを挿入した後だと全く
// 効果がないのでその前に実行すること。
//
// アイディアと実装とカーニングペアの定義の下敷きはこちら。
// http://fladdict.net/blog/2011/02/auto-kerning.html
function textfunc4_jizume(textNode)
{
	var is_proportional = function() {// ¿等幅フォントもしくはほぼ等幅のメイリオではない? フォントごとにカーニングペアのひとつひとつについてテストするのもいいかもね。
		try {
			var canvas = textNode.ownerDocument.createElement("canvas");
			textNode.parentNode.appendChild(canvas);
			var ctx = canvas.getContext("2d");
			ctx.font = "100% "+getComputedStyle(textNode.parentElement||textNode.parentNode).getPropertyValue("font-family"); // If returned value is the used value, appendChild may be removed.
			//console.log(""+ctx.measureText("」「").width+" "+ctx.measureText("@@").width);
			return ctx.measureText("」「").width < ctx.measureText("@@").width;
		} catch(e) {
			return true; /* 字詰めをやりすぎないように無難な方を返す。*/
		} finally {
			if (canvas) {
				textNode.parentNode.removeChild(canvas);
			}
		}
	};
	var ki = is_proportional() ? textfunc4_jizume.kerning_info_for_all : textfunc4_jizume.kerning_info_for_monospace;
	var enclose_in_letterspacing_span = function(textNode, em) {
		var span = textNode.ownerDocument.createElement("span");
		span.style.letterSpacing = ""+em+"em";
		span.appendChild(textNode.parentNode.replaceChild(span, textNode));
	};
	var next_element_char_or_empty = function(textNode) {
		var skip_blankTextNode = function(node) {
			for (;node && node.nodeType == Node.TEXT_NODE && /^\s*$/.test(node.nodeValue); node = node.nextSibling) {
				// empty loop-body;
			}
			return node;
		};
		var nextNode = skip_blankTextNode(textNode.nextSibling) || skip_blankTextNode(textNode.parentNode.nextSibling);
		for (; nextNode && nextNode.nodeType != Node.TEXT_NODE; nextNode = skip_blankTextNode(nextNode.firstChild)) {
			// empty loop-body.
		}
		return (nextNode && nextNode.nodeType == Node.TEXT_NODE) ? nextNode.nodeValue.charAt(0) : "";
		// あんまり先の方の文字を読んで letter-spacingを設定しても意味ないかもね。
		// 何もしないか、むしろ先の文字に負の marginなりを設定したほうが意味があるかも。
	};
	for (var i = 0; i < textNode.nodeValue.length;) {
		var     char = textNode.nodeValue.charAt(i);
		var nextChar = textNode.nodeValue.charAt(i+1) || next_element_char_or_empty(textNode) || "*";
		var space = (ki[char+nextChar] || (ki[char+"*"]||0)+(ki["*"+nextChar]||0));
		if (space) {
			var charNode = textNode.splitText(i);
			    textNode = charNode.splitText(1);
			enclose_in_letterspacing_span(charNode, space);
			i = 0;
		} else {
			i += 1;
		}
	}
	return textNode; // the last Node of splitted textNodes.
}
/*
	カーニングペアの定義
	単位はem。 -0.5(em) でボックス0.5個分詰まる。
	"*く" と定義した場合、"あく"、"いく"、"うく"、というように、全ての"○く"の組み合わせにカーニングが設定される。
	"あく" と定義をした場合、 "あく"という文字のペアのみにカーニングが設定される。
	ワイルドカードペアと直接指定のペアが衝突する場合、直接指定のペアが優先される。
*/
textfunc4_jizume.kerning_info_for_all = { // プロポーショナルフォントでも削られていない(であろう)余白に関して設定する。
// 直接指定のカーニングペア
"か。":-0.05,
"が。":-0.10,
"け。":-0.10,
"げ。":-0.15,
"す。":-0.15,
"ず。":-0.15,
"み。":-0.05,
"。」":-0.25
};

textfunc4_jizume.kerning_info_for_monospace = { // プロポーショナルフォントでは削られている(ことが多い)余白に関して設定する。
// 前後の文字をワイルドカード指定した汎用のカーニングペア
"*ト":-0.10,
"*ド":-0.10,
"*「":-0.25,
"」*":-0.25,
"*(":-0.25,
")*":-0.25,
"、*":-0.25,
"。*":-0.25,
"・*":-0.25,
"*・":-0.25,
"*:":-0.25,
":*":-0.25,

// 直接指定のカーニングペア
"」「":-0.75,
"」。":-0.50,
"」、":-0.25,
"、「":-0.75,
"。「":-0.75,
"、『":-0.75,
"。『":-0.75,
"、(":-0.75,
"。(":-0.75
};
for (var k in textfunc4_jizume.kerning_info_for_all) {
	textfunc4_jizume.kerning_info_for_monospace[k] = textfunc4_jizume.kerning_info_for_all[k];
}
 getComputedStyle(textNode.parentElement||textNode.parentNode)に関して。

IE9がテキストノードに parentElementを定義していないらしい。まさにこれですよ。

一部のブラウザーでは、parentElement プロパティは Element ノードでのみ定義されており、特にテキストノードに対して定義されていない場合がある点に注意して下さい。

で、parentNodeでなく parentElementを呼んでるのは getComputedStyleの第一引数が Elementでないといけないと書いてあるからで

The first argument must be an Element, not a Node (as in a #text Node).

IEの場合だけ parentNodeで誤魔化します。parentである Nodeはいつでも Elementであるという仮定は、確かめてないけど、ほぼ正しいでしょう。

 @2013-11-25「Mozilla, Opera でも日本語を均等割付(deztec.jp)

知ってるドメインだけど 11年前の記事とあって読んだことはなかった。2番目に見つかった、空白挿入で均等割り付けをやった人。ただし対象は WebKitでなく Operaと Mozilla。今の Firefox(1.5から?)は日本語文字間に調整用の字間を挿入してくれる。英字間にまったく配分しないのが行内の疎密の偏りとなって時々目立つというのが現在の問題。

 スピードアップ

is_proportionalが全体の50数%の時間を使っていたので結果をメモすることにした。読み込み完了直前に一回だけ実行する処理だから、スタイルやフォントの変更について行けなくても構わないよね。関数の消費時間の40%が削減できたので全体では2割の削減。

 @2013-11-27 defer, async. ねえ waitは?

スピードアップの一環。SCRIPTタグに deferやら asyncを付けようと思うたびに未定義の関数を呼び出すエラーに悩まされて結局諦める。こういう非同期・遅延属性を付けたいのは完全自動の後付けスクリプトか共有ライブラリのどちらかだ。完全自動タイプは問題ない。deferと asnycを両方付けて非同期・最速で実行してもらえればいい。完全自動だけどイベントドリブンではないなら、対象要素の登場後に配置するなり deferだけを付けるなりすればいい(※あれもこれも deferにすると一部の非常に遅い外部スクリプトが以降の deferスクリプトの実行をブロックしかねない?)。ライブラリの場合は、ライブラリ関数を呼び出すコードがどこかにある。それが独立した .jsファイルであればライブラリ共々 defer属性だけを付けると順番に実行されることが期待される(HTML5に限って?)。でもページやその内容に依存して呼び出しコードが変化する場合は?ページの一部として HTML内に埋め込まれている場合は? src属性がない SCRIPTタグには deferも asyncも付けてはいけないという。

ライブラリは非同期・最速で読み込んでほしい。それを待って実行したい HTML埋め込みスクリプトはどうやって待つ?

DOMContentLoadedイベントの発火は Blink Opera(18)で非同期スクリプトの読み込み・実行より早いことがある。仕様がどうあれ、画像やスタイルシートの読み込みがまだかもしれないのと同様、スクリプトの読み込みが完了しているとは期待できないのが現状。

答えは jQueryのソースとかこのへんに(丸投げた)>「<script>タグのasync属性を使わずに非同期でJavaScriptを読み込む方法 | さくらたんどっとびーず」「script.onloadを使ってJavaScriptがロードされた時の処理を記述する | さくらたんどっとびーず」。

やってらんない。非常に保守的で asyncや deferを付けすぎても全然問題を起こさない(でも読み込みは並列でやってる) Firefoxに限って script.onloadだとか document.currentScript、document.onafterscriptexecuteといったお役立ち手段が充実しているという。やってらんない。

<追記@2013-12-02>Promiseという名前/概念を仕入れた。asyncな SCRIPT要素が Promiseを返してくれれば、Promise.every(これと、これと、このスクリプト).then(function(scripts){ 指定したスクリプトに依存した処理 })とか書けそう。scriptsが何の配列なのかという疑問は残るが。</追記>


一番遅いのはスクリプトによるブロック(HTML解釈DOM構築中断。スタイルシート読み込み完了待ち(ただし Operaはインチキしてたらしい))ではなく、tDiaryのレスポンスなんだけどね。静的ファイル書き出しには抗いがたい魅力がある。.htaccessにはこういう記述があるんだけど

# Rewrite rule1
#  shows static html, if exists.
RewriteCond /home/vvvvvv/www/cgi_file/ds14050/diary/snap/$1.html -f
#RewriteCond %{REQUEST_METHOD} =GET [OR]
RewriteCond %{REQUEST_METHOD} =HEAD
RewriteCond %{HTTP:Cache-Control} !=no-cache [nocase]
RewriteRule ^([0-9]{8})\.html$ /cgi_file/ds14050/diary/snap/$1.html [L]

なんでやめた(GETの場合を除外した)んだっけか。<テーマがまだら模様になるからだ。コメントやらなにやらで更新のあった日は、隣の書きっぱなしの日とは異なる更新日時点のテーマをまとって新たに書き出される。常に現在のテーマで表示されるも良し、12月に書いた日記がいつまでも12月のテーマで表示されるも良し。でもモザイクはいただけない。SSIででも対処したらいいんだけど1行コメントアウトする方が簡単だったとか、単体で完結しない .htmlファイルを書き出すことへの抵抗だとか(※SSIがサーバーを選ぶことは気にしない)。

Latestモードや Monthモードでレスポンスを一日一日順番に出力していくとかでもブラウザの助けになるんではないかと思うけど、どうだろうか。思い立ったが吉日でちょっとやってみよう。<<ETagとか Content-Lengthヘッダをどうするんだって。

ETagは必ずしもレスポンスボディから生成する必要はなかった>「ETagをどう生成するか - 岩本隆史の日記帳

Content-Lengthはどうか。

  • 転送コーディングが施されている場合、Content-Length ヘッダは送られてはならないし、仮に送られてもこれを無視しなければならない
  • 転送コーディングが施されていない場合、Content-Length ヘッダは送られなければならないが、これはメッセージボディ中のオクテット数と正確に一致しなければならない
  • Content-Length ヘッダが送られない場合は、接続の終了を持ってエンティティボディの終末を判断する事ができるが、リクエストボディにおいてこの手法は推奨されないし、サーバは 411 レスポンスを持って明示的に拒否する事ができる

レスポンスの場合は接続を閉じることで内容・転送の終了を告げることができるので必ずしも Content-Lengthが必要ではない、のかな?

body = tdiary.eval_rhtml

tdiary.write_to($stdout)

みたいな形にする方向でやってみる。


いろいろのバッファの他に HTTPDが間に入って、実際のところどうなってるんでしょ。

 @2013-12-06 字間調整・改。そして、独立分離している文字列処理を実行時に効率的に融合する方法。

WBRタグでは効果を得られない。不可視の空白を挿入する方法ではスタイルシートを切ったときに空白が見える。またページ内検索でも、単語の文字と文字の間に空白を挿入しないと見つけられなくなる。第3の方法は CSS content属性を使うこと。スタイルシートを切ると空白ごと消えるし、ページ内検索でも挿入された空白は無視される。また、どのブラウザでも字間挿入効果があることは確認済み。

実装の問題は、要素の挿入ではなく要素で文字を包むことになるのを、他の文字列処理(禁則, 字詰め)が知って対処する必要があること。実際のところそれはもう済んでいて、さらに以前は禁則と字詰めと字間調整がバッティングする場面(「A」「B」など)で字詰めが効いていなかったのだが、これも解決した。解決していないのは、各々の文字列処理がテキストをぶった切るのだが、2番目3番目の処理は細切れのテキストノードを相手にしなければならないがために、メソッドの呼び出しとメソッドの固定された初期処理とタグを乗り越えて文字を先読みすることの回数が増え、効率が著しく低下すること。2段3段の文字列処理を加えるなら切断は一度で済ませたい。

対象が共通。操作も若干のバリエーションはあるものの大枠は同じ。対象と操作を選び組み合わせるところが固有。固有部分を分けながら実行時に効率的に融合させる方法。マニピュレータ付きのコンテクストを引き回す。操作を一旦ため込んで、マージしてから実行に移す。

  • style_to_range(begin, end, style) // letter-spacingなど
  • unbreakable_style_to_range(begin, end, style) // white-space:nowrapなど
  • unbreakable_tag_to_range(begin, end, tag) //NOBRで囲むのを white-space:nowrapに替えるなら今のところ不要。
  • 要素挿入メソッド。幅のないrangeに対する unbreakable_xxxとして扱えるはず。でも字間調整のためにはもう不要。

あとで。

 @2013-12-08 アップロードした>textfunc.ver2.js@2015-03-04

いいかげん HTMLに埋め込むには長々しいので独立した .jsファイルに。textfuncX_*はより簡素になったが、それを走らせる基礎を新しく書いた。過去の記録>textfunc.ver1.js。オーバーヘッドは ver2の方が大きい。が、多段のテキスト処理を前提にするなら組み合わせによる所要時間の増大は ver2の方が抑えられている、と思う。誤算がひとつ。IE9は visibility:hiddenな content属性値をクリップボードにコピーしてしまう。それでも、スタイルシートOFFで挿入した空白が見えてしまうことやページ内検索がほとんど不可能になってしまうことに比べればずっとマシではある。

 TODO: 高速なフォント判別。ブラウザ判別によらないテキストレイアウトの妥当性検証。混植?

フォント判別(is_proportional)が一番コスト高なのは変わらず。テキストレイアウト(禁則・字間・字詰め)が満足できるものかどうかをブラウザ判別に頼らず機能検出によって判断する(※jQuery.browserがなくなり代わりに現れた jQuery.supportの使用が推奨されるらしいが「jQuery.supportでのブラウザ判別」という面白記事の登場は避けられない世の流れ(惰性)か。ブラウザを判別したいその理由、ブラウザ間の差異を検出するロジックを jQuery.supportに追加するのが本道だろうに)方法は、(より軽量な)フォント判別にもつながっているように思う。.offsetWidthとかね。結局のところ初めて見つけた .measureTextを使ってみたかったというのが is_proportionalを構成する理由の8割だから、中身を置き換えると存在理由までが消えてしまいかねないのだが。

<追記@2013-12-22>is_proportionalが遅いのは先送りされていたブラウザの処理が getComputedStyleをきっかけにして実行に移されるから。is_proportionalから getComputedStyleを取り除くと処理時間のほぼすべてが is_inline_element(これも getComputedStyleを呼んでいる)へと移る。DOM操作のコストが getComputedStyleをきっかけにして噴出してるとみるのが正しい。例えば関連するスタイル(wbrクラス)の定義をスクリプト実行の後ろに移すことでわずかに改善する。</追記>


混植だって。「「Webデザイナーのためのタイポグラフィと文字組版(Reloaded)」鷹野 雅弘(スイッチ)」やろうと思えば正規表現で文字を拾い出して最小限の SPAN要素でフォント指定ができますよ。他の処理を有効にしたままで。……と思ったらここに衝突の種が。

フォントを変更する処理とフォントに依存する字詰めを並行して行うことはできない。一段目としてフォントを変更する処理を単独で(テキストノードの分割を最小限に抑えながら)行い、二段目にその他の処理をまとめて行うワークアラウンドが必要になるだろう。


上の方でリンクした MLで名前を見た人達がいたので。おもしろい。

 @2013-12-11 .offsetWidthによるボトルネック(is_proportional)の解消&テキストレイアウトの検証……のはずだったけど

.offsetWidthを使った is_proportionalは Firefox23で .measureTextの倍の 1.4秒かかった。Operaでも .measureTextの消費時間が2桁ミリ秒のところ .offsetWidthは4桁ミリ秒を消費する。意外にも canvasを作って .measureTextする方が早かった。

is_proportionalのテスト過程で気が付いたんだけど、Opera18と Safari5.1.7で等幅フォントとして欧文フォント Consolasを指定すると、日本語用のフォールバックが、FontLinkではメイリオを指定してるにも関わらず MS Pゴシックになる。font-family:monospaceを指定してるのに台無しだよ!

.offsetWidth/.offsetHeightを使ったテキストレイアウトの検証は function TextLayoutInfo() として textfunc.ver2.js@2015-03-04に追加した。


結果が出てから調べる奴。

Text measurement: element vs. canvas · jsPerf

Firefox23での実行結果
Measure text in span is 78% slower than Canvas measureText().
Internet Explorer9での実行結果
Measure text in span is 95% slower than Canvas measureText().
Opera18での実行結果
Measure text in span is 93% slower than Canvas measureText().
Safari5.1.7での実行結果
Measure text in span is 91% slower than Canvas measureText().

圧倒的じゃあないか。


まだ高速化をあきらめない。is_proportionalの結果を要素にメモするのでなく font-familyの値をキーにしたメモをどこかに作ればいい気がした。でもそれには computed valueの定義の新旧を乗り越えないといけない(関連:used value)。


Fx23で1割しか改善しない。getComputedStyleで時間を食ってるのかも(Operaだと組み込み関数のプロファイルもとれるんだけど、Fxでは?)。

これね(またしても後手)。

測りごとの罠 - EfficientJavaScript - Dev.Opera

前に読んでたけど「offsetWidth のようなプロパティを使ったり getComputedStyle のようなメソッドを呼ぶと」なんてそのものズバリの具体例が頭に残ってなかった。


 そろそろおしまい

経緯をふり返ってみる。

  1. URLが折り返されなくてはみ出したり、長い単語がごっそり次の行へ送られて右端が余ったりするのが嫌で word-break:break-allを指定する(横幅に制限のあるテーマだったから word-wrap:break-wordとともに最初から指定してあった)。
  2. Firefox(と Safari)で禁則処理が行われないのがありえない。(word-break:break-allが原因)
  3. NOBRタグを使って全てとはいわないが大部分の禁則処理を補完する。
  4. 右端が揃ってないのが気になってきた。(禁則処理が悪化させた)
  5. text-align:justify, text-justify:distribute(inter-ideograph)で行末を揃える。
  6. Firefoxと Safariで生じる字間の偏りが気になる。(IEと違い text-justifyで行末揃えの具体的方法を指定できないのが原因)
  7. 不可視の空白を分離許可・分割許可の目印として挿入することで是正する。
  8. 字詰め(カーニング)も同じ仕組みで実現できそうなのでやってみた。
  9. あれ?分離許可・分割許可の空白を挿入してるなら word-break:break-allする必要なくね?
  10. word-break:normalにしてブラウザの禁則処理を利用すると、タグをまたいだ禁則も有効になって嬉しい。<<imkk
  11. (これから) IE以外で英単語がぶった切られる結果になるのは問題があると思ってる。word-break:break-allや word-wrap:break-wordや無差別な分離許可・分割許可の挿入にかえて、text-justify:distribute, line-break:loose, hyphens:autoが使える日を待ってる。日本語か英語しか書けないのだしハイフンを挿入できる位置を自分で調べるという手もあるにはあるが……(機械的にできるのか利用可能な辞書があるのかまったく不明)。

word-break:break-allから始まる一連のもぐらたたきの結末は、word-break:break-allをやめて自分で分離分割許可ポイントをブラウザに提示することだった。

一般的に参考になるのはこちらの記事でしょうね。自分の場合は優先順位が異なるので紆余曲折を経る必要があったわけだけど。

長い英単語を途中で折り返したいときの CSS の指定方法: Days on the Moon


検索したら TeXとクヌース先生に行き着くのですね。ハイフネーションは第一義にはデータ圧縮問題だそうです。packed trieとかなんとか出てきた(そこに至るまでに linked trie, indexed trie, compressed trieなど。勉強になるなあ。WEB+DB PRESS vol.42で読んだ『高速文字列解析の世界』の人の記事はそれ自体が圧縮されていて何度読み返しても難しかった)。いつもの、書いたからやる羽目に陥ってるような気がしないでもない。&shy;によるソフトハイフンは一番遅かった Firefoxでもとっくにサポートされてるらしい。URLみたいなのには、コピーには影響しないとしても、折り返し点でハイフンが表示されてほしくないな。うまく無視して word-wrap:break-wordに任せるか、代わりに WBRタグを挿入するか。字間調整のために挿入するスペース(.wbr:before{content:" "})の代わりに &shy;(.wbr:before{content:"\0000AD"})を挿入してみたけど Firefoxでは期待通りのソフトハイフン。Safari5.1.7/Opera18では異常な空間が生じる。IE9は単語間調整を優先して折り返してくれない。ジャスティファイのやり過ぎが嫌でいろいろやってるのに、調整幅の最大値を制限できないせいで。ぐぬぬ。

 @2013-12-27 text-justify: distribute

まだやっていました。textfunc5_nobr_over_asciiwordを追加。これは textfunc2, textfunc3から英単語を分割する効果のみを取り除くもの。分離効果が残ってるのがミソで、英字間に両端揃えのためのスペースを分配はするが、英単語の途中で折り返しはしなくなる。これは要するに word-break: normal; text-align: justify; text-justify: distribute を指定して得られるはずの結果と同じ。

  • 日本語文では、ほぼすべての文字間隔が調整対象で、禁則を例外としてどこでも折り返せるので、違和感なく両端揃えが行われる。
  • 英文では、単語間隔と文字間隔をバランス良く調整対象にすることで、単語や文字が散漫に散らばる印象を抑えることができる。
    • ハイフネーションによっても同じ効果が得られるがこれはまた別の話。
    • TODO: WebKit系の字間分配アルゴリズムが「右端から順番に」なので行末がルーズになりがち。
  • 日本語英語混じり文では、日本語文に対しては英単語内で折り返せないことで、英文に対しては英単語間のスペースの数が限られていることで、字間調整量が過大になりがちで、ところどころ散漫な行が目につく。混合文に限って word-break: break-all でぶった切るのが無難だと思うが、lang属性を積極的に活用する運用になってないので見分けられない。これの満足のいく表示が一番難しい。

見苦しい部分が少ないながら残るものの総合的にみて満足してる。Firefoxでスクリプトを切ってこの日記を表示すると、より多くの散漫な行が目に入る。Opera18でスクリプトを切って見ると、行末が揃わない、ぽっかり数文字分の空白がそこここに現れる。IE9でスクリプトを切って見ると、何も変わらない……。IE9は英語でも日本語でもありえない表示は行わないし、text-justifyによって選択肢を提供してくれるけど、スクリプトによってそれを逸脱するような操作を行う余地がない(何をしても効果が見られない)。ま、いじれなくてももともと見られない表示ではないので構いません。字詰めに関してはどのブラウザでも同じように効果を発揮してる。かぎ括弧の余白が削れて満足。

 @2014-01-05 JSLint

jslint.comで textfunc.ver2.jsのクリーンさをチェック。タブインデントをオプションで許してもらうと、残ったエラーは4つ。

  • Missing 'use strict' statement. line 19 character 55

    var sorted_unbreakables = [];
  • Unexpected '--'. line 25 character 60

    for (begin_first = this.length; 0 <= begin_first-1; --begin_first) {
  • Unexpected '++'. line 30 character 8

    for (begin_last = begin_first; begin_last < this.length; ++begin_last) {
  • Move 'var' declarations to the top of the function.

    for (var pos = begin_first; pos <= begin_last; ++pos) {

for文に関するものはスタイルの問題なので変えない(そういえば前に別のスクリプトで jslint.jsを試したときも forまわりが多かった>20080212p01.02.01)。間違ってるのは JavaScriptの仕様です(letがデメリット無しで使えるなら varから乗り換えるけども)。"use strict"; は早速ファイルの先頭に置いてみたけど何も変わらなかった。varを1か所取り除いて暗黙のグローバル変数を作ってみたらエラーが出たので無視はされてないみたい。未来への保険/保障/保証*4としてそのまま置いておく。

 @2014-03-07 FLAutoKerning.jsの亜種。

jquery.kerning.js|http://karappoinc.github.io/jquery.kerning.js/

Notice

使用するには、フォントに合わせたカーニングデータの作成が必要です。 現時点では、ソースコードに付属しているテンプレートを参考に、使用するフォントに合わせたカーニングデータを別途作成する必要があります。

※ フォントデータから自動的にjsonデータを生成するツールを試作中です。

こういうのはカーニングデータの重要度が9割以上だと思う。そしてカーニングデータはフォントに固有。そして使用されているフォントをほぼ確実に知るためには Webフォントが必要。字詰めの実装方法が letter-spacingであるとか、display:inline-block と margin-left、margin-rightの組み合わせであるとかは些細な違い(といいながら興味津々でソースをのぞいてるわけだ。俺の関心はそっち)。

 @2015-01-19「音声出力環境における擬似要素(::before, ::after)の内容(content)の読み上げについて|Web制作 W3G

content属性値は読まれてしまうそうです。空白文字に関しては、単語としての認識を阻害し、息継ぎが発生するらしい。

 @2015-02-17「Webフォントサービス「FONTPLUS」に文字詰め機能を実装|ソフトバンク・テクノロジー株式会社のプレスリリース

提供するフォントから余白に関するデータを一括で削除し、そこに CSSで余白を付けるという手順。フォント間の差異を一度リセットすることで共通のカーニングデータを利用できるようにする試みなんだろうか。

  • 同じ字でもあまり形が違うとフォント間でカーニングデータを流用できない
  • どうせ Webフォントを使うなら最終的にはフォント固有のカーニングに行き着く

だろうとはいえ悪くはない。手抜きでも完璧でもないほどほどを狙ったサービスじゃないかと。

 一だったり十だったりする人の日記でも字詰め(@2015-04-25)

中里一日記: Mobile SafariのJavascriptで約物のアキ調整 Posted by hajime at 2011年09月29日 15:21

イカしたスクリプト>tsume.js

文字列って元をたどれば文字配列であって、れっきとしたデータ構造であり、しかもただの配列より便利なメソッドがいっぱい生えてるんだってことを思い出させてくれる。

日本語識別子って読む分にはいいけど、IMEを切り替えて入力するのはすんごく面倒なんだぜ。

 @2015-03-22 Opera28がやった!

百聞は一見に如かず>20131101.html rendered by Opera28.pdf(1.5MiB)<スクリプトなしで右端が揃っている

いくつか前のバージョンでは日本語英語交じり文の字間を暗黙の text-justify:inter-word で調整した結果(※IEと違い text-justifyで他の調整方法を指定させてくれなかった)、わずかな空白文字が何文字分にも引き延ばされたりまったく調整できていなかったりした。Opera28では、選択肢がないのは同じだが、英字部分が text-justify:inter-word で、日本語部分が text-justify:distribute(inter-ideograph) 相当で調整されているらしく、これ以上を求めるなら個別に手作業するしかないんじゃないだろうかというまともな表示になっている。

英字部分が inter-word相当(distributeではない)ということで、英単語の中のアルファベット間には調整字間が分配されない。なので、行の大部分を英単語が占める場合に日本語部分が散らかってしまうことがある。俺は日本語をメインに考えてるので、textfunc.jk.jsでは英字部分にもスペースを散らして所々英単語を間延びさせていた(とはいえ、調整箇所が増えると目に見える影響は減ることが期待される。あくまでも期待>20140607)。優劣ではなく優先度の問題。これ以上の完璧を求めるなら手作業が必要だということの理由。

他の Blink搭載ブラウザはどうなんだろ(インストールしてないから確認できない)。

@2015-04-15 Google Chromeだけでなく WebKitの Safariでも改善していたみたい。やったね。

全角・半角が混在したテキストにおける CSS text-align: justify | アカベコマイリ

 TextLayoutInfo, ブラウザ検出と機能検出

textfunc.jk.jsが読み込まれてるページ(たとえばここ)でコンソールを開いて

new TextLayoutInfo

と打ってみると、複数のプロパティを持つオブジェクトが返ってくる。その名前と値の表がこう

TextLayoutInfo\browserFirefox 22Opera 28
fontsize_zero_is_availabletruetrue
no_kinsoku_under_wordbreak_breakalltruefalse
no_whitespace_distribution_inter_asciicharactertruetrue
no_whitespace_distribution_inter_ideographfalsefalse

trueになっている項目(※fontsize_zero...以外)が textfunc.jk.jsによる修正対象。以前の Operaは no_whitespace_distribution_inter_ideographが trueになっていたはずだが、いつの間にか falseになっている。その結果 Firefoxの場合と同等の、処理の対象を英字のみに絞った低負荷バージョンが選択される、自動的に。これがブラウザ検出ではなく機能検出を使う理由。>>「HTML5 - ブラウザーと機能検出(msdn.microsoft.com)

TextLayoutInfoによれば(※自作なのでいまいち信用できない)句読点の禁則にも改善があったみたい。word-break:break-allでの、禁則を無視したありえない表示がなくなったのではないだろうか。これに関しては一貫して IEがすぐれていたし、Firefoxが word-break:break-allに対応した15以来ダメな点だ(この日記のタイトルを見よ)。

 textfunc.ver2.js (12.1KiB, 2015-05-14) + textfunc.jk.js (19.8KiB, 2015-03-22)

 何であるか

word-break:break-allと text-align:justifyで各ブラウザに起こる諸問題を解決し、未だサポートされない text-justify:distributeを実現するためのスクリプト。

 具体的に
  • word-break:break-allでも(タグ境界を除いて)禁則。(Fx, Safari, Blink Opera)
  • WebKit系でも justify. (Safari, Blink Opera)
  • word-break:normal下(=折り返しが制限されて justifyの調整量が過大になりがち)でもなるべく調整の余白が目立たないように。(Fx, Safari, Blink Opera)
  • (同じ仕組みを利用したおまけ) メイリオの約物の等幅チックな余白を詰める。(IE, Fx, Safari, Blink Opera)
 備考
  • 各ブラウザのバージョンは IE9, Firefox23, Safari5.1.7, Opera18.
  • Presto Operaでは最小フォントサイズ設定が災いして文字が散らばってしまうので、TextLayoutInfo.fontsize_zero_is_availableでこれを検知して処理を諦める(諦めちゃいます。先のない Presto限定なので)。
  • Opera 28で禁則とジャスティファイに改善がありました。20131101p01.34
  • Opera 31(現在の最新版)で textfunk.jk.jsの is_proportional関数が満足に機能していない。たぶんこれの影響で判定と表示でフォントのフォールバックが一致してないとかじゃない?「Chrome 42でフォントがMS Pゴシックからメイリオに変更&元に戻す方法 | What I Know ~ワッタイナ
  • CSSの unicode-rangeデスクリプタで特定の文字範囲にだけ有効なフォント指定がしてある場合に、要素を対象にする is_proportional関数は対応できない。
 変更履歴
2015-05-14
コメント追加。
2015-04-10
バグ修正。文字列をSPAN要素で囲ってスタイルをセットする時に、無関係な属性に undefined をセットしていた(単に無視されて無害だがコンソールがうるさい)。副作用で1割くらい速くなった。
2015-03-17
Firefoxで実行時間の短縮。このページでいうと2000msから1200msへ。詳細
2015-03-11
unbreakable_element_to_rangeと unbreakable_class_to_rangeを廃止し、unbreakable_style_to_rangeに統合。
2015-03-10
unbreakable_element_to_range追加。(textfunc.ver2.js)
textfunc6_non_strict_kinsoku追加。(textfunc.jk.js)
2015-03-04
構成変更。「本来別ファイルに分離すべきもの」とコメントしてた部分を textfunc.ver2.js から textfunc.jk.js として分離した。
2015-02-19
action_to_range追加 (js/textfunc.kururi.jsで利用するため)
textfuncに渡されるテキストが前から順番になるように深さ優先で。

* 全角文字の字間は拡大するが英字はそのままなので、一部は間延びして一部は詰まって見える。全体に散らした方が目立たないのに。@2013-11-19 Safariと同じ方法をより限定された対象に適用するだけで矯正できるのでやってしまった。対象を限定しなくても副作用はないけど処理負荷を考えて限定した。コピペコード率高し(だが構わない。分岐直後なので当然のこと。ツールファンクションは共有してもいいけど)。@2013-12-16 ■の連続も分割されないな。どういう Unicodeプロパティを参照してるんだろ。

 @2013-11-09 勘違いしてたけどこの部分は Rubyなのでパターンの中にパターンを埋め込む方法で繰り返しをなくせる。それに後に明らかになったように、この後の書き換えは各種ブラウザの JavaScriptで無効な、Rubyでしか使えない方法だった。ECMAScriptとしてあまりありがたくない動作が規定されてるのかも。

 雰囲気で動詞化してみました。

@2015-03-04,(2) textfunc.ver2.js の一部を textfunc.jk.js に分離した。

 今は無駄だけど将来のうっかりな変更にそなえて。

 うっかりな変更を許さない。

*4 うっかりしたコードが存在しないこと。

詳細 getComputedStyleを textfunc内から取り除くことは機能低下を招いてできないので、中間データをすべてため込んで DOM操作を最後にまとめて行うことにした。それだけ。副作用で GCできない使用中のメモリが一時的に増える。ちなみに Blink(Opera28)では悪化も改善もしない。Firefox(23)には最適化の余地があるってことだ。


2010年09月07日 (火)

最終更新: 2010-09-15T20:40+0900

[SakuraEditor][正規表現] 検索。置換。

Command_REPLACE(置換)や Command_REPLACE_ALL(すべて置換)が Command_SEARCH_NEXT(下検索)を呼ぶのをやめたい。連携手段がレイアウト座標を基にした選択範囲しかなく、一文字で表現される CRLFの LFだけがマッチしたときに情報が欠落するし、Command_SEARCH_NEXTが持つ検索以外の事前・事後処理が無駄になるし、正規表現検索が BMatch(検索), BSubst(検索&置換)の計二回行われるのも無駄。

wchar_t単位で検索を行う CSearchAgent::SearchWordが、検索結果をレイアウト座標に変換する際の誤差(前述)を考慮してマッチ範囲を拡大し、結果を不正確なものにするのは誤りだと思う。それより、上の層でこれを呼び出している CLayoutMgr::SearchWordがその配慮を行うべきでは。

 あったらいい操作

Match
対象文字列の対象範囲全体がパターンに一致するか調べる。
Search
対象文字列の対象範囲からパターンに一致する部分を探す。
Replace
対象文字列の対象範囲からパターンに一致する部分を探し、与えられたフォーマットの文字列に置き換えた文字列を返す。(返るのは対象文字列の対象範囲に相当する文字列)
Expand
Matchや Searchの結果を用い、マッチ全体($0)を与えられたフォーマットの文字列に置き換えた文字列を返す。
パターン
BREGEXP.DLLは m/pattern/flags/pattern/replace/flag も同じ一つのパターンとして扱うので、検索用のパターンと置換用のパターンに互換性がない。そうではなく、Search(Compile("pattern", flag), "target", startindex), Replace(Compile("pattern", flag), "target", startIndex, "replace") だったら良かった。(※ "pattern" と "target" と "replace" は実際は二つの引数で表す)

bregonig.dllや bregexp.dllを使いながらパターンの共通化や Expandの不足に対処するにはどうするか。自分で Expandを実装し、dllが用意した置換機能(置換パターン)を使わないで済ませる。でもねえ、実装の数だけ仕様がある状態を避けたいから Expand機能をライブラリに用意して欲しいわけで。(置換文字列の $$ が $に展開されなかったり、\1 が展開されたり、$1が展開されなかったり、色々あるんよね)


 @2010-09-10 悪夢のような「クリップボードから貼り付ける」置換オプション。(本当は矩形選択が)

  • CRLFの CRだけや LFだけをクリップボードの内容で置き換えることができない。
  • 正規表現検索のとき検索始点(終点)挿入オプションがきかない。
  • クリップボードの形式が矩形テキストだったときに検索条件によっては無限ループ。

    例えばクリップボードに

    789
    456
    123

    という 3行9文字(改行文字はない)がおさめられているとき、マッチと置き換えられるのは 789 であり、456 123 はそれぞれ次行、次々行に挿入される。つまり、456 123 はこれから検索対象になるということだ。検索条件が \d{3} だったり ^ だったり、矩形テキストの二行目以降にマッチするものだったら無限ループ。

置換操作が二階層潜らないとロジック単位にならないというのも悩みの種。検索はロジック単位で行えてるのに、それをレイアウト単位に変換してそれがさらにロジック単位に変換されて、一対一対応じゃないからごにょごにょしないと思い通りに置換されなくて。

 @2010-09-15

どうして最上層のレイアウトを基準にして文字列処理を行わなければいけないか。文書(wchar_t列としておく)の変更を LayoutMgrに通知する仕組みを整備するのを怠って、LayoutMgrを通して文書の変更を行うことで通知を不要にしていることが背景にあるのでは? GUIを通しての文字列操作に限ってはそれで十分だからそうなるのも仕方ないし、必要に迫られた人間(俺とか)がより汎用的な手段を整備せずにダーティハック*に頼る方が罪は重いかも。

* 挿入位置・置換範囲を文字レイアウト境界まで拡大して、挿入・置換文字列も同じだけ拡張する。


2010年07月09日 (金) Windows Confidential: The Third Rail of Keyboard Shortcuts | TechNet Magazine」を読んでの違和感。レイモンド チェンは Win+Eがどこを開くかの話をしている。おれは Win+Eがどこを開くかなんて確かめたことがなく、Windows Explorer――フォルダを表示するときに利用される簡易表示ではなくフォルダツリーが付いた正式版――を起動するためのショートカットだと思っていた。そんで、だから、使ってない。

最終更新: 2014-01-02T18:04+0900

[SakuraEditor][正規表現] 「鬼車と bregonigに hitEnd(20100531p01)機能が搭載されることを願う他力本願日記」

なんてことをこの日記の冒頭に掲げたもんだから自分でやってみた(どういうこと?)。

 更新履歴

rev3 (2010-09-05, そのうち書く)
(サクラエディタ) 複数行検索を利用した複数行置換を実装。(複数行全置換はまだ。似たようなコードだし必要なのは手間だけだけど)
rev2.1 (2011-01-29)
(サクラエディタ) バグ修正。複数行検索モードでマッチ位置をバッファ内インデックスから(行,桁)に変換する際にミス。誤った検索結果が表示されることがあった。
rev2 (2010-08-27)
(サクラエディタ) 複数行検索モード実装。(正規表現を使った下検索のみ。50MiB制限あり。制限による探索打ち切り・マッチ範囲切り上げの通知なし)
テストバイナリ+変更点(test_multiine_search.zip)
rev1
(鬼車) 普通のマッチなしと、入力次第でマッチする可能性のあるマッチなしに異なる戻り値を割り当てた。
(bregonig) 入力不足のときに BMatchの戻り値を 0(正常終了,マッチなし)にして、エラーメッセージで入力延長によるマッチ成功の可能性を伝えている。
(サクラエディタ) 下検索での入力不足によるマッチ失敗をステータスバーで通知。

 bregonig

既存アプリに影響があるので良くないけど、bregonigへの暫定的な変更はこう。

 regexec_onig(bregonig.cpp)
	} else {
		/* ERROR */
		onig_err_to_bregexp_msg(err_code, NULL, msg);
-		return -1;
+		return err_code == ONIG_MISMATCH_INPUTSHORTAGE ? 0 : -1;
	}

入力が足りなくてマッチしなかったときは、エラーメッセージをセットするけど戻り値は負数(エラー)ではなく 0(マッチなし)。

 鬼車

鬼車(5.9.2)に「K.Takata's software : bregonig.dll」で手に入る onig-5.9.2-mod.diffを適用したものをさらに変更。マッチに失敗したときのエラーの種類で hitendしたかどうかを伝える。ちゃんと動くのか非常にあやしい代物。TODOもいっぱいある。

  • 特定のパターンのパターンに向けた最適化を無効にしている。(そこまで手が回らない)
  • backward searchに対していつ hitendフラグを立てていいのかわからない。(ので未対応)
  • "aaaaa\n" という文字列から [a\r\n]+a というパターンを検索したときにバックトラックにより "aaaaa" がマッチするわけだが、その次の行にも "aaaa.." という文字が続いていた場合は……。一応マッチは見つかっているが hitendフラグも立てたい。
  • [\w\W\s\S] というようなパターンで、メタ文字の登場順([\W\S\w\s]とか)に 依存しておかしな結果になる。(もちろん俺のミス。原因は解らない)

やっぱり鬼車は手に負えないかも。

 サクラエディタ

なんのことはない、影響を受ける既存アプリにはサクラエディタが含まれている。正規表現のコンパイルエラーと入力不足によるマッチなしを区別するためにちょっと変更した。

 複数行検索モード

一行検索(従来動作)してみて、マッチが行末まで続いていたり次の行の内容次第でマッチが成功に変わりそうなときはとりあえず二行ぶん検索してみる。それでも状況が同じなら 50MiBのバッファを埋めてから三度目の検索を行う。50MiBって大きすぎるだろうか。大きさの問題だろうか。かかか。

実装は CSearchAgent::SearchWord()に。これって単語検索専用のメソッドではなかったのですよ。CGrepAgentもこれを利用したらよかった。

バッファの実体は std::wstringで。文字列を比較するにも wmemcmpなりを使おうとして結局 std::wstring("hoge") == L"hoge" を使ったへたれなので、str系のライブラリ関数は恐ろしくて毎回すぐに投げ出します。(あれを使いこなせる人は VBや PHPも使いこなせると思うんですよ)

 TODO
  • 上検索。
  • 検索語ハイライトの処理がたぶん一行ごとに行われている。一行ずつ進みながら複数行を対象に検索を行ってるってわけ。無駄だし目に見えて遅い。
  • バッファ長の制限により、マッチ範囲が途中で切られたりマッチの探索が打ち切られたりしても何も言わないのをなんとか。(制限をなくすかメッセージでも)
  • SourceForge.net: Sakura Editor: Detail: 2309002 - 正規表現による複数行検索対応(簡易版)」のコメントを見ると、検索語ハイライトのほかに「すべて置換」にもパッチが必要そう。パッチを流し読みしてたら BookmarkManager(::MarkSearchWord)までが GrepAgentが持ってたような検索の勝手実装を持ってるらし。SearchAgentを使ってよね。(あ、いや、SearchAgentは当時なかったんだっけ)
  • (複数行)検索にマッチしたなかでも、最初の行がマッチに含まれているか含まれていないかというのは区別する価値がある。サクラエディタの既存の実装は行指向が強いだろうから、「マッチした(ただし先のほうの行で)」ということを勘違いしかねない。(SearchAgentや選択範囲にはそういう指向がないから運良く複数行検索が自然に実装できただけ)
  • (@2011-08-01) [\s\S]*$ というパターン。最後の行の末尾までマッチして欲しいのに一行目の改行直前で止まってしまう。マッチに成功したときの hitEndフラグの扱い。

 @2010-07-12 「鬼車 for Java

hitEnd()の実装の参考になるか?と思ったけどそうもいかなそう。

Revision  74 hitEnd()の実装(但し仕様は満たしていない)。
Revision  85 useAnchoringBounds()及びuseTransparentBounds()に対応。→hitEnd()の実装を修正。
Revision 103 hitEnd()に@Deprecatedを追加。
    /**
     * This method is experimentation phase, and implementation has not been completed yet.
     * @return
     * @deprecated
     */
    @Deprecated
    public boolean hitEnd() {
        return (hasAnchoringBounds ? (range == region.end(0)) : (input.length() == end()));
    }

hasAnchoringBoundsが設定されてる場合は無視するとして、そうでないときは入力の長さとマッチの末尾(end())が一致してることをテストしてるだけに見える。それって hitEnd()とは違うよね。5月31日の日記文字列 aa に対してパターン aa を適用したときに hitEnd()が falseになった事例を引用した。

そうそう。これこそが hitEnd()の使い道 >Check if string is a prefix of a Javascript RegExp - Stack Overflow コメントの最後に見たことのある名前が☺


 @2010-07-12 PCRE

PCRE(現在 ver. 8.10)は戻り読みも再帰も(パターンのコンパイル時の)改行指定オプションもスキャナー的に使うための hitEnd()のような機能(read pcrepartial)も、およそ欲しいものをすべて備えている。悪名高いスタックオーバーフローにも、1.スタック使用量を減らす。2.代替メモリ確保関数を使う(汎用ゆえに遅いmalloc/freeか自作)。3.pcre_exec()の代わりに pcre_dfa_exec()を使う。みたいな各種対策があるらしい。


2010年05月31日 (月)

最終更新: 2010-07-10T01:01+0900

[正規表現] requireEnd(), hitEnd()

hitEnd

public boolean hitEnd()

このマッチャーが行なった最前のマッチング操作によって、入力シーケンスの終わりに達していたら、trueを返します。

このメソッドがtrueを返したときは、さらに新たな入力を加えると最後のマッチの結果が変わる可能性があります。

requireEnd

public boolean requireEnd()

入力を新たに増やすことによって、それまでのマッチが不成功に変わるならtrueを返します。

マッチが見つかっていたときにこのメソッドがtrueを返したら、さら入力を増やすとそのマッチがなくなることを意味します。マッチが見つかっていたときにこのメソッドがfalseを返したら、さらに入力を増やすとマッチが変わるかもしれないが、マッチがなくなることはないことを意味します。マッチが見つかっていないときにrequireEndを呼ぶことは、無意味です。

まず 1.5 で導入された hitEnd メソッドと requireEnd メソッドですが、 hitEnd メソッドはドキュメントでは入力の末尾がヒットした場合にtrueを返すとありますが、入力というのは領域を指しているようなのですが、末尾というのが最後の文字なのか末尾の位置なのかよく分かりませんでした。例えば aa をマッチング対象の入力シーケンスとして正規表現 aa でマッチングを行った場合、入力シーケンス全体にマッチしますが、この場合に hitEnd は false を返します。つまり最後の文字がマッチしていても true にはならないようです。ところが正規表現 a+ や a* でマッチングを行うと aa と同様に入力シーケンス全体にマッチしますが、この場合は hitEnd は true となります。つまりマッチした部分だけでなく正規表現パターン自体も hitEnd の返す値に影響するようです。

どちらも入力が一つの文字列におさまっていないときに役に立つメソッド。

  1. 二番目の引用内容と、
  2. requireEnd()が trueを返した場合と hitEnd()が trueを返した場合の対比と、
  3. 自分が行文字列のリストを対象に鬼車でマッチングを行うときに欲しい機能から、

hitEnd()について想像するなら、

search engineが遷移先を残したまま入力を消費し尽くしたときに hitEnd()が trueになる。それの意味することは、直前のマッチが成功していたのなら、さらなる入力によりマッチの範囲が延長される可能性があるということ。直前のマッチが失敗していたのなら、さらなる入力によりマッチが成功するかもしれないということ。

ところで、hitEnd()が trueかつ requireEnd()が trueの場合って存在するだろうか。あるとして、どんな入力とパターンになるだろう。

requireEnd()はなくても困りはしない。マッチの範囲が入力の末尾まで続いていれば無条件に入力を増やしてリトライすることで requireEnd()が trueだったのか falseだったのかは確かめられる。

hitEnd()も、マッチが成功していたときは重要ではない。真価は、マッチが不成功だったときに hitEnd()が falseを返した場合は、さらなる入力をつなげてもマッチが成功に変わることがないと確かにいえること。hitEnd()がなければすべての入力を連結せずにはマッチが存在しないことを断定できない。


そうそうこれこれ。>http://www.mail-archive.com/classpath-patches@gnu.org/msg08501.html

さらに、こっちのドキュメントは詳細だしバグにも触れてある。上のスレッドで報告されてるのとは別のバグみたいだけど。

boolean hitEnd()

(This method is unreliable in Java 1.5; a workaround is presented on page 392.)

This method indicates whether the regex engine tried to inspect beyond the trailing end of the input during the previous match attempt (regardless of whether that attempt was ultimately successful). This includes the inspection done by boundaries such as \b and $.

If hitEnd returns true, more input could have changed the result (changed failure to success, changed success to failure, or changed the span of text matched). On the other hand, false means that the results from the previous attempt were derived solely from the input the regex engine had to work with, and, as such, appending additional text could not have changed the result.

The common application is that if you have a successful match after which hitEnd is true, you need to wait for more input before committing to a decision. If you have a failed match attempt and hitEnd is true, you'll want to allow more input to come in, rather than aborting with a syntax error.

本日のツッコミ(全4件) ツッコミを入れる

ds14050>ところで、hitEnd()が trueかつ requireEnd()が trueの場合って存在するだろうか。 a..

ds14050なぜだか上のコメントがスパム扱い。@data_path/log/debug.logを見た。 >I, [2012-0..

ds14050>[\/url] ではなく \[\/url] が正しい。 正真正銘正しいのは \[\/url\] だった。 しかし..

ds14050>requireEnd()はなくても困りはしない。マッチの範囲が入力の末尾まで続いていれば無条件に入力を増やしてリト..


2010年05月19日 (水) [SakuraEditor] コードブロックの折りたたみっていうのは、アウトライン解析の結果(ツリー)を別窓でなくエディタ自体に表示するようなものなのですね。byte_stream>char_stream(codepoint>glyph)>line_stream>layoutline_streamの上にさらに folding_streamを乗っけるイメージ?

最終更新: 2010-05-19T16:21+0900

[正規表現] 鬼車。条件付き選択。

348 nobodyさん 2008/07/27(日) 01:11:32 ID:sEx2Pn85
    質問させてください。

    サクラエディタに鬼車5.9.1を搭載して正規表現の勉強をしているのですが、手元にある詳説正規表現には

    (<)?\w+(?(1)>)

    このような例があり、<があれば>のマッチを試みる?ということができるみたいです。

    ただ、鬼車はこの表現をサポートしていないみたいです。
    http://www.geocities.jp/kosako3/oniguruma/doc/RE.ja.txt

    同様のことを鬼車でも実現する方法ってあるのでしょうか? 

鬼車は条件付き選択をサポートしていないのが玉に瑕だと、以前から思っている。なんとかできないかと上の例で考えてみたら、こうなった。

(<|)\w+(\1{100}|>)

せこい。姑息だ。100ってなんだよ。こんなの( <abc<<<...(94個の<を省略)...<<<> )にもマッチするじゃねーか。


2010年03月29日 (月) XRegExp-1.5.0がでている。

最終更新: 2010-04-04T07:43+0900

[正規表現] 正規表現の空の文字クラスのブラウザ実装 > 文字クラス(suika.fam.cx)

まとめはリンク先に任せるとして、最新の Opera(10.51)ではどうかね、と思ってアドレスバーに javascript……と打ち込んだら空の文字クラスのテストスクリプトと思しき、まさに今打ち込もうとしていたものが補完された。笑える(他に Operaの使い道はないのか)。このときの履歴だろう。さておき、結果は

// Opera10.51
/[]/.test("a") //=> false
/[^]/.test("a") //=> true

9.61のときとは違って一般に期待される結果になっているよう。次いで IE8の結果は……どちらもスクリプトエラー。

// IE8
/[]/.test("a") //=> "正規表現の中に ']' を指定してください。" (Perlと同じ。だけど IEのバージョンももう 8だよ)
/[^]/.test("a") //=> "正規表現の中に ']' を指定してください。" (Perlと同じ。だけど IEのバージョンももう 8だよ)

// このエラーが導くのは、パターンのこういう解釈と結果。
/[]]/.test("]") //=> true
/[^]]/.test("]") //=> false

変わっていない。ECMAScript3に準拠するならマッチ結果の食い違いはともかくエラーはないはずだけど。

ちょっと前にニュースになった IE9 Preview版(IEBlog : About the Platform Preview)もせっかくだしインストールして試してみた。「IEBlog : The New JavaScript Engine in Internet Explorer 9」なんて記事も見つけたし。結果は /[]/.test("a") も /[^]/.test("a") もクラッシュ。残念至極。


2009年09月22日 (火) [790FX-GD70] BIOS v1.5

最終更新: 2016-03-05T00:30+0900

[SakuraEditor][正規表現] 正規表現を使った検索・置換で、改行の意味を LFのみから CRも含むように。

経緯 > サクラエディタBBS[r7030]

差分 > fix_cr_handling_of_regex(下に修正版がある)

お試し > sakuraW.zip (547KiB)(下に修正版がある) (正規表現検索・置換を試すには bregonig.dll(Unicode対応版)が別途必要)

検索、置換を数度試したが機能しているみたい。ただ、$ が本当に改行の手前でマッチする関係で

^.*$

を空文字列に置換するという最初に提起されたケースでは、置換後の空行までが置換対象になってしまう(置換回数が 2倍)。目的に適う、より適切なパターンは

^.+$

あるいは、エディタの行置換機能を使っているのだから、もっと単純に

.+

で良い。


 @2009-09-24

正規表現 - SakuraEditorWiki」を見ていて気付いた。\c[\c]\c$\c. という制御文字のひとつを表すパターンが存在する。「鬼車 正規表現 Version 5.9.1」によれば \C-[\C-]\C-$\C-.\M-[\M-]\M-$\M-. も存在しうる。\M-\C-[ なども存在しそうに思ったけど、これはこういう結果になった。

irb(main):001:0> /\M-\C-[/
SyntaxError: (irb):1: too short escaped multibyte character: /\M-\C-[/
        from c:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>'
irb(main):002:0

制御文字なんて扱ったことがないからなあ(もはや relicだという認識)。対処の必要性がさっぱり感じられないけど……。


 @2009-09-25

一括置換で $ が CRLFの CR直前、LF直前、LF直後(正規表現DLLに与えた文字列末尾)の三カ所にマッチしてしまうとの指摘 >サクラエディタBBS[r7039]

逐一、置換を実行した場合は問題ないことを確認していたのだが、一括置換はライブラリに全部お任せで、検索開始位置を調整することもできないから動作が違っていたのだろう。$ が CRと LFの間にマッチするのはわかっていたが、明示的に \r を食べた場合にだけ影響があると思っていた。一括置換なんてありふれた操作でそれが明るみに出るとは思いもせず。

急いで修正 > fix_cr_handling_of_regex.rev2sakuraW.rev2.zip (547KiB)(さらなる修正版 rev3が下に)

初めて戻り読みを使った。なんとなく反則的な手段の気がして、使わないですむ方法をいつも考えるのだけど無理だった。これで bregonig.dll依存が決定的になった。[] の入れ子のことだけなら ] が見つかったときに charset_levelを一気に 0 にするだけで BREGEXP.DLL対応もできたのだが。


\C-X、\M-X というのは Ruby向けなのかも。サクラエディタ(+bregonig.dll)で \C-[ を検索しようとすると「premature end of char-class」というメッセージが出る。となれば、\cX だけが引っかかった小骨ということになる。

対処 > fix_cr_handling_of_regex.rev3sakuraW.rev3.zip

<追記>bregonig.dllでは \c\X が \cX の意味になるとか。もう知らねー。</追記>


個人プロジェクトでもないと色々大変そう。ドッグフードでも食べながら様子見します。とりあえず反応だけ。

 2.非対応となっているBREGEXP.DLL(ANSI版)への対処方法
 ANSI版とUNICODE版は別仕様としてしまうのか?

使用できる正規表現自体が別物なので BREGEXP.DLLは CBregexp::MakePattern()でよいかと。ユーザーを驚かさないための変更なので、. が \r にマッチすることを期待していた人以外に影響はないつもりでいる。

<追記>ANSI版+bregonig.dll向けのパッチを用意したので、別仕様は ANSI版+(BREGEXP.DLL/bregonig.dll)と Unicode版+bregonig.dllの間、または、 ANSI版+BREGEXP.dllと (ANSI版/Unicode版)+bregonig.dllの間のどちらでも選べる。BREGEXP.DLLのサポートするオーソドックスな(戻り読みをサポートしない)正規表現で、$を改行直後の行文字列終端にマッチさせない方法が見つからない限り BREGEXP.DLL対応は無理。</追記>

<追記>BREGEXP.DLL版も用意した。>_ANSI.rev2 >_ANSI.rev3</追記>

 3.$ が改行なし最終行のEOF手前ともマッチするように改善すること
 $ を (?=\r\n?|(?<!\r)\n|(?<[^\r\n])$) に置き換える方法を試してみたけどエラーで動きませんでした。

肯定の戻り読みは (?<=) でした(なにせ使用経験がないもので)。気を取り直して、パターンを (?=\r\n?|(?<!\r)\n|(?<=[^\r\n])$)* に置き換えたところ、検索は予想通り、最終行がEOFのみの場合を除いて文書末にマッチするようになったのだけど、置換が行われない。(以前は行われていたのだろうか? マッチのインデックスが文書の長さと同じ(=1つはみ出した状態)になっているはずだから特別な対処が必要なのだろう)

「以前は行われていたのだろうか?」< 行われていなかった。なら、(間違ってるけど)おいておこう。

 4.検索強調表示が検索時の選択反転表示と一致すること
 $ を (?=\r\n?|(?<!\r)\n) に置き換えた版で $ を検索すると、改行文字自体は選択反転表示にはならない(マッチしない)のに検索強調表示されている。
 また、なぜか上方向検索では改行文字自体にマッチしたかのように選択反転表示になる。

$で検索したときに改行文字が強調表示されるのは、幅0のハイライトには意味がないので、実用面から今のように最低でも幅 1を保つべきだと思っていた。

上検索で改行が選択されてしまうのは間違いなので修正したい。これまでが、知らず $ が改行にマッチする仕様に依存していたのかもしれず、こういう修正は正しい方向に向かうためのものだと考えている。

修正した(rev4)。無限ループを避けるためにマッチ幅が 0のときに検索開始位置を特別にインクリメントするんだけど、そのタイミングが悪くて検索開始位置だけでなくマッチの範囲までがインクリメントされていたのが原因。

 5.正規表現キーワードでの $, . 指定も検索・置換と挙動が一致すること
 現状、正規表現キーワードには $, . に検索・置換でやっているような細工が入っておらず、素の正規表現ライブラリ挙動になっている模様。
 検索・置換時の . の細工([^\r\n]への置換)が追加されると、今よりも差異が大きくなって混乱しそう。

すでに書いたように、. が \r にマッチすること、$ が CRLF の真ん中にマッチすることを期待していた人(いるのか?)以外は違いに気付かないだろう。

\r\n$ みたいな書き方をしていた場合にマッチしなくなる。このケースはなくはないかも。

 6.検索・置換や正規表現キーワードの複数行対応への順応性

ノーチェック。sフラグが含まれる( . があらゆる文字にマッチするようになる)ときには . を置き換えないようにする、mフラグが含まれない( ^、$ が行頭、行末にマッチしなくなる)ときには $ を置き換えないようにする、とか?


>fix_cr_handling_of_regex.rev4sakuraW.rev4.zip


2ch民は 1.6.5.0をつかうのね。♥マークはいらないんだ。Consolasも使いたくないんだ。(これは俺の理由だけど) r1663を使おうぜ。


 @2009-09-28

Unicode版Revision1662>http://sakura-editor.svn.sourceforge.net/viewvc/sakura-editor?view=rev&sortby=date&revision=1662

Ansi版Patch>http://sourceforge.net/tracker/?func=detail&aid=2869238&group_id=12488&atid=312488


勘違いしていた。Unicode版のサクラエディタで使用できる正規表現ライブラリは bregonig.dll(Unicode版)だけだ、という事実がいつの間にか、bregonig.dllは Unicode版専用だ、という思い込みにすり替わっていた。

だったら、採用の可否はともかく、Ansi版(trunk2の (Release|Debug)_Ansiビルドのことでなく trunkのビルド産物のこと、だろう)向けのパッチを作成する意味はあるわけだ。CBregexpのインスタンスがその寿命を通じて 1つの DLLだけを扱うのであれば、コストを初回に払うだけで、処理の振り分けを行うことができる。どっちかな?

>>> DLL初期化時に呼ばれる仮想関数がありました。(そのたびにチェックを行えばいい。実際は 1回しか呼ばれないだろう)


CheckRegexpSyntax() は癌だ。検索ボタンを押すたびに DLLのロードからはじめて、文法をチェックしたら使い捨てるって何事。文法チェックは Compile()で十分。その後の Match()のための準備にもなってちょうどいい。もろもろの手順を共通化したいのなら引数として CBregexpを受け取るべきだ。正規表現のチェックをしたい(=正規表現を利用したい)部分ではもちろん CBregexpなりなんなりをすでに持っているだろう。CheckRegexpSyntax()がこんな重量級のローカル変数をもつ必要はない。無効な検索パターンを履歴に追加したくないがために、検索の主体でない検索ダイアログが利用しているのかもしれないけど。


 @2009-09-29

やっつけで一応。これで昨日書いた「コストを初回に払うだけで、処理の振り分けを行うことができる」が事実になった。昨日の段階では、検索ボタンを押すたびに DLLがサポートする文法をチェックする関数が実行されていた(初回どころではない)。これでもまだ、検索ボタンを押すたびに Compile()が 2回走るのは変わっていない。

おまけとして、bregexp.dllだけが sakura.exeの隣にある状態でエディタを起動し、その後 bregonig.dllを配置したとき、検索ダイアログでは 「bregonig.dll Ver....」と表示され、bregonig.dllしかサポートしない戻り読みを使用しても正規表現エラーにはならないものの、実際の検索には bregexp.dllが使用されるためだろう、戻り読みが機能していればマッチするはずの条件でもマッチ無しになってしまう、こういう、説明もややこしい起こりづらい状況が起こらなくなる。(文法チェックと実際の検索が CBregexpの同じインスタンスによって行われるようになるから)

本当は CBregexpが CDllHandlerを継承するのをやめて分離して、1つの CDllHandler(DLLインスタンス)を多数の CBregexp(BREGEXP構造体)から参照するのがいいのかも。もっといえば、BREGEXP構造体もコンパイル済みパターンとマッチ結果に分離したい。サポートする文法の情報はもちろん DLL付きの情報。CDllHandlerは汎用的すぎるから、その任には、CDllHandlerを継承したいまの CBregexpから BREGEXP構造体だけを追い出したものを充てればいい。

いまの CBregexpは InitDll()を呼び出されて、途中で違う正規表現ライブラリを読み込まされたとき、コンパイル済みの BREGEXP構造体を正しく解放できるのだろうか?


 @2009-10-01

BREGEXP.DLLでも . と $ を置き換えるようにしてみた > fix_cr_handling_of_regex_ANSI.rev2(下に rev3)

副作用があって、XYZ(CR)(LF) という行に対して XYZを検索すると XYZ(CR)(LF) がマッチする。マッチ結果が改行に隣接しているとき、改行がマッチに含められます。$を検索すると (CR)(LF) がマッチするのは以前からの通り。ここが変わらないのは、一括置換での過剰な置換を防ぐ手立てがこれしか BREGEXP.DLLからは与えられていない、ということ。

ついでに気になった、\r\n を検索したときステータスバーに 1 bytes selected. と表示されるのを修正。>fix_selection_area_and_selected_byte_count_ANSI 選択領域が中途半端なサイズだったのも直った。それもこれも CLayoutが EOLの長さを常に 1とカウントしていたせい。マッチ範囲が勝手に 1切り詰められていた。

表示としては ↵ も ⇠ も ⇣ も同じ一字だから CLayoutのすることにも一理あるのかもしれない。それなら改行文字の部分の選択領域をせめて全角幅にして検索結果のハイライト部分と大きさをそろえよう。

ANSI版の View関連のソースを見てたら気が遠くなる。Unicode版で

  • CRLFを全角幅で表示
  • CRLFの CR、LFのみハイライト、選択
  • LF、CRの全角表示、全角幅選択(or半角幅ハイライト)
  • 行頭マッチ(^)でキャレット描画

あたり、なんとかならんかな。検索結果の選択範囲とハイライト領域のずれが気になる。


 @2009-10-03

ANSI版を BREGEXP.DLLと組み合わせているときに、不必要に改行が検索結果に含まれてしまう場合を rev2より大幅に減らした。意地悪なパターンを与えられたときにどうなるかは把握しきれていない。

> fix_cr_handling_of_regex_ANSI.rev3(rev2と rev3にバグ発覚。rev4へ)


fix_cr_handling_of_regex_ANSI.rev4

どういうバグだったかというと、一括置換をしたときに改行や行文字列末尾付近で過剰に置換が行われないように、戻り読みが使えない BREGEXP.DLLのときは検索パターンと置換文字列に細工を施すのだけど、検索パターンの置き換え方がまずかった。

 誤: /A|B/ -> /A|B((?:\r\n?|\n\r?)?)/
 正: /A|B/ -> /(?:A|B)((?:\r\n?|\n\r?)?)/

選択 | の結合順位は一番低いのでした。


バグは CBregexp.cppを好き勝手にいじっていた結果をテストしている最中にたまたま見つかった。

  • メンバ関数に constをつけたり
  • 正規表現フラグを指定する引数の型を intから専用のものに変えたり
  • CBregexp::IsLookAhead(const char *pattern)が内部状態を変更して、CBregexp::Match()の結果に意図しない影響を与える可能性を排除したり

していた。これは単なる自己満足。


 @2009-10-04

不必要に選択範囲が改行にかかっていたケースををさらに減少。> fix_cr_handling_of_regex_ANSI.rev5 検索パターンが LF直前や文字列末尾に幅0マッチしそうなときにだけ細工を行うことにした。なんというか、盆栽趣味?

バイナリ>sakura.zip


 @2009-11-25

AINI版では LFCRの LFと CRの間に幅0マッチしそうなときも細工を行わないといけないだろうな。やらないけど。(LFCR?なにそれ?という立場)

* あえて (?<=[^\r\n])$ を使ったつもりでいたけど、実は (?<![\r\n])$ の方が最適だった可能性。二者の違いは>[[20080528p01]]。ただし、サクラエディタ的には EOFのみの行は存在しない(行番号も表示しない)ものらしいので、どちらのパターンを使っても実際の違いは生じない。

 扱うファイルが ASCII onlyという可能性、FontLinkパッチ<https://sourceforge.net/tracker/?func=detail&aid=1832567&group_id=12488&atid=312488>を自分で当てている可能性はある。


2009年07月03日 (金) 時計回り、反時計回りはわかる。右回り、左回りはどっちがどっちかわからない。目の付け所が恣意的ではないか、と小学生のころ思っていた。<< 円を正面から見るのでなく、中心に立ってみればよかったのだ。(やっと訪れた理解)

最終更新: 2009-08-25T02:45+0900

[正規表現][Firefox] XRegExp-1.0.1と Firefoxの /(?!)/

20090628p01で書いた Firefoxのバグっぽいものが XRegExpに影響するみたい。

// Firefox 3.0.11 + XRegExp 1.0.1 での実行結果
alert( XRegExp("[]").test("a") ); //=> true
alert(  RegExp("[]").test("a") ); //=> false

[] と [^] の意味するところってなんでしょうね。[] は空の文字集合だから「どれでもない一文字」とマッチする(=空文字列にもマッチせず必ず失敗する)。[^] は空の文字集合の補集合だから「任意の一文字」だろうか。

参考ページを見つけた > 文字クラス(http://suika.fam.cx)

結局、こういうことだ。

  • 主要 4ブラウザのうち [][^] が使えないのは IEだけ。
  • Opera(9.61)は [][^] がおかしい(他と違う)。
  • Firefox(3.0.11, 3.5RC3)は (?!)(?=) がおかしい(他と違うし、自分自身の (?!|)(?=|) とも違う)。

(IEの件を除いて)どのパターンも規格上の正解やデファクトスタンダードがなくて混乱してんじゃないかなあ。こういうのには近づかないに限る。


 追記@2009-07-04 04:44: 内容確認。

正しくコメントが読めてるといいんだけど……。

IEが空の文字セット [] を処理しないのは(ECMAScriptの仕様をすっとばして) Perlその他、JavaScript以外の多くの実装にならっているもので、[ に続く最初の文字を文字通りの文字 ] だとしているから。だから []][\]] と同じ意味になる。

Firefoxで (?!) の代わりに (?!|)(?=) の代わりに (?=|) を使うのは良くなくて、つまりこれらの結果は同じになるべきだけど、どちらになるかは将来の Firefox次第だから。バックトラックの発生にもつながって効率的にも不利。だから (?!) の代わりは \b\B(?=) の代わりは (?:) が良い。

\b\B というのは覚えておこう。


 追記@2009-07-04: はずしてた。

冒頭の指摘ははずしてる。考える前に、気付いたことをただ書いたせいだ。

XRegExpの目的の一つは、ブラウザごとにまちまちな挙動を見せる JavaScriptの正規表現に関するメソッドを仕様に準拠させることだったと思う(String.split()とか RegExp.lastIndexとか)。

だから空の文字セット( [][^] )を提供する第一の目的は 5ブラウザ(IE, Fx, Opera, Safari, Google Chrome)のうち不当にこれを受け付けない IEにおいて、仕様で許された正規表現パターンを利用可能にすること、かもしれない。

その過程で、Firefoxがネイティブに処理する空の文字セットと、XRegExpの提供する空の文字セットの挙動が食い違ってしまっている(1行目 1、2列)、というのが冒頭の指摘。でもこれは重要ではないし、同様の指摘を Operaについても行うことができる(3行目 1、2列と 3、4列)。

そうではなくて、ライブラリ利用者として「仕様に準拠した」「ブラウザ間で統一された」機能を望む視点から、XRegExpの提供する空の文字セットが Firefoxにおいてのみ違う意味になること(2列目)をこそ指摘すべきだった。(実際そうしたんだけど、日記の内容だけが古かったのでこうして追記している)

1列目2列目3列目4列目
RegExp("[]")XRegExp("[]")RegExp("[^]")XRegExp("[^]")
1行目Firefox(3.0.11, 3.5RC3)必ず失敗必ず成功成功したり失敗したり(後述)任意の一文字任意の一文字
2行目IE(8.0)例外必ず失敗例外任意の一文字
3行目Opera(9.64)任意の一文字必ず失敗必ず失敗任意の一文字
4行目Safari(4.0)必ず失敗必ず失敗任意の一文字任意の一文字
5行目Chrome(2.0.172.33)必ず失敗必ず失敗任意の一文字任意の一文字
 すごくおかしな Firefox(3.0.11)の (?!)

全てのパターンに (?!) が含まれているから、本当は全て falseになってほしい。

/(?!)/.test("abc")     //=> true!
/(?!)(?!)/.test("abc") //=> false

/^(?!)/.test("abc")    //=> false
/(?!)$/.test("abc")    //=> true!

/a(?!)/.test("abc")    //=> false
/(?!)a/.test("abc")    //=> true!

/\b(?!)/.test("abc")   //=> false
/(?!)\b/.test("abc")   //=> true!

/(?:)(?!)/.test("abc") //=> true!
/(?!)(?:)/.test("abc") //=> true!

(?!) が実質的にパターンの先頭にあるときだけ、存在しないかのように振る舞っているような。すごく限定された状況でのみ発動するんだったんだ。

ここまで書いてなかったことに気付いたけど、これが XRegExp("[]") に影響するのはこのパターンが内部的に (?!) に書き換えられるから。XRegExp (1.1.0, 2009-07-04)では \b\B に置き換えられるようになっていて、Firefoxのおかしな挙動に影響されて他のブラウザと違う結果になることはなくなっている


もう書かれてた! > http://blog.stevenlevithan.com/archives/xregexp-1-0/comment-page-1#comment-37653


 追記@2009-07-05 04:40: XRegExp-1.0の設計の良さを示す好例 > 20090703c02

自身の機能を XRegExp.addToken()で提供しており、ユーザーも同じ方法で拡張できる。トークンの有効範囲が文字セット [……] の内か外か、その両方かは addToken()の第3引数のビットフラグ( XRegExp.INSIDE_CLASS, XRegExp.OUTSIDE_CLASS )で指定できるし、エスケープシークェンスは予め処理されているので \(?!) を \\b\B に置き換えてしまう心配もない。簡単でしょう。

でも鬼車の \g を追加しようとして、名前付きキャプチャ内部のパターンを手に入れられず、中断。


 追記@2009-07-05 04:40: \gの実装サンプルを見てみた。

その \gの利用例が次。

XRegExp("(?<any_backref>\\3|\\4){0}     \
         (?<smiles>(?::-[D)])+){0}      \
         ([ab]) ([12]) \\g<any_backref>+\
         \\g<smiles>                    ", "x").test("a22aaa:-):-D"); // true

解説すると、まず名前付きキャプチャを使って再利用を前提としたパターンを定義する(any_backrefと smiles)。ポイントは量指定子{0}の存在で、現状ではこれが必ず必要になる。これによりこの名前付きキャプチャはマッチに参加せず、一文字も消費せずに成功する。その存在意義は \g<smiles> のように、後で名前を使ってパターンを再利用できること。

上の例で、実際に文字を消費するパターンは後半分、次の部分だけ。

    /([ab])([12])\g<any_backref>+\g<smiles>/

Rubyで同じ意図を持って書くとこういうふうになる。

irb> any_backref = /\1|\2/
=> /\1|\2/
irb> smiles = /(?::-[D)])+/
=> /(?::-[D)])+/
irb> re = /([ab])([12])#{any_backref}+#{smiles}/
=> /([ab])([12])(?-mix:\1|\2)+(?-mix:(?::-[D)])+)/
irb> re.match( "a22aaa:-):-D" ).to_a
=> ["a22aaa:-):-D", "a", "2"]

さて、パターンの再利用が \g の利用法だけど、個人的にそれが最大の効果を発揮するのは、パターン定義の中でパターンを呼び出したとき、再帰的なパターンを定義できることだと思っている。20080111p01で既に書いているが、

 /%[Qq]?(?<brace>\{[^\{}]*(?:\g<brace>[^\{}]*)*})/

名前付きキャプチャ (?<brace>……) の中に \g<brace> がある。こういう GNUの命名のような再帰的定義ができてこそ \g を利用する価値があると個人的には思う。

addToken()で \g を定義する方法として考えていたのは「パターン文字列を、上限を決めて再帰的に展開する」というもの。このように。

  1. (?<brace>{\g<brace>})
  2. (?<brace>{(?:{\g<brace>})})
  3. (?<brace>{(?:{(?:{(?:{\g<brace>})}))})
  4. (?<brace>{(?:{(?:{(?:{(?:)})}))})

実際の手順は addTokenの第二引数が文字列を返す代わりに toString()を定義したオブジェクトを返し、xregexp.jsの下の引用部分、output.join("") の時点で、名前付きキャプチャ内のパターンを参照してパターン文字列を返そうというもの。

            regex = RegExp(output.join(""), real.replace.call(flags, /[^gimy]+/g, ""));
            regex._xregexp = {
                source:       pattern,

コードの枠組みはこんなの。もちろんこのままでは動かない。

function PatternOfNamedCapture(name) {
	this.name = name;
}
PatternOfNamedCapture.prototype.toString = function(){
	// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
	if(MAX_RECURSION < count++) {
		return "(?:)";
	} else {
		return output.slice(
			defs[this.name].start,
			defs[this.name].end
		).join("");
	}
	// ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
};
XRegExp.addToken(
	/\\g<([$\w]+)>/,
	function(match) {
		return new PatternOfNamedCapture(match[1]);
	}
);
本日のツッコミ(全4件) ツッコミを入れる

Steven LevithanActually, IE treats [] and [^] like Perl and almost every ..

Steven LevithanThanks for finding and documenting the Firefox (?!) bugs. ..

Steven LevithanRegarding your latest update (dated 2009-07-05 04:40), Oni..

Steven LevithanI just updated the example page I linked to so that the so..


2009年06月28日 (日)

最終更新: 2009-10-28T01:40+0900

[正規表現][Firefox] .NETの正規表現の再帰について(20080111p01の補足)。

再帰っていっていいのかな?鬼車は明らかに再帰だったけど。

.NETの正規表現を利用した経験はありません(念のため)。想像です。

.NETの再帰に関係した部分のドキュメントがあまりにわかりにくかったので整理。(実はわかりにくさの半分は日本語のせいだった。英語の方のドキュメントを読みましょう)

 1.スタックされる名前付きキャプチャグループ: (?<name1>……), (?'name1'……)

キャプチャ内容を読み出す従来の記法が \1, \2。名前付きキャプチャの場合は \k<name1>, \k'name1'など(実装ごとに異なる)。キャプチャグループに量指定子がくっついている場合、これらで読み出せるのは最後のキャプチャ内容だけ。だけどキャプチャの記憶領域はキャプチャグループごとに一つではなくスタックになっていて、その一番上の内容を読み出していると考える(.NETだか Javaだかではプログラムからこのスタックにアクセスできたはず)。

 2.名前付きキャプチャ name1のスタックを POPする: (?<name2-name1>……)

まず、これは name2という名前付きキャプチャグループ。と同時に、name1のキャプチャスタックを POPする。name2を省略して name1を POPするだけも可能。MSDNの日本語ドキュメントを名前の部分だけこちらに合うように書きかえたのが次。

既に定義されていたグループ name1 の定義を削除し、既に定義されていた name1 グループと現在のグループの間隔をグループ name2 に格納します。

実際に name2のキャプチャ内容がどういうものになるのか(直前の name1のキャプチャ内容、name2の……にマッチした部分、その間、がそれぞれ含まれるのかどうか)は読み取れないけど、直前の name1キャプチャをなかったことにし、name1のキャプチャ部分から ……までを name2に保存するという。(覚え方: name2は直前の name1から……まで)

当然のこと、name1のキャプチャスタックが空のとき name2のキャプチャは ……のマッチ正否に関わらず失敗する。

 3.名前付きキャプチャ name1のスタックが空であることをテストするイディオム: (?(name1)(?!))

name1のキャプチャに成功しているかどうかで適用するパターンを変化させる条件分岐 (?(name1)truepattern|falsepattern) と、必ず失敗するパターン (?!) の組み合わせにより、name1のキャプチャスタックが空でないと必ず失敗する。逆に name1のキャプチャスタックが空のときは(省略された空の falsepatternが何にでもマッチして)必ず成功する(はず)。

(?(name1)(?!)) が成功する(=name1のキャプチャスタックが空である)とは、name1に含まれるパターンが開きかっこ、name2のパターンが閉じかっこにマッチし、その間のパターンが開き閉じどちらのかっこにもマッチしないとき、開きかっこと閉じかっこがバランスしている、ということ(MSDNの例がこれ)。

なんてこったい

// Firefox 3.0.11と 3.5RC3のロケーションバーでの実行結果。
javascript:alert(/(?!)/.test("")) //=> true (falseであってほしい)
javascript:alert(/(?=)/.test("")) //=> false (trueであってほしい)
javascript:alert(/(?:)/.test("")) //=>
// Windows Vistaでの JScript(WSH)実行結果。
WScript.Echo(""+ /(?!)/.test("")) //=> false (期待通り)
WScript.Echo(""+ /(?=)/.test("")) //=> true (期待通り)
WScript.Echo(""+ /(?:)/.test("")) //=> true (期待通り)

必ず失敗するパターンを試してみたら Firefoxで真逆の結果が出てしまった。Firefoxが間違っててくれないと困るよ。(ブラウザにより逆の結果がでていることがもう困るけど)

(参考)間違いなく失敗する* > 詳説正規表現第3版 - Google ブック検索, 詳説正規表現第3版 - Google ブック検索

Internet Explorer 8.0.6001.18783 64-bit Edition、Safari 4.0.530.17、Opera 9.64、Google Chrome 2.0.172.33 でもみんな Firefoxとは逆の結果になる。Firefoxだけが 3.0から 3.5RC3になっても違っているのは悪夢だ。

Firefoxに理がないことは次の例からも判断できないか。空のパターンを空と空の選択パターンにするだけで結果がひっくり返ってる。

// Firefox 3.0.11のロケーションバーでの実行結果。
javascript:alert(/(?!)/.test("")) //=> true (下と同じであるべき)
javascript:alert(/(?!|)/.test("")) //=> false (期待通り)

javascript:alert(/(?=)/.test("")) //=> false (下と同じであるべき)
javascript:alert(/(?=|)/.test("")) //=> true (期待通り)

javascript:alert(/(?:)/.test("")) //=> true (期待通り)
javascript:alert(/(?:|)/.test("")) //=> true (期待通り)

* 念のため断っておくと、書籍の文脈では、この断定は Perlと .NETの正規表現に(ちゃんと)限られている。


2009年03月17日 (火) XRegExp(シンプルな正規表現しか使えない JavaScriptにモダンな拡張機能を提供する JavaScriptライブラリ)の作者が『Regular Expressions Cookbook』を書いた(共著)って。気になるけどクックブックってどんなんなんだろう。「読める」本ではないような不安があるんだけど。

[][正規表現] [ペーパーバック] Jan Goyvaerts, Steven Levithan 【Regular Expression Cookbook】 Oreilly & Associates Inc

Regular Expression Cookbook Regular Expression Cookbook
Jan Goyvaerts/Steven Levithan
Oreilly & Associates Inc
¥ 4,073

XRegExp: JavaScript regex library」は、「SyntaxHighlighter(Ver.2.0)」で使われていたので知った。XRegExpの作者が、本の著者の一人、Steven Levithan。

XRegExp(Ver.0.6.1)が、Firefox 2,3、Internet Explorer 5.5-8β1、Safari 3.1、Opera 9.27の JavaScriptに付け加えて利用可能にする正規表現の機能は

  •  名前付きキャプチャグループ

    後方参照も可。String.replace()での使用も可。

  •  s(singleline)フラグ

    「.」が改行にもマッチ。

  •  x(extended)フラグ

    ほとんどの空白を無意味なものにしたり(=パターンを自由にインデントできる)、行コメント(#から改行まで)を利用可能にする。

  •  インラインコメント

    (?#ここにコメント)

  •  Unicode Properties & Blocks サポート

    こんなの。\p{L}, \p{M}, \p{N}, \p{InHiragana}, \p{InKatakana}。否定は大文字のPで \P{}。

    文字集合の中で使えないという制限があるが、若干の工夫でなんとかなる。

使い所が限定されていそうだったり、使い方が難しそうな機能として

  •  XRegExp.matchRecursive(string, left, right, modifiers, options)

    (独り言) 名前付きキャプチャグループをサポートしたなら、そのキャプチャ結果をスタックして、そこにパターンを繰り返し適用する書き方を用意することで、matchRecursive()なんてスペシャルメソッドは不要にできるのでは? <何を言ってるのか自分でわかってないよ

    XRegExpのコンストラクタに「脳log[2008-01-11-p01] 鬼車すごい。正規表現で再帰ができる。」で書いたようなパターン文字列を渡して、再帰を認識するマッチングを行いたいです。

正規表現に関連するいくつかのメソッドを上書きし、ブラウザ間の差異を吸収するとともに、仕様通りの動作に統一したりもする。

  •  /pattern/g.lastIndex

    IEなどが、幅0の文字にマッチしたときに lastIndexを不当にインクリメントするのを修正。

  •  String.split(separator, limit)

    • 分割パターン中のキャプチャグループを戻り値の配列に挿入する。
    • マッチに参加しなかったキャプチャグループの位置に undefinedを挿入する。
    • 連続するセパレータの間などに存在する空文字列を省略せずに戻り値の配列に含める。

    (独り言) limitを指定したときに戻ってくる配列の要素数が limitと一致しない。XRegExpのバグ? もちろん separatorにキャプチャグループは使っていない。


 再帰パターン

例えば再帰の深さの上限を 10や 20と決めてしまって、XRegExpのコンストラクタでごにょごにょパターン文字列を展開してみればどうだろう。JavaScriptで正規表現を一から実装しようというプロジェクトではないから、自ずと制限が定まってしまうのだけど、上限付きでもそれが 20もあれば十分という気がする。


 追記@2009-06-25: XRegExp 1.0がリリースされている(2009-06-24)。

驚いた。大部分がライブラリの機能紹介という退屈な(<作者本人が一番よく知っているから)文章に紛れ込んだバグ報告(とも呼べそうにないもの)を不自由な Google翻訳から見つけ出すとは。

1.0のソースも眺めてみたいけれど巨大な変更履歴にちょっと後込み。そのうちね。

本日のツッコミ(全1件) ツッコミを入れる

Steven LevithanHi. I translated this page using Google Translate. The la..


2008年11月10日 (月) 自動二輪でデュアルパーパス車(デュアルといいつつ実質オフ寄り)を嗜好し、自転車ではクロスバイク(の中でもロード寄り)を好むのに、共通点はどこにあるのかと思えば、軽くて扱いやすい所だったらしい。オートバイにライトウェイトスポーツというカテゴリはないのか。スーパーモタードだけなのか。(それでいいんだけど)

[正規表現] >Ruby 初心者スレッド Part 22 >>861 (http://www.kt.rim.or.jp/%7ekbk/zakkicho/08/zakkicho0811a.html#D20081109-6 経由)

if line =~ /.*Sector:<.*(Basic Materials|Conglomerates|Consumer Goods|Financial|Healthcare|Industrial Goods|Services|Technology|Utilities)/
    p $1
end 

HTMLをその場その場の正規表現で処理したくはないけど、それはそれとして、こうする。要は「Sector:HOGEHOGE」というテキストにタグがいろいろ付いていて、それらを無視してセクタ名を取り出したいということかと。

   /Sector:(?:<[^>]+>)*(Basic Materials|Conglomerates|Consumer Goods|Financial|Healthcare|Industrial Goods|Services|Technology|Utilities)/

元のパターン冒頭の .* は全く無駄。一度文字列全部を食べてしまうことに無駄以外の意味はない。(後ろから「Sector:」を探すか、前から「Sector:」を探すかという違いはあったりして)

二番目の .* が以降の文字列すべてを食べてしまうのも無駄。それにそれじゃあ「Sector:」から最も離れたセクタ名と同じ単語に一番最初にマッチしてしまう。

以上お目汚しでした。それより、この質問への最初の回答は金言。良いなあ(こんなレスがすぐに付くなんて)。

 正規表現は書き方を覚えないと駄目
 なぜなら、ほんの少し変えようと思っただけで別物になるから
 コピペでやろうとすると異常に遠回りになる

基本的に覚えることは

  • 文字クラスとメタ文字(\w,\n,\s,...)
  • アンカー(^,$,\b,...)と先読み(戻り読み)
  • パターンのグルーピングと選択
  • 量指定子(これは文字にもグループにも付けられる)

だけだもの。


2008年05月28日 (水) コンテントネゴシエイションによる表示言語の切り替えはうまくない。内容が同じなら日本語で表示された方が読みやすいが、英語の方が情報が新しいのが常。Accept-Languageを切り替えるより URLを書き換える方が圧倒的に楽でしょう。読み手に選択肢を! >>>>>>http://www.mozilla.com/firefox/

[正規表現] 今日やられた正規表現

/^(?=\W)/
/^(?!\w)/

二つの違いは? (ヒント:空文字列/空行)


/^(?=\W)/  //=> 単語に使われる以外の文字から始まる行の頭にマッチ
/^(?!\w)/  //=> 単語に使われる文字から始まらない行の頭にマッチ
           //   (最初のパターンと違い、一文字もない場合(空行)にもマッチする)