Home > 開発
開発 Archive
[Android] オリジナルの ContentProvider を作成する
前回は ContentProvider クラスを使って コンタクトリスト から電話帳データ を取得してみました。
今回はオリジナルの ContentProvider を作ってみようと思います。
ContentProvider クラスを使い実装することで、他のアプリケーションからもデータを利用することが可能となります。
データへのアクセスは、ContentURI という URI を用い、

という形式でアクセスすることになります。
A :固定の プリフィックス
B :Authority パート。一意であることを保証する為、ContentProvider のパッケージ名を含んだクラス名とします。
AndroidManifest.xml の
C :ContentProvider が操作するデータの種類を示します。
D :データのIDを示します。省略すると全てのレコードを取得します。
となっています。
今回はGoogle が提供しているチュートリアルで作ったNotepadv3 アプリを改造します。
Notepadv3ではデータの保存に SQLite を使っていますが、これを ContentProvider を使うように変更してみましょう。
Notepadv3 プロジェクトをコピーして Notepadv4 プロジェクトを作成
既存のものをディレクトリ毎複製したあと "v3" となっている部分を "v4" に書き換えて "create project from existing source" を選択してプロジェクト作成しました。 この時点で、エミュレータ で動作させてちゃんと表示されればOK。NotesProvider クラスで操作するデータを表現するクラスを作成
Provider を作る前に、データを表現する為の NotePad クラスを作成しておきましょう。 Notes は、DB 上では _id,title,body という3つのカラムを持ちます。com.google.android.demo.provider パッケージを新しく作り、そこに以下のクラスを作ります。
クラス内には、データのパスを示す ContentURI やカラムを示す 定数を指定します。
BaseColumns を継承することにより、_id にあたる部分は省略できます。
package com.google.android.demo.provider;
import android.net.ContentURI;
import android.provider.BaseColumns;
public final class NotePad {
public static final class Notes implements BaseColumns {
/**
* このデータへアクセスする為の URI
*/
public static final ContentURI CONTENT_URI
= ContentURI.create("content://com.google.android.demo.provider.NotePad/notes");
/**
* デフォルトのソート順
*/
public static final String DEFAULT_SORT_ORDER = "_id DESC";
/**
* タイトル
*/
public static final String TITLE = "title";
/**
* ノート
*/
public static final String BODY = "body";
}
}
ContentProvider クラスを拡張して、NotesProvider クラスを作成
これまでデータの保存に利用していた DBHelper の代わりになる、NotesProvider クラスを作ります。com.google.android.demo.notepad4 パッケージ内に NotesProvider クラスを作りましょう。
下の図のように、ContentProvider クラスを親クラスにします。

