月別アーカイブ: 2017年4月

Search RegexプラグインでWordPress投稿を正規表現検索置換

※今回の記事の内容、特に自己責任で試してね。投稿記事の破損についてなど一切の責任は負いません。

そもそもの問題

過去にこのような投稿を上げていた。

小ネタ:WordPressがHTML5に対応して、過去記事の見出しに困る

WordPressがHTML5に対応した際、それまで記事部分での見出しレベルトップはh3であったのが、h2に繰り上げとなった。その影響で過去投稿をいちいち引っ張り出してきて、手動で修正するのが面倒だよねという話。
それに対して、見出しレベルを変更するこんなプラグインもありますよと紹介していたのがこの投稿。

WordPressテーマ変更に伴う記事のルート見出しレベルの変動を吸収するプラグイン WP hn Convert

記事内でも書いている通り、このプラグインを使ってWordPress関数the_content()をリプレースする形だと、以降も見出しトップレベルをh3にして投稿をし続けなければならず、この時は採用を断念していた。結局、見出しトップレベルの問題は全投稿の見出しを手動で書き換えることで解決した。

そんな貴方にオススメ。Search Regexプラグイン!

3年くらい経って、解決法に気付いた。全投稿を正規表現を用いて検索・置換するプラグインがあれば、この問題は容易に解決することが出来たのではないだろうか。そういう痒いところに手が届くプラグイン、きっとあるはず。
ありました。それがSearch Regexプラグイン。早速インストールして、具体的な指定方法を考えてみよう。

こんな感じで、インストール後は"ツール"メニューに出現

こんな感じで、インストール後は”ツール”メニューに出現

まず何より、左下にあるRegexチェックボックスにチェックを入れよう。これに気付かず、しばらく格闘してしまった(ココの表示がボールドになっているの、おかしくない?)。Search patternフィールドに検索文字列を、Replace patternフィールドに置換文字列を入力する。Replaceボタンを押すと、置換例を表示してくれるけれどデータベースに反映はしない。Replace & Saveボタンは押すと取り返しのつかないボタン。データベースに即時反映するヤツなので、前者のボタンで置換例を見て微調整しつつ、上手くいっているようだったら後者のボタンを押すようにしよう。

正規表現を使って見出しレベルを一段ずつ上げる

では実際にやってみよう。見出しレベルが一段ずつ上がるので、たとえば先にh6を置換してh5にして…というように下からやっていってしまうと、最終的に全部h2になってしまう(笑)。h3をh2に、その次にh4をh3に…という順序で手をつけよう。

 
Search pattern:/<h3>(.*?)<\/h3>/
Replace pattern:<h2>$1</h2>

このようなパターンで、h3の見出しが見出し語そのままh2に昇格する。Replace pattern側の$1というのが参照文字(preg_replaceの解説なども参照)で、h3タグに挟まれた文字を一旦預かり、置換パターン側に展開してくれる。もし預かる箇所が2ヶ所以上だったら、$2、$3…という形で指定が出来る。ちなみに$0にはh3タグも含んだ文字列全体が格納されている。

Search Regexによる置換イメージ

Search Regexによる置換イメージ

この置換を、次はh4…とどんどんやっていけば良い。

自サイトの内部リンクからtarget=”_blank”を外す

次の課題。昔のWEB制作ではひたすらリンクにtarget=”_blank”を付与していたものである。とりあえず付けられるなら付けておくか、といった感覚で、WordPressのエディタでもチェックボックス1つで付けられてしまうものだからかかさず付けていた。
それが時代の変遷とともに訪問者の閲覧環境がモバイルデバイス中心になると、モバイルブラウザ上でウィンドウやタブを何枚も開くことは難しく、即ユーザビリティの低下となるため、頻繁にtarget=”_blank”を使うサイトは忌避されユーザの離脱率が高くなるという問題が顕在化した。そのため、最近は内部リンクについてはtarget=”_blank”をつけないページ作りが正解になっているらしい。
では、過去の投稿の内部リンクからtarget=”_blank”をはがすパターンについて考えてみよう。

 
Search pattern:/<a\shref="http:\/\/akisi\.tabiyaku\.net([^>]*?)\starget="_blank">/
Replace pattern:<a href="http://akisi.tabiyaku.net$1>

