/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the pRDFData. * * The Initial Developer of the Original Code is SHIMODA Hiroshi. * Portions created by the Initial Developer are Copyright (C) 2002-2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): SHIMODA Hiroshi * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* ++Abstract: a class for operate RDF datasource with an RDF container. the constructor needs 7 parameters when create instance. * the ID of the container * the URI of the RDF datasource * type of container (seq|bag|alt, default is seq) * namespace URI (optional) * prefix URI of IDs of items (optional) * format of ID of data (optional, see the instruction following) * a boolean value, indicates the wrapper should flush RDF datasource when you edit data (optional, default is false, see the instruction following) ++Example: var obj = new pRDFData( 'DATA', 'file:///e:/extensions.rdf' ); obj.setData('item1', 'Value', 'foobar'); obj.setData('item2', 'Value', 'test'); var value = obj.getData('item1', 'Value'), // you can get "foobar" length = obj.length; // you can get '2' for (var i = 0; i < obj.length; i++) { alert(obj.getData(obj.item(i), 'Value') } ++Format of RDF files generated by Mozilla: After these operations above, the resource was formatted like following: ++Appendix: nsIRDF* objects have some problems, so you should not use instances of this class. For example, if you create an instance with "ONLOAD", don't use it in the same "ONLOAD", so you should use it with a delayed function, like "setTimeout(func, 0)". IDs of data is specified like: '%namespaceURI%urn:%containerID%:%itemID%' (default) %namespaceURI% : namespace URI of the items %containerID% : ID of the container (like "DATA") %itemID% : ID of the item %itemID% is required. If there is no the parameter, it is appended at the last of item ID automatically. The ID of an item you specified is stored as a encoded text as URI. If you want to use ID with other format, like bookmarks, please get the resource getResource(aName) and operate with getData, setData. If you give "true" as the seventh argument, this wrapper updates the datasource file when you edit data by some methods. (setData, etc.) If not, or "false" is given, the datasource will be updated when the window is closed. To update the file on demand, use the "flush()" method with an argument, "true". */ /* ■概要: RDFコンテナとセットでデータソースを操作するクラス。 インスタンス生成時に以下のパラメータを要する。 ・コンテナのID ・データソースのURI ・コンテナのタイプ(seq|bag|alt, 省略可, デフォルトはseq) ・名前空間のURI(省略可) ・リソースのベースURI(省略可) ・保存するリソースのidの形式(省略化:詳しくは下を参照) ・データを編集したときにファイルを更新するかどうかを示す真偽値 (省略化, デフォルトはfalse, 詳しくは下記を参照) ■使用例: var obj = new pRDFData( 'DATA', 'file:///e:/extensions.rdf' ); obj.setData('item1', 'Value', 'ほげほげ'); obj.setData('item2', 'Value', 'はげはげ'); var value = obj.getData('item1', 'Value'), // 'ほげほげ' が得られる length = obj.length; // '2'が得られる // 登録済みの全リソースについて値を調べる for (var i = 0; i < obj.length; i++) { alert(obj.getData(obj.item(i), 'Value') } ■生成されるRDFファイルの形式: 上記の操作を行った段階で、こんな感じのRDFリソースができていると思って 下さい。 ■捕捉: 初期化の処理の関係上、呼び出してすぐには使わない方がいいです。 (nsIRDF*の各オブジェクトのバグ(仕様?)対策) onloadなどでインスタンスを生成した場合、そのonloadの処理中では呼び出さず、 setTimeout(func, 0) などで処理を遅らせると、正しく動作します。 保存する各リソースのidの形式は、このような書式で文字列で指定します。 '%namespaceURI%urn:%containerID%:%itemID%' (デフォルトはこの形) %namespaceURI% : 名前空間URI %containerID% : コンテナのID(ここでは"DATA") %itemID% : アイテムのID %itemID% は必須項目です。もし書式指定の中にこれが含まれていない場合は、 末尾に勝手に加えます。なお、コンテナのIDの書式は変更できません。 アイテムのIDはURI用にエンコードされて保存されます。エンコードされては困る 場合(ブックマークのURIと同じIDを使うときなど)は、getResource(aName)でリ ソースを取得してそれをIDの文字列の代わりにgetData, setDataなどに渡して下 さい。なお、保存済みアイテムについてはitem(n)でもリソースを取得できます。 第7の引数としてtrueを渡すと、setDataなどでデータを編集した際に即座に RDFデータソースのファイルを自動で更新します。無指定あるいはfalseだと、 ウィンドウを閉じるまで、あるいはflush()メソッドをtrueを引数として渡して 使うまで、ファイルは更新されません。 */ if ('pRDFData' in window) delete window.pRDFData; function pRDFData(aID, aDataSourceURI, aType, aNS, aRsourceBaseURI, aIDFormat, aShouldFlushOnEdit) { this.id = aID; // urn::root 、のの部分になる this.type = (aType) ? aType.toLowerCase() : 'seq' ; this.NS = aNS || 'http://white.sakura.ne.jp/~piro/rdf#'; this.resURIPrefix = aRsourceBaseURI || '' ; this.id_format = aIDFormat || '%namespaceURI%urn:%containerID%:%itemID%' ; this.shouldFlush = aShouldFlushOnEdit || false ; if (!this.id_format.match(/%itemID%/)) this.id_format += '%itemID%'; var ProfD = this.getURISpecFromKey('ProfD'); if (!ProfD.match(/\/$/)) ProfD += '/'; this.dsource_uri = (aDataSourceURI && aDataSourceURI.match(/^\w+:/)) ? aDataSourceURI : ProfD+aDataSourceURI ; this._resources = []; this.init(); } pRDFData.dataSourcesShouldBeFlushed = []; pRDFData.onUnloadRegistered = false; pRDFData.onUnload = function() { for (var i in pRDFData.dataSourcesShouldBeFlushed) pRDFData.dataSourcesShouldBeFlushed[i].flush(true); }; pRDFData.prototype = { shouldFlushOnUnload : false, RDF : Components.classes['@mozilla.org/rdf/rdf-service;1'].getService(Components.interfaces.nsIRDFService), RDFCUtils : Components.classes['@mozilla.org/rdf/container-utils;1'].getService(Components.interfaces.nsIRDFContainerUtils), TextToSubURI : Components.classes['@mozilla.org/intl/texttosuburi;1'].getService(Components.interfaces.nsITextToSubURI), container : null, // Initializing // 初期化 init : function() { this.resURI = this.id_format.replace(/%namespaceURI%/gi, this.resURIPrefix).replace(/%containerID%/gi, this.id); this.dsource = this.RDF.GetDataSource(this.dsource_uri); this.container = Components.classes['@mozilla.org/rdf/container;1'].createInstance(Components.interfaces.nsIRDFContainer); this.makeContainer(); this.reset(); window.setTimeout(this.initWithDelay, 0, this); }, initWithDelay : function(aObject) { aObject.reset(); if (!aObject.length) { // remove empty container, because we cannot append element to the empty container which is *loaded*. So, delete the container and re-create with startup. aObject.removeResource(aObject.containerNode); aObject.makeContainer(); aObject.reset(); // dump(aObject.id+' is recreated.\n'); } }, makeContainer : function() { this.containerNode = this.RDF.GetResource(this.resURIPrefix+'urn:'+this.id+':root'); var type = this.dsource.GetTarget(this.containerNode, this.RDF.GetResource('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'), true); if ( !type || !type.Value || type.Value.toLowerCase().idnexOf(this.type) < 0 ) { switch(this.type) { default: case 'seq': this.RDFCUtils.MakeSeq(this.dsource, this.containerNode); break; case 'bag': this.RDFCUtils.MakeBag(this.dsource, this.containerNode); break; case 'alt': this.RDFCUtils.MakeAlt(this.dsource, this.containerNode); break; } } this.container.Init(this.dsource, this.containerNode); }, // reset container and array // RDFコンテナと配列を初期化する reset : function() { try { this.container.Init(this.dsource, this.containerNode); } catch(e) { return false; } var nodes = this.container.GetElements(), res; this._resources = []; while (nodes.hasMoreElements()) { res = this.RDF.GetResource(nodes.getNext().QueryInterface(Components.interfaces.nsIRDFResource).Value); this._resources.push(res); } return true; }, // basic operations // リソースの基本操作 // get a resource from id // 指定した名前のリソースを得る getResource : function(aIDOrResource) { try { // if a resource is handed, return it if (aIDOrResource && 'QueryInterface' in aIDOrResource) { aIDOrResource = aIDOrResource.QueryInterface(Components.interfaces.nsIRDFResource); return aIDOrResource } } catch(e) { } var id = aIDOrResource; var res = this.RDF.GetResource(this.resURI.replace(/%itemID%/gi, escape(id))); var old = this.RDF.GetResource(this.resURI.replace(/%itemID%/gi, this.escape(id))); return (this.dsource.ArcLabelsOut(res).hasMoreElements()) ? res : old ; }, // get a resource from index // n個目のリソースを得る item : function(aIndex) { return (aIndex >= this.length) ? null : this._resources[aIndex] ; }, // get an id from resource // 指定したリソースのidを得る getID : function(aResource) { return this.unescapeString(aResource.Value.replace(/^([^#]+#)?urn:[^:]*:/, '')); }, // get an index from resource // リソースがRDFコンテナの中にあれば、その位置を返す indexOf : function(aResourceOrID) { var index = this.container.IndexOf(this.getResource(aResourceOrID)); return (index > 0) ? index-1 : index ; }, // save data // データの保存 // save data to the resource // if the resource is only specified, remove the resource. // ex. data.setData(aIDorResource, aKey1, aValue1, aKey2, aValue2, ...) // 指定されたリソースに、データを保存する。値がない場合、データを削除する。 // 第一引数がリソース。残りは、キー名と値の対。リソースは名前で指定することも可能。 // リソースだけを渡した場合、リソース自体を削除する。 setData : function() { var arg = (arguments[0].constructor == Array) ? arguments[0] : this.concat(arguments); var resourceOrID = arg.shift(); var removedData = []; if (!resourceOrID) return removedData; resourceOrID = this.getResource(resourceOrID); var valueName, newValue, oldValue; switch (arg.length) { case 0: try { this.container.RemoveElement(resourceOrID, true); removedData = this.removeResource(resourceOrID); } catch(e) { // dump(e+'\n'); } break; default: for (var i = 0; i < arg.length; i += 2) { valueName = this.RDF.GetResource(this.NS+arg[i]); try { // 古いデータを削除 oldValue = this.dsource.GetTarget(resourceOrID, valueName, true); removedData[arg[i]] = oldValue.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; this.dsource.Unassert(resourceOrID, valueName, oldValue); } catch(e) { // dump(e+'\n'); } // 値がundefinedの場合、削除するだけで終わる if (arg[i+1] === void(0)) continue; newValue = this.RDF.GetLiteral(arg[i+1] === null ? '' : String(arg[i+1])); this.dsource.Assert(resourceOrID, valueName, newValue, true); } if (this.indexOf(resourceOrID) < 0) this.container.AppendElement(resourceOrID); break; } this.reset(); this.flush(); return removedData; }, // save file to local disk // ファイルの更新 flush : function(aForce) { if (!aForce && !this.shouldFlush) { // flush on unload if (this.shouldFlushOnUnload) return; pRDFData.dataSourcesShouldBeFlushed[this.dsource_uri] = this; if (!pRDFData.onUnloadRegistered) window.addEventListener( 'unload', pRDFData.onUnload, false ); this.shouldFlushOnUnload = true; } else this.dsource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource).Flush(); }, // load data // リソースからデータを得る // get stored data of a resource with a key // 指定されたリソースから、キーの値を得る。 // リソースは名前で指定することも可能。 getData : function(aResourceOrID, valueKey) { aResourceOrID = this.getResource(aResourceOrID); var value; try { value = this.dsource.GetTarget( aResourceOrID, this.RDF.GetResource(this.NS+valueKey), true ); value = (value) ? value.QueryInterface(Components.interfaces.nsIRDFLiteral).Value : '' ; } catch(e) { value = ''; } return value; }, // get stored data of a resource of directory with a key // 与えられたURLから上層へディレクトリを辿っていき、最も近いディレクトリのデータを返す getDataFromPath : function(aPath, aKey) { var ret = ''; if (!aPath) return ''; if (aPath.match(/^[^:\/]+:[^:\/]+:\//i) || aPath.match(/^(view-source|about|jar):/i)) return ''; var dir = this.getParentDirs(aPath); for (var i in dir) { ret = this.getData(dir[i], aKey); if (ret) break; } return ret; }, // operate data // リソースの操作 // delete data // we can specfy the data by ID // リソースの削除 // リソースは名前で指定することも可能。 removeData : function(aResourceOrID) { aResourceOrID = this.getResource(aResourceOrID); return this.setData(aResourceOrID); }, // delete resource removeResource : function(aResourceOrID) { aResourceOrID = this.getResource(aResourceOrID); var removedData = []; var names = this.dsource.ArcLabelsOut(aResourceOrID), name, value, removed; // Unassertで全てのデータを削除すると、RDF:Descriptionも自動的に削除される。 while (names.hasMoreElements()) { try { name = names.getNext().QueryInterface(Components.interfaces.nsIRDFResource); value = this.dsource.GetTarget(aResourceOrID, name, true); // if the removed data is a literal string, add to the log try { removed = value.QueryInterface(Components.interfaces.nsIRDFLiteral).Value; removedData[name.Value.split('#')[1]] = removed; } catch(e) { } // dump('REMOVED: '+this.id+' / '+name.Value+'\n'); this.dsource.Unassert(aResourceOrID, name, value); } catch(e) { // dump('FAIL TO REMOVE: '+this.id+'\n'); // dump(e+'\n'); } } return removedData; }, // copy a data to new data // リソースをコピーする copyData : function(aOldResourceOrName, aNewaName) { if (!aOldResourceOrName || !aNewaName) return; aOldResourceOrName = this.getResource(aOldResourceOrName); var names = this.dsource.ArcLabelsOut(aOldResourceOrName), name; while (names.hasMoreElements()) { name = names.getNext().QueryInterface(Components.interfaces.nsIRDFResource).Value.match(/[^#]+$/); this.setData(aNewaName, name, this.getData(aOldResourceOrName, name)); } return; }, // rename a data // リソースのリネーム renameData : function(aOldResourceOrName, aNewaName) { if (!aOldResourceOrName || !aNewaName || this.getData(aNewaName, 'Name')) // if the item exists, do no action return null; var position = this.indexOf(aOldResourceOrName); this.copyData(aOldResourceOrName, aNewaName); this.removeData(aOldResourceOrName); this.moveElementTo(aNewaName, position); this.setData(aNewaName, 'Name', aNewaName); return aOldResourceOrName; }, // move a data to (with absolute index) // リソースの登録順をに変更する(動かす) moveElementTo : function(aResourceOrID, aIndex) { aResourceOrID = this.getResource(aResourceOrID); var current = this.indexOf(aResourceOrID); if (current < 0 || aIndex == current || aIndex < 0 || aIndex >= this.length) return false; this.container.RemoveElementAt(current+1, true); this.container.InsertElementAt(aResourceOrID, aIndex+1, true); this.reset(); this.flush(); return true; }, // move a data by (relative index) // リソースの登録順を分だけ動かす // リソースは名前で指定することも可能。 // 動かせなかった場合、falseを返す moveElement : function(aResourceOrID, aOrder) { aResourceOrID = this.getResource(aResourceOrID); var index = this.indexOf(aResourceOrID), toIndex = index+aOrder; if (index < 0) return false; return this.moveElementTo(aResourceOrID, toIndex); }, // clear all data // 全てのリソースの削除 clearData : function() { var count = this.length; if (count) for (var i = count-1; i > -1; i--) this.removeData(this.item(i)); }, // clean up the datasource (delete all of garbages) // 名前が「:root」で終わっておらず親を持っていない孤立したリソースを全て消す cleanUp : function(aForce) { var resources = this.dsource.GetAllResources(); var resource; while (resources.hasMoreElements()) { resource = resources.getNext().QueryInterface(Components.interfaces.nsIRDFResource); if (!resource.Value.match(/:root$/) && !this.dsource.ArcLabelsIn(resource).hasMoreElements()) this.removeResource(resource); } this.flush(aForce); }, // common methods // 汎用メソッド・プロパティ unescapeString : function(aString) { aString = String(aString || ''); var unescapedAsStr = unescape(aString); var unescapedAsURI = this.unescape(aString); return (this.escape(unescapedAsURI) == aString) ? unescapedAsURI : unescapedAsStr ; }, escape : function(aString) { return this.TextToSubURI.ConvertAndEscape('UTF-8', String(aString || '').replace(/ /g, '%20')).replace(/%2520/g, '%20').replace(/%2B/g, '+'); }, unescape : function(aString) { return this.TextToSubURI.UnEscapeAndConvert('UTF-8', String(aString || '').replace(/%%20/g, '+').replace(/\+/g, '%2B')); }, getURISpecFromKey : function(aKey) { const DIR = Components.classes['@mozilla.org/file/directory_service;1'].getService(Components.interfaces.nsIProperties); var dir = DIR.get(aKey, Components.interfaces.nsILocalFile); return this.getURLSpecFromFilePath(dir.path); }, getURLSpecFromFilePath : function(aPath) { var tempLocalFile = this.makeFileWithPath(aPath); try { return this.IOService.newFileURI(tempLocalFile).spec; } catch(e) { // [[interchangeability for Mozilla 1.1]] return this.IOService.getURLSpecFromFile(tempLocalFile); } }, makeFileWithPath : function(aPath) { var newFile = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); newFile.initWithPath(aPath); return newFile; }, createSupportsArray : function() { return Components.classes['@mozilla.org/supports-array;1'].createInstance(Components.interfaces.nsISupportsArray); }, concat : function() { var ret = [], i, j; for (i = 0; i < arguments.length; i++) for (j = 0; j < arguments[i].length; j++) ret.push(arguments[i][j]); return ret; }, // extract the current directory from an URI // URIからディレクトリを抜き出す getCurrentDir : function(aURI) { return (aURI) ? aURI.replace(/[#?].*|[^\/]*$/g, '') : '' ; }, // extract the parent directory from an URI // URIから親ディレクトリを抜き出す getParentDir : function(aURI) { var dir = this.getCurrentDir(aURI || ''); return (aURI && dir == aURI.match(/^[^:]+:\/\/[^\/]*\//)) ? dir : dir.replace(/[^\/]*\/$/, '') ; }, // extract parent directories from an URI and return an array // 親ディレクトリをルートまで辿り、配列として返す getParentDirs : function(aURI) { var dir = [this.getCurrentDir(aURI || '')]; for (var i = 1; dir[i-1] != this.getParentDir(dir[i-1]); i++) dir[i] = this.getParentDir(dir[i-1]); return dir; }, get IOService() { if (!this._IOService) { this._IOService = Components.classes['@mozilla.org/network/io-service;1'].getService(Components.interfaces.nsIIOService); } return this._IOService; }, _IOService : null, // methods compatible Array // Array 的プロパティ・メソッド // length get length() { var length; try { length = this.container.GetCount(); } catch(e) { length = 0; } if (length != this._resources.length) this.reset(); return length; }, // push(); push : function() { var resources = this.concat(arguments); for (var i in resources) this.container.AppendElement(resources[i]); this.reset(); return this.length; }, // unshift(); unshift : function() { var resources = this.concat(arguments); for (var i in resources) this.container.InsertElementAt(resources[i], i, true); this.reset(); return this.length; }, // pop(); pop : function() { var resource = this.item(obj.length-1); this.container.RemoveElement(resource, true); this.reset(); return resource; }, // shift(); shift : function() { var resource = this.item(0); this.container.RemoveElement(resource, true); this.reset(); return resource; }, // methods compatible DOM // DOM-Node 的プロパティ・メソッド // appendChild(); appendChild : function(aResource) { this.container.AppendElement(aResource); this.reset(); return aResource; }, // insertBefore(); insertBefore : function(aResource, aRefResource) { this.container.InsertElementAt(aResource, this.indexOf(aRefResource), true); this.reset(); return aResource; } // }; window.pRDFDataR = window.pRDFData; // Refined Version