生成された NotesProvider クラスのソースを見てみると、オーバーライドしなくてはいけない関数がいくつかるようです。
public int delete(ContentURI uri, String selection, String[] selectionArgs) public String getType(ContentURI uri) public ContentURI insert(ContentURI uri, ContentValues values) public boolean onCreate() public Cursor query(ContentURI uri, String[] projection, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder) public int update(ContentURI uri, ContentValues values, String selection, String[] selectionArgs)
onCreate :この Provider が生成された時に実行されるメソッドです。
query :データを取得する為のメソッドです。
getType :メソッドは、この Provider で取得できる ContentURI の META タイプを返します。
insert,update,delete :それぞれ データの新規作成時、更新、削除時のメソッドです。
これらの実装は後述します。
DBへのアクセス方法を変更
これまで使っていた DBHelper クラスから、必要な処理を移植します。 まずは、インスタンスが生成された時の初期化処理です。 DBHelper クラスでは、public DBHelper(Context ctx) {
db = ctx.openDatabase(DATABASE_NAME, null);
とあるように、コンストラクタの中から DB へアクセスする為のインスタンスが取得できました。
ContentProvider では、ContentProviderDatabaseHelper を拡張したクラスを使ってDB へのアクセスを行います。
/**
* DB アクセス用のHelper クラス
* @author hal
*/
private static class DatabaseHelper extends ContentProviderDatabaseHelper {
/**
* テーブルのCreate文。Helper が最初に生成された際に発行される
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table notes (_id integer primary key autoincrement, "
+ "title text not null, body text not null);");
}
/**
* アップグレード時に実行される。
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
Notepadv3 では、Database が存在していない場合に Create Database 文を発行していましたが、ContentProviderDatabaseHelper では自動でやってくれるので必要ありません。
残りのメソッドを実装
query,insert,updateなどを実装します。 流れとしては、ContentURIParser クラスの URL_MATCHER を使って渡されてきた URI をチェックし、onCreate で生成した mDB にクエリを発行しています。 query() で使っている QueryBuilder は、 DB へ発行するクエリを生成する為のクラスです。 getType() メソッドは、この Provider を利用して取得できるデータの METAタイプを返します。 NotesProvider クラス全体は以下のようになります。package com.google.android.demo.notepad4;
import java.util.HashMap;
import com.google.android.demo.provider.NotePad;
import android.content.ContentProvider;
import android.content.ContentProviderDatabaseHelper;
import android.content.ContentURIParser;
import android.content.ContentValues;
import android.content.QueryBuilder;
import android.content.Resources;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.ContentURI;
import android.provider.BaseColumns;
import android.text.TextUtils;
public class NotesProvider extends ContentProvider {
private SQLiteDatabase mDB;
// URI のパース用
private static final ContentURIParser URL_MATCHER;
/**
* DB アクセス用のHelper クラス
* @author hal
*/
private static class DatabaseHelper extends ContentProviderDatabaseHelper {
/**
* テーブルのCreate文。Helper が最初に生成された際に発行される
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table notes (_id integer primary key autoincrement, "
+ "title text not null, body text not null);");
}
/**
* アップグレード時。
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS notes");
onCreate(db);
}
}
// DB名
private static final String DATABASE_NAME = "notepad.db";
// テーブル名
private static final String DATABASE_TABLE = "notes";
// DB バージョン
private static final int DATABASE_VERSION = 1;
private static final int NOTES = 1;
private static final int NOTE_ID = 2;
private static final HashMap<String, String> NOTES_LIST_PROJECTION_MAP;
// URL_MATCHER と NOTES_LIST_PROJECTION_MAP を作成
static {
// URI パース用
URL_MATCHER = new ContentURIParser(ContentURIParser.NO_MATCH);
URL_MATCHER.addURI("com.google.android.demo.provider.NotePad", "notes", NOTES);
URL_MATCHER.addURI("com.google.android.demo.provider.NotePad", "notes/#", NOTE_ID);
NOTES_LIST_PROJECTION_MAP = new HashMap<String, String>();
NOTES_LIST_PROJECTION_MAP.put(BaseColumns._ID, "_id");
NOTES_LIST_PROJECTION_MAP.put(NotePad.Notes.TITLE, "title");
NOTES_LIST_PROJECTION_MAP.put(NotePad.Notes.BODY, "body");
}
/**
* 初期化処理
*/
@Override
public boolean onCreate() {
DatabaseHelper dbHelper = new DatabaseHelper();
mDB = dbHelper.openDatabase(getContext(), DATABASE_NAME, null, DATABASE_VERSION);
return (mDB == null) ? false : true;
}
/**
* 削除処理
*/
@Override
public int delete(ContentURI uri, String selection, String[] selectionArgs) {
int count;
switch (URL_MATCHER.match(uri)) {
case NOTES:
count = mDB.delete(DATABASE_TABLE, selection, selectionArgs);
break;
case NOTE_ID:
String segment = uri.getPathSegment(1);
count = mDB
.delete(DATABASE_TABLE, "_id="
+ segment
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ')' : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/**
* この URIが返すデータ型
*/
@Override
public String getType(ContentURI uri) {
switch (URL_MATCHER.match(uri)) {
case NOTES:
return "vnd.android.cursor.dir/vnd.google.note";
case NOTE_ID:
return "vnd.android.cursor.item/vnd.google.note";
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
}
@Override
/**
* データの挿入
*/
public ContentURI insert(ContentURI uri, ContentValues initialValues) {
long rowID;
ContentValues values;
// データがなければ新規に作る
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
// 正しいURI かどうかチェック
if (URL_MATCHER.match(uri) != NOTES) {
throw new IllegalArgumentException("Unknown URI " + uri);
}
Resources r = Resources.getSystem();
// タイトル設定
if (values.containsKey(NotePad.Notes.TITLE) == false) {
values.put(NotePad.Notes.TITLE, r.getString(android.R.string.untitled));
}
// 本文設定
if (values.containsKey(NotePad.Notes.BODY) == false) {
values.put(NotePad.Notes.BODY, "");
}
// 挿入
rowID = mDB.insert(DATABASE_TABLE, "note", values);
if (rowID > 0) {
ContentURI newuri = NotePad.Notes.CONTENT_URI.addId(rowID);
// 変更を通知
getContext().getContentResolver().notifyChange(uri, null);
return newuri;
}
throw new SQLException("Failed to insert row into " + uri);
}
/**
* データの取得
*/
@Override
public Cursor query(ContentURI uri, String[] projection, String selection,
String[] selectionArgs, String groupBy, String having,
String sortOrder) {
QueryBuilder qb = new QueryBuilder();
switch (URL_MATCHER.match(uri)) {
case NOTES:
qb.setTables(DATABASE_TABLE);
qb.setProjectionMap(NOTES_LIST_PROJECTION_MAP);
break;
case NOTE_ID:
qb.setTables(DATABASE_TABLE);
qb.appendWhere("_id=" + uri.getPathSegment(1));
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
// ソート順がなければデフォルトのものを使う
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = NotePad.Notes.DEFAULT_SORT_ORDER;
} else {
orderBy = sortOrder;
}
// カーソル作成
Cursor c = qb.query(mDB, projection, selection, selectionArgs, groupBy,
having, orderBy);
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/**
* 更新処理
*/
@Override
public int update(ContentURI uri, ContentValues values, String selection,
String[] selectionArgs) {
int count;
switch (URL_MATCHER.match(uri)) {
// 全て
case NOTES:
count = mDB.update(DATABASE_TABLE, values, selection, selectionArgs);
break;
// ID 指定
case NOTE_ID:
String segment = uri.getPathSegment(1);
count = mDB.update(DATABASE_TABLE, values, "_id="
+ segment
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ')' : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
// 更新を通知
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
Naotepadv4 クラスの修正
DBHelper ではなく、ContentProvider クラスを利用するように変更します。データを取得するのに、前回の例と同じように managedQuery メソッド が使えるようになっていると思います。
getIntent().setData(NotePad.Notes.CONTENT_URI);
cur = managedQuery(getIntent().getData(), PROJECTION, null, null);
の部分ですね。
また、編集時に編集画面へデータを渡すには、
ContentURI uri = getIntent().getData().addId({編集したい行の_ID});
startSubActivity(new Intent(Intent.EDIT_ACTION, uri), ACTIVITY_EDIT);
のように、編集したい行の URI を示す ContentURI クラスを渡します。
今回から、startSubActivity へ渡す Intent には、Intent.INSERT_ACTION や Intent.EDIT_ACTION という定数を渡しています。
このように、Intent で ACTION を指定する場合には、AndroidManifest.xml への記述が必要となります。
AndroidManifest.xml については、後述します。
package com.google.android.demo.notepad4;
import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.database.Cursor;
import android.net.ContentURI;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.view.Menu;
import android.view.View;
import android.view.Menu.Item;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import com.google.android.demo.provider.NotePad;
public class Notepadv4 extends ListActivity
{
private static final int ACTIVITY_CREATE=0;
private static final int ACTIVITY_EDIT=1;
private static final int INSERT_ID = Menu.FIRST;
private static final int DELETE_ID = Menu.FIRST + 1;
private Cursor cur;
private ArrayList<Long> mItems ;
/**
* DB から取得したい 項目
*/
private static final String[] PROJECTION = new String[] {
BaseColumns._ID, NotePad.Notes.TITLE};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle)
{
super.onCreate(icicle);
setContentView(R.layout.notes_list);
fillData();
}
// データ表示
private void fillData() {
// クエリ発行
getIntent().setData(NotePad.Notes.CONTENT_URI);
cur = managedQuery(getIntent().getData(), PROJECTION, null, null);
// ID保持用
mItems = new ArrayList<Long>();
// タイトル保持用
ArrayList<String> items = new ArrayList<String>();
int titleIndex = cur.getColumnIndex(NotePad.Notes.TITLE);
// データ取得
while (cur.next()){
mItems.add(cur.getLong(cur.getColumnIndex(NotePad.Notes._ID)));
items.add(cur.getString(titleIndex));
}
// リスト作成
ArrayAdapter<String> notes =
new ArrayAdapter<String>(this, R.layout.notes_row, items);
setListAdapter(notes);
}
/**
* オプションメニュー生成
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, R.string.menu_insert);
menu.add(0, DELETE_ID, R.string.menu_delete);
return true;
}
/**
* メニュー選択時
*/
@Override
public boolean onMenuItemSelected(int featureId, Item item) {
super.onMenuItemSelected(featureId, item);
switch(item.getId()) {
case INSERT_ID:
Intent i = new Intent(Intent.INSERT_ACTION, getIntent().getData());
startSubActivity(i, ACTIVITY_CREATE);
break;
case DELETE_ID:
// データ削除
cur.moveTo(getSelection());
cur.deleteRow();
fillData();
break;
}
return true;
}
/**
* リストアイテムクリック
*/
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
ContentURI uri = getIntent().getData().addId(mItems.get((int)getSelectionRowID()));
startSubActivity(new Intent(Intent.EDIT_ACTION, uri), ACTIVITY_EDIT);
}
/**
* SubActivity 終了時
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, String data, Bundle extras) {
super.onActivityResult(requestCode, resultCode, data, extras);
fillData();
}
}
NoteEdit.java の変更
NoteEdit.java 側も変更を加えます。package com.google.android.demo.notepad4;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.ContentURI;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.google.android.demo.provider.NotePad;
public class NoteEdit extends Activity {
private EditText titleText;
private EditText bodyText;
private Cursor cur ;
private ContentURI mURI;
private static final String[] PROJECTION = new String[] {
BaseColumns._ID, NotePad.Notes.TITLE, NotePad.Notes.BODY};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.note_edit);
titleText = (EditText) findViewById(R.id.title);
bodyText = (EditText) findViewById(R.id.body);
Button confirmButton = (Button) findViewById(R.id.confirm);
final Intent intent = getIntent();
final String action = intent.getAction();
if (action.equals(Intent.INSERT_ACTION)) {
// ContentResolver にURI を渡し、新規のカーソルを作成する
mURI = getContentResolver().insert(intent.getData(), null);
// エラー
if (mURI == null) {
Log.e("Notes", "Failed to insert new note into "
+ getIntent().getData());
finish();
return;
}
// キャンセルボタンなどを実装したい場合、setResult には RESULT_CANCEL を渡すこと
setResult(RESULT_OK, mURI.toString());
}else if (action.equals(Intent.EDIT_ACTION)) {
// 編集時
mURI = intent.getData();
setResult(RESULT_OK, mURI.toString());
}
// クエリを発行
cur = managedQuery(mURI, PROJECTION, null, null);
// データ表示
populateField();
// Submit ボタンクリック時のイベント
confirmButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View arg0) {
setResult(RESULT_OK);
finish();
}
});
}
/**
* アクティビティの終了、一時停止時
*/
@Override
protected void onPause() {
// 使っているリソースを開放する
super.onPause();
saveState();
}
/**
* Pause 状態からの復旧時
*/
@Override
protected void onResume() {
// データを復旧
super.onResume();
populateField();
}
/**
* データ保存用のメソッド
*/
private void saveState(){
String title = titleText.getText().toString();
String body = bodyText.getText().toString();
// cur.updateString で、データを更新
if (cur != null) {
cur.updateString(cur.getColumnIndex(NotePad.Notes.TITLE), title);
cur.updateString(cur.getColumnIndex(NotePad.Notes.BODY), body);
managedCommitUpdates(cur);
}
}
/**
* 編集用のフィールド表示
*/
private void populateField() {
if (cur != null) {
cur.first();
String title = cur.getString(cur.getColumnIndex(NotePad.Notes.TITLE));
titleText.setText(title);
String body = cur.getString(cur.getColumnIndex(NotePad.Notes.BODY));
bodyText.setText(body);
}
}
}
AndroidManifest.xml の更新
最後に、IntentFilter や provider の設定を、AndroidManifest.xml に記述します。<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.demo.notepad4">
<application android:icon="@drawable/icon"
android:theme="@android:style/Theme.Dark">
<provider class="NotesProvider" android:authorities="com.google.android.demo.provider.NotePad" />
<activity class=".Notepadv4" android:label="@string/app_name">
<intent-filter>
<action android:value="android.intent.action.MAIN" />
<action android:value="android.intent.action.INSERT" />
<action android:value="android.intent.action.EDIT" />
<category android:value="android.intent.category.LAUNCHER" />
<type android:value="vnd.android.cursor.dir/vnd.google.note" />
</intent-filter>
</activity>
<activity class=".NoteEdit" android:label="@string/edit_note">
<intent-filter>
<category android:value="android.intent.category.DEFAULT" />
<type android:value="vnd.android.cursor.dir/vnd.google.note" />
<type android:value="vnd.android.cursor.item/vnd.google.note" />
</intent-filter>
</activity>
</application>
</manifest>
<provider class="NotesProvider" android:authorities="com.google.android.demo.provider.NotePad" />
の部分が、今回作った ContentProvider を使う為の記述です。authorities 属性の value 値が、ContentURI に対応しているのがわかると思います。
<intent-filter>内の
<action android:value="android.intent.action.INSERT" />
<action android:value="android.intent.action.EDIT" />
は、Intent.EDIT_ACTION などを 別の Activity に渡す際に必要な記述になります。
<type android:value="vnd.android.cursor.dir/vnd.google.note" />
部分は、Provider の getType() に対応しています。
以上の変更を加えることで、オリジナルの ContentProvider を利用できるようになっています。
RUN してみると、これまでと同じように NotePad アプリケーションが動作すると思います。
今回はソースをまるまる貼り付けているので、かなりエントリが長くなってしまいました。
[Android] ContentProvider を使って コンタクトリストのデータを取得する
Android の ContentProvider クラス について。
チュートリアルにある NotePad アプリケーションではデータの保存に SQLite を使っていますが、
For this exercise, we are just going to use a SQLite database directly to store our data, but in a real application it would be much better to write a proper ContentProvider to encapsulate this behavior instead.If you are interested, you can find out more about content providers or the whole subject of Storing, Retrieving, and Exposing Data.
とあるように、実際アプリケーションを作る場合は ContentProvider を使う方がいいようです。
というわけで、以下のURL を参考に ContentProvider を使ってみました。
http://code.google.com/android/devel/data/contentproviders.html
まずは、サンプルコードにあるように、アドレス帳のデータを取り出してリスト形式で表示してみることにします。
1. 新しい Android プロジェクトを作る。
プロジェクト名、アプリケーション名は ContactView にしました。
2. layout 用の xml ファイルを作成。
main.xml に ListView を追加
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<ListView id="@+id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView id="@+id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="No Contacts!"/>
</LinearLayout>
リスト内の表示用に、contacts_row.xml を追加
<?xml version="1.0" encoding="utf-8"?>
<TextView id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
3. コンタクトリストからデータを表示するプログラムを書きます。
package jp.plantsweb.android.contact;
import java.util.ArrayList;
import java.util.List;
import android.app.ListActivity;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.ArrayAdapter;
public class ContactList extends ListActivity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
fillData();
}
/**
* コンタクトデータを取得して表示
*/
private void fillData(){
List items = new ArrayList();
// コンタクトリストから取得するカラムを指定
String[] projection = new String[] {
android.provider.BaseColumns._ID,
android.provider.Contacts.PeopleColumns.NAME
};
// クエリを発行する
Cursor cur = managedQuery( android.provider.Contacts.Phones.CONTENT_URI,
projection, // 取得するカラムを指定
null, // WHERE句にあたる部分。全部必要なのでnull
android.provider.Contacts.PeopleColumns.NAME + " ASC"); // Order by
String name;
// データ取得用のインデックスを取得する
int nameColumn = cur.getColumnIndex(android.provider.Contacts.PeopleColumns.NAME);
// データ取得開始
while (cur.next()) {
// データ取得
name = cur.getString(nameColumn);
// 表示用の配列に追加
items.add(name);
}
// アダプタを介してデータ表示
ArrayAdapter contacts = new ArrayAdapter(
this, R.layout.contacts_row, items);
setListAdapter(contacts);
}
}
Run してみたところ、SecurityException が発生しました。
デフォルトではコンタクトリストには許可されたアプリケーションしかアクセスできない模様です。
4. パーミッションの設定をする
セキュリティの章に答えがありました。 http://code.google.com/android/devel/security.html
保護されているデータにアクセスする場合、AndroidManifest.xml に、
<uses-permission id="android.permission.RECEIVE_SMS" />
というような宣言を書く必要があるようです。
パーミッションの一覧が以下の URL にあり、
http://code.google.com/android/reference/android/Manifest.permission.html
READ_CONTACT というパーミッションがありました。AndoroidManifest.xml に
<uses-permission id="android.permission.READ_CONTACTS" />
という行を追加して RUN してみたところ、無事コンタクトリストの内容が表示されました!
しかし、コンタクトリストには2名しか登録していないはずですが、4行になっていますね。調べてみたところ、ContactList 内のデータは、携帯と自宅と登録されている場合は2行分に分かれているようです。
次回はオリジナルの ContentProvider を作ってみようと思います。
trac から pukiwiki 形式に変換する、trac2pukiwiki.sh
- 2007-11-22 (木)
- 開発
/Users/hal/bin/trac2pukiwiki.rb
#!/opt/local/bin/ruby
#####################################################
# trac で書かれたコードを pukiwiki 記法に変更します。
#####################################################
require 'nkf'
while gets
NKF.nkf('-w -S',$_).split("¥r").each() { |l|
print NKF.nkf('-W -s', l.gsub(/^(¥s*)(¥*+)([¥w¥W]+)/) { |m|
m = ' ' * $1.length + '-' * ($2.length) + ' ' + $3 # * を - に変換
}.gsub(/(=+)¥s+([¥w¥W]+)¥s+(=+)/){|m|
m = '*' * ($1.length) + " " + $2 # == 見出し == を ** 見出し に変換
}.gsub(/¥{¥{¥{/,'#code(){{'). # {{{ を {{ に変換
gsub(/¥}¥}¥}/,'}}'). # }}} を }} に変換
gsub(/¥|¥|/,'|'). # || を | に変換
gsub(/¥[¥[br¥]¥]/, '‾'). # [[BR]] を ‾ に変換
gsub(/¥[¥[PageOutline¥]¥]/, '#contents'). # [[PageOutline]] を #contents に変換
gsub(/¥[wiki:([^¥s]*)¥s([¥w¥W]*)¥]/, '[[¥2>¥1]]')) + "¥n" # [wiki:リンク] を [[リンク]] に変換
}
end
このスクリプトの標準入力に trac記法の ソース を食わせると、pukiwiki 形式の ソース が標準出力に出力されます。
NKF.nkf('-w -S',$_) や NKF.nkf('-W -s',... としているのは、osx のクリップボードがなぜか SJIS だから。
そしてなぜ osx のクリップボードが必要かというと、以下の シェル で、クリップボードから直接変換をするようにしているからです。
#!/bin/bash pbpaste | /Users/hal/bin/trac2pukiwiki.rb | pbcopy
pbpaste は osx 固有のコマンドで、クリップボードの中身を取得するコマンドです。そして、pbcopy は、標準入力から入力されたものを クリップボード へコピーします。
というわけで、このシェルを QuickSilver に登録しておき、tracの編集画面でソースをコピー。
その後QuickSilver から シェル を実行した後 pukiwiki の編集画面へペーストすると、無事移行完了です。
実は、上記スクリプトは若干手抜きで、インデントを付けた記述が、ちゃんと変換されなかったりします。
代表的なタグ(?)しか変換してないし、あくまで補助ツールということで。
オープンgungiの打ち合わせに行ってきた
- 2007-04-28 (土)
- 開発
今日は次回のオープンgungiの打ち合わせ。
次回は僕は特に話をしたりはしないので、気楽な感じで参加。
で、フォートラベルの山路さんと、ブログウォッチャーの羽野さんに話を聞く。
地域情報やコミュニティ、検索の話で盛り上がった。
特にスゴい地図の話が面白かったなぁ。
クリエイティブなサービスというのは、生まれる過程もかなり魅力的です。
本番も楽しみです。詳細決まったらここでも告知しますね。
sshで接続したサーバのディレクトリをFinder上で操作できる、sshfsを試してみた。
- 2007-03-05 (月)
- 開発
メッセで、O氏に「これいいッスよ」と教えてもらった、sshfsを試してみました。
Google CodeにMacFUSEというuserspaceファイルシステムフレームワークが公開されたので、ファイルアクセス用のAPIさえ書けば、いろんなものをFinder上で操作できるということです。
Google Codeのダウンロードページから落とせます。
sshfsを立ち上げてサーバのドメイン名とユーザ名/パスワードを入れるだけで、Finder上にマウントされます。
もともとTerminal&Emacs派なので、リモートサーバへのファイルコピー等はscpでやっていることもあり、頻繁につかうことはないかもしれませんが、ここぞというときに活躍しそうな予感です。
オープンソースCMS「Drupal」
- 2006-03-02 (木)
- 開発
オープンソースCMS「Drupal」の日本語情報サイトがオープン:ITpro
http://itpro.nikkeibp.co.jp/article/NEWS/20060302/231544/
3月1日、オープンソースの(CMS)コンテンツ管理システム「Drupal」 の日本語情報サイト「Drupal-jBox.net」がオープンした。サイトの構築、 運営を行っているのはDrupal日本語ユーザー有志。
Drupalの特徴は、ブログ、フォーラム発言、画像等の、あらゆるタイプのコンテンツを「ノード」 として統一して扱うこと。また、軽量であり、APIにより拡張が容易であるという。
面白そう。使ってみるリスト入りしました。
現在サイトにアクセスするとエラーですが、早く復旧するといいな
Home > 開発