角丸 / getElementsBySelector() - refactored

呼び出される度にクロージャを作るのは効率悪いよね、ってことでリファクタリング
この変更で

Rounder.roundAndPad.mapToElementsBySelector('selector');

というのはできなくなって、

(function(e){Rounder.roundAndPad(e)}).mapToElementsBySelector('selector');

と書かないといけなくなりました。
また、getElementsBySelector():(first|last)-child 疑似クラスに対応しました。

NiftyCorner.js

/* 基本オブジェクトを拡張 */
if (!String.trim)   { String.prototype.trim   = function() { return this.replace(/^\s+|\s+$/g, ''); }}
if (!String.ltrim)  { String.prototype.ltrim  = function() { return this.replace(/^\s+/, ''); }}
if (!String.rtrim)  { String.prototype.rtrim  = function() { return this.replace(/\s+$/, ''); }}
if (!Number.square) { Number.prototype.square = function() { return Math.pow(this, 2); }}
if (!Number.cube)   { Number.prototype.cube   = function() { return Math.pow(this, 3); }}
if (!Number.sqrt)   { Number.prototype.sqrt   = function() { return Math.sqrt(this);   }}
if (!Number.round)  { Number.prototype.round  = function() { return Math.round(this);  }}
if (!Number.floor)  { Number.prototype.floor  = function() { return Math.floor(this);  }}
if (!Number.ceil)   { Number.prototype.ceil   = function() { return Math.ceil(this);   }}
if (!Math.hypot) { Math.hypot = function(x, y) { return (x.square() + y.square()).sqrt(); }}
if (!Math.base)  { Math.base  = function(z, y) { return (z.square() - y.square()).sqrt(); }}

/* スタイルシート */
var NiftyStyle
  = '<style type="text/css">'
  + 'span.NiftyElement {'
  + 'display:block;'
  + 'margin:0;'
  + 'padding:0;'
  + 'overflow:hidden;'
  + 'overflow-x:hidden;'
  + 'overflow-y:hidden;'
  + '}'
  + '</style>';
document.write(NiftyStyle);

/**
 * 角丸化オブジェクト本体
 */
function NiftyCorner()
{
  // {{{ 初期化

  var mode = 'round';
  if (arguments.length > 0) {
    mode = arguments[0].toString();
    switch (arguments[0]) {
    case 0:
      mode = 'linear';
      break;
    case -1:
      mode = 'flipped';
      break;
    }
  }

  this._bgcolor = (arguments.length > 1) ? arguments[1].toString() : '#fff';
  this._color   = (arguments.length > 2) ? arguments[2].toString() : '#ccc';
  this._size    = (arguments.length > 3 && parseInt(arguments[3]) > 0) ? parseInt(arguments[3]) : 5;
  this._step    = (arguments.length > 4 && parseInt(arguments[4]) > 0) ? parseInt(arguments[4]) : 1;
  this._size    = Math.max(this._size, this._step);
  this._bdcolor = (arguments.length > 5) ? arguments[5].toString() : null;
  this._bdwidth = (arguments.length > 6 && parseInt(arguments[6]) > 0) ? parseInt(arguments[6]) : 1;
  this._bdwidth = Math.min(Math.floor(this._size / this._step), this._bdwidth);

  this._default = [this._bgcolor, this._color, this._size, this._step, this._bdcolor, this._bdwidth];
  this._defaultId = this._default.join();
  this._defaultTop = null;
  this._defaultBottom = null;

  // }}}
  // {{{ モードに適したマージン計算メソッドを設定

  switch (mode) {
  case 'flip':
  case 'flipped':
  case 'eclipse':
    this.calcMargin = this.calcFlippedMargin;
    this.calcMargin2 = this.calcFlippedMargin2;
    break;
  case 'line':
  case 'linear':
  case 'straight':
    this.calcMargin = this.calcLinearMargin;
    this.calcMargin2 = this.calcLinearMargin2;
    break;
  case 'round':
  default:
    this.calcMargin = this.calcRoundMargin;
    this.calcMargin2 = this.calcRoundMargin2;
  }

  // }}}
  // {{{ デフォルト値で作成した要素をキャッシュ

  this._defaultTop = this.createTop(this._default);
  this._defaultBottom = this.createBottom(this._default);

  // }}}
}