このサイトのアドレスがhttp://akisi.tabiyaku.netから始まるので前方一致に入れているわけで、自身のサイトで流用する場合は適宜自サイトのアドレスに変えて欲しい。また、このパターンはtarget=”_blank”がaタグの最後に来るという性善説で考えているので、流用の際にはそこも注意である。

その他Search Regexの応用例として、Tabnabbing防止にtarget=”_blank”付の外部リンクに自動的にrel=”noopener”を付ける処理を出そうかとも思ったのだけれども、なんだか分岐の可能性が多そうなのでやめました(元々rel属性に何かの値が指定されていた場合など)。我こそはと思う方はやってみて、上手いやり方を教えて下さいな。


W3CのHTML5.1勧告でsection直下h1の見出しレベル自動解釈が無効に?

一応結論だけ先に書いておくと、WEB制作上の影響はほぼ無いのでは?という話。

HTML5と見出しレベルの解釈

HTML5が世に放たれた際に、それまでのHTML4.01ないしXHTML1.0の宣言を行ったページでは不可能であったアウトラインの作り方として、セクション毎にh1を頂点とした独立した見出し階層を作り上げても、ページ全体で自動的に適切な見出しレベルに調整して解釈してくれるというものがあった。
たとえば、下のようなコード。

<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/base.css" type="text/css">
<meta name="description" content="サンプル株式会社の商品一覧ページです。">
<meta name="keywords" content="サンプル株式会社,ネットショップ">
<title>当社商品の一覧 | サンプル株式会社</title>
</head>
<body>
<div id="wrapper">
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="main">
<article>
<h1>サンプル株式会社の商品一覧</h1>
<p>当社が取り扱う商品の一覧です。</p>
<ul id="itemlist">
<li>
<article>
<h1>商品A</h1>
<p>さわやかなミントの香りの牛車です(牛は食べられません)</p>
<p>100,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=1" title="商品Aの商品詳細">>>商品詳細へ</a></p>
</article>
</li><li>
<section>
<h1>商品B</h1>
<p>官位です。これがあれば思いのまま(返品不可)</p>
<p>3,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=2" title="商品Bの商品詳細">>>商品詳細へ</a></p>
</section>
</li><li>
<section>
<h1>商品C</h1>
<p>古今和歌集です。新版が出たので在庫一掃セールです</p>
<p>16,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=10" title="商品Cの商品詳細">>>商品詳細へ</a></p>
</section>
</li><li>
<section>
<h1>商品D</h1>
<p>商品説明</p>
<p>100円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=15" title="商品Dの商品詳細">>>商品詳細へ</a></p>
</section>
</li></ul>
</article>
<article>
<h1>当社のモットー</h1>
<p>適当な仕事。深刻な社会的被害。</p>
</article>
</div>
<footer>
<p>サンプル株式会社 2012-2017 all rights reserved.</p>
</footer>
</div>
</body>
</html>

このコードを、htmlコードを入力すると自動的に見出しの階層構造を抽出してくれるWEBサービスHTML 5 Outlinerに与えると次のような結果が帰ってくる。

3階層のアウトライン

3階層のアウトライン

つまり各所で指定した見出しレベル(h1)以外にも、sectionタグやarticleタグなどのセクショニング・コンテンツを示すタグを参考に見出し階層を推測してくれているのだ。これが大体のブラウザや検索エンジンの解釈となる。
一方、従来的なhtmlのように、明示的に見出しレベルを指定(h1,h2,h3)しても同様の結果が帰ってくる。

