最終更新: 2015-03-31T20:06+0900
この日記ではバグを再現できないので tdiary/wiki_style.rbの該当部を見てみた。
hikihtml = HikiDoc::HTMLOutput.new html.gsub!( %r!<a href="(.*?)">(.*?)</a>! ) do k, u = hikihtml.unescape_html($2), hikihtml.unescape_html($1) if /^(\d{4}|\d{6}|\d{8}|\d{8}-\d+)\D*([pctf]\d+(?:[-.]\d+)*)?$/ =~ u then %Q[<%=my '#{$1}#{$2}', '#{escape_quote CGI.escapeHTML k}' %>] elsif /:/ =~ u scheme, path = u.split( /:/, 2 ) if /\A(?:https?|ftp|mailto|javascript)\z/ =~ scheme u.sub!( %r@\A(?!mailto|javascript)\w+:(?!//)@, '' ) %Q[<a href="#{CGI.escapeHTML u}">#{CGI.escapeHTML k}</a>] elsif ( k == u ) %Q[<%=kw '#{escape_quote u}'%>] else %Q[<%=kw '#{escape_quote u}', '#{escape_quote k}'%>] end elsif k.empty? %Q[<a href="#{CGI.escapeHTML u}">#{CGI.escapeHTML u}</a>] elsif u.empty? %Q[<%=kw '#{escape_quote k}'%>] elsif k == u %Q[<%=kw '#{escape_quote u}', '#{escape_quote k}'%>] else %Q[<a href="#{CGI.escapeHTML u}">#{CGI.escapeHTML k}</a>] end end html end
154行目の正規表現は同じ問題を含んでるけど、ざっと見ただけで
あたりが違う感じ。どれが影響してエラーにならないんだろ。
……ならないんだけど、自分が正しいと信じる解決策を下のように適用しておいた(量指定子はこの日記のもともとの仕様に従って +? ではなく *? を使うように変更してある)。
html.gsub!( %r!<a href="([^"\n]*?)">(.*?)</a>! ) do
* メンバ変数に依存しないメンバ関数って……。まったく無駄な new!!!
最終更新: 2014-08-08T21:03+0900
少なくとも今使っている Firefox ver.23までは存在している不具合というか不都合というか。
にフォーカスがある状態でタブを切り替えて戻ってくると、テキスト全体が選択されておりキャレットのあった位置が失われている。
ただし、タブ切り替えでなくウィンドウ/アプリケーションの切り替えでは起こらない。
ただし、マウスクリックによって他の場所から input要素にフォーカスを移動していた場合には起こらない。
たぶんタブキーやラベルクリックによって input要素がフォーカスを得たときにテキスト全体を選択するという付加処理が関係してる。そういう選択処理が行われたか行われていないかによって、マウスクリックで input要素にフォーカスを移動したとき、一瞬テキストが選択されて即座に解除される様子が見えたり見えなかったりする。
これによって tDiaryで日付のそばのタイトル欄に日記を書くのにずっと不便を感じてるんだよね。セーフモードでスクリプト無効でも直らないし。
最終更新: 2014-09-04T19:38+0900
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>)
とか (?!-->)
と書くなど(だけではなく否定先読みとドットをグループ化したものでドットを置き換えるようにしないといけないかも)。空白を閉じタグのどこに挿入できるかできないかは調べられなかった。
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}"}
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)
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
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 を表示できるようになる。
やる気のないブラウザのことなんて知らない。
* 本来の意味の姑息を使うべき場面ですよ。
最終更新: 2014-01-03T21:32+0900
多分に自作成分(#p03.05)が入っているので指摘が公式版に当てはまるかは不明。
サブタイトル内で脚注プラグインを呼び出している過去のセクションに言及すると、言及した方のセクションにその脚注が現れる。
日記本文内で [[YYYYMMDDpSS]]
形式(Wikiスタイルの場合)でリンクを作成すると、それは my プラグインの呼び出しに展開され、my-ex.rb内で再定義された拡張版 my メソッドは apply_plugin( section.subtitle_to_html, true )
によってリンク先のサブタイトルの取得を試みる。これは evalに相当する処理であり、脚注プラグインのようにセクションの入り口で初期化処理を行いその後のプラグインメソッド呼び出しを待ち構えるタイプのプラグインを撹乱しているのではないか。
公式版で現象が報告されていないのは、このサブタイトル取得機能は言及された日付の日記データが @diaries変数に予め入れられていることを前提としており、実質的に同月内の直近のエントリへのリンクでしか機能しないからではないか。この日記で顕在化しやすいのは取得できるサブタイトルに制限がないからだ(20090114p01, 20110220)。
*_enter_procと *_leave_procの実行順序に期待できない Hikiでどうやっているか知りたい。
プラグインファイルのトップレベルでベタっと初期化していた。参考にならない。
同じ内容の脚注をまとめられる。
「* * * 脚注文章」とか「* *2 *3 脚注文章」という表示になる(アンカーはユニークでないといけないので並べる必要がある)。二番目三番目はともに fn(1) または fn(1, "*") と書いている。三番目は fn(2) と書いても良かったかも(その場合は自動的に決まる脚注マークに少し影響する)。
脚注マーク(title属性には脚注文章が入る)の遅延文字列化はない*⁑ので、まだ書いていない脚注を fn(4) などと参照することはできない。
脚注マークがかぶっていないときは連番を振らない。
これで冒頭の数文字を脚注マークに転用しやすくなる。二番目が登場したときに番号1をさかのぼって振れないのが少し不満。
第一引数と第二引数がともに文字列のとき、短い方を脚注マークと判断する。
これで冒頭の数文字を脚注マークに転用しやすくなるし、頭を悩ませることなくどう書いても正解になる。普通ならね。
無駄に手が込んでるのだけど、脚注よりはかっこ書きが、かっこ書きよりは整理して流れのあるひとつの文を構成する方が良い。タッチ操作で脚注マークの title属性を読む方法もわからないしね。
最終更新: 2016-11-18T12:59+0900
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
が指定された結果である。であるが、だからといって行頭の句点、句点だけの行(ぽつーん)はありえないだろうと思うのです。
かといって空白が関係するわけではないから white-space:nowrap とか使えないし、使えたとして FONTタグを <span style="font:~"></span>に置き換えるようなことに意味を見いだせたのはそれが 2000年頃のことだったからだし、段落全体を一行で表示したいわけでもないので Pタグに対してルールを追加することもできず、代替案が見つからない。
HikiDocで意味を持ってそうな記号を避けつつ句点+1文字を<nobr>で囲ってる。整形式でないというエラーが出るパターンがまだ残ってるかもしれない。footnoteプラグインとかわりと文章を渡すから危険だ。
全体を対象にするなら JavaScriptでやる。でもテキストを対象にしつつタグをインジェクトする方法がわからなかった。
マッチしたテキストノードをドキュメントフラグメント(テキスト+NOBRエレメント+テキスト)と置き換えればよかったのか?具体的方法が見えたところだが、レイアウトが変わってしまう変更を読み込み完了間際にスクリプトで行うというのはやっぱりよくないかも。フラグメント付きの URLでアクセスしたときターゲットが画面外に逃げてしまいかねない。
Operaは独自のレンダリングエンジンを放棄したし、Windows版Safariはアップデートが止まったし、Google Chromeはインストール場所がキモいから(今でもそうかは知らない。当然やめるだろうと予想するほどに、そして一瞬でアンインストールしてしまうほどにキモかったということだ)。
break-all
任意の位置で自動改行を行うが、日本語のテキストでは「line-break:normal」と指定したときと同じようにゆるい禁則処理を行う。
これを期待して待ってたんだけど。
この「word-break」というスタイルシートは文の改行の仕方を指定するもので、もともと Internet Explorer 独自の物だったようなのですが、最近になって Firefox もこれを採用したらしいのです。そのため、以前の Firefox では無効化されることを想定してこのスタイルシートが指定されていたと考えられます。
サイト制作者の想定としては IE のみにこのスタイルシートを指定したつもりが、意図せず Firefox でも有効化されてしまい、禁則処理のされない読みづらい記事が発生してしまったということだと思います。おそらく、IE の場合はこのスタイルシートが指定されていても禁則処理には影響がなかったのではないでしょうか。
先行する IEはまとも。後追いの Firefoxはバカ。日本人の貢献が足りないのか?
良くないと思います。
余談:Googleの検索仕様変更について
1ヶ月ほど前から、Google検索の左メニューが無くなっちゃいましたねえ。
スクリプトを無効にしてると今でも出てくるんですよ。畳まれたメニューをクリックしなくても大体の選択肢が羅列されてるんですよ。フォールバックが機能してるのをほめる前に、スクリプトを使って使いにくくしてることにあきれる。
auto_nobr処理の中の
/[^{}\[\]()*#"!'`=:|][、。」』!?!?)]+|[「『(]+[^{}\[\]()*#"!'`=:|]/u
というパターンは要するに
HikiDocで特別な意味を持たない1文字+行頭禁則文字、または、行末禁則文字+HikiDocで特別な意味を持たない1文字
という意味なんだけど、HikiDocで~という部分が
文字列を組み合わせて 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と空文字列の混同だとしてもありそうな話ではある。
ここまで書いてから見つけたのがこれ。
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で禁則処理が行われるのが本当で、それこそが望みの結果なのに。
「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"));
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完全対応待ち。
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」。</追記>
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を統合するのはなくなったんだろうか。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で規定)」。腹腔といい縦中横といい、ローカルルールでもこうと決めたからには正解!みたいなのってどうなん?
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*
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は分割(改行(折り返し)の挿入)を許可する要素だから自動的に字間の挿入も許可しているはず。期待して待っていていいかな?</追記>
WebKit以外は禁則に対応している。CSSには禁則ルールを「通常」から「厳しくする」と3段階の設定がある。両端揃えについては実装依存、「どういう実装をしてもいい。ただ日本語ではJLREQを参照するといい」としている。EPUB仕様も同様である。
Firefoxで word-break:break-allを指定すると禁則処理が行われなくなるのが問題。(対応してるが適用外)
Safariが全半角混在文章に対して日本語向けの両端揃えを選ばないのが問題。(xml:lang="ja-JP"なのに JLREQを参照すべき場面だと思われていない)
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とは雲泥の差。見るに堪えない。
URBANOのブラウザは Google Chromeではなかった。バージョンが4.XXXだということがかろうじてわかったがまったく素性が知れない。これは不自由ゆえか不慣れゆえか。
スマホの Google Chromeで表示してみると PCで見るより一行の文字数が少ない。固定サイズの背景画像を基準にして横幅をピクセルで指定しているために、ひと文字ひと文字をより多くのピクセルを使って精細に描画する(高DPIってこと)スマホでは文字数が減るんだろう。こういうのってベクタ画像をスケーリングしたり、左上角+辺の繰り返し+右上角の画像3枚構成で横幅を可変にしたりするのだろうか。そういうことができるのかどうかも知らないけど。それかこういうときにこそ画像の DPI値(JPEGにそういう値がなかった?)が役目を果たしてブラウザが勝手にリサイズしてくれたりするんじゃないんだろうか。DPIとピクセル数で現実世界の長さがわかるわけで、それを共通の尺度にしてブラウザが再度スマホにおいて画像を表示するのに使うべきピクセル数を求められるはずでは?でも現実世界の長さを共通の尺度にするとスマホの画面サイズは PCのモニタと比べて小さすぎるのか。でもそれはスマホが DPI設定をそれなりの高さに抑えておいてズームを駆使すればいいだけのことじゃないか。Windowsの DPI設定がモニタのピクセル密度を全然反映していないのだから何を考えても机上の空論か。そもそも、日記のテーマ(chiffon_leafgreen)で使われてる画像が DPI値をセットできない
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と同じレイアウトになって欲しいんだけど……。
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とレイアウトが同じになることに納得できる。
そして、1inが現実の 1インチに対応するパターンの他に、1pxが "reference pixel"に結びつけられるパターンが認められている。Androidの Google Chromeでは reference pixelを媒介にして CSS3 px と dpが一致してるってことなのかも。
これは字詰め。禁則とも字間調整(広げる方)ともかち合うので、これを適用しようとするとタグの交差に対処する方法を真剣に考えないといけない。でもやりたいなあ。読点とかぎ括弧、中黒は全角だと間延びしすぎだし、だからって半角文字を使うのも違う気がするし。ああでも、フォントを知らないと
<link href='https://fonts.googleapis.com/css?family=Noto+Sans:400,700,400italic,700italic&subset=latin,cyrillic,greek,vietnamese' rel='stylesheet' type='text/css'>
やった。ほとんど昨日貼ったリンク先のスクリプト(FLAutoKerning.js)の移植。行頭の約物の処理だけ省いた。折り返しでの天付きをせずにそれだけするのもどうかと思って。いい加減長いので詳細はソースの textfunc4_jizumeへ。
まとめると、フォントにメイリオを指定した Firefox(25.0)、Internet Explorer(9)以外お断り仕様になってしまいました。
Firefox25での表示(ベンチマーク)> 20131101p01.pdf(610KiB)
スクリーンショット追加@2013-12-05
Fx23 on Vistaとの違い。
Fx23 on Vistaとの違い。
プロポーショナルフォントにはもともと余分な空白はないし(※)、等幅フォントが指定してあるなら等幅であることを尊重すべきだし、実質、ほとんど等幅のプロポーショナルフォントであるメイリオでしか見るべき効果がない。
※参考にすること(メトリクスカーニングとオプティカルカーニング)@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]; }
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であるという仮定は、確かめてないけど、ほぼ正しいでしょう。
知ってるドメインだけど 11年前の記事とあって読んだことはなかった。2番目に見つかった、空白挿入で均等割り付けをやった人。ただし対象は WebKitでなく Operaと Mozilla。今の Firefox(1.5から?)は日本語文字間に調整用の字間を挿入してくれる。英字間にまったく配分しないのが行内の疎密の偏りとなって時々目立つというのが現在の問題。
is_proportionalが全体の50数%の時間を使っていたので結果をメモすることにした。読み込み完了直前に一回だけ実行する処理だから、スタイルやフォントの変更について行けなくても構わないよね。関数の消費時間の40%が削減できたので全体では2割の削減。
スピードアップの一環。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が間に入って、実際のところどうなってるんでしょ。
WBRタグでは効果を得られない。不可視の空白を挿入する方法ではスタイルシートを切ったときに空白が見える。またページ内検索でも、単語の文字と文字の間に空白を挿入しないと見つけられなくなる。第3の方法は CSS content属性を使うこと。スタイルシートを切ると空白ごと消えるし、ページ内検索でも挿入された空白は無視される。また、どのブラウザでも字間挿入効果があることは確認済み。
実装の問題は、要素の挿入ではなく要素で文字を包むことになるのを、他の文字列処理(禁則, 字詰め)が知って対処する必要があること。実際のところそれはもう済んでいて、さらに以前は禁則と字詰めと字間調整がバッティングする場面(「A」「B」など)で字詰めが効いていなかったのだが、これも解決した。解決していないのは、各々の文字列処理がテキストをぶった切るのだが、2番目3番目の処理は細切れのテキストノードを相手にしなければならないがために、メソッドの呼び出しとメソッドの固定された初期処理とタグを乗り越えて文字を先読みすることの回数が増え、効率が著しく低下すること。2段3段の文字列処理を加えるなら切断は一度で済ませたい。
対象が共通。操作も若干のバリエーションはあるものの大枠は同じ。対象と操作を選び組み合わせるところが固有。固有部分を分けながら実行時に効率的に融合させる方法。マニピュレータ付きのコンテクストを引き回す。操作を一旦ため込んで、マージしてから実行に移す。
あとで。
いいかげん HTMLに埋め込むには長々しいので独立した .jsファイルに。textfuncX_*はより簡素になったが、それを走らせる基礎を新しく書いた。過去の記録>textfunc.ver1.js。オーバーヘッドは ver2の方が大きい。が、多段のテキスト処理を前提にするなら組み合わせによる所要時間の増大は ver2の方が抑えられている、と思う。誤算がひとつ。IE9は visibility:hiddenな content属性値をクリップボードにコピーしてしまう。それでも、スタイルシートOFFで挿入した空白が見えてしまうことやページ内検索がほとんど不可能になってしまうことに比べればずっとマシではある。
フォント判別(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で名前を見た人達がいたので。おもしろい。
.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に追加した。
結果が出てから調べる奴。
圧倒的じゃあないか。
まだ高速化をあきらめない。is_proportionalの結果を要素にメモするのでなく font-familyの値をキーにしたメモをどこかに作ればいい気がした。でもそれには computed valueの定義の新旧を乗り越えないといけない(関連:used value)。
Fx23で1割しか改善しない。getComputedStyleで時間を食ってるのかも(Operaだと組み込み関数のプロファイルもとれるんだけど、Fxでは?)。
これね(またしても後手)。
前に読んでたけど「offsetWidth のようなプロパティを使ったり getComputedStyle のようなメソッドを呼ぶと」なんてそのものズバリの具体例が頭に残ってなかった。
経緯をふり返ってみる。
word-break:break-allから始まる一連のもぐらたたきの結末は、word-break:break-allをやめて自分で分離分割許可ポイントをブラウザに提示することだった。
一般的に参考になるのはこちらの記事でしょうね。自分の場合は優先順位が異なるので紆余曲折を経る必要があったわけだけど。
検索したら TeXとクヌース先生に行き着くのですね。ハイフネーションは第一義にはデータ圧縮問題だそうです。packed trieとかなんとか出てきた(そこに至るまでに linked trie, indexed trie, compressed trieなど。勉強になるなあ。WEB+DB PRESS vol.42で読んだ『高速文字列解析の世界』の人の記事はそれ自体が圧縮されていて何度読み返しても難しかった)。いつもの、書いたからやる羽目に陥ってるような気がしないでもない。­によるソフトハイフンは一番遅かった Firefoxでもとっくにサポートされてるらしい。URLみたいなのには、コピーには影響しないとしても、折り返し点でハイフンが表示されてほしくないな。うまく無視して word-wrap:break-wordに任せるか、代わりに WBRタグを挿入するか。字間調整のために挿入するスペース(.wbr:before{content:" "})の代わりに ­(.wbr:before{content:"\0000AD"})を挿入してみたけど Firefoxでは期待通りのソフトハイフン。Safari5.1.7/Opera18では異常な空間が生じる。IE9は単語間調整を優先して折り返してくれない。ジャスティファイのやり過ぎが嫌でいろいろやってるのに、調整幅の最大値を制限できないせいで。ぐぬぬ。
まだやっていました。textfunc5_nobr_over_asciiwordを追加。これは textfunc2, textfunc3から英単語を分割する効果のみを取り除くもの。分離効果が残ってるのがミソで、英字間に両端揃えのためのスペースを分配はするが、英単語の途中で折り返しはしなくなる。これは要するに word-break: normal; text-align: justify; text-justify: distribute
を指定して得られるはずの結果と同じ。
word-break: break-all
でぶった切るのが無難だと思うが、lang属性を積極的に活用する運用になってないので見分けられない。これの満足のいく表示が一番難しい。見苦しい部分が少ないながら残るものの総合的にみて満足してる。Firefoxでスクリプトを切ってこの日記を表示すると、より多くの散漫な行が目に入る。Opera18でスクリプトを切って見ると、行末が揃わない、ぽっかり数文字分の空白がそこここに現れる。IE9でスクリプトを切って見ると、何も変わらない……。IE9は英語でも日本語でもありえない表示は行わないし、text-justifyによって選択肢を提供してくれるけど、スクリプトによってそれを逸脱するような操作を行う余地がない(何をしても効果が見られない)。ま、いじれなくてももともと見られない表示ではないので構いません。字詰めに関してはどのブラウザでも同じように効果を発揮してる。かぎ括弧の余白が削れて満足。
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としてそのまま置いておく。
jquery.kerning.js|http://karappoinc.github.io/jquery.kerning.js/
Notice
使用するには、フォントに合わせたカーニングデータの作成が必要です。 現時点では、ソースコードに付属しているテンプレートを参考に、使用するフォントに合わせたカーニングデータを別途作成する必要があります。
※ フォントデータから自動的にjsonデータを生成するツールを試作中です。
こういうのはカーニングデータの重要度が9割以上だと思う。そしてカーニングデータはフォントに固有。そして使用されているフォントをほぼ確実に知るためには Webフォントが必要。字詰めの実装方法が letter-spacingであるとか、display:inline-block と margin-left、margin-rightの組み合わせであるとかは些細な違い(といいながら興味津々でソースをのぞいてるわけだ。俺の関心はそっち)。
content属性値は読まれてしまうそうです。空白文字に関しては、単語としての認識を阻害し、息継ぎが発生するらしい。
提供するフォントから余白に関するデータを一括で削除し、そこに CSSで余白を付けるという手順。フォント間の差異を一度リセットすることで共通のカーニングデータを利用できるようにする試みなんだろうか。
だろうとはいえ悪くはない。手抜きでも完璧でもないほどほどを狙ったサービスじゃないかと。
中里一日記: Mobile SafariのJavascriptで約物のアキ調整 Posted by hajime at 2011年09月29日 15:21
イカしたスクリプト>tsume.js
文字列って元をたどれば文字配列であって、れっきとしたデータ構造であり、しかもただの配列より便利なメソッドがいっぱい生えてるんだってことを思い出させてくれる。
日本語識別子って読む分にはいいけど、IMEを切り替えて入力するのはすんごく面倒なんだぜ。
百聞は一見に如かず>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でも改善していたみたい。やったね。
textfunc.jk.jsが読み込まれてるページ(たとえばここ)でコンソールを開いて
new TextLayoutInfo
と打ってみると、複数のプロパティを持つオブジェクトが返ってくる。その名前と値の表がこう
TextLayoutInfo\browser | Firefox 22 | Opera 28 |
---|---|---|
fontsize_zero_is_available | true | true |
no_kinsoku_under_wordbreak_breakall | true | false |
no_whitespace_distribution_inter_asciicharacter | true | true |
no_whitespace_distribution_inter_ideograph | false | false |
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以来ダメな点だ(この日記のタイトルを見よ)。
word-break:break-allと text-align:justifyで各ブラウザに起こる諸問題を解決し、未だサポートされない text-justify:distributeを実現するためのスクリプト。
undefined
をセットしていた(単に無視されて無害だがコンソールがうるさい)。副作用で1割くらい速くなった。* 全角文字の字間は拡大するが英字はそのままなので、一部は間延びして一部は詰まって見える。全体に散らした方が目立たないのに。@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)には最適化の余地があるってことだ。
最終更新: 2013-11-08T01:03+0900
表意文字で文章を書いていて使わない手はないでしょう(ローマ字がないと交差点の名前とか読めませんから)。こんなかんじで書くと、
ルビを付けたいテキスト ルビを付けたいテキスト
こういう HTMLになる(※インデントとコメントは後から追加した)。
<ruby> <rb>ルビを付けたいテキスト</rb> <rp>(</rp> <rt>ルビ</rt> <rp>)</rp> </ruby> <ruby> <rbc><rb>ルビを付けたいテキスト</rb></rbc> <rp>(</rp><!--本当ならここに rpタグは使えません--> <rtc><rt>ルビ1</rt></rtc> <rp> / </rp><!--本当ならここに rpタグは使えません--> <rtc><rt>ルビ2</rt></rtc> <rp>)</rp><!--本当ならここに rpタグは使えません--> </ruby>
2番目はうまく表示されないね。
シグナルイエローには《危険信号》っていう字があるから、本名と二つ名をルビで並記しようとするとルビのルビが必要になる。が、rb,rt要素は rubyを除いたインライン要素しか包含できない。
2番目の複雑形式は非サポートブラウザ向けのフォールバックがない(www.w3.org)らしいから rpタグを挿入するのは間違いだった。1.2.3 Complex ruby markupの最後に The rp element is not availableってしっかり書いてある。Firefoxも Operaもルビをサポートしないなりに期待通りの表示をしてくれたので良しとしておくが、よくよく考えてみれば、対応ブラウザには表示しないことを求め、非対応ブラウザにはタグを無視して中身をそのまま表示してもらうことを期待したタグなんだから、細かい出現規則(単純形式だけとか 0回か 2回(※rt要素の前後に限る)しか出現できないとか)を決めるのも従うのも意味がないのではないか。HTML5(dev.w3.org)は1番目の形式だけ、それも rbタグを取り除いたものがサポートされるらしい(ただしテキスト-rt要素の並びが1つの ruby要素の中に複数出現可能。だがベーステキストとルビがインターリーブされることで読み上げ、スタイルの適用、スクリプト操作、コピペなどに不都合があるとも)。ドラフトを読むと rubyタグのネストとか連続する rt要素で
細かい補足。パッチは別のパッチをあてた hikidoc.rbがベースだから素直にはあたらない。変更点の参考程度に。二重かっこでマークすることにしたけどかっこの対応は気にしてない(このいい加減さが rubyタグのネストを防いでたりする)。rtcタグが一度に1回か2回しか出現できないと知らなくて、ルビをいくつでも振れるかのようなコードになってるのが良くない。URLはリンクになるし強調とか打ち消しとかの装飾も、ベースになるテキストとルビ双方で有効みたい(hikidoc的にも XHTML的にも)。ルビに装飾って有用性がわからんけど、実は単なるインラインのテーブルだからできちゃいました(※後で苦労するフラグ)ってことなんか?
rubyforge.orgは Subversionサポートをやめたのかな。http://hikidoc.rubyforge.org/svn/ にリソースが見つからない。hikidocの最新版はどこから?
octocatいいよね(最後にこれはちゃぶ台返し?)。
自分でそういう書き方をするつもりがない(見た目が嫌いだ)から配慮しなかったけど、熟語単位でルビを付けつつ、ルビを個々の文字の上に配置したいかもしれない。熟語の上にじゅくごではなく、熟の上にじゅく、語の上にごを置きたいが、熟(じゅく)語(ご)みたいには書くのも表示されるのも嫌だという場合。セパレータを何か決めて、
熟,語 火,継,ぎの,祭,祀,場
こうかな?2番目はひらがな部分のルビを省略してるからフォールバックがうまくいかないだろうな。かといってルビで単語を分断したくないからこういう書き方を考案してるわけで、ひらがなにもルビをつけるしかないのか@2013-11-08。
hikidocで表のセルを横方向に連結する文字が > だから、 > や >> で区切ったときはその直前のセルが2マス3マス分に伸びるとか?
蟷 螂 大 人 気
入力のしやすさやノイズとしてあまり目立たないことからいえばスペースで区切りたいけど、ルビの部分に英語がきたり、空白で区切った姓名にルビを振るときに困る。とりあえず、使わないのに無用な複雑さは導入しない(さっさと決めないとアップデートで既存の文章を壊す危険性が増すとも考えられるが)。TODOにしとく。
1998年のルビの文書「ルビ(www.doraneko.org)」(翻訳されたW3Cワーキングドラフト)を読んでたら ruby-devで見かける名前を見つけた。バナナの下の力持ち。えらい人だなあ。
「ルビのために(hp.vector.co.jp)」という文書を読んでいたら最後にぼんやりと見覚えのある名前(筆者)を発見。そうだこの名前はここで見た。「JintrickのマイクロWeb日記, 2012 秋」
狭いなあというよりは。怖いなあ。
という経過を辿った。1分も考えなかった。ルビを振りたいテキストを囲むかっこはなくても困らないんだろうけど、1番目の理由によって存在している。
@2013-11-08 [[2.4. Autohiding Annotations(www.w3.org)|http://www.w3.org/TR/2013/WD-css3-ruby-20130919/#autohide]]
というよりは [[大規模ネットワークの性質と先端グラフアルゴリズム|http://www.slideshare.net/iwiwi/ss-15517666/7]]
最終更新: 2012-01-17T01:44+0900
自分の稼働中の tdiary.rbを見てみると該当部分はこうなっていた。
def load_plugins calendar @lazy_diaries.update( @diaries ) if @plugin @plugin.diaries = @lazy_diaries @plugin.date = @date @plugin.last_modified = last_modified @plugin.comment = @comment return @plugin end @plugin = Plugin::new( 'conf' => @conf, 'mode' => mode, 'diaries' => @lazy_diaries, 'cgi' => @cgi, 'years' => @years, 'cache_path' => cache_path, 'date' => @date, 'comment' => @comment, 'last_modified' => last_modified, 'logger' => @logger ) end
if @plugin ... end部分はたしか kazuhikoさんのコードだったような……。
そうそうこれこれ*。>>脳log[2009-08-06-p01] tdiary.rbと plugin/navi_user.rbにパッチあて(plugin/recent_list.rbの分は使用してないのでスルー) + セクションごとの最終更新日時に一票 >>[tDiary-devel] all_filtersとかload_pluginsが呼ばれ過ぎで遅い件
@plugin.diariesと @plugin.dateと @plugin.comment、それとたぶん @plugin.last_modifiedに更新が必要なのはすでにわかってた話なんじゃないの、ってことなんだけど、このコードは tDiary-3.0.1になったときに失われてる。svn logは「Merge branch 'master' into svn」なので経緯は不明。
>>Re: [tDiary-devel] #194 blog-category.rbが動作しない
* 「@plugin」で引っかからなかったので「捨ておけぬ」でサイト検索して見つけた。
最終更新: 2011-10-28T14:10+0900
^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(1\d)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1th of「\3」→ \2 ^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*1)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1st of「\3」→ \2 ^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*2)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1nd of「\3」→ \2 ^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d*3)\b.*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1rd of「\3」→ \2 ^http://www\.google\.(?:[^/]+)/url\?.*?[&;]cd=(\d+).*?[&;]url=[^&;]*?(\d{6}[\dpct]*).*?[&;]q=([^&;]+).*$ google検索 \1th of「\3」→ \2
パラメータの順番を仮定しないために、結局こうなった。名前付きキャプチャは関係なくて、繰り返し付きキャプチャに最終的に入るものを把握することが大事。
^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(1\d)|(?!cd=)[^&;]*))*$ google検索 \3th of「\2」→ \1 ^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*1)|(?!cd=)[^&;]*))*$ google検索 \3st of「\2」→ \1 ^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*2)|(?!cd=)[^&;]*))*$ google検索 \3nd of「\2」→ \1 ^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d*3)|(?!cd=)[^&;]*))*$ google検索 \3rd of「\2」→ \1 ^http://www\.google\.(?:[^/]+)/url(?:[?&;](?:url=[^&;]*?(\d{6}[\dpct]*)[^&;]*|q=([^&;]+)|cd=(\d+)|(?!cd=)[^&;]*))*$ google検索 \3th of「\2」→ \1
リファラ周りを覗いたついでに、セルフリンクの記録を解禁した。my-sequel.rbプラグインの機能を代替できるかな、と。
tdiary.confのベースとした tdiary.conf.sampleに最初からセルフリンクよけがあるのでこれを削除。dayモードからのリンクでないものを除外するように(=dayモードからのリンクだけを記録するように)ブラウザで設定した。(index.rbの有無とか日付を ?date=形式で渡してるかどうかとかサイトに合わせた調整が必要)
リンク元記録除外リスト ^http://vvvvvv\.sakura\.ne\.jp(?!/ds14050/diary/\d{8})
最新の日記にだけ表示される「以前の日記へのリンク元」からのリンクは自己言及とは言えずノイズでしかないので 05referer.rbの referer_save_currentに次のコードを挿入した。
# reject self-reference from (maybe) volatile list return if referer.start_with?(@conf.base_url) and (date = referer.scan(/\b(\d{4})(\d\d)(\d\d)\b/).last) and latest_day?(Struct.new(:date).new(Time.local(*date)))
期待通り動いてるか動いてないかはまだ観察できてない。
「以前の日記へのリンク元」へセルフリンクを記録しない方法をとらなかったのは、そこで過去の日記の動きを観察したかったから。でもリストに記録された日からどこへ移動したかはわからないね。リファラを集約するときに「どこへ(のリンクか)」という情報を蔑ろにしたせいだ。余計なお世話だと思ってた googleの新しい形式(といっても2009年)のリファラがその情報を与えてくれてたりする。
tdiary.conf.beginnerには FeedReaderからのリンクを記録しない設定が予め用意されている。俺も除外したいと思った。でも tdiary.conf.sampleには用意されていない。beginnerの定義、.beginnerと .sampleを分ける法が知りたい。
どうやら volatile referer listからのリンクの除外に失敗してる。原理的にリファラを表示したときとリンクを踏んだときの間に日記の投稿があった場合には除外できないんだけど、それにしては数が多い。volatile referer listは特別に内部リンクを表示しないことにする。
@referer_volatile.each_referer( limit ) do |count,ref| next if ref.start_with?(@conf.base_url) # この一行。 result << %Q[<li>#{count} <a rel="nofollow" href="#{h ref}">#{h disp_referer( @referer_table, ref )}</a></li>\n] end
最終更新: 2011-08-04T03:29+0900
日と日の境が行き過ぎてしまわないようにスクロール量を制限する。
うっとうしいかな? スムーズスクロールも効かないしな。でも AutoPagerizeの機能の一部としてあってもいいような気もする。
動作確認済み> Firefox 5.0, Internet Explorer 9, Safari 5.0.2, Opera 11.50
* この日記(tDiary-2.3.3.20091124)にはまだ js_urlメソッドとか enable_jsメソッドとか無いのです。
最終更新: 2017-09-15T10:23+0900
なんというか……ばっちい(だから非公開(書き直すつもりがないので観念した))。だらだら書き下すとこうなるのね、っていう。申し訳のように抜き出したカスタマイズポイントもうまく分離できなかった記憶がある。たんに切り取って別の場所に貼り付けただけだろう、と。svn logを見てみたら 2009年3月に書いたものらしい。最初はセクションごとに表示する仕様で、それだと日記を分断して邪魔なので一日の最後に表示するオプションを付けたとか。……logによれば。とりあえずこの日記(tDiary-2.3.3.20091124, Ruby-1.8.7-p248)では動いてるみたい。
「カテゴリ[……]の他の日記」リンクの URLはこの日記に特有の、最新表示とカテゴリ表示をくっつけたもののだから他の日記では使えないね(それも非公開の理由)。
2007年や 2009年の日記のタイトルが 2011年のこの日記から参照できてるのは「プラグインが自由に日記データを取得できる手段を提供した」恩恵を my-exプラグインが受けているからじゃないかと推測してる。
最終更新: 2012-12-12T02:17+0900
適当にカスタマイズして設置した。
XHTML化キットの存在は投稿されたアナウンスを読んで知ってたんだけど、XHTML好きにもかかわらず今まで導入してなかったのは、日記に数式を書く機会がなかったのと、出力段階での文字列置換による XHTML化が乱暴に思えたから。結局、他に方法がないので目をつぶったが。
閲覧不可能になった日記がないか月別表示で全日記を表示してみたら 10か所くらい見つかった。パターンは以下の通り。
<script>タグの中であっても & は & と書かなければいけない。不等号も。でも if(a && b) {}
とか置換してしまったらアホだ。CDATAセクションを使う。
scriptタグが解釈できないブラウザなんて PCを手に入れた当時から持ってなかったので
<script><!-- --></script>
なんて最初から書かなかったし、XHTMLを知ってからは下のように書くことにしてる。
<script>/*<![CDATA[*/ /*]]>*/</script>
コメントで囲うのは JavaScriptとして正しくあるため。より字数の少ない行コメントにしないのは、なにかでエラーになったから。MSがリリースしてた JavaScriptを暗号化(難読化)するツールに <script>タグを含んだ HTMLファイルを通したときだったか……。
但し書きを付けても無駄。HTMLと日本語は混ぜるな危険。中途半端な HTMLの隠蔽は悪。tDiaryスタイルと etDiaryスタイルより Wikiスタイル。
自業自得。さすがに、タグに挟まれた部分に & や <, > を放置してたわけではない。
<a href="ttp://hoge/script.cgi?a=b&c=d"> <img src="ttp://hoge/script.cgi?a=b&c=d">
すべてこの形の & がエラーになっていた。HTMLを出力するスクリプトでは、属性値は機械的に必ず HTMLエスケープするようにしてるんだけど、手書きだと上のような (X)HTMLは正しく見えてしまうのか、忘れられてた。こんなことがあるとやっぱりセミコロンで区切りたいねえ。
それにしても、ちらちら目に入る古い日記が恥ずかしくて死ねる。読む人間も、そもそも読める内容もないのであえて気づかぬふりで放置するけども。
最終更新: 2010-05-01T17:13+0900
リストをネストしたときだけ生のテキストに<p>タグが付くのがオリジナルのはてなと違うんだそう。ついでに空の<p>タグが生成されるのも気になるけどそれは別。
最初に。<li>はブロック要素もインライン要素も包含可能らしいけど両方を並べて含んでいいのかはわからなかった。読めない(block or inlineの 0以上の繰り返し、だという気はする)。Another HTML-lintが文句を言わなかったのでいいものとしよう。
次に、CodeReposの hatena_style.rbの中を見てみる。Hatena::Blockがなにやら特別な位置を占めてるけど、一応 Hatena::Inlineと同じように振る舞うみたい。ところで、Hatena::Blockはブロック要素の連なり、Hatena::Inlineはインライン要素の連なりを表してるけど、その両方の連なり(<li>の中身とか)を表すクラスがない。
てっとりばやく済ませるために、Hatena::BlockAndorInlineというクラスを用意して Hatena::Blockが行っていた仕事をごっそり移動するそれだけでなく。Hatena::BlockAndorInlineにはインライン要素を含んでいいのかを示すフラグを initializeを通して与え、それにより何が何でも<p>タグで囲むのかそのままにするのかという動作を変えることにした。現状、求められてるのがその違いだけだから。
Hatenaスタイルは使ってないからあからさまなバグがあっても気付かない可能性が大きいです。
suppress_ptag_in_nested_list.patch (7.1KiB, 2010-04-16, r37217との差分)古くなりました。
順序付きリストや定義リストの対応を忘れてる。
<dt>の中はインライン要素の 0以上の繰り返し、<dd>の中はインライン要素とブロック要素の 0以上の繰り返しっぽい。Hatenaスタイルでは定義リストにはインライン要素しか認めていないので対応は必要なし。順序付きリストだけ。
suppress_ptag_in_nested_list.rev2.patch (8.2KiB, 2010-04-16)
svn diffで -x -p (-x <ARG>: diffに渡すオプション, -p: Cの関数名を表示)を初めて付けてみたけど、Rubyスクリプトであってもクラス名は拾ってくれるみたい。
次々空いてくる穴をふさぐのはどちらかといえば好きだけど……。
それはそれとして、
Hatena::Section#bodyは期待通りうごかないかも。(目的がデバッグ出力だったなら OKだけど)
def body @tree.body.to_s end
@treeは以前は Hatena::Blockか Hatena::Inlineだった、俺の変更で Hatena::BlockAndorInlineか Hatena::Inlineになった。カスタマイズされた to_sメソッドを持っているのは Hatena::Blockだけ。
肝心の深すぎる再帰は再現できない。自分自身を子要素に持ってるなんてことはないはずだから convertメソッドが自分自身を呼び出すしかない。その可能性があったのは Hatena::Block#convertのみ。そのときは if @body == selfでチェックして @body(Hatena::Block).convertを回避していたし、今は @bodyに Hatena::Inlineか Hatena::BlockAndorInlineしか入っていないから自分自身を呼ぶことは考えられない。わからん。キャッシュのせいだったら拍子抜けだなあ。<<<以前は @bodyに Hatena::Blockが入っていたんだから、古いデータと新しいコードの組み合わせで無限再帰は十分ありうる話。.parserキャッシュにはクラス名やインスタンス変数も入ってるし。
fix_c37239.patch (623B, c37239の修正)
サブタイトルの有無の判断が間違って反対になっていた。
サブタイトルのみのときにエラーになっていた。
fix_c37240.patch (955B, c37240の修正)
順序付き、順序なしリストの階層が勝手に深くなっていた。
セクションの分割に失敗していた。
たぶん HTMLソースレベルの空行なんだと思う。Hatena::BlockAndorInline#convertでインライン要素の後だけは改行を付加しないようにすればなくせるが、<p>の有無と違って(white-space:preとかしない限り)見た目に関与しないのでこだわらない。(<p>の後ろと</p>の前にも改行が挿入される、だとか考え始めるときりがない)
スレ主は hatena_style.rbを使うことにこだわってはいないという。こちらは楽しんでやってるんだけど、いつまでもデバッグに付き合ってもらうわけにもいかないだろう。はてなはウクレレ記法ができてこそのはてなだと思ってるので、それができない hatena_style.rbから乗り換えるのも悪くないと思う。
「ウクレレ記法」で検索したら主犯が見つかった。最初は Hikiに実装したって書いてるよ。(始めて一か月ほどだったとも書かれていて)つくづくプログラミングは何に使うかが大事だと思う。(英語なんかと一緒でツール)
http://coderepos.org/share/ に書かれている通りにメールを送ってみたが音沙汰がない。スパム判定されたか。comitterってそのページに書かれてるので全てだよね。行動力のある人間なら(というか人並みの人でも) Twitterなり IRCなり blogなりでアクションを起こすんだろうが……。(だから手続きが自動化されてなさそうなのが嫌だったんだ。漏れるし遅れるし。それにサービス自体個人の奉仕に依存してる)
気になる。自分のバグも他人のバグも気になる。
Hatena::Section#initializeの部分の修正は、既に行頭の *で分割された文字列が与えられるから問題ないとして先のファイル(fix_c37240.patch)に含めてなかったけど、まあ subが gsubになったって減るもんじゃなし。
それだけでなく 実は一行だけ buffer = '' を buffer = lines.shiftに書き換えた。これがないと無限ループに陥るケース(「>>>」という本文。3文字目は他の文字でもいい)があるみたいなので。
最終更新: 2010-05-07T12:01+0900
設定画面に jQueryが仕込まれたのと、JavaScriptの置き場所が用意されたので、ファイルのアップロードを非同期に行う準備が整ったのではないか。独自のサブミットボタンを編集画面に貼り付けるプラグインが全部非同期になれば、プレビュー画面にアップロードフォームを表示しても(編集中の本文が消えたと)怒られないだろう。
ジャンクだけど Firefox3.6と Internet Explorer8と Google Chrome4.1で動くものをこの日記(最新じゃないので jQuery対応ではない)に仕込んでみた。
これで本文を書きながらプレビューもできるしファイルのアップロードもできる。
クリップボードアクセスのために Flashを使うよりはましだけど、<iframe>に頼らなければいけないというのもわりと屈辱的。<frameset><frame>以上に、これまで存在を認めてこなかったのに。
何となくできないものと思っていたけど <iframe>の中から外のドキュメントにアクセスすることができるみたい。それができたら編集画面が無制限に入れ子になる(編集画面の<iframe>の中の編集画面の<iframe>の中の……)ことを今より簡単に防止できて、スクリプトももうちょっと使いやすく書き換えられる。
「Karetta|[JavaScript] Firefoxでtextareaのカーソル位置に文字列を挿入した後にスクロールが先頭に戻ってしまう問題(karetta.jp)」で紹介されている通りで直った。といってもできるだけ余計なことをしないように条件を付けたが。
var scrollTop = textarea.scrollTop; /* ここで、テキストエリアに文字列を挿入する。 */ if(0 == textarea.scrollTop && 0 < scrollTop) { // スクロール位置がリセットされていたので復元する。 textarea.scrollTop = scrollTop; }
ところで、Internet Explorer 8だけはテキストエリアのキャレット位置を保存しようなんてことを考えなかったみたいで、いったんテキストエリアがフォーカスを失うとキャレットが必ず末尾に移動する。スクロール位置がどうこうどころではないわな。
「何となくできないものと思っていたけど <iframe>の中から外のドキュメントにアクセスすること」 < HTML5 sanbbox属性
最終更新: 2014-12-05T17:23+0900
送信元が限られてるのかワンパターンだったので、これまでは </a> を NGワードにしてほとんどの spamを防いでいたんだけど、今日は手ひどくやられた。
フィルタはインストール方法がよくわからない。Comment-key Filterを tdiary/filter/ に設置したがエラーが出るので、とりあえず最新の 2.3.3.20091124にアップデートした。そうすると plugin/60sf.rbが有効になっているはずなので misc/filter/ にフィルタスクリプトを、misc/filter/plugin/ にフィルタに関連する設定などを表示するプラグインスクリプトを、misc/filter/plugin/ja/ などにプラグインの言語リソースをインストールし、設定画面でフィルタを有効にするといずれのスクリプトも読み込まれるようになる。
コメントフォームにキー文字列を埋め込み、コメントが HTMLフォームを通して投稿されたことを確認する。キーは日記設置者の決めた文字列と日記の日付によって一意に決められるので、ある日のキー文字列を一度取得すればその日には何件でも機械的に投稿することができる。GETして HTMLを切り刻む手間をかければ最初から機械的に投稿することもできる。そんなのは防げない。
tDiary-2.3.3.20091124にインストールするのなら key.rbを misc/filter/key.rbへ、comment_key.rbを misc/filter/plugin/key.rbへ、ja/comment_key.rbを misc/filter/plugin/ja/key.rbへコピーすれば良い。key.rbというファイル名と KeyFilterというクラス名は対応しているので、key.rbから comment_key.rbへの改名はやめたほうがいい。コピー後にフィルタを有効にし、キー文字列を設定する。
連投対策。同じ IPアドレスからの連続する投稿をはじく。30分につき 3件までしか許可しない、など。
# coding: utf-8 # # limit_freq.rb: # # a spam-filtering plugin of tDiary # rejects frequent comments posted from an IP address. module TDiary::Filter class LimitFreqFilter < Filter def comment_filter( diary, comment ) now = Time.now.to_i comment_i = Comment_t.new( now, @cgi.remote_addr ) # 数値形式の日時と文字列形式の IPアドレスの二要素 # 配列の配列を、日時をもとに昇順にソートしたもの。 # log = [[123456, "1.2.3.4"], [234567, "5.6.7.8"],...] log = [] require 'pstore' ps = PStore.new( cache_path ) ps.transaction( false ) { |db| log = db.fetch( DBRoot, log ) # 新しいコメントを logに追加する。 log.insert( ArrayExtension.lower_bound(log){|pair| pair.first - comment_i.time }, [comment_i.time, comment_i.ipaddr] ) # 古い logを捨てる。 oldest = now - time_span log.slice!( 0 ... ArrayExtension.lower_bound(log){|pair| pair.first - oldest } ) db[DBRoot] = log db.commit } # 残った logに投稿者の IPアドレスがいくつ含まれるか数え、 # その数が閾値を超えていないか確かめる。 count = 0 if log.all?{|pair| count += 1 if pair.last == comment_i.ipaddr count < threshold } then return this_is_not_a_spam( comment ) else debug("limit_freq.rb: spam: the # of comments posted from #{comment_i.ipaddr} exceeds threshold:#{threshold} within the last #{time_span.to_i}seconds.") return this_is_a_spam( comment ) end rescue debug("limit_freq.rb: BUG: #{$!}") return this_is_not_a_spam( comment ) end private DBRoot = 'LimitFreqLog' Comment_t = Struct.new(:time, :ipaddr) def cache_path return File.join( (@conf.cache_path || "#{@conf.data_path}cache"), 'limit_freq.data' ) end # どれだけの期間(秒単位)、コメントの投稿頻度の判定に # 利用するログ(日時とIPアドレスのペア)を保存するか。 def time_span 30 * 60 end # time_span当たり、何件目の投稿からを拒否するか。 def threshold 4 end module ArrayExtension # ソート済みだという前提を活かしていない! def lower_bound( arr, &block ) index = 0 arr.length.times{ return index if 0 <= yield( arr[index] ) index += 1 } return index end module_function :lower_bound end end end
やめればいいのに本体もちょろちょろと変更。
Index: core/tdiary.rb =================================================================== --- core/tdiary.rb (リビジョン 44436) +++ core/tdiary.rb (作業コピー) @@ -342,6 +342,22 @@ @logger.info("#{@cgi.remote_addr}->#{(@cgi.params['date'][0] || 'no date').dump}: #{msg}") end + + private + # config: hide or drop spam comment + def hide_spam? + return @conf.options.include?('spamfilter.filter_mode') && @conf.options['spamfilter.filter_mode'] + end + + def this_is_a_spam( comment ) + comment.show = false + return hide_spam? + rescue + return false # comment could be a String(@cgi.referer). + end + def this_is_not_a_spam( comment ) + return true + end end end @@ -1303,7 +1319,7 @@ filter_path = @conf.filter_path || "#{PATH}/tdiary/filter" Dir::glob( "#{filter_path}/*.rb" ).sort.each do |file| require file.untaint - @filters << TDiary::Filter::const_get( "#{File::basename( file, '.rb' ).capitalize}Filter" )::new( @cgi, @conf, @logger ) + @filters << TDiary::Filter::const_get( "#{File::basename( file, '.rb' ).split(/[-_]+/).map{|x|x.capitalize}.join('')}Filter" )::new( @cgi, @conf, @logger ) end end Index: core/plugin/60sf.rb =================================================================== --- core/plugin/60sf.rb (リビジョン 44436) +++ core/plugin/60sf.rb (作業コピー) @@ -4,10 +4,9 @@ # Modified by KURODA Hiraku. SF_PREFIX = 'sf' -@sf_path = ( @conf["#{SF_PREFIX}.path"] || "#{::TDiary::PATH}/misc/filter" ).to_a -@sf_path = @sf_path.collect do |path| - /\/$/ =~ path ? path.chop : path -end +@sf_path = ( @conf["#{SF_PREFIX}.path"] || "#{::TDiary::PATH}/misc/filter" ).to_a.map{|path| + path.chomp('/') +} # get plugin option def sf_option( key ) @@ -128,7 +127,7 @@ if File.readable?( path ) then begin require path - @sf_filters << TDiary::Filter::const_get("#{File::basename(filename, ".rb").capitalize}Filter")::new(@cgi, @conf, @logger) + @sf_filters << ::TDiary::Filter::const_get("#{File::basename(filename, ".rb").split(/[-_]+/).map{|x|x.capitalize}.join('')}Filter")::new(@cgi, @conf, @logger) plugin_path = "#{dir}/plugin/#{filename}" load_plugin(plugin_path) if File.readable?(plugin_path) rescue Exception
一件二件すり抜けるぐらいはいいかと思っていたがここ数日は毎日なのでいいかげん腹が立つ。なによりアクセスログを見たらヒット数、転送量トップが spammerだというのが決定的に許せない。節度を知れ。iptablesはいじれないので .htaccessで Apacheに拒否してもらう。
spamコメントがくる月っていうのは、いつもの決まった 2、3か国からのアクセスではなく 10近い国から少数ずつアクセスがあるもんだけど、日単位では固定の IPアドレスから数十件の spamがくるのが常だった。でも今日は一件一件みごとに IPアドレスが異なっている。IPアドレスブロックで次のステージに進んでしまったのか。
spamコメントの目的が特定の URLへの誘導であるかぎりは、コメントに含まれる URLのドメインが白か黒かを判定する方法が有効でしょうね。外部に問い合わせるのは避けたかったんだけど、spamコメントの内容が URLも含めてワンパターンだから実際の問い合わせはごくごく限られた回数にできそうだし、悪くないかな。
ここで上記の comment-keyフィルタも含めてスパムフィルタの最新版が管理されていた。 >http://coderepos.org/share/browser/platform/tdiary/filter
plugin/00default.rbに含まれるメソッド navi_itemを自分でも使っていたのだけど、tDiaryをアップデートしたら三番目の引数が真偽値からリンクの rel属性文字列へと変更されているせいで rel="true" なるリンクができていた。こうする。
def navi_item( link, label, rel = nil ) rel = "nofollow" if rel == true # backward compatibility
スパムコメント一掃のために YYYYMM.tdcを削除したけど日記の編集画面からコメントが消えない。YYYYMM.parserを開くとコメントが含まれていたし、これを削除すると編集画面からもコメントが消えた。原因はデータファイルを直接削除するというイレギュラーな操作だけど、日記の編集はマスターデータに対して行いたい気もする。