// }}}
/* 共通処理はプロトタイプに定義 */
// {{{ 角丸化メソッド

/**
 * 四隅を丸くする
 */
NiftyCorner.prototype.round = function()
{
  var params = this.parseArguments(arguments);
  var target = params.shift();
  var top = target.insertBefore(this.createTop(params), target.firstChild);
  var bottom = target.appendChild(this.createBottom(params));
  return [top, bottom];
}

/**
 * 上の角を丸くする
 */
NiftyCorner.prototype.roundTop = function()
{
  var params = this.parseArguments(arguments);
  var target = params.shift();
  return target.insertBefore(this.createTop(params), target.firstChild);
}

/**
 * 下の角を丸くする
 */
NiftyCorner.prototype.roundBottom = function()
{
  var params = this.parseArguments(arguments);
  var target = params.shift();
  return target.appendChild(this.createBottom(params));
}

/**
 * 四隅を丸くし、内容を自動でパディングする
 */
NiftyCorner.prototype.roundAndPad = function()
{
  var params  = this.parseArguments(arguments);
  var target  = params.shift();
  var bgcolor = params[0];
  var color   = params[1];
  var size    = params[2];
  var step    = params[3];
  var bdcolor = params[4];
  var bdwidth = params[5];
  var container = document.createElement('div');
  container.style.backgroundColor = bgcolor;
  container.style.margin = '0';
  if (bdcolor) {
    var bdsize = step * bdwidth;
    container.style.padding = this.intToPixelsHorizontal(size - bdsize);
    container.style.borderColor = bdcolor;
    container.style.borderStyle = 'solid';
    container.style.borderWidth = this.intToPixelsHorizontal(bdsize);
  } else {
    container.style.padding = this.intToPixelsHorizontal(size);
  }
  if (target.childNodes) {
    while (target.childNodes.length) {
      container.appendChild(target.removeChild(target.firstChild));
    }
  }
  target.appendChild(container);
  var top = target.insertBefore(this.createTop(params), target.firstChild);
  var bottom = target.appendChild(this.createBottom(params));
  return [top, bottom, container];
}

/**
 * 上の角を丸くし、内容を自動でパディングする
 */
NiftyCorner.prototype.roundAndPadTop = function()
{
  var params  = this.parseArguments(arguments);
  var target  = params.shift();
  var bgcolor = params[0];
  var color   = params[1];
  var size    = params[2];
  var step    = params[3];
  var bdcolor = params[4];
  var bdwidth = params[5];
  var container = document.createElement('div');
  container.style.backgroundColor = bgcolor;
  container.style.margin = '0';
  if (bdcolor) {
    var bdsize = step * bdwidth;
    var p = ' ' + this.intToPixels(size - bdsize);
    var b = ' ' + this.intToPixels(bdsize);
    container.style.padding = '0' + p + p + p;
    container.style.borderColor = bdcolor;
    container.style.borderStyle = 'solid';
    container.style.borderWidth = '0' + b + b + b;
  } else {
    var p = ' ' + this.intToPixels(size);
    container.style.padding = '0' + p + p + p;
  }
  if (target.childNodes) {
    while (target.childNodes.length) {
      container.appendChild(target.removeChild(target.firstChild));
    }
  }
  target.appendChild(container);
  var top = target.insertBefore(this.createTop(params), target.firstChild);
  return [top, container];
}

/**
 * 下の角を丸くし、内容を自動でパディングする
 */
NiftyCorner.prototype.roundAndPadBottom = function()
{
  var params  = this.parseArguments(arguments);
  var target  = params.shift();
  var bgcolor = params[0];
  var color   = params[1];
  var size    = params[2];
  var step    = params[3];
  var bdcolor = params[4];
  var bdwidth = params[5];
  var container = document.createElement('div');
  container.style.backgroundColor = bgcolor;
  container.style.margin = '0';
  if (bdcolor) {
    var bdsize = step * bdwidth;
    var p = ' ' + this.intToPixels(size - bdsize);
    var b = ' ' + this.intToPixels(bdsize);
    container.style.padding = (p + p + ' 0' + p).ltrim();
    container.style.borderColor = bdcolor;
    container.style.borderStyle = 'solid';
    container.style.borderWidth = (b + b + ' 0' + b).ltrim();
  } else {
    var p = ' ' + this.intToPixels(size - bdsize);
    container.style.padding = (p + p + ' 0' + p).ltrim();
  }
  if (target.childNodes) {
    while (target.childNodes.length) {
      container.appendChild(target.removeChild(target.firstChild));
    }
  }
  target.appendChild(container);
  var bottom = target.appendChild(this.createBottom(params));
  return [bottom, container];
}