<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/base.css" type="text/css">
<meta name="description" content="サンプル株式会社の商品一覧ページです。">
<meta name="keywords" content="サンプル株式会社,ネットショップ">
<title>当社商品の一覧 | サンプル株式会社</title>
</head>
<body>
<div id="wrapper">
<header>
<h1>サンプル株式会社</h1>
</header>
<div id="main">
<article>
<h2>サンプル株式会社の商品一覧</h2>
<p>当社が取り扱う商品の一覧です。</p>
<ul id="itemlist">
<li>
<article>
<h3>商品A</h3>
<p>さわやかなミントの香りの牛車です(牛は食べられません)</p>
<p>100,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=1" title="商品Aの商品詳細">>>商品詳細へ</a></p>
</article>
</li><li>
<section>
<h3>商品B</h3>
<p>官位です。これがあれば思いのまま(返品不可)</p>
<p>3,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=2" title="商品Bの商品詳細">>>商品詳細へ</a></p>
</section>
</li><li>
<section>
<h3>商品C</h3>
<p>古今和歌集です。新版が出たので在庫一掃セールです</p>
<p>16,000円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=10" title="商品Cの商品詳細">>>商品詳細へ</a></p>
</section>
</li><li>
<section>
<h3>商品D</h3>
<p>商品説明</p>
<p>100円</p>
<p><a href="http://xxxxxxxx.xxx/item.php?id=15" title="商品Dの商品詳細">>>商品詳細へ</a></p>
</section>
</li></ul>
</article>
<article>
<h2>当社のモットー</h2>
<p>適当な仕事。深刻な社会的被害。</p>
</article>
</div>
<footer>
<p>サンプル株式会社 2012-2017 all rights reserved.</p>
</footer>
</div>
</body>
</html>
やはり3階層のアウトライン

やはり3階層のアウトライン

この場合sectionやarticleなどのセクショニング・コンテンツを示すタグは、見出し階層の推測において無視されている。
HTML5では、このようにセクションを表すタグを使って見出しレベルを推測させることもできるし、従来のhtmlのようにあくまで明示された見出し階層のみを推測の根拠とさせることもできる。

W3CのHTML5.1勧告で自動解釈は撤回

そのような状況であったのだが、2016年11月1日に公開されたW3CのHTML5.1勧告で、様々なタグや属性の追加とともにある1つの仕様の削除について言及されていた。

The use of nested section elements each with an h1 to create an outline.

これはつまり、sectionやarticleのようなセクショニング・コンテンツを示すタグは、ページ内の最上階層で使われるもの以外、h1を頂点とした見出しレベルを構成してはいけなくなったということである。
問題をより簡潔に表現するならば、ページ内でh1は1回しか使うなということ。HTML5の登場以前からコーディングを行っている人間には常識のことであるし、登場以降もh1の濫用には何かペナルティがあるのではないかという認識の人間も多かったろう。彼らの時代が再びやって来たのだ。さあ新人コーダをいじめよう!

W3C勧告が当面のWEB制作上影響が無い理由

W3Cと言えば、WEB業界で使われる技術の標準化を行う団体で、HTML5が世に出たての頃など、ドキュメントも少ないので何か分からないタグの使用方法などあったらとにかくW3Cのページを見に行ったものである。そのW3Cのお墨付きがあるのだから、これからブラウザや検索エンジンの解釈も今回の勧告を基準にしたものに変わるのではないかということで、HTML5の登場とともにh1濫用型のコーディングに切り替えた私自身もビックリした(もう納品しちゃってるし…)。
ただ今回の経緯などを調べるうちに分かってきたのだが、確かにHTML5登場当時にW3CはWEB標準化の最前線と言ってほぼ間違いない団体であったが、それは次世代標準の提唱に頓挫してWHATWGという別の団体が提唱していたHTML5を全面的に受け容れることにした経緯によるもので、以降WHATWGとW3Cは協調路線を取っておらず、主要ブラウザなどの実装はWHATWGの提唱するHTML Living Standardのみを基準にしているようなのである(まあ実際、WHATWGの正体がブラウザベンダーの寄り合いのようなものなので当たり前である)。
ではW3C勧告が何の意味を持っているのかというと、実質何の意味も持たない。WHATWG側からHTML Living Standardの剽窃だとか欠陥フォークだとか、現在進行形で厳しい評価を受け続けている。

ということで、ページのアウトラインの形成においてHTML5.1勧告が世に出た後も依然2通りのやり方が使えるのは間違いない。一応、見出しレベルをタグの数字で明示するやり方だとW3Cの要求も満たすことが出来るということが両者の違い。セクション毎にh1を置くやり方はコンテンツの追加時に見出しレベルを気にせず追加できるというメリットがあるし、タグの数字で明示するやり方はソースコードを人間が見て一目で階層が分かり易い(ただし実際にどう解釈されているかを反映していない)というメリットがある。お好きな方でこれからも大丈夫。


