2013-04-23
■[PHP][phpstudy]第67回PHP勉強会
第67回PHP勉強会に参加して来ました。幹事の@yandoさん、@mugesoさん、会場を提供していただいた株式会社EngineYardさん、ありがとうございました:-)
PHP勉強会は2年ぶり。前回はAndroidでPHPを動かすネタだったか。。。
で、今回のネタは先日見つけたPHP Refactoring Browserです。プレゼン資料とデモで使用したコード一式はGitHubに上げてあります。
PHP Refactoring Browserの紹介
PHP Refactoring Browserの内部構造
内部構造のLTの方で、Encapsulate Fieldのデモができなくてすみません。。。修正版はGitHubにpushしてありますので、ローカル環境で試してみてください。
ローカル環境でやってみたいと思う奇特な方へ
設置方法は以下の様な感じです。
- refactor.batをPATHの通ったディレクトリに保存
- refactor.pharは適宜の場所へ配置
- php_refactor.macはマクロを保存しているディレクトリに保存
- 任意で、適宜ショートカットキーを割り当てる
また、Windows版GNU patchは別途ダウンロードしてPATHを通す必要があります。
追記(2013/04/24 13:55)
内部構造のプレゼンの10ページに出てくるコードのparseで使われてるライブラリですが、PHP Parserが正しいです。すみません><
ちなみにこのParser、zend_language_parser.yをベースとした構文解析をやってます。で、Parserをrebuildするには、id:moriyoshiさんのforkしたkmyaccが必要になるとのこと。なんか、すごい所で繋がった感が。。。
2013-02-19
■[javascript]JsViewsのonPropertyChangeイベント
随分と書いてなかった。。。というか、ようやく2013年初エントリ。あけましておめでとうございます。
先日(といっても3ヶ月前)のJsViews入門 - Do You PHP はてなに書いたJsViewsネタです。
JsViewsは、JavaScript側で持っている配列などのデータとテンプレートをリンクさせ、データに変更があったタイミングで自動的に再描画するためのライブラリですが、この時に発生するイベントはonPropertyChangeです。
たとえば、プルダウンとデータをリンクさせていて、項目が選択した時にデータの値を使ってデータを取得し再描画したい場合、プルダウンのonchangeイベントを使うと、リンクしているデータがタイミングによっては変更されていない(完了していない)事があり得ます。この場合、データが変更されたタイミングでイベントが発生してくれれば問題ないはずです。
$(function() { // 描画するデータ var data = [ ... ]; $.templates({ linkTemplate: "#template" }); $.link.linkTemplate("#list", fruits); $('#foo').on('click', function() { // dataの値を使ってURLを作成し、新しいdataを再読み込み // 画面を再描画する }); });
ここで、先ほどのonPropertyChangeイベントを使うと、以下の様なコードで期待通りの動作をするようになります。
$(function() { var data = [ ... ]; $.templates({linkTemplate: "#template"}); $.link.linkTemplate("#list", fruits); // dataに対してハンドラを付ける $(data).on('propertyChange', function() { // dataの値を使ってURLを作成し、新しいdataを再読み込み // 画面を再描画する }); });
2012-12-13
■[PHP][Symfony2]UPGRADE-2.2.mdをざっと訳してみた
最新の和訳は以下を参照してください。
ちらほらとSymfony2.2の話が出てきていますが、Symfony2.1の時と同様にUPGRADE.mdをざっと訳してみました。
訳したMarkdownファイルはGithubに上げてありますので、誤訳・漏れを見つけたら煮るなり焼くなりどうぞ。
以下、はてダ記法にざっと直したものを貼り付けときます。
UPGRADE FROM 2.1 to 2.2
=======================
HttpFoundation
- MongoDbSessionHandler のデフォルトフィールド名と timestamp タイプが変更されました。`sess_` プレフィックスはデフォルトフィールド名から削除されました。セッション ID はデフォルトでは `_id` フィールドに保持されます。セッション日付は `MongoTimestamp` の代わりに `MongoDate`に保持されます。`gc()` メソッドに依存する代わりに MongoDB 2.2 以上のTTLコレクションを利用することも可能になりました。
- _method リクエストパラメータのサポートはデフォルトで無効になりました (有効にするには、Request::enableHttpMethodParameterOverride() をコールしてください) 。
非推奨
- `Request::splitHttpAcceptHeader()` は非推奨で、Symfony 2.3 で削除されます。リクエストの accept-* ヘッダをパースする便利なメソッド群を提供する `AcceptHeader` を使用するようにしてください。次の例を見てください:
<?php $accept = AcceptHeader::fromString($request->headers->get('Accept')); if ($accept->has('text/html') { $item = $accept->get('html'); $charset = $item->getAttribute('charset', 'utf-8'); $quality = $item->getQuality(); } // 項目は降順ソートされる $accepts = AcceptHeader::fromString($request->headers->get('Accept'))->all();
フォーム
- PasswordType はデフォルトでトリムされなくなりました。
ルーティング
- RouteCollection は木構造ではなく、Routes のフラットな配列のように動作します。このため、RouteCollection を構築するためにPHPを使用する場合、親コレクションに追加する前に子コレクションにルートを追加するのを忘れないようにしてください (ルートを定義するのにYAML や XML を使用している場合は関係ありません) 。
変更前:
<?php $rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $rootCollection->addCollection($subCollection); $subCollection->add('foo', new Route('/foo'));
変更後:
<?php $rootCollection = new RouteCollection(); $subCollection = new RouteCollection(); $subCollection->add('foo', new Route('/foo')); $rootCollection->addCollection($subCollection);
また、すべての階層で `addCollection` をコールする必要があります。正しいシーケンスは次のようになります (逆は不可です) 。
<?php $childCollection->->addCollection($grandchildCollection); $rootCollection->addCollection($childCollection);
- `RouteCollection::getParent()` と `RouteCollection::getRoot()` メソッドは非推奨で、Symfony 2.3 で削除されます。
- デフォルト値や必須項目の値、任意項目の値を追加するための `RouteCollection::addPrefix` メソッドをプレフィックスの追加なしで誤用することはサポートされなくなりました。このため、`addPrefix` を `addPrefix('', $defaultsArray, $requirementsArray, $optionsArray)` のように空のプレフィックスもしくは `/` のみ (both have no relevance) でコールする場合、新しく提供された代替メソッド `addDefaults($defaultsArray)`, `addRequirements($requirementsArray)`, もしくは、 `addOptions($optionsArray)` を使用する必要があります。
- `RouteCollection::addPrefix()` の `$options` オプションは非推奨になりました。オプションを追加することとパスのプレフィックスを追加することは関係ないためです。RouteCollection内のすべての子ルートにオプションを追加したい場合は、`addOptions()` を使用してください。
- `RouteCollection::getPrefix()` メソッドは非推奨になりました。コレクション内のすべてのルートはこのプレフィックスを持っており、必ずしも真ではないからです。コレクションの先頭において、ツリー構造はもはやありませんので、このメソッドも無用です。
- `RouteCollection::addCollection(RouteCollection $collection)` は単一パラメータのみで使用するようにしてください。他のパラメータ `$prefix`, `$default`, `$requirements`, `$options` はまだ有効ですが非推奨になりました。代わりに、`addPrefix` メソッドはこのユースケースで使用するようにしてください。
変更前:
<?php $parentCollection->addCollection($collection, '/prefix', array(...), array(...));
変更後:
<?php $collection->addPrefix('/prefix', array(...), array(...)); $parentCollection->addCollection($collection);
バリデータ
- インターフェース郡は `ConstraintViolation`, `ConstraintViolationList`, `GlobalExecutionContext`, `ExecutionContext` の各クラスのために作られました。もし、これらのクラスをタイプヒントとして追加する場合、これらのインターフェースをタイプヒントとして追加することを推奨します。
変更前:
<?php use Symfony\Component\Validator\ExecutionContext; public function validateCustomLogic(ExecutionContext $context)
変更後:
<?php use Symfony\Component\Validator\ExecutionContextInterface; public function validateCustomLogic(ExecutionContextInterface $context)
`ConstraintValidatorInterface` のすべての実装について、この変更は `initialize` メソッドに対して必須です。
変更前:
<?php use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ExecutionContext; class MyValidator implements ConstraintValidatorInterface { public function initialize(ExecutionContext $context) { // ... } }
変更後:
<?php use Symfony\Component\Validator\ConstraintValidatorInterface; use Symfony\Component\Validator\ExecutionContextInterface; class MyValidator implements ConstraintValidatorInterface { public function initialize(ExecutionContextInterface $context) { // ... } }
非推奨
- `ClassMetadataFactoryInterface` インターフェースは非推奨で、Symfony 2.3 で削除されます。代わりに `MetadataFactoryInterface` を実装するようにしてください。メソッド名が `getClassMetadata` から `getMetadataFor` に変更され、任意の値 (たとえばクラス名やオブジェクト、数値など) を受け取ります。独自の実装では、与えられた値のメタデータをサポートしていない場合 `NoSuchMetadataException` を投げるようにしてください。
変更前:
<?php use Symfony\Component\Validator\Mapping\ClassMetadataFactoryInterface; class MyMetadataFactory implements ClassMetadataFactoryInterface { public function getClassMetadata($class) { // ... } }
変更後:
<?php use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\Exception\NoSuchMetadataException; class MyMetadataFactory implements MetadataFactoryInterface { public function getMetadataFor($value) { if (is_object($value)) { $value = get_class($value); } if (!is_string($value) || (!class_exists($value) && !interface_exists($value))) { throw new NoSuchMetadataException(...); } // ... } }
`ValidatorInterface::getMetadataFactory()` の戻り値も `MetadataFactoryInterface` に変更されました。このメソッドの戻り値の `getClassMetadata` を呼び出す箇所を `getMetadataFor` に変更することを忘れないようにしてください。
変更前:
<?php $metadataFactory = $validator->getMetadataFactory(); $metadata = $metadataFactory->getClassMetadata('Vendor\MyClass');
変更後:
<?php $metadataFactory = $validator->getMetadataFactory(); $metadata = $metadataFactory->getMetadataFor('Vendor\MyClass');
- `GraphWalker` クラスとアクセサ `ExecutionContext::getGraphWalker()` は非推奨で、Symfony 2.3 で削除されます。代わりに、`ExecutionContextInterface::validate()` と `ExecutionContextInterface::validateValue()` メソッドを使用するようにしてください。
変更前:
<?php use Symfony\Component\Validator\ExecutionContext; public function validateCustomLogic(ExecutionContext $context) { if (/* ... */) { $path = $context->getPropertyPath(); $group = $context->getGroup(); if (!empty($path)) { $path .= '.'; } $context->getGraphWalker()->walkReference($someObject, $group, $path . 'myProperty', false); } }
変更後:
<?php use Symfony\Component\Validator\ExecutionContextInterface; public function validateCustomLogic(ExecutionContextInterface $context) { if (/* ... */) { $context->validate($someObject, 'myProperty'); } }
- `ExecutionContext::addViolationAtSubPath()` メソッドは非推奨で、Symfony 2.3 で削除されます。代わりに `addViolationAt()` を使用してください。
変更前:
<?php use Symfony\Component\Validator\ExecutionContext; public function validateCustomLogic(ExecutionContext $context) { if (/* ... */) { $context->addViolationAtSubPath('myProperty', 'This value is invalid'); } }
変更後:
<?php use Symfony\Component\Validator\ExecutionContextInterface; public function validateCustomLogic(ExecutionContextInterface $context) { if (/* ... */) { $context->addViolationAt('myProperty', 'This value is invalid'); } }
- `ExecutionContext::getCurrentClass()`, `ExecutionContext::getCurrentProperty()`, `ExecutionContext::getCurrentValue()` メソッドは非推奨で、Symfony 2.3 で削除されます。 代わりに `getClassName()`, `getPropertyName()`, `getValue()` を使用してください。
変更前:
<?php use Symfony\Component\Validator\ExecutionContext; public function validateCustomLogic(ExecutionContext $context) { $class = $context->getCurrentClass(); $property = $context->getCurrentProperty(); $value = $context->getCurrentValue(); // ... }
変更後:
<?php use Symfony\Component\Validator\ExecutionContextInterface; public function validateCustomLogic(ExecutionContextInterface $context) { $class = $context->getClassName(); $property = $context->getPropertyName(); $value = $context->getValue(); // ... }
2012-11-29
■[javascript]JsViews入門
いい加減書かないと、絶対に書かないような気がしたので。。。
先日(といっても2ヶ月前)のJsRender入門 - Do You PHP はてなの続きです。
JavaScript製のテンプレートエンジンであるJsRenderを使うとJavaScriptでHTMLコンテンツを出力する(特にメンテナンス)のがかなり楽になるんですが、データが変更された場合に自動的に再描画してくれるわけではありません。
たとえば、JsRender入門 - Do You PHP はてなの"使い方"にあるサンプルに追記し、配列fruitsに要素を追加するようにしたものが以下のサンプルです。
<html> <body> <div id="list"></div> <!-- 追加用のボタン --> <button id="add">add</button> <script src="http://code.jquery.com/jquery.js" type="text/javascript"></script> <script src="js/jsrender.js" type="text/javascript"></script> <script type="text/javascript"> $(function() { var fruits = [ { code: "01", name: "りんご" }, { code: "02", name: "バナナ" }, { code: "03", name: "パイナップル" } ]; // 配列にデータを追加する $('#add').click(function() { fruits.push({ code: "99", name:"ドリアン"}); // ※1 }); // ※2テンプレートを使ってレンダリング $("#list").html( $("#template").render(fruits) ); }); </script> <script id="template" type="text/x-jsrender"> <div> {{:#index+1}}: <a href="/fruit/{{>code}}/detail">{{>name}}</a> </div> </script> </body> </html>
これをブラウザで開いてボタンを押下しても表示は変わりません。まあ、当然といえば当然なんですが。。。とりあえず、※2の部分を※1にも記述すると再描画されるようにはなりますが、なんかイマイチな感じです。
で、ここから本題。
このように、JavaScript側で持っている配列などのデータとテンプレートをリンクさせ、データに変更があったタイミングで自動的に再描画するためのライブラリがJsViewsです。
ただし、こちらもJsRenderと同様、
Warning: JsViews is not yet Beta, and there may be frequent changes to APIs and features in the coming period.
BorisMoore/jsviews · GitHub
となっています。
使い方
JsRenderの流れと似ています。
以下は上のサンプルにJsViewsを組み込んだ場合の例です。
<html> <body> <div id="list"></div> <button id="add">add</button> <script src="http://code.jquery.com/jquery.js" type="text/javascript"></script> <script src="js/jsrender.js" type="text/javascript"></script> <script src="js/jquery.observable.js" type="text/javascript"></script> <script src="js/jquery.views.js" type="text/javascript"></script> <script type="text/javascript"> $(function() { // データの配列(もしくはオブジェクト) var fruits = [ { code: "01", name: "りんご" }, { code: "02", name: "バナナ" }, { code: "03", name: "パイナップル" } ]; $('#add').click(function() { // 配列fruitsを監視(observe)しつつ、要素を追加 $.observable(fruits).insert(fruits.length, { code: "99", name: "ドリアン" }); }); // テンプレートに名前をつけて登録する $.templates({ linkTemplate: "#template", }); // テンプレートlinkTemplate(#template)と変数fruitsをリンクさせ、 // レンダリング結果をid="list"に表示 $.link.linkTemplate("#list", fruits); }); </script> <script id="template" type="text/x-jsrender"> <div> <!-- data-link属性に注目! --> {{:#index+1}}: <a href="/fruit/{{>code}}/detail" data-link="name">※3</a> (<span data-link="code">※4</span>) </div> </script> </body> </html>
JSONデータを更新する場合、おおまかに2つの流れがあります
XHRでデータを取得して全データを差し替える場合は前者の方法が楽かと思います。
テンプレート内のdata-link属性
基本的にテンプレートはJsRenderと同じですが、特定の要素の値とデータをリンクさせたい場合、data-link属性を使います。
<script id="template" type="text/x-jsrender"> <div> <!-- data-link属性に注目! --> {{:#index+1}}: <a href="/fruit/{{>code}}/detail" data-link="name">※3</a> (<span data-link="code">※4</span>) </div> </script>
先ほどのサンプルのテンプレート部分ですが、2箇所ほどdata-link属性を付けています。こうすることで、リンクされたデータのnameプロパティの値が※3、codeプロパティが※4にそれぞれ自動的に埋め込まれてレンダリングされます。
で、ここからが超重要!
フォームの場合も同様にdata-link属性を付けることで、value属性を書かずにテキストを入力した状態にしたり、ラジオボタンを選択済みの状態にすることができますが、逆に入力フォームの値を変更することでリンク元のJSONデータも変更されるようになります。
たとえば、先程のサンプルのテンプレートを以下のものに変更した後ブラウザで表示し、テキストボックスの内容やプルダウン、ラジオボタンを変更してみてください。そして、FireBugなどでfruitsの内容を確認してみてください。
<script id="template" type="text/x-jsrender"> <div> 名称:<input type="text" data-link="name" /> コード:<select data-link="code"> <option value="">選択してください</option> <option value="01">01</option> <option value="02">02</option> <option value="03">03</option> <option value="99">99</option> コード:<input type="radio" id="code{{:#index}}_01" name="code{{:#index}}" data-link="code" value="01" /><label for="code{{:#index}}_01">01</label> <input type="radio" id="code{{:#index}}_02" name="code{{:#index}}" data-link="code" value="02" /><label for="code{{:#index}}_02">02</label> <input type="radio" id="code{{:#index}}_03" name="code{{:#index}}" data-link="code" value="03" /><label for="code{{:#index}}_03">03</label> <input type="radio" id="code{{:#index}}_99" name="code{{:#index}}" data-link="code" value="99" /><label for="code{{:#index}}_99">99</label> </div> </script>
ちなみに、同じデータにリンクしているプルダウンとラジオボタンは、たとえばプルダウン側を変更すると自動的にラジオボタン側も変更されます。
また、$.param(JSON.parse(JSON.stringify(fruits)))とかするだけでクエリ文字列やPOSTデータを作ったりできるようになります。項目数が多い画面を作ってると、この部分って単純作業なくせに面倒だったりしてたんですよ。。。javascriptコードもかなりすっきりします。
まとめ
ざっとですが、data-link属性が結構強力で、管理画面を作る際はかなり重宝してます。その他細かい機能については、デモを見てみてください。
参考URL
2012-11-20
■[PHP]Request for Comments: Supports finally keywordを和訳してみた
先日PHP5.5.0α1がリリースされましたが、ようやくfinallyサポートが入ったようです。
Javaとか使ったことある人にはお馴染みですが、何でtry/catchをサポートした時に入れなかったのか不思議なぐらいです。
ということで、PHP WikiにあるRFCをざっと和訳してみました。また、githubにも上げてありますので、間違いがあればそっちを直して貰えればと。
- translations/rfc_Supports_finally_keyword.ja.txt at master ? shimooka/translations ? GitHub
- PHP: rfc:finally [PHP Wiki] (PHP Wikiのオリジナル)
導入
このRFCは、FR #32100, #36779でリクエストされた例外発生時の 'finally' サポートについての導入です。
この機能がない場合、利用者は処理できない例外が発生した時に以下のような後処理を行うコードを記述しなければなりません。
<?php $db = mysqli_connect(); try { call_some_function($db); } catch (Exception $e) { mysqli_close($db); throw $e; } mysql_close($db);
finallyを導入することは、このような状況で1,2行短くすることではなく、このような問題を処理するためのより正しい方法を提供することです。
提案
finallyブロックはtryブロックが終了するときに常に実行されます。これは、finallyブロックが予期しない例外が発生した場合でも実行される、ということを保証します。しかし、finallyはただ例外処理のためだけではなく、より有効です。たとえば、returnやcontinue、breakによって後処理のコードが迂回されるのを防ぐ、といったことです。finallyブロックに後処理コードを記述することは常に、たとえ例外が予測されていない場合でさえも、良いプラクティスです。
<?php $db = mysqli_connect(); try { call_some_function($db);//この関数は処理できない例外を投げるかも知れない } finally { mysqli_close($db); }
最も間違えやすい部分は "try/catchブロック中のreturn"です。この場合でもfinallyブロックはコールされます。
<?php try { return 2; } finally { echo "this will be called\n"; } //ここは決してコールされない echo "you can not see me";
上のスクリプトは以下のように出力します:
this will be called //return int(2)
また、以下のようなネストしたtry catch finally:
<?php function foo ($a) { try { echo "1"; try { echo "2"; throw new Exception("ex"); } catch (Exception $e) { echo "3"; } finally { echo "4"; throw new Exception("ex"); } } catch (Exception $e) { echo "3"; } finally { echo "2"; } return 1; } var_dump(foo("para"));
では、以下のように出力されます:
123432int(1)
以下にあるTests & Examplesセクションに、多くのエッジケースについての例があります。
パッチ
Tests & Examples
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_001.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_002.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_003.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_004.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_005.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/catch_finally_006.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/try_finally_001.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/try_finally_002.phpt
- https://github.com/laruence/php-src/blob/finally/Zend/tests/try_finally_003.phpt
2012-11-13
■[雑記]内部から見た「ウノウ」という会社 その2
この記事はUnoh Advent Calendarの参加記事であり、これ(もしくはこれ)の補足です;-)
以下、当時の思い出をグダグダ書きます。
入社まで
大学出てから11年ほどいたSI会社をこんなメール出して退職し、2007年9月に意気揚々と入社したのがウノウ。退職と入社の間に週末を挟んでて、PHPカンファレンス2007が開催されたのを覚えてます。ウノウ・GREEの両CTO対談とか、PHPの話が一切出てこないモテ系LTとかあったなぁ。あと@rskyさんと初対面だったな。ウノウでは入れ違いだったけど。
内部から見た「ウノウ」という会社
これ(もしくはこれ)に書いた通りの社内。今ではツールを使った情報共有や社内勉強会、コードレビューも珍しくなくなって来てると思いますが、2007年当時は結構刺激的でしたね。
そういえば、コタツもあったね。あまり使われてなかったけど。
個人的にはランチデーは色々な話が聞けてすごく良かった印象が残ってます。あ、あとロッカーに付いてたダイヤル式の鍵を開けられなくて苦労したのも今となってはいい思い出です。
フォト蔵
入社して担当したのがフォト蔵。
当時、全く稼いでいないサービスで、「広告を載せて小金を稼いだらどうか?」とか「イケてるフレームワーク使ってPHP5で書きなおそーぜ」とか色々あったなぁ。今では@hiro_yさんと@rskyさんがうまくやったみたいです。
個人的にはほとんど貢献できなかったんですが、唯一、(当時の)データベース負荷の原因調査と負荷低減の施策だけは「少しは貢献できたかな?」と思えるかな、と。まあ、原因調査に結構時間がかかってしまったのがアレですが。。。
あ、そうそう。今だからぶっちゃけちゃいますが、一番最初にシステム(H/W、ネットワーク)構成全体の把握をしたい方(そうでないと怖くて触れない)なんですが、そういった資料が皆無ですごく苦労した覚えが。。。なので、システム全体がひと目で分かる資料を置き土産的にいくつか作ってから退職しました。
なので、ウノウ時代にコードをガリガリ書いてた、という覚えはなくて、MySQLのログとSQLを睨めっこしてたという感じです。
あと、フォト蔵のピックアップ写真を選ぶのは、そのユーザーさんだけじゃなく周りの人にも喜んでもらえるので楽しかったですよ;-)
ラボブログ
当時「ウノウラボさんでしょ?」と言われるほどの「ウノウの看板」の中心にあったラボブログですが、社外から見てても「うまくブランディングしたなぁ」という印象がありました。
んで、書いたエントリは結局4本。公開から1週間で100はてブ付くとAmazonギフト券もらえるとかありましたねぇ。
そういえば、最後に書いたラボブログのエントリでpharを扱っていますが、composerが当たり前になりつつありpharがようやくマトモに使われるようになったとか感慨深いです。ちなみに、あの最後の行は、ブコメにある通りアレを言うためのエントリです。
退社とそれから
2007年のクリスマスツリーが飾られる頃にはすでに退職と次の会社が決まってたわけですが、まあ、あの会社を外と中の両方から見られたのは良かったのかな、と。貴重な4ヶ月だったんだろうと思いますよ。
2012-11-07
■[Firefox]AutoCopy1.0.4がFx16.0.2で動作しない
また寄り道してます。。。
選択したテキストを自動的にコピーするアドオンのAutoCopyですが、Fx16.0.2でバージョン1.0.4が動作していないことに気づきました。
アドオンの設定を開いてみると、次のようなエラーが。。。
XML パースエラー: 外部実体参照の処理中にエラーが発生しました。 URL: chrome://autocopy/content/autocopyOptions.xul 行番号: 7, 列番号: 64: <!DOCTYPE window SYSTEM "chrome://autocopy/locale/autocopy.dtd"> ---------------------------------------------------------------^
DTDの定義ミスか?と思い調べてみると、案の定、xpiに含まれるchrome/locale/autocopy/ja-JP/autocopy.dtdでダブルクオートが1つ足りない模様。
<!ENTITY autocopyAbout.Contributor "Contributor>
これを
<!ENTITY autocopyAbout.Contributor "Contributor">