// }}}
// {{{ 要素作成メソッド

/**
 * 上の角を作成する
 */
NiftyCorner.prototype.createTop = function(p)
{
  if (this._defaultTop && p.join() == this._defaultId) {
    return this._defaultTop.cloneNode(true);
  }
  var corner = this.createCorner(p[1], p[2]);
  corner.appendChild = function(newChild)
  {
    return this.insertBefore(newChild, this.firstChild);
  }
  if (p[4] == null) {
    this._appendLines(corner, p[0], p[2], p[3]);
  } else if (p[5] <= 1) {
    this._appendLinesBordered(corner, p[0], p[2], p[3], p[4]);
  } else {
    this._appendLinesBordered2(corner, p[0], p[2], p[3], p[4], p[5]);
  }
  return corner;
}

/**
 * 下の角を作成する
 */
NiftyCorner.prototype.createBottom = function(p)
{
  if (this._defaultBottom && p.join() == this._defaultId) {
    return this._defaultBottom.cloneNode(true);
  }
  var corner = this.createCorner(p[1], p[2]);
  if (p[4] == null) {
    this._appendLines(corner, p[0], p[2], p[3]);
  } else if (p[5] <= 1) {
    this._appendLinesBordered(corner, p[0], p[2], p[3], p[4]);
  } else {
    this._appendLinesBordered2(corner, p[0], p[2], p[3], p[4], p[5]);
  }
  return corner;
}

/**
 * 角のコンテナを作成する
 */
NiftyCorner.prototype.createCorner = function(bgcolor, height)
{
  var corner = document.createElement('span');
  corner.className = 'NiftyElement';
  corner.style.backgroundColor = bgcolor;
  corner.style.height = this.intToPixels(height);
  return corner;
}

/**
 * 角の実体を作成する
 */
NiftyCorner.prototype.createLine = function(bgcolor, height, margin)
{
  var line = document.createElement('span');
  line.className = 'NiftyElement';
  line.style.backgroundColor = bgcolor;
  line.style.height = this.intToPixels(height);
  line.style.margin = this.intToPixelsHorizontal(margin);
  return line;
}

/**
 * 境界線つきの角の実体を作成する
 */
NiftyCorner.prototype.createLineBordered = function(bgcolor, height, margin, bdcolor, bdwidth)
{
  var line = document.createElement('span');
  line.className = 'NiftyElement';
  line.style.backgroundColor = bgcolor;
  line.style.height = this.intToPixels(height);
  line.style.margin = this.intToPixelsHorizontal(margin);
  line.style.borderColor = bdcolor;
  line.style.borderStyle = 'solid';
  line.style.borderWidth = this.intToPixelsHorizontal(bdwidth);
  return line;
}

// }}}
// {{{ コーナー描画メソッド

/**
 * コンテナに線を追加し、角を描画する
 */
NiftyCorner.prototype._appendLines = function(corner, bgcolor, size, step)
{
  var i, line;
  for (i = 0; i < size; i += step) {
    line = this.createLine(bgcolor, step, this.calcMargin(size, i, step));
    corner.appendChild(line);
  }
}

/**
 * コンテナに線を追加し、線の高さと同じ幅の境界線をもった角を描画する
 */
NiftyCorner.prototype._appendLinesBordered = function(corner, bgcolor, size, step, bdcolor)
{
  var i, line, margin, bdsize;
  for (i = 0; i < size - step; i += step) {
    margin = this.calcMargin(size, i, step);
    bdsize = Math.max(step, this.calcMargin(size, i + step, step) - margin);
    line = this.createLineBordered(bgcolor, step, margin, bdcolor, bdsize);
    corner.appendChild(line);
  }
  line = this.createLine(bdcolor, step, this.calcMargin(size, i, step));
  corner.appendChild(line);
}