box-sizing : border-box 最近はpaddingとborderを幅・高さから引かない

凄く今更な話だけれども、数年くらいWEB制作から離れていると見落としがちな常識の転換。

旧世代CSS:要素の幅・高さはあらかじめ余白と境界を引いた値にしよう

CSSで要素に対して幅や高さを指定する際に、要素の周囲を囲むborderの太さならびに境界内部の余白paddingの値は、あらかじめ目星をつけておかないといけない。というのも、widthやheightといったプロパティで指定された値に、borderとpaddingそれぞれの幅を加えたものが要素が親要素内で占有する大きさとなるからである。

旧padding,border計算方法

旧padding,border計算方法

上図の例で言うと、要素に対してwidthとheightで指定された幅200px高さ150pxという値は、余白paddingや境界線borderの幅がいくら変わっても保証される。その代わり要素自体の大きさは指定幅に加算されていくので、たとえば親要素の幅が400pxのところにこの.classが指定されたボックスをfloatを使って2つ並べたい場合、paddingとborderの幅を取ってしまうと2つ目のボックスが下に落ちてしまう。
そこで、ボックス落ちを防ぐためにはwidthとheightの値を、あらかじめpaddingとborderにあてがう幅分引いて指定しないといけない。上の例で言うならば、widthを150(200-20*2-5*2)px、heightを100(150-20*2-5*2)pxで指定しないと、ボックス落ちである。そして後々ページの見映えを変えたくて、paddingやborderの幅を変更したくなったらもう大変。変更の都度widthとheightの値を動かさないといけない。うわー面倒臭い。

かつてこんな感じに解決してました

この面倒臭さを解決する方法。一つは、クライアントにレビューの際にIE6を使わせる。IE6の場合paddingとborderはwidth,heightから引くという超モダンな解釈をしてくれる。そこで上の例で言えばwidthを200px、heightを150pxと指定しておいて、後からpaddingとborderを変更してもボックス落ちしない。クライアントには、IE6こそが先進的ブラウザで、その他99.9%のブラウザはゴミですと言い聞かせよう。
と、まあ冗談はさておき。問題を分割して、padding部分だけでも解決する場合。要素内にもう一つの要素を放り込んで、余白部分は内側の要素のmarginで指定するようにする。inner_**とかそういった命名の要素があれば、この問題を解決しようと苦心している可能性が高い。
そしてもう一つスマートな解決法として、CSSの変数を使用するというものがあった。CSS標準のものであれば、CSS Variables。

 
:root{
--tekitou-padding-value: 20; /* 変数名は--から始まる適当な名前 */
--tekitou-border-value: 5;
}
 
