// // HTML.msl - HTMLファイル編集支援ライブラリ Version 1.03 // Copyright 2004-2007 Anchor Systems Corporation // $Id: HTML.msl,v 1.56 2007/03/26 14:58:36 monnai Exp $ // //【概要】 // HTMLファイル編集の支援スクリプトを作成するためのライブラリです。 // 個々のタグの編集機能、色の編集機能などを使って利用することができます。 // 編集対象外の属性やその出現順序は、できるだけそのまま保存するので、安心 // して編集できます。 // //【HTMLタグヘルプに関して】 // HTML.tagHelpURL に設定されている URL の の部分を実際の小文字タグ // 名に置換したものを参照します。 デフォルトでは、「とほほのWWW入門」を参 // 照させてもらっています。「サイト内のどのページでもリンクは確認不要」と // あり、大変便利に使わせてもらっています。ありがとうございます。各タグの // 編集ダイアログボックスからは、F1キーでヘルプを表示できます。 // //【グローバルオブジェクト】 // このライブラリを読み込むと、以下の2つオブジェクトがグローバルオブジェ // クトに追加されます。 // // HtmlTag ... HTMLタグを表すクラス // HTML ...... HTMLの編集をサポートする関数、定数を集めたオブジェクト // // グローバルオブジェクトではありませんが、何かと便利なので String クラス // に splitcsv() というメソッドを追加しています。これは、文字列をコンマで // 分離し(文字列の)配列に変換するメソッドです。コンマの前後のスペースも区 // 切りの一部と見なして削除されます。これは、配列を [ "a", "b", "c", ...] // とだらだら記述するより、文字列を1つにまとめてそれを分解した方が簡潔に // 見え、しかもプログラムのコンパクトになるためです。 // //【TODO】 // 1. リンクの検証で以外にも、 // などもチェックしたい。 // 2. 同じファイル内で name 属性がユニークであることの検証もあれば便利。 // ただしチェックやラジオボタンの名前を賢く処理する必要がある。 // 3. スタイルシートの編集のサポート。 // 4. href="/abc/def.html"のように、絶対パスで記述されている場合、どこが // ルートかを指定する方法が必要。もちろん自動判別できれば、なおよい。 // (たぶんファイルのあるところからフォルダを上がっていって index.html // のあるところがROOT?) // 5. フレームを使ったページを表示しているとき、そのどこかに表示されてい // るファイルを編集した後での再描画を行えるようにしたい。 // 6. タグの終りを示す > の前にスペースを入れるオプション。 // //【ライセンス】 // このライブラリの著作権は、アンカーシステムズ株式会社にあります。誰でも // このライブラリを無償で使用、改変あるいは改変することなく再配布できます。 // ただし、オリジナルの著作権表示はそのまま残さなければなりません。 // //【保証】 // このライブラリを使った結果に対してアンカーシステムズ(株)は如何なる責任 // も負うものではありません。あくまでも利用される方の判断と責任においてご // 利用ください。 // //【バージョン】 // HTML.msl Version 1.03 2007/03/26 // H1、H2、H3、H4、H5、H6 のヘルプを H1 で代表させるようにしました。 // HTML.msl Version 1.02 2007/02/02 // エンティティからの変換で、' を ' に変換するようにしました。 // HTML.msl Version 1.01 2007/01/18 // エンティティへの変換で、" を " に変換しないように修正しました。 // HTML.msl Version 1.00 2006/06/06 // この拡張スクリプトは、Peggy Series バージョン 4.14 以上でお使い下さい。 // ///////////////////////////////////////////////////////////////////////////// // HtmlTag - HTML TAG を表すクラス global.HtmlTag = HtmlTag; String.prototype.splitcsv = function() { return this.split(/\s*,\s*/); }; function HtmlTag(elem, type) { // HtmlTag クラスのコンストラクタ。 this.elementName = HtmlTag.formatNewElementName(elem); this.type = type || 1; // 1: <...>、2: 、3: <.../> this.pieces = [ ]; // 属性と空白の断片の順序を記憶する配列 this.values = { }; // 連想配列: 小文字の属性名 => 値 this.quotes = { }; // 連想配列: 小文字の属性名 => 引用符 } HtmlTag.elemCase = 0; // エレメント名の大文字小文字を -> 0:変えない、1:小文字、2:大文字、3:強制CAP HtmlTag.attrCase = 0; // 属性名の大文字小文字を -> 0:変えない、1:小文字、2:大文字、3:強制CAP HtmlTag.newElemCase = 1; // 新規エレメント名の大/小文字 -> 0:(未使用)、1:小文字、2:大文字、3:強制CAP HtmlTag.newAttrCase = 1; // 新規属性名の大/小文字 -> 0:(未使用)、1:小文字、2:大文字、3:強制CAP HtmlTag.caseConverters = // 大文字/小文字変換関数の配列 (0〜3) [ function(str) { return str; }, function(str) { return str.toLowerCase(); }, function(str) { return str.toUpperCase(); }, function(str) { return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase(); } ]; HtmlTag.valueQuote = 1; // 属性値の引用符を // 0: できるだけ変えない(新規属性には付けない) // 1: できるだけ変えない(新規属性にはつける) // 2: できるだけ付けない(必要なときだけ付ける) // 3: 数字以外付ける // 4: 必ず付ける HtmlTag.quoteChar = "\""; // 引用符として使うデフォルト文字( " または ' または空文字列 ) HtmlTag.quoteName = true; // name, href, src の値には常に引用符を付ける HtmlTag.typefaces = // タグでコンボボックスから選択できるタイプフェイスリスト [ "MS ゴシック", "MS Pゴシック", "MS UI Gothic", "MS 明朝", "MS P明朝", "Arial", "Century", "Times New Roman", "Terminal", "Courier" ]; HtmlTag.parse = function(vw, pt) { // ポイント pt、またはカーソル位置のHTMLタグを解析するクラスメソッド。 // view がない、pt、カーソル位置にタグがない、またはエレメント名が正し // く取得できなかった場合は、null を返す。 // // NOTE: // 言語モードは、HTML、JSP、PHP など HTML ベースのものであること。 // この関数を呼び出してもカーソルや選択範囲は変化しない。 // ポイント pt、カーソルは、タグ開始位置(<の位置)からタグの途中のどこに // あってもよい。そのタグ全体を解析して以下の形式で返す。 // // tag[0] ............. Point 開始ポイント位置 // tag[1] ............. Point 終了ポイント位置 // tag.elementName .... string エレメント名 // tag.type ........... integer タグ形式: 1=<...>、2=、3=<.../> // tag.pieces ......... Array 属性と空白の配列 (出現順・大文字小文字もそのまま) // tag.values ......... Object 連想配列: 小文字の属性名 => 値 (引用符なし) // tag.quotes ......... Object 連想配列: 小文字の属性名 => 引用符 // tag.text ........... string タグ文字列 // tag.editor ......... View 編集ウインドウ // tag.postOpen ....... string 開きタグの後に追加する文字列 // tag.preClose ....... string 閉じタグの前に追加する文字列 // tag.postProcess .... function タグ挿入後に行う追加処理関数 // // NOTE: 属性の表し方 // values にエントリーがない場合は、タグに属性が存在しないことを意味する。 // values にエントリーがある場合は、下表の値と引用符の組み合わせに従う。 // // =========== =============== ========================================= // 属性値 引用符 属性値 // ----------- --------------- ----------------------------------------- // あり あり ="value" または ='value' // あり 空 =value // あり 未定義 新規登録属性である // なし あり ="" または ='' // なし 空 フラグ属性 (=value の部分がない属性) // なし 未定義 ( 同上 ) // =========== =============== ========================================= // var tag = vw && vw.htmlGetTag(pt || vw.CP); if (!tag) return null; // のようなタグ(?)ではエレメント名がセットされないので、 // エラーを避けるため、取りあえず ! をセットしている。 if (!tag.elementName) tag.elementName = "!"; // テキストを取得し、マッチング開始位置(エレメント名の直後)をセットする。 var text = vw.getTextBetween(tag[0], tag[1]); var rexp = /([A-Za-z_][A-Za-z0-9_-]*)\s*(=\s*("[^"]*"|'[^']*'|[^\s>]+))?|\s+/g; rexp.lastIndex = tag.elementName.getByteLength() + (tag.type == 2 ? 2 : 1); // 属性を解析し、属性名と空白の出現順序をpieces配列、属性値をvalues連想配列、 // 引用符をquotes連想配列へそれぞれ保存する。 var pieces = [ ]; var values = { }; var quotes = { }; var array; while (array = rexp.exec(text)) { var name = array[1]; if (name) { // 属性の名前、値、引用符の種類("、'、なし)を分離してを記録する。 pieces.push(name); var quote = ""; var value = array[2] ? array[3] : ""; var first = value.substr(0, 1); if (value.length >= 2 && (first == "\"" || first == "'") && first == value.substr(-1)) { quote = first; value = value.slice(1, -1); } name = name.toLowerCase(); quotes[name] = quote; values[name] = value; } else { // 属性間の空白文字を記録する。 pieces.push(array[0]); } } // 属性とその値の解析結果をtagに追加し結果として返す。 tag.pieces = pieces; tag.values = values; tag.quotes = quotes; tag.editor = vw; tag.text = text; // tag を HtmlTag クラスのインスタンスに化けさせる。 tag.__proto__ = this.prototype; return tag; }; HtmlTag.prototype.dump = function(page) { // タグの内容をデバッグ用にできるだけ加工せずにダンプする。 if (!page) page = output[7]; // エレメント名とタイプをダンプする。 page.writeln("Element Name: ", this.elementName); page.writeln("Element Type: ", [ "unknown", "open", "close", "self-close" ][this.type || 0]); if (this[0] && this[1]) page.writeln("Points: (", this[0].line, ", ", this[0].index, ") 〜 (", this[1].line, ", ", this[1].index, ")"); // 属性情報をダンプする。 var pieces = this.pieces; var values = this.values; var quotes = this.quotes; var check = { }; writeln("Attributes:"); for (var i in pieces) { // 属性間の空白は無視する。 var piece = pieces[i]; if (HtmlTag.isBlank(piece)) continue; // pieces に重複して登録されている属性を無視する。 var lower = piece.toLowerCase(); if (check[lower]) continue; check[lower] = true; // values に登録されている属性と値をダンプする。 if (lower in values) { var quote = quotes[lower]; writeln("\t", piece, " = ", quote, values[lower], quote); } } }; HtmlTag.getTagText = function(elem, type) { // 単純なテキスト表現のタグを作成する。 return (type == 2 && !HtmlTag.wantClose(elem)) ? "" : (new HtmlTag(elem, type)).compose(); }; HtmlTag.prototype.compose = function(separator) { // HTML タグテキストを組み立てる。 // separator が指定されているときは、それを使って属性間を区切る。 // 指定されていないときは、できるだけ元の空白文字列を使って区切る。 if (!this.type || !this.elementName) return ""; // タグ開始部分を作成する。 var text = (this.type == 2 ? "" : ">"); }; HtmlTag.formatElementName = function(name) { // エレメント名の大文字/小文字を整形する。 return HtmlTag.caseConverters[ HtmlTag.elemCase || 0 ](name); }; HtmlTag.formatNewElementName = function(name) { // エレメント名の大文字/小文字を整形する。 return HtmlTag.caseConverters[ HtmlTag.newElemCase || 1 ](name); }; HtmlTag.formatAttrName = function(name) { // 属性名の大文字/小文字を整形する。 return HtmlTag.caseConverters[ HtmlTag.attrCase || 0 ](name); }; HtmlTag.formatNewAttrName = function(name) { // 属性名の大文字/小文字を整形する。 return HtmlTag.caseConverters[ HtmlTag.newAttrCase || 1 ](name); }; HtmlTag.formatAttrValue = function(attr, value, quote) { // 属性値を整形し、設定により必要に応じて引用符を付ける。 var qchar = HtmlTag.quoteChar || quote || "\""; // 設定に従って属性値に引用符を付ける。 switch (HtmlTag.valueQuote) { case 1: // できるだけ変えない(新規属性にはつける) --> quoteが未定義なら新規定義なので付ける。 if (quote === undefined) quote = qchar; case 0: // できるだけ変えない(新規属性には付けない) --> 付いていなくて必要なら付ける。 if (quote) break; case 2: // できるだけ付けない --> A-Z、a-z、0-9、_、-、:、. だけなら付けない。 // NOTE: HTML4.01 から _ と : が引用符省略可能文字に追加された。 if (HtmlTag.quoteName && (String.stricmp(attr, "href") == 0 || String.stricmp(attr, "src") == 0 || String.stricmp(attr, "name") == 0)) quote = qchar; else quote = /^[-.:_0-9A-Za-z]+$/.test(value) ? "" : qchar; break; case 3: // 数字以外付ける --> 数字は付けない。 quote = /^\d+$/.test(value) ? "" : qchar; break; default: // 必ず付ける。 quote = qchar; break; } if (quote) { // 値に含まれている引用符は使えないのでもう一方に変更する。 // どちらも含まれていないのであれば、そのまま使えばよい。 var sq = /'/.test(value); var dq = /"/.test(value); if (sq && dq) { // 両方ある場合は、" を " に変換してから " を使う? value = value.replace(/"/g, """); quote = "\""; } else if (dq) quote = "\'"; else if (sq) quote = "\""; } return quote + value + quote; }; HtmlTag.isBlank = function(str) { // 空白文字だけからなる文字列かどうかテストする。 return !str || !str.trim(); }; HtmlTag.prototype.getAttr = function(attr) { // 属性値を取得する。 return this.values[ attr.toLowerCase() ]; }; HtmlTag.prototype.setAttr = function(attr, value, quote) { // 属性値を設定する。 var lower = attr.toLowerCase(); if (!(lower in this.values)) { // 初めての属性ならば、順序配列の最後に追加する。 // 新規属性を追加する場合は、新規属性の大文字/小文字ルールに従う。 this.pieces.push(HtmlTag.formatNewAttrName(attr)); } // 値と(指定されていれば)引用付の形式を設定する。 this.values[lower] = value; if (arguments.length >= 3) this.quotes[lower] = quote; }; HtmlTag.prototype.delAttr = function(attr) { // 属性値を削除する。 return delete this.values[ attr.toLowerCase() ]; }; HtmlTag.prototype.testAttr = function(attr, value) { // 属性値を大文字小文字を区別せずに一致しているかどうか調べる。 var val = this.getAttr(attr); return val && String.stricmp(val, value) == 0; }; HtmlTag.prototype.updateAttr = function(attr, value) { // 属性を更新(セットまたは削除)する。 if (value === undefined || value == "") this.delAttr(attr); else this.setAttr(attr, String(value)); }; HtmlTag.prototype.updateByNames = function(names, values) { // namesに名前が列挙されている複数の属性をまとめて更新する。 // 名前リストとして文字列が渡された場合は、| を区切りと見なして分解する。 if (typeof names == "string") names = names.splitcsv(); for (var i in names) { var notrim = false; var attr = names[i]; var code = attr.charCodeAt(0); if (code == 0x21 /* "!" */) { // 属性名の先頭が(!)なら、フラグとして処理する。 attr = attr.substr(1); this.updateFlag(attr, values[attr]); continue; } if (code == 0x23 /* "#" */) { // 属性名の先頭が(#)なら、属性値のトリミングをしない。 attr = attr.substr(1); notrim = true; } var value = String(values[attr]); this.updateAttr(attr, notrim ? value : value.trim()); } }; HtmlTag.prototype.updateFlag = function(attr, flag) { // 属性名があることだけが意味を持つ属性(例:
)を設定/削除する。 if (flag) this.setAttr(attr, "", ""); else this.delAttr(attr); }; HtmlTag.prototype.getQuote = function(attr) { // 属性値が囲まれていた引用符を返す。 return this.quotes[ attr.toLowerCase() ] || ""; }; HtmlTag.prototype.setQuote = function(attr, quote) { // 属性値が囲まれている引用符を設定する。 this.quotes[ attr.toLowerCase() ] = quote || ""; }; HtmlTag.prototype.setPostPre = function(post, pre) { this.postOpen = post; this.preClose = pre; }; ///////////////////////////////////////////////////////////////////////////// // HtmlTag - 閉じタグ必要性の判定 HtmlTag.wantClose = function(elem) { // elem が閉じタグを必要とするかどうかを判定する。 // closeTags[] になければ必要とみなす。 // closeTags[] にあれば、それをインデックスとして closeOpt[] を参照する。 elem = elem.toLowerCase(); return !(elem in HtmlTag.closeTags) || HtmlTag.closeOpt[ HtmlTag.closeTags[elem] ]; }; HtmlTag.mustClose = function(elem) { // elem が必ず閉じなければならないタグかどうかを判定する。 return !(elem.toLowerCase() in HtmlTag.closeTags); }; HtmlTag.isOpenOnly = function(elem) { // elem が閉じタグなしのタグであるかどうかを判定する。 return HtmlTag.closeTags[ elem.toLowerCase() ] === 0; }; HtmlTag.mayOmitClose = function(elem) { // elem が省略可能タグであるかどうかを判定する。 return HtmlTag.closeTags[ elem.toLowerCase() ] >= 2; }; HtmlTag.closeTags = { // Group 0 - 閉じタグなし area: 0, base: 0, basefont: 0, bgsound: 0, br: 0, col: 0, embed: 0, frame: 0, hr: 0, img: 0, input: 0, isindex: 0, keygen: 0, link: 0, meta: 0, nextid: 0, param: 0, plaintext: 0, spacer: 0, wbr: 0, // Group 1 - 常に必要 (この連想配列になければ常に必要なので該当なし) // Group 2 - 省略可能

p: 2, // Group 3 - 省略可能 (1) 関連タグ tr: 3, th: 3, td: 3, // Group 4 - 省略可能
(2) 関連タグ colgroup: 4, // Group 5 - 省略可能
(3) 関連タグ thead: 5, tfoot: 5, tbody: 5, // Group 6 - 省略可能
      関連タグ li: 6, // Group 7 - 省略可能
      ....
      関連タグ dt: 7, dd: 7, // Group 8 - 省略可能
(1) 関連 、
false, // Group 4 - (2) 関連 false, // Group 5 -
(3) 関連 、、 false, // Group 6 -
      関連
    • false, // Group 7 -
      関連
      false, // Group 8 -