/**
 * コンテナに線を追加し、任意の幅の境界線をもった角を描画する
 */
NiftyCorner.prototype._appendLinesBordered2 = function(corner, bgcolor, size, step, bdcolor, bdwidth)
{
  var bdsize = step * bdwidth;
  var insize = size - bdsize ;
  var i, line, margins;
  for (i = 0; i < insize; i += step) {
    margins = this.calcMargin2(size, insize, bdsize, i, step);
    line = this.createLineBordered(bgcolor, step, margins[0], bdcolor, margins[1]);
    corner.appendChild(line);
  }
  while (i < size) {
    line = this.createLine(bdcolor, step, this.calcMargin2(size, insize, bdsize, i, step)[0]);
    corner.appendChild(line);
    i += step;
  }
}

// }}}
// {{{ マージン計算メソッド

/**
 * 角丸のマージンを計算する
 */
NiftyCorner.prototype.calcRoundMargin = function(size, level, step)
{
  return size - Math.floor(Math.base(size, level + step) / step) * step;
}

/**
 * 角丸のマージンと境界線の横幅を計算する
 */
NiftyCorner.prototype.calcRoundMargin2 = function(size, insize, bdsize, level, step)
{
  var m0 = 0, m1 = 0;
  m0 = this.calcRoundMargin(size, level, step);
  if (level < insize) {
    m1 = Math.max(this.calcRoundMargin(insize, level, step), this.calcRoundMargin(size, level + bdsize, step));
  }
  return [m0, Math.max(bdsize, m1 - m0)];
}

/**
 * 面取りのマージンを計算する
 */
NiftyCorner.prototype.calcLinearMargin = function(size, level, step)
{
  return level;
}

/**
 * 面取りのマージンと境界線の横幅を計算する
 */
NiftyCorner.prototype.calcLinearMargin2 = function(size, insize, bdsize, level, step)
{
  var x = Math.round(bdsize * (Math.SQRT2 - 1));
  return [level - x, bdsize + x];
}

/**
 * 切り欠きのマージンを計算する
 */
NiftyCorner.prototype.calcFlippedMargin = function(size, level, step)
{
  return Math.floor(Math.base(size, size - level) / step) * step;
}

/**
 * 切り欠きのマージンと境界線の横幅を計算する
 */
NiftyCorner.prototype.calcFlippedMargin2 = function(size, insize, bdsize, level, step)
{
  var m0 = 0, m1 = 0;
  if (level >= bdsize) {
    m0 = this.calcFlippedMargin(insize, level - bdsize, step);
  }
  if (level < insize) {
    m1 = Math.max(this.calcFlippedMargin(size, level, step), this.calcFlippedMargin(insize, level, step));
  }
  return [m0, Math.max(bdsize, m1 - m0)];
}

// }}}
// {{{ ユーティリティメソッド

/**
 * 引数をパースし、適切な値を設定する
 */
NiftyCorner.prototype.parseArguments = function(args)
{
  var params = [];
  if (args.lengs == 0) {
    window.alert('argument #1 is required.');
    return [];
  }
  params[0] = (typeof args[0] == 'string') ? document.getElementById(args[0]) : args[0];
  if (!params[0] || !params[0].appendChild) {
    window.alert('argument #1 must be a DOM Element or an existing Element ID.');
    return [];
  }
  params[1] = (args.length > 1) ? args[1].toString() : this._bgcolor;
  params[2] = (args.length > 2) ? args[2].toString() : this._color;
  params[3] = (args.length > 3 && parseInt(args[3]) > 0) ? parseInt(args[3]) : this._size;
  params[4] = (args.length > 4 && parseInt(args[4]) > 0) ? parseInt(args[4]) : this._step;
  params[5] = (args.length > 5) ? args[5].toString() : this._bdcolor;
  params[6] = (args.length > 6 && parseInt(args[6]) > 0) ? parseInt(args[6]) : this._bdwidth;

  params[3] = Math.max(params[3], params[4]);
  params[6] = Math.min(Math.floor(params[3] / params[4]), params[6]);

  return params;
}

/**
 * 数値をスタイルシート用のピクセル表記に変換する
 */
NiftyCorner.prototype.intToPixels = function(num)
{
  return parseInt(num).toString() + 'px';
}