.class{
width: calc(200 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
height: calc(150 - var(--tekitou-padding-value) * 2 - var(--tekitou-border-value) * 2);
padding: var(--tekitou-padding-value);
border: var(--tekitou-border-value);
/* var(変数)で変数を定義された値に展開 */
/* calc(数式)で数式を展開 */
}

このようにすれば、後でpaddingとborderの値を変更しても、自動的にwidthとheightの値も連動する。
ただし、このCSS Variablesやcalc関数については対応ブラウザの問題がある。ベンダープレフィックスをつけないと動かない可能性は勿論のこと、CSS Variablesの方はEdgeの登場によりお役御免となったIEでサポートされてないのが痛い。ということで、変数を使う場合は主にSassやLESSのようなCSSプリプロセッサに頼ることになるだろう(コード例は省略)。CSSプリプロセッサを使う理由の全てがこの変数と数式計算という人も、きっと多いはず。

新世代CSS:box-sizing : border-boxで余白と境界をサイズに含めない

この余白と境界の値をあらかじめ考慮しないとボックス落ちしてしまうという仕様は、レスポンシブデザインにおいてはたいへん都合が悪かった。というのも、画面幅に応じて要素の幅も伸縮するレスポンシブデザインでは、幅の指定にパーセントを使用するからである。ボックス落ちしない余白と境界の値をあらかじめ知るのは不可能なので、デザインに遊びを持たせるか、フラットデザインのようにして影響を最小限にするなどしなければならなかった。勿論、CSSプリプロセッサで多少解決出来る部分もあるけれど、それはそれ。
かつてIE6的な余白と境界の解釈を標準化という錦の御旗のもと根絶してしまってから、実はそっちの方が都合が良かったと気付いたのだ。そこでCSS3ではbox-sizingというプロパティが出来て、余白と境界をサイズに含めない計算方法も選べるようになった。

box-sizing : border-boxを指定した場合の計算方法

box-sizing : border-boxを指定した場合の計算方法

指定方法は、box-sizingプロパティの値をborder-boxと指定するだけ(無指定の値はcontent-boxで、これは今までの計算方法だ)。これにより要素のwidthとheightで指定されたサイズが保証されて、paddingとborderはその値から減算することになる。後付けで値の変更を行っても、影響は最小限だ。

box-sizing : border-boxをどこで宣言するか

ちなみにこのbox-sizingプロパティにはinheritという値もあり、これが指定されていると上位要素で宣言された値を継承する。ページ全体にわたってbox-sizingを有効にする場合、一番簡単なのは、全称セレクタを使って全要素に明示的に指定することだけれども、

 
/* 全称セレクタだけでは疑似要素に適用されないので、beforeとafterもついでに指定 */
/* 疑似要素使わないなら指定の必要なし */
 
*, *:before, *:after{
box-sizing: border-box;
}

ただこれだとcontent-box指定を前提として書かれたCSSを拝借してくる場合などに、対象となる全要素をいちいちcontent-box指定に直すのが面倒臭い。そこで、inherit指定を上手く活用した方がbetter。

 
/* 最上位要素のhtmlに対してborder-boxを指定 */
 
html{
box-sizing: border-box;
}
 
/* 全ての要素のbox-sizingがinheritとなるように指定 */
 
*, *:before, *:after{
box-sizing: inherit;
}

このようにすれば、拝借してきたCSSがcontent-box指定の場合でも、その上位要素に対してcontent-boxを指定し直すだけでよい。

box-sizing : border-box入りのリセット/ノーマライズCSS

全称セレクタで全要素に対して指定をするならば、どうせならリセット/ノーマライズCSSで同時にこれをやってくれるものの使用も選択肢として考えたい。確認した範囲で以下のリセット/ノーマライズCSSが最新版で対応している(リンク先はGitHub)。

  • sanitize.css(全要素にinherit&html要素に対してborder-box指定)
  • minireset.css(全要素にinherit&html要素に対してborder-box指定)
  • ress(全要素にinherit&html要素に対してborder-box指定)
  • Marx(全要素にinherit&:root疑似要素に対してborder-box指定)

で、上の画像にも付記してあることだけれども、みんな大好きなTwitter Bootstrapではv2.3.2までにはborder-box指定がなく、2013年8月にリリースされたv3.0.0以降からborder-box指定が入る(全要素にinherit&html要素に対してborder-box指定)。

世代間の壁の存在に留意が必要

そんなわけで、昨今WEB制作の道に入門してくる人達は完全にborder-boxの常識でコーディングを行うだろうし、それ以前の常識で書かれたコードやWEB上のサンプルコードなどはcontent-boxの場合が多い。ここで混乱が起こる可能性がありそう(特にWordPressテーマとプラグインの関係で顕著)。
一応頭でinherit指定を行っておけば世代の異なるCSSコードの付け足しの際、直上の親要素に明示的指定を打って流用することが出来るけれども、付け足しが多数になってくるとあとあと訳が分からなくなってしまう。
そこで、出来ることならば新世代の常識の方に寄せる方向で旧世代コードも手直ししておいた方が良いのではないかと思う。Bootstrap等が軒並み新世代であることや、ブラウザのベンダープリフィックスなしbox-sizing対応がほぼ完了したことも鑑みて(box-sizingの各ブラウザ実装状況)。あと、購入する参考書の発行年などにも今後注意が必要だね。