/**
 * 数値をスタイルシート用のピクセル表記に変換する(上下は指定された数値、左右はゼロ)
 */
NiftyCorner.prototype.intToPixelsVertical = function(num)
{
  return this.intToPixels(num) + ' 0';
}

/**
 * 数値をスタイルシート用のピクセル表記に変換する(左右は指定された数値、上下はゼロ)
 */
NiftyCorner.prototype.intToPixelsHorizontal = function(num)
{
  return '0 ' + this.intToPixels(num);
}

// }}}

getElementsBySelector.js

// {{{ document.getElementsBySelector()

/**
 * CSS2 のセレクタで DOM Element を取得するメソッド
 * document オブジェクトを拡張
 */
document.getElementsBySelector = function(selector)
{
  // {{{ 変数の初期化

  // myself
  var self = document.getElementsBySelector;

  // valid characters
  var c = '[A-Za-z_][0-9A-Za-z_\\-]*';
  // valid characters with namaspace
  var ns = '(?:'+c+'\\\\:)?'+c;
  // regular expression object of valid characters
  var re = new RegExp('^'+c+'$');

  // regular expression of valid selector
  var slctr_pat = '(\\*|'+ns+')((?:\\['+ns+'(?:[~|]?=".*?")?\\]|[#.:]'+c+')*)';
  // regular expression of non-element (pseudo,id,class,attribute) selector
  var picas_pat = '\\[('+ns+')(?:([~|]?=)"(.*?)")?\\]|([#.:])('+c+')';
  // regular expression object of valid selector
  var slctr_re = new RegExp(slctr_pat, 'g');
  // regular expression object of non-element selector
  var picas_re = new RegExp();

  // last match position of selector
  var slctr_pos = 0;
  // offset from last match position of selector
  var slctr_off = 0;
  // last match position of non-element selector
  var picas_pos = 0;
  // offset from last match position of non-element selector
  var picas_off = 0;

  // list of elements
  var roots = [document];
  // variables for scanning
  var slctr, dvsr, elems, i, j, m, n, o, p;

  // }}}
  // {{{ セレクタをパースし、要素を走査

  while (m = slctr_re.exec(selector)) {
    // {{{ 前処理

    /* 前回の検索終了位置と今回の検索開始位置の差分 */
    slctr_off = slctr_re.lastIndex - m[0].length - slctr_pos;

    /* セレクタ間の区切りを検証 */
    if (slctr_pos > 0 && slctr_off == 0) {
      self.warn(selector, slctr_pos, 'Bad selector given.');
      return [];
    } else if (slctr_off != 0) {
      dvsr = selector.substr(slctr_pos, slctr_off).replace(/^[\t\r\n ]+|[\t\r\n ]+$/g, '');
      if (!self.in_array(dvsr, ['', '+', '>'])) {
        self.warn(selector, slctr_pos, 'Bad selector given.');
        return [];
      }
    } else {
      dvsr = '';
    }

    /* セレクタ・オブジェクトを作成、タグ名 or ワイルドカード (アスタリスク) を設定 */
    slctr = {tag:m[1].replace('\\:', ':')};

    // }}}
    // {{{ 属性,ID,クラス,疑似クラス,疑似要素をパース

    if (m[2]) {
      //picas_re.compile(picas_pat, 'g'); // RegExp.compile() is not implemented in Safari.
      picas_re = new RegExp(picas_pat, 'g');
      picas_pos = 0;
      p = slctr_re.lastIndex - m[2].length;

      while (n = picas_re.exec(m[2])) {
        /* 全体の先頭から今回の検索開始位置へのオフセット */
        o = p + picas_pos;

        /* 前回の検索終了位置と今回の検索開始位置の差分 */
        picas_off = picas_re.lastIndex - n[0].length - picas_pos;
        if (picas_off != 0) {
          self.warn(selector, o, 'Bad selector given.');
          return [];
        }

        /* 属性 */
        if (n[1]) {
          o += 1 + n[1].length + n[2].length + 1; // '[' + attribute + operator + '"'
          if (!slctr.attributes) {
            slctr.attributes = [];
          }
          j = slctr.attributes.length;
          slctr.attributes[j] = {name:n[1].replace('\\:', ':')};
          switch (n[2]) {
          case '=':
            slctr.attributes[j].value = n[3];
            break;
          case '~=':
            slctr.attributes[j].list = n[3].split(' ');
            break;
          case '|=':
            if (!re.test(n[3])) {
              self.warn(selector, o, 'Only ' + c + ' can be used with "|=" attribute operator.');
              return[];
            }
            slctr.attributes[j].re = new RegExp('^' + n[3] + '(-|$)');
            break;
          }
        /* ID,クラス,疑似クラス,疑似要素 */
        /* 疑似要素は最後のセレクタにしか指定できない (そもそもこの関数では非対応) */
        } else {
          sw: // break label
          switch (n[4]) {
          case '#':
            if (slctr.id) {
              self.warn(selector, o, 'Only 1 id is allowed.');
              return [];
            }
            slctr.id = n[5];
            break;
          case '.':
            if (!slctr.classes) {
              slctr.classes = [];
            }
            slctr.classes.push(new RegExp('\\b' + n[5] + '\\b'));
            break;
          case ':':
            switch (n[5]) {
            case 'first-child':
              slctr.firstChild = true;
              break sw;
            case 'last-child':
              slctr.lastChild = true;
              break sw;
            default:
              self.warn(selector, o, 'Pseudo classes/elements, excluding "(first|last)-child" are not supported.');
              return [];
            }
            //break;
          }
        }

        /* 検索終了位置を記録 */
        picas_pos = picas_re.lastIndex;
      }
    }

    // }}}
    // {{{ 要素を走査

    elems = [];
    for (i = 0; i < roots.length; i++) {
      elems = elems.concat(self.scanElements(roots[i], slctr, dvsr));
    }
    if (elems.length == 0) {
      return [];
    }
    roots = self.array_unique(elems);

    /* 検索終了位置を記録 */
    slctr_pos = slctr_re.lastIndex;

    // }}}
  }

  // }}}
  // {{{ 最終チェック

  if (slctr_pos == 0) {
    self.warn(selector, 0, 'Bad selector given.');
    return [];
  } else if (slctr_pos != selector.length) {
    if (selector.substring(slctr_pos, selector.length).replace(/[\t\r\n ]+$/, '') != '') {
      self.warn(selector, slctr_pos, 'Bad selector given.');
      return [];
    }
  }

  // }}}

  return roots;
}

// }}}
/* document.getElementsBySelector から呼び出されるユーティリティメソッド群 */
// {{{ document.getElementsBySelector.in_array()

/**
 * 変数 v が配列 a に含まれるか確認するメソッド
 */
document.getElementsBySelector.in_array = function(v, a)
{
  for (var i = 0; i < a.length; i++) {
    if (v == a[i]) {
      return true;
    }
  }
  return false;
}

// }}}
// {{{ document.getElementsBySelector.array_unique()

/**
 * 配列 a からユニークな値を取り出すメソッド
 */
document.getElementsBySelector.array_unique = function(a)
{
  var A = [];
  for (var i = 0; i < a.length; i++) {
    if (!document.getElementsBySelector.in_array(a[i], A)) {
      A.push(a[i]);
    }
  }
  return A;
}

// }}}
// {{{ document.getElementsBySelector.scanElements()

/**
 * 要素を走査するメソッド
 */
document.getElementsBySelector.scanElements = function(root, slctr, dvsr)
{
  var self = document.getElementsBySelector.scanElements;
  var tag = slctr.tag.toLowerCase();
  var items = [], elems = [];

  // {{{ 対象要素の候補を取得

  if (slctr.id) {
    var item = root.getElementById(slctr.id);
    if (item && self.checkTagName(item, tag)) {
      items.push(item);
    }
  } else if (dvsr == '+')  {
    if (root.nextSibling && self.checkTagName(root.nextSibling, tag)) {
      items.push(root.nextSibling);
    }
  } else if (dvsr == '>')  {
    for (var i = 0; i < root.childNodes.length; i++) {
      if (self.checkTagName(root.childNodes[i], tag)) {
        items.push(root.childNodes[i]);
      }
    }
  } else {
    items = root.getElementsByTagName(tag);
  }

  if (items.length == 0) {
    return [];
  }

  // }}}
  // {{{ 属性・クラスで対象要素を絞り込む

  for (var j = 0; j < items.length; j++) {
    if (slctr.attributes && !self.checkAttributes(items[j], slctr.attributes)) {
      continue;
    }
    if (slctr.classes && !self.checkClasses(items[j], slctr.classes)) {
      continue;
    }
    elems.push(items[j]);
  }

  // }}}

  if (!(slctr.firstChild || slctr.lastChild)) {
    return elems;
  }

  // {{{ 疑似クラスが指定されているとき

  var pseudoes = [];
  for (var k = 0; k < elems.length; k++) {
    if (!elems[k].childNodes || !elems[k].childNodes.length) {
      continue;
    }
    if (slctr.firstChild && slctr.lastChild) {
      if (!elems[k].childNodes.length != 1 || elems[k].firstChild.nodeType != 1) {
        continue;
      }
      pseudoes.push(elems[k].firstChild);
    } else if (slctr.firstChild) {
      if (elems[k].firstChild.nodeType != 1) {
        continue;
      }
      pseudoes.push(elems[k].firstChild);
    } else if (slctr.lastChild) {
      if (elems[k].lastChild.nodeType != 1) {
        continue;
      }
      pseudoes.push(elems[k].lastChild);
    }
  }
  return pseudoes;

  // }}}
}

// }}}
// {{{ document.getElementsBySelector.scanElements.checkAttributes()

/**
 * 属性をチェックするメソッド
 */
document.getElementsBySelector.scanElements.checkAttributes = function(elem, attrs)
{
  var attr, val;
  for (var i = 0; i < attrs.length; i++) {
    if ((attr = elem.getAttribute(attrs[i].name)) === null) {
      return false;
    }
    val = attr.toString();
    if (attrs[i].value) {
      if (attrs[i].value != val) {
        return false;
      }
    } else if (attrs[i].list) {
      if (!document.getElementsBySelector.in_array(val, attrs[i].list)) {
        return false;
      }
    } else if (attrs[i].re) {
      if (!attrs[i].re.test(val)) {
        return false;
      }
    }
  }
  return true;
}

// }}}
// {{{ document.getElementsBySelector.scanElements.checkClasses()

/**
 * クラスをチェックするメソッド
 */
document.getElementsBySelector.scanElements.checkClasses = function(elem, classes)
{
  if (!elem.className) {
    return false;
  }
  for (var i = 0; i < classes.length; i++) {
    if (!classes[i].test(elem.className)) {
      return false;
    }
  }
  return true;
}

// }}}
// {{{ document.getElementsBySelector.scanElements.checkTagName()

/**
 * タグ名をチェックするメソッド
 * 本当は大文字小文字も比較したいが、ブラウザによっては DOMElement.tagName() が
 * タグ名を大文字で返すようなのでケースインセンシティブにする
 */
document.getElementsBySelector.scanElements.checkTagName = function(elem, tag)
{
  if (!elem.nodeType || elem.nodeType != 1) {
    return false;
  }
  if (tag == '*' || elem.tagName.toLowerCase() == tag) {
    return true;
  }
  return false;
}

// }}}
// {{{ document.getElementsBySelector.warn()

/**
 * エラー情報を表示するメソッド
 */
document.getElementsBySelector.warn = function(selector, offset)
{
  var i, errmsg = [];
  for (i = 2; i < arguments.length; i++) {
    errmsg.push(arguments[i]);
  }
  errmsg.push('The given selectors are "' + selector + '".');
  errmsg.push('An error found at offset ' + offset.toString() + ', near "' + selector.substr(offset, 5) + '".');
  window.alert(errmsg.join('\n'));
}

// }}}
// {{{ Function.prototype.mapToElementsBySelector()

/**
 * CSS2 のセレクタによって DOM Element を取得し、一つずつ
 * それを第一引数にとる関数に適用するメソッド
 * 任意の個数の追加の引数を指定することも可能
 * Function オブジェクトのプロトタイプを拡張
 */
Function.prototype.mapToElementsBySelector = function(selector)
{
  var elements = document.getElementsBySelector(selector);
  var params = [];
  for (var i = 1; i < arguments.length; i++) {
    params.push(arguments[i]);
  }
  for (var j = 0; j < elements.length; j++) {
    params.unshift(elements[j]);
    this.apply(this, params);
    params.shift();
  }
}

// }}}