• atwiki
  • TEST
  • JavaScript for HTML Browsers: CSS Selectors and Media Queries

TEST

JavaScript for HTML Browsers: CSS Selectors and Media Queries

最終更新:

eriax

- view
管理者のみ編集可

制限

  • DOM-HTML 限定。名前空間を認識しない。大文字・小文字の区別はテキトー。長さは px しか認識しない。
  • 構文エラーからの回復はしない(構文エラー回復を含めた CSS パーサは別所を参照)。
  • 1000 行以内を目指したが現状 1050 行。パックして 20kb ぐらい。
  • マッチング主体なので、ノードをかき集める速度は期待できない。

使用例

セレクタテスト

var sapi = CSS_Selectors_and_MediaQueries_for_HTML_Browsers._selectors_group;

// セレクタテストの生成。
var selector = sapi('body:lang(ja) p#HOGE:nth-child(2n+1)');

// テストしたい要素。
var target = document.getElementById('HOGE');

// テストを実行。this として all 配列を渡すと :scope にマッチする。
var result = selector.call({ all: [document.body] }, target);

戻り値は [[p, null], [body, null]] のように単純セレクタにマッチした要素の配列(または null)。セレクタとは逆順に入っており、0 番がセレクタ全体にマッチした要素。この例で null になっている部分は疑似要素を表すレンジ。例えば p::before なら、p の最初の子の直前に位置するレンジになる。

セレクタを使って要素をかき集める簡便メソッド

var applySelectors = CSS_Selectors_and_MediaQueries_for_HTML_Browsers.applySelectors;

// body 以下で非表示の要素をかき集める。戻り値は配列。
var result = applySelectors('*[aria-hidden="true"]', document.body);

メディアクエリテスト

var mql = CSS_Selectors_and_MediaQueries_for_HTML_Browsers._media_query_list;

// メディアクエリテストの生成。
var mediump = mql('only screen and (min-width: 800px), screen and (min-color: 16)');

// window をメディアグループ 'visual' としてクエリテスト。戻り値は真偽値。
if (mediump.call({ ondefault: function (e) { return true; } }, window, 'visual'))
    alert('OK');

this 値として ondefault ハンドラを渡すと、未知の特徴を処理できる。

メディアグループ 'interactive' としてテストする簡便メソッド

var matchesWindow = CSS_Selectors_and_MediaQueries_for_HTML_Browsers.matchesWindow;

if (matchesWindow('(orientation: portrait)', window))
    alert('OK');

ソースコード

var CSS_Selectors_and_MediaQueries_for_HTML_Browsers = new function () { /*@cc_on@*/
  var h = '[0-9a-f]';
  var nonascii = '[^\\u0000-\\u007F]';
  var unicode = '\\\\' + h + '{1,6}(?:\\r\\n|[\x20\\n\\r\\t\\f])?';
  var num = '(?:[0-9]+|[0-9]*\\.[0-9]+)';
  var nl = '(?:\\n|\\r\\n|\\r|\\f)';
  var w = '[\x20\\t\\r\\n\\f]*';
  var s = '[\x20\\t\\r\\n\\f]+';
  var comment = '\\/\\*[^\*]*\\*+(?:[^\/\*][^\*]*\\*+)*\\/';
  var escape = unicode + '|\\\\[^\\n\\r\\f0-9A-Fa-f]';
  var nmchar = '(?:[_0-9a-z-]|' + nonascii + '|' + escape + ')';
  var name = nmchar + '+';
  var nmstart = '(?:[_a-z]|' + nonascii + '|' + escape + ')';
  var ident = '-?' + nmstart + nmchar + '*';
  var string1 = '\"(?:[^\\n\\r\\f\\\\\"]|\\\\' + nl + '|' + nonascii + '|' + escape + ')*\"';
  var string2 = '\'(?:[^\\n\\r\\f\\\\\']|\\\\' + nl + '|' + nonascii + '|' + escape + ')*\'';
  var string = '(?:' + string1 + '|' + string2 + ')';
  var invalid1 = '\"(?:[^\\n\\r\\f\\\\\"]|\\\\' + nl + '|' + nonascii + '|' + escape + ')*';
  var invalid2 = '\'(?:[^\\n\\r\\f\\\\\']|\\\\' + nl + '|' + nonascii + '|' + escape + ')*';
  var invalid = '(?:' + invalid1 + '|' + invalid2 + ')';
  var url = '(?:[!#$%&*-~]|' + nonascii + '|' + escape + ')*';
  //
  var A = '(?:a|\\\\0{0,4}[46]1(?:\\r\\n|[ \\t\\r\\n\\f])?)';
  var D = '(?:d|\\\\0{0,4}[46]4(?:\\r\\n|[ \\t\\r\\n\\f])?)';
  var E = '(?:e|\\\\0{0,4}[46]5(?:\\r\\n|[ \\t\\r\\n\\f])?)';
  var L = '(?:l|\\\\0{0,4}[46]c(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\l)';
  var N = '(?:n|\\\\0{0,4}[46]e(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\n)';
  var O = '(?:o|\\\\0{0,4}[46]f(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\o)';
  var P = '(?:p|\\\\0{0,4}[57]0(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\p)';
  var R = '(?:r|\\\\0{0,4}[57]2(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\r)';
  var T = '(?:t|\\\\0{0,4}[57]4(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\t)';
  var U = '(?:u|\\\\0{0,4}[57]5(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\u)';
  var V = '(?:v|\\\\0{0,4}[57]6(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\v)';
  var X = '(?:x|\\\\0{0,4}[57]8(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\x)';
  var Y = '(?:y|\\\\0{0,4}[57]9(?:\\r\\n|[ \\t\\r\\n\\f])?|\\\\y)';
  //
  var S0 = w;
  var S1 = s;
  var INCLUDES = '~=';
  var DASHMATCH = '\\|=';
  var PREFIXMATCH = '\\^=';
  var SUFFIXMATCH = '\\$=';
  var SUBSTRINGMATCH = '\\*=';
  var PLUS = w + '\\+';
  var GREATER = w + '>';
  var COMMA = w + ',';
  var TILDE = w + '~(?!=)';
  var NOT = ':not\\(';
  var STRING = string;
  var IDENT = ident;
  var HASH = '#' + name;
  var DIMENSION = num + ident;
  var PERCENTAGE = num + '%';
  var NUMBER = num;
  var URI = U + R + L + '\\(' + w + string + w + '\\)|' + U + R + L + '\\(' + w + url + w + '\\)';
  var FUNCTION = ident + '\\(';
  var s_unary_operator = PLUS + '|-';
  var s_hexcolor = HASH + S0;
  //
  var p_Sh = new RegExp('^' + S1);
  var p_St = new RegExp(S1 + '$');
  var f_trim = function (stringData) {
    return stringData.replace(p_Sh, '').replace(p_St, '');
  };
  var p_escaped = /\\([0-9a-f]{1,6})(?:\r\n|[\x20\n\r\t\f])?|\\([^\n\r\f0-9a-f])/gi;
  var f_unescape_replace = function (str, x, c) {
    if (x) {
      return String.fromCharCode(parseInt(x, 16)); // not accurate
    }
    return c;
  };
  var f_unescape = function (stringData) {
    return stringData.replace(p_escaped, f_unescape_replace);
  };
  //
  // css3-selectors
  var p_selector_type = new RegExp('^(?:(' + IDENT + '(?=\\|)|\\*(?=\\|))?\\|)?(' + IDENT + '|\\*)', 'i');
  var p_selector_subject = new RegExp;
  p_selector_subject.exec = function (stringData) {
    var src = stringData;
    var cmp;
    if ((cmp = p_selector_type.exec(src))) { // subject
      var res1 = [];
      var res2 = [];
      var code;
      var ns = cmp[1];
      var ln = cmp[2];
      if (!ns || ns === '*') {
        if (ln === '*') {
          code = 'return\x20n;';
        }
        else {
          code = f_unescape(ln).replace(/\W/g, '\\$&');
          code = 'return\x20/^' + code + '$/i.test(n.nodeName)?n:null;';
        }
        res2[res2.length] = code;
        cmp = cmp[0];
        res1[res1.length] = cmp;
      }
      else {
        throw new Error('Namespace is not supported');
      }
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  var p_selector_ID = new RegExp('^#(' + name + ')', 'i');
  var p_selector_class = new RegExp('^\\.(' + IDENT + ')', 'i');
  var p_selector_attrib = new RegExp('\\[' + S0 + '((' + IDENT + '(?=\\|)|\\*(?=\\|))?\\|)?(' + IDENT + ')' + S0 + '(?:(' + PREFIXMATCH + '|' + SUFFIXMATCH + '|' + SUBSTRINGMATCH + '|' + INCLUDES + '|' + DASHMATCH + '|=)' + S0 + '(?:(' + STRING + ')|(' + IDENT + '))' + S0 + ')?' + '\\]', 'i');
  var p_selector_negation_begin = new RegExp('^' + NOT, 'i');
  var p_selector_negation_end = new RegExp('^' + S0 + '\\)');
  var p_selector_pseudo_class = new RegExp('^(::?)(?:(' + FUNCTION + ')' + S0 + '(' + '(?:(?:' + DIMENSION + '|' + STRING + '|' + IDENT + '|' + NUMBER + '|' + PLUS + '|-)' + S0 + ')+' + ')' + '\\)' + '|(' + IDENT + '))', 'i');
  var p_selector_predicate_common = new RegExp;
  p_selector_predicate_common.exec = function (stringData) {
    var src = stringData;
    var cmp;
    var res1 = [];
    var res2 = [];
    var code;
    switch (src.charAt(0)) {
    case '#':
      if ((cmp = p_selector_ID.exec(src))) {
        code = f_unescape(cmp[1]).replace(/[\"\\]/g, '\\$&');
        code = 'return\x20(n.id==="' + code + '")?n:null;';
        break;
      }
      return null;
    case '.':
      if ((cmp = p_selector_class.exec(src))) {
        code = f_unescape(cmp[1]).replace(/\W/g, '\\$&');
        code = 'return\x20/(?:^|' + S1 + ')' + code + '(?:' + S0 + '|$)/.test(n.className)?n:null;';
        break;
      }
      return null;
    case '[':
      if ((cmp = p_selector_attrib.exec(src))) {
        var ns = cmp[2];
        if (ns && ns !== '*') {
          throw new Error('Namespace is not supported');
        }
        var ln = f_unescape(cmp[3]).replace(/[\"\\]/g, '\\$&');
        var opr = cmp[4];
        var val;
        code = 'var\x20a=n.getAttributeNode("' + ln + '"); ';
        if (opr) {
          val = ((val = cmp[5])) ? val.slice(1, -1) : cmp[6];
          val = f_unescape(val).replace(/\W/g, '\\$&');
        }
        switch (opr) {
        case '=':
          val = '/^' + val + '$/.test(a.value)';
          break;
        case '^=':
          val = '/^' + val + '/.test(a.value)';
          break;
        case '$=':
          val = '/' + val + '$/.test(a.value)';
          break;
        case '*=':
          val = '/' + val + '/.test(a.value)';
          break;
        case '~=':
          val = '/(?:^|' + S1 + ')' + val + '(?:' + S1 + '|$)/.test(a.value)';
          break;
        case '|=':
          val = '/^' + val + '(?=-|$)/.test(a.value)';
          break;
        default:
          val = 'a.specified';
          break;
        }
        code += 'return\x20(a&&' + val + ')?n:null;';
        break;
      }
      return null;
    case ':':
      if ((cmp = p_selector_pseudo_class.exec(src))) {
        var type = cmp[1];
        var name;
        var args, a, b;
        if ((name = cmp[2])) {
          name = name.slice(0, -1);
          args = cmp[3];
        }
        else {
          name = cmp[4];
          args = '';
        }
        switch (type + name) {
        case ':root':
          code = 'return\x20(n===n.ownerDocument.documentElement)?n:null;';
          break;
        case ':nth-child':
          args = f_parse_nth(args), a = args[0], b = args[1];
          code = 'var\x20m,i;for(m=n,i=1;m=m.previousSibling;)if(m.nodeType===1)i++;';
          code += (a === 0) ? 'return\x20(i===' + b + ')?n:null;' : 'var\x20j=i-' + b + ';return((j%' + a + '===0) && (j / ' + a + '>=0))?n:null;';
          break;
        case ':nth-last-child':
          args = f_parse_nth(args), a = args[0], b = args[1];
          code = 'var\x20m,i;for(m=n,i=1;m=m.nextSibling;)if(m.nodeType===1)i++;';
          code += (a === 0) ? 'return\x20(i===' + b + ')?n:null;' : 'var\x20j=i-' + b + ';return((j%' + a + '===0) && (j / ' + a + '>=0))?n:null;';
          break;
        case ':nth-of-type':
          args = f_parse_nth(args), a = args[0], b = args[1];
          code = 'var\x20s=n.tagName,m,i;for(m=n,i=1;m=m.previousSibling;)if(m.nodeType===1&&m.tagName===s)i++;';
          code += (a === 0) ? 'return\x20(i===' + b + ')?n:null;' : 'var\x20j=i-' + b + ';return((j%' + a + '===0)&&(j/' + a + '>=0))?n:null;';
          break;
        case ':nth-last-of-type':
          args = f_parse_nth(args), a = args[0], b = args[1];
          code = 'var\x20s=n.tagName,m,i;for(m=n,i=1;m=m.nextSibling;)if(m.nodeType===1&&m.tagName===s)i++;';
          code += (a === 0) ? 'return\x20(i===' + b + ')?n:null;' : 'var\x20j=i-' + b + ';return((j%' + a + '===0)&&(j/' + a + '>=0))?n:null;';
          break;
        case ':first-child':
          code = 'var\x20m;for(m=n;m=m.previousSibling;)if(m.nodeType===1)return\x20null;return\x20n;';
          break;
        case ':last-child':
          code = 'var\x20m;for(m=n;m=m.nextSibling;)if(m.nodeType===1)return\x20null;return\x20n;';
          break;
        case ':first-of-type':
          code = 'var\x20s=n.tagName,m;for(m=n;m=m.previousSibling;)if(m.nodeType===1&&m.tagName===s)return\x20null;return\x20n;';
          break;
        case ':last-of-type':
          code = 'var\x20s=n.tagName,m;for(m=n;m=m.nextSibling;)if(m.nodeType===1&&m.tagName===s)return\x20null;return\x20n;';
          break;
        case ':only-child':
          code = 'var\x20m;for(m=n;m=m.previousSibling;)if(m.nodeType===1)return\x20null;for(m=n;m=m.nextSibling;)if(m.nodeType===1)return\x20null;return\x20n;';
          break;
        case ':only-of-type':
          code = 'var\x20s=n.tagName,m;for(m=n;m=m.previousSibling;)if(m.nodeType===1&&m.tagName===s)return\x20null;for(m=n;m=m.nextSibling;)if(m.nodeType===1&&m.tagName===s)return\x20null;return\x20n;';
          break;
        case ':empty':
          code = 'var\x20m;for(m=n.firstChild;m;m=m.nextSibling)switch(m.nodeType){case\x201:return\x20null;case\x203:case\x204:if(m.length>0)return\x20null;default:continue;}return\x20n;';
          break;
        case ':link':
        case ':visited':
        case ':active':
        case ':hover':
          throw new Error(name + ' is not supported');
        case ':focus':
          code = 'var\x20d;return((d=n.ownerDocument)&&(n===d.activeElement))?n:null;';
          break;
        case ':target':
          code = 'var\x20d,s,w;return((d=n.ownerDocument)&&(w=d.defaultView)&&(s=w.location.hash)&&(s=s.slice(1))&&(n.id===s))?n:null;';
          break;
        case ':lang':
          args = f_trim(args).replace(/\W/g, '\\$&');
          code = 'for(var\x20m=n,v;m;m=m.parentNode)if(m.nodeType===1)if((v=m.lang))return\x20/^' + args + '(?:-|$)/i.test(v)?n:null;return\x20null;';
          break;
        case ':enabled':
          code = 'return\x20(n.disabled===false)?n:null;';
          break;
        case ':disabled':
          code = 'return\x20(n.disabled===true)?n:null;';
          break;
        case ':checked':
          code = 'return\x20(n.checked===true)?n:null;';
          break;
        case ':scope':
          code = 'var\x20c=this.all;if(!c)return\x20null;var\x20I=c.length,i;for(i=0;i<I;i++)if(n===c[i])return\x20n;return\x20null;';
          break;
        case '::first-line':
        case ':first-line':
          throw new Error(name + ' is not supported');
        case '::first-letter':
        case ':first-letter':
          code = 'var\x20d,r;if((d=n.ownerDocument)){r=d.createRange();while(n.hasChildNodes())n=n.firstChild;if(n.nodeType===3)if(n.length>0){r.setStart(n,0);r.setEnd(n,1);return\x20r;}}return\x20null;';
          break;
        case '::selection':
        case ':selection':
          code = 'var\x20d,w,s;if((d=n.ownerDocument)&&(w=d.defaultView))return\x20w.getSelection().getRangeAt(0);return\x20null';
          break;
        case '::before':
        case ':before':
          code = 'var\x20d,r;if((d=n.ownerDocument)){r=d.createRange();r.setStart(n,0);return\x20r;}return\x20null;';
          break;
        case '::after':
        case ':after':
          code = 'var\x20d,r;if((d=n.ownerDocument)){r=d.createRange();switch(n.nodeType){case\x203:case\x204:case\x207:case\x208:r.setStart(n,n.data.length);break;default:r.setStart(n,n.childNodes.length);break;}return\x20r;}return\x20null;';
          break;
        default:
          throw new Error(name + ' is not supported');
        }
        break;
      }
      return null;
    default:
      return null;
    }
    res2[res2.length] = code;
    cmp = cmp[0];
    res1[res1.length] = cmp;
    return [].concat(res1.join(''), res2);
  };
  //
  var p_selector_negation_arg = new RegExp;
  p_selector_negation_arg.exec = function (stringData) {
    var src = stringData;
    var cmp;
    var res1 = [];
    var res2 = [];
    var code;
    switch (src.charAt(0)) {
    case '#':
    case '.':
    case '[':
    case ':':
      if ((cmp = p_selector_predicate_common.exec(src))) {
        code = cmp.slice(1);
        break;
      }
      return null;
    default:
      if ((cmp = p_selector_subject.exec(src))) {
        code = cmp.slice(1);
        break;
      }
      return null;
    }
    res2[res2.length] = 'return\x20!(function(n){' + code.join('') + '})(n)?n:null;';
    cmp = cmp[0];
    res1[res1.length] = cmp;
    return [].concat(res1.join(''), res2);
  };
  //
  var p_simple_selector_sequence = new RegExp;
  p_simple_selector_sequence.exec = function (stringData) {
    var src = stringData;
    var cmp;
    var res1 = [];
    var res2 = [];
    var code;
    var subj = false;
    if ((cmp = p_selector_subject.exec(src))) { // subject
      subj = true;
      res2 = res2.concat(cmp.slice(1));
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
    }
    A: while (src.length > 0) { // predicates
      switch (src.charAt(0)) {
      case '#':
      case '.':
      case '[':
        if ((cmp = p_selector_predicate_common.exec(src))) {
          code = cmp.slice(1);
          break;
        }
        return null;
      case ':':
        if ((cmp = p_selector_negation_begin.exec(src))) {
          cmp = cmp[0];
          var zrc = src.slice(cmp.length);
          var kmp;
          var rez1 = [cmp];
          var rez2 = [];
          if ((kmp = p_selector_negation_arg.exec(zrc))) {
            rez2 = rez2.concat(kmp.slice(1));
            kmp = kmp[0];
            rez1[rez1.length] = kmp;
            zrc = zrc.slice(kmp.length);
            if ((kmp = p_selector_negation_end.exec(zrc))) {
              kmp = kmp[0];
              rez1[rez1.length] = kmp;
              zrc = zrc.slice(kmp.length);
              //
              src = zrc;
              res1.push.apply(res1, rez1);
              res2.push.apply(res2, rez2);
              continue;
            }
          }
          break A;
        }
        if ((cmp = p_selector_predicate_common.exec(src))) {
          code = cmp.slice(1);
          break;
        }
        break A;
      default:
        break A;
      }
      res2 = res2.concat(code);
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
    }
    if (res1.length > 0) {
      if (!subj) {
        cmp = p_selector_subject.exec('*');
        res2 = [].concat(cmp.slice(1), res2);
      }
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  var p_combinator = new RegExp('^(?:(' + PLUS + ')' + S0 + '|(' + GREATER + ')' + S0 + '|(' + TILDE + ')' + S0 + '|(' + S1 + '))', 'i');
  var o_combinator_table = {
    '': function (node, patterns) {
      A: if (node.nodeType === 1) {
        var count = patterns.length;
        var i, n;
        for (i = 0; i < count; i++) {
          n = patterns[i].call(this, node);
          if (!n) {
            break A;
          }
        }
        return [node, (node !== n) ? n : null];
      }
      return null;
    },
    '\x20': function (node, patterns) {
      var count = patterns.length;
      var i, n;
      A: while ((node = node.parentNode)) {
        if (node.nodeType === 1) {
          for (i = 0; i < count; i++) {
            n = patterns[i].call(this, node);
            if (!n) {
              continue A;
            }
          }
          return [node, (node !== n) ? n : null];
        }
      }
      return null;
    },
    '>': function (node, patterns) {
      var count = patterns.length;
      var i, n;
      A: if ((node = node.parentNode)) {
        if (node.nodeType === 1) {
          for (i = 0; i < count; i++) {
            n = patterns[i].call(this, node);
            if (!n) {
              break A;
            }
          }
          return [node, (node !== n) ? n : null];
        }
      }
      return null;
    },
    '~': function (node, patterns) {
      var count = patterns.length;
      var i, n;
      A: while ((node = node.previousSibling)) {
        if (node.nodeType === 1) {
          for (i = 0; i < count; i++) {
            n = patterns[i].call(this, node);
            if (!n) {
              continue A;
            }
          }
          return [node, (node !== n) ? n : null];
        }
      }
      return null;
    },
    '+': function (node, patterns) {
      var count = patterns.length;
      var i, n;
      A: while ((node = node.previousSibling)) {
        if (node.nodeType === 1) {
          for (i = 0; i < count; i++) {
            n = patterns[i].call(this, node);
            if (!n) {
              break A;
            }
          }
          return [node, (node !== n) ? n : null];
        }
      }
      return null;
    }
  };
  //
  var p_selector = new RegExp;
  p_selector.exec = function (stringData) {
    var src = stringData;
    var cmp;
    if ((cmp = p_simple_selector_sequence.exec(src))) {
      var res1 = [];
      var res2 = [];
      var sel;
      var cmb;
      sel = cmp.slice(1);
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
      while ((cmp = p_combinator.exec(src))) {
        cmb = cmp[1] ? '+' : cmp[2] ? '>' : cmp[3] ? '~' : '\x20';
        res2[res2.length] = [sel, cmb];
        sel = null;
        cmp = cmp[0];
        res1[res1.length] = cmp;
        src = src.slice(cmp.length);
        if ((cmp = p_simple_selector_sequence.exec(src))) {
          sel = cmp.slice(1);
          cmp = cmp[0];
          res1[res1.length] = cmp;
          src = src.slice(cmp.length);
          continue;
        }
        break;
      }
      if (sel) {
        res2[res2.length] = [sel, ''];
      }
      else {
        var lst = res2[res2.length - 1];
        var lsn = lst.length - 1;
        if (lst[lsn] === '\x20') {
          lst[lsn] = '';
        }
        else {
          return null;
        }
      }
      for (var simple, patterns, i = 0, I = res2.length; i < I; i++) {
        simple = res2[i], patterns = simple[0];
        res2[i] = {
          patterns: patterns,
          relation: o_combinator_table[simple[1]]
        };
        for (var j = 0, J = patterns.length; j < J; j++) {
          patterns[j] = new Function('n', patterns[j]);
        }
      }
      res2.reverse();
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  var p_selector_separator = new RegExp('^' + COMMA + w);
  var p_selectors_group = new RegExp;
  p_selectors_group.exec = function (stringData) {
    var src = stringData;
    var cmp;
    if ((cmp = p_selector.exec(src))) {
      var res1 = [];
      var res2 = [];
      res2[res2.length] = cmp.slice(1);
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
      while ((cmp = p_selector_separator.exec(src))) {
        cmp = cmp[0];
        res1[res1.length] = cmp;
        src = src.slice(cmp.length);
        if ((cmp = p_selector.exec(src))) {
          res2[res2.length] = cmp.slice(1);
          cmp = cmp[0];
          res1[res1.length] = cmp;
          src = src.slice(cmp.length);
          continue;
        }
        break;
      }
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  var s_integer = '[0-9]+';
  var p_nth = new RegExp(w + '(?:([-+]?(?:' + s_integer + ')?)' + N + '(?:' + w + '([-+])' + w + '(' + s_integer + '))?|([-+]?(?:' + s_integer + '))|(' + O + D + D + ')|(' + E + V + E + N + '))' + w, 'i');
  var f_parse_nth = function (stringData) {
    var cmp = p_nth.exec(stringData);
    if (cmp) {
      var i, a, b;
      for (i = 0; !cmp[++i];);
      switch (i) {
      case 1: // '2n+1'
        a = cmp[i];
        b = cmp[i + 1] + cmp[i + 2];
        return [a === '+' ? 1 : a === '-' ? -1 : parseInt(a, 10), parseInt(b, 10) || 0];
      case 4: // '1'
        return [0, parseInt(cmp[i], 10) || 0];
      case 5: // 'odd'
        return [2, 1];
      case 6: // 'even'
        return [2, 0];
      }
    }
    return null;
  };
  //
  var o_selectors_group_cache = { };
  var f_create_selectors_group = function (stringData) {
    var selectors = p_selectors_group.exec(stringData);
    if (!selectors || stringData.length !== selectors[0].length) {
      throw new Error('malformed CSS selectors');
    }
    var fn = o_selectors_group_cache[stringData];
    if ('function' === typeof fn) {
      return fn;
    }
    return o_selectors_group_cache[stringData] = function (node) {
      var selectorCount = selectors.length;
      var i;
      A: for (i = 1; i < selectorCount; i++) {
        var n = node;
        var result = [];
        var simples = selectors[i];
        var simpleCount = simples.length;
        var j;
        for (j = 0; j < simpleCount; j++) {
          var simple = simples[j];
          var patterns = simple.patterns;
          var relation = simple.relation;
          if ((n = relation.call(this, n, patterns))) {
            result[result.length] = n;
            n = n[0];
            continue;
          }
          continue A;
        }
        return result;
      }
      return null;
    };
  };
  this._selectors_group = f_create_selectors_group;
  //
  // css3-values
  var p_exprs = new RegExp('^(?:' + '(' + FUNCTION + ')' + '|(\\))' + '|(' + '(?:' + s_unary_operator + ')?' + '(?:' + PERCENTAGE + '|' + DIMENSION + '|' + NUMBER + ')' + '|' + URI + '|' + STRING + '|' + IDENT + '(?!\\()' + '|' + s_hexcolor + ')|(' + COMMA + S0 + '|' + S0 + '/' + S0 + '|' + S1 + ')' + ')', 'i');
  var p_expr = new RegExp;
  p_expr.exec = function (stringData) {
    var src = stringData;
    var cmp;
    var tmp;
    var res1 = [];
    var res2 = [];
    var depth = 0;
    for (; cmp = p_exprs.exec(src); src = src.slice(tmp.length)) {
      if ((tmp = cmp[1])) { // 'f('
        depth++, res1[res1.length] = tmp;
        res2[res2.length] = f_unescape(tmp);
        continue;
      }
      if ((tmp = cmp[2])) { // ')'
        if (depth > 0) {
          depth--, res1[res1.length] = tmp;
          res2[res2.length] = tmp;
          continue;
        }
        break;
      }
      if ((tmp = cmp[3])) {
        res1[res1.length] = tmp;
        res2[res2.length] = f_unescape(tmp);
        continue;
      }
      if ((tmp = cmp[4])) { // operator
        res1[res1.length] = tmp;
        res2[res2.length] = f_trim(tmp) || '\x20';
        continue;
      }
    }
    if (depth === 0) {
      if (res2[res2.length - 1] === '\x20') {
        res2.pop();
      }
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  // css3-mediaqueries
  var p_media_expression_prefix = new RegExp('^\\(' + S0 + '(' + IDENT + ')' + S0, 'i');
  var p_media_expression_infix = new RegExp('^:' + S0);
  var p_media_expression_suffix = new RegExp('^\\)' + S0);
  var p_val_px = new RegExp('^' + w + '(' + NUMBER + ')' + P + X + w + '$', 'i');
  var p_val_ratio = new RegExp('^' + w + '(' + NUMBER + ')' + w + '/' + w + '(' + NUMBER + ')' + w + '$', 'i');
  var p_val_num = new RegExp('^' + S0 + '(' + NUMBER + ')' + S0 + '$', 'i');
  var p_val_pattern = new RegExp('^' + S0 + '(' + STRING + ')' + S0 + ',' + S0 + '(' + STRING + ')' + S0 + '$');
  var c_media_width = 'w.innerWidth';
  var c_media_height = 'w.innerHeight';
  var c_media_width_div_height = 'w.innerWidth/w.innerHeight';
  var c_media_device_width = 'w.screen.availWidth';
  var c_media_device_height = 'w.screen.availHeight';
  var c_media_device_width_div_device_height = 'w.screen.availWidth/w.screen.availHeight';
  var c_media_color = 'w.screen.colorDepth';
  var p_media_expression = new RegExp;
  p_media_expression.exec = function (stringData) {
    var src = stringData;
    var cmp;
    if ((cmp = p_media_expression_prefix.exec(src))) {
      var res1 = [];
      var res2 = [];
      res2 = res2.concat(cmp.slice(1));
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
      if ((cmp = p_media_expression_infix.exec(src))) {
        cmp = cmp[0];
        res1[res1.length] = cmp;
        src = src.slice(cmp.length);
        if ((cmp = p_expr.exec(src))) {
          res2 = res2.concat(cmp.slice(1));
          cmp = cmp[0];
          res1[res1.length] = cmp;
          src = src.slice(cmp.length);
        }
      }
      if ((cmp = p_media_expression_suffix.exec(src))) {
        cmp = cmp[0];
        res1[res1.length] = cmp;
        var feature = res2[0].toLowerCase();
        var value = res2.slice(1).join('');
        var accessor;
        var operator;
        var code;
        /*@{
          c_media_width = '(function(d){d=(d.compatMode==="CSS1Compat")?d.documentElement:d.body;return\x20d.clientWidth;})(w.document)';
          c_media_height = '(function(d){d=(d.compatMode==="CSS1Compat")?d.documentElement:d.body;return\x20d.clientHeight;})(w.document)';
          c_media_width_div_height = '(function(d){d=(d.compatMode==="CSS1Compat")?d.documentElement:d.body;return\x20d.clientWidth/d.clientHeight;})(w.document)';
        }@*/
        if (/(?:^|-)(?:width|height)$/i.test(feature)) {
          switch (feature) {
          case 'width':
            accessor = c_media_width, operator = '===';
            break;
          case 'height':
            accessor = c_media_height, operator = '===';
            break;
          case 'max-width':
            accessor = c_media_width, operator = '<=';
            break;
          case 'min-width':
            accessor = c_media_width, operator = '>=';
            break;
          case 'max-height':
            accessor = c_media_height, operator = '<=';
            break;
          case 'min-height':
            accessor = c_media_height, operator = '>=';
            break;
          case 'device-width':
            accessor = c_media_device_width, operator = '===';
            break;
          case 'device-height':
            accessor = c_media_device_height, operator = '===';
            break;
          case 'max-device-width':
            accessor = c_media_device_width, operator = '<=';
            break;
          case 'min-device-width':
            accessor = c_media_device_width, operator = '>=';
            break;
          case 'max-device-height':
            accessor = c_media_device_height, operator = '<=';
            break;
          case 'min-device-height':
            accessor = c_media_device_height, operator = '>=';
            break;
          default:
            accessor = 'false';
          }
          code = 'return\x20' + accessor;
          if (value && (value = p_val_px.exec(value))) {
            code += operator + value[1] + ';';
          }
          else if (/^[<>]/.test(operator)) {
            code += ';';
          }
          else {
            code += '!==0;';
          }
        }
        else if (/(?:^|-)aspect-ratio$/i.test(feature)) {
          switch (feature) {
          case 'aspect-ratio':
            accessor = c_media_width_div_height, operator = '==';
            break;
          case 'max-aspect-ratio':
            accessor = c_media_width_div_height, operator = '<=';
            break;
          case 'min-aspect-ratio':
            accessor = c_media_width_div_height, operator = '>=';
            break;
          case 'device-aspect-ratio':
            accessor = c_media_device_width_div_device_height, operator = '==';
            break;
          case 'max-device-aspect-ratio':
            accessor = c_media_device_width_div_device_height, operator = '<=';
            break;
          case 'min-device-aspect-ratio':
            accessor = c_media_device_width_div_device_height, operator = '>=';
            break;
          default:
            accessor = 'false';
          }
          code = 'return\x20' + accessor;
          if (value && (value = p_val_ratio.exec(value))) {
            code += operator + value[1] + ';';
          }
          else if (/^[<>]/.test(operator)) {
            code += ';';
          }
          else {
            code += '!==0;';
          }
        }
        else if (/(?:^|-)color$/i.test(feature)) {
          switch (feature) {
          case 'color':
            accessor = c_media_color, operator = '===';
            break;
          case 'max-color':
            accessor = c_media_color, operator = '<=';
            break;
          case 'min-color':
            accessor = c_media_color, operator = '>=';
            break;
          default:
            accessor = 'false';
          }
          code = 'return\x20' + accessor;
          if (value && (value = p_val_num.exec(value))) {
            code += operator + value[1] + ';';
          }
          else if (/^[<>]/.test(operator)) {
            code += ';';
          }
          else {
            code += '!==0;';
          }
        }
        else {
          switch (feature) {
          case 'orientation':
            if (value) {
              value = f_trim(value).toLowerCase();
              switch (value) {
              case 'portrait':
                code = 'return\x20' + c_media_width + '<=' + c_media_height + ';';
                break;
              case 'landscape':
                code = 'return\x20' + c_media_width + '>' + c_media_height + ';';
                break;
              default:
                code = 'return\x20false;';
                break;
              }
            }
            else {
              code = 'return\x20false;';
            }
            break;
          case '-x-domain-pattern':
            value = p_val_pattern.exec(value);
            if (value) {
              var psrc = value[1].slice(1, -1).replace(/[\/\\]/g, '\\$&');
              var flag = value[2].slice(1, -1).replace(/\\/g, '\\$&');
              code = /\W/.test(flag) ? 'return\x20false;' : 'return\x20/' + psrc + '/' + flag + '.test(w.location);';
            }
            else {
              code = 'return\x20false;';
            }
            break;
          default:
            code = 'var\x20h=this.ondefault;if(h){return\x20h.call(this,{target:w,feature:"' + feature + '",value:"' + value.replace(/[\"\\]/g, '\\$&') + '"});}return\x20false;';
            break;
          }
        }
        res2 = code;
        return [].concat(res1.join(''), res2);
      }
    }
    return null;
  };
  //
  var o_media_groups = {
    'continuous': /\s*(?:^|,)\s*(?:braille|handheld|screen|speech|tty|tv)\s*(?:,|$)\s*/i,
    'paged': /\s*(?:^|,)\s*(?:handheld|embossed|print|projection|tv)\s*(?:,|$)\s*/i,
    'visual': /\s*(?:^|,)\s*(?:handheld|print|projection|screen|tty|tv)\s*(?:,|$)\s*/i,
    'audio': /\s*(?:^|,)\s*(?:handheld|screen|tv)\s*(?:,|$)\s*/i,
    'speechg': /\s*(?:^|,)\s*(?:handheld|speech)\s*(?:,|$)\s*/i,
    'tactile': /\s*(?:^|,)\s*(?:braille|embossed)\s*(?:,|$)\s*/i,
    'grid': /\s*(?:^|,)\s*(?:braille|embossed|handheld|tty)\s*(?:,|$)\s*/i,
    'bitmap': /\s*(?:^|,)\s*(?:handheld|print|projection|screen|tv)\s*(?:,|$)\s*/i,
    'interactive': /\s*(?:^|,)\s*(?:braille|handheld|projection|screen|speech|tty|tv)\s*(?:,|$)\s*/i,
    'static': /\s*(?:^|,)\s*(?:braille|embossed|handheld|print|screen|speech|tty|tv)\s*(?:,|$)\s*/i,
    //
    'braille': /\s*(?:^|,)\s*(?:continuous|tactile|grid|interactive|static)\s*(?:,|$)\s*/i,
    'embossed': /\s*(?:^|,)\s*(?:paged|tactile|grid|static)\s*(?:,|$)\s*/i,
    'handheld': /\s*(?:^|,)\s*(?:continuous|paged|visual|audio|speech|grid|bitmap|interactive|static)\s*(?:,|$)\s*/i,
    'print': /\s*(?:^|,)\s*(?:paged|visual|bitmap|static)\s*(?:,|$)\s*/i,
    'projection': /\s*(?:^|,)\s*(?:paged|visual|bitmap|interactive)\s*(?:,|$)\s*/i,
    'screen': /\s*(?:^|,)\s*(?:continuous|visual|audio|bitmap|interactive|static)\s*(?:,|$)\s*/i,
    'speech': /\s*(?:^|,)\s*(?:continuous|speech|interactive|static)\s*(?:,|$)\s*/i,
    'tty': /\s*(?:^|,)\s*(?:continuous|visual|grid|interactive|static)\s*(?:,|$)\s*/i,
    'tv': /\s*(?:^|,)\s*(?:continuous|paged|visual|audio|bitmap|interactive|static)\s*(?:,|$)\s*/i
  };
  //
  var p_media_query_prefix = new RegExp('^' + S0 + '(' + O + N + L + Y + '|' + N + O + T + ')?' + S0 + '(' + IDENT + ')' + S0, 'i');
  var p_media_query_and = new RegExp('^' + A + N + D + S1, 'i');
  var p_media_query = new RegExp;
  p_media_query.exec = function (stringData) {
    var src = stringData;
    var cmp;
    var res1 = [];
    var res2 = [];
    var code1;
    var code2;
    A: {
      if ((cmp = p_media_query_prefix.exec(src))) { // only | not
        code1 = (code1 = cmp[1]) ? f_unescape(code1).toLowerCase() : 'only';
        code2 = f_unescape(cmp[2]).toLowerCase();
        break A;
      }
      if ((cmp = p_media_expression.exec(src))) {
        code1 = 'only';
        code2 = 'all';
        res2[res2.length] = new Function('w', cmp.slice(1).join(''));
        break A;
      }
      return null;
    }
    cmp = cmp[0];
    res1[res1.length] = cmp;
    src = src.slice(cmp.length);
    // only or not
    code1 = (code1 === 'not') ? 'return\x20!b;' : 'return\x20b;';
    // media type
    if (code2 === 'all') {
      code2 = 'return\x20true;';
    }
    else {
      var pattern = o_media_groups[code2];
      code2 = 'if(/(?:^|' + S0 + ',)' + S0 + '(?:' + code2.replace(/\W/g, '\\$&') + ')' + S0 + '(?:,' + S0 + '|$)/i.test(m))\x20return\x20true;';
      if (pattern) {
        code2 += 'return\x20' + pattern + '.test(m);'; // be careful not to contain '/'
      }
      else {
        code2 += 'return\x20false;';
      }
    }
    res2 = [].concat(new Function('b', code1), new Function('m', code2), res2);
    // media queries
    while ((cmp = p_media_query_and.exec(src))) {
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
      if ((cmp = p_media_expression.exec(src))) {
        res2[res2.length] = new Function('w', cmp.slice(1).join(''));
        cmp = cmp[0];
        res1[res1.length] = cmp;
        src = src.slice(cmp.length);
        continue;
      }
      return null;
    }
    return [].concat(res1.join(''), res2);
  };
  //
  var p_media_query_list_prefix = new RegExp('^' + S0);
  var p_media_query_list_infix = new RegExp('^' + COMMA + S0, 'i');
  var p_media_query_list = new RegExp;
  p_media_query_list.exec = function (stringData) {
    var src = stringData;
    var cmp;
    if ((cmp = p_media_query_list_prefix.exec(src))) {
      var res1 = [];
      var res2 = [];
      cmp = cmp[0];
      res1[res1.length] = cmp;
      src = src.slice(cmp.length);
      if ((cmp = p_media_query.exec(src))) {
        res2[res2.length] = cmp.slice(1);
        cmp = cmp[0];
        res1[res1.length] = cmp;
        src = src.slice(cmp.length);
        while ((cmp = p_media_query_list_infix.exec(src))) {
          cmp = cmp[0];
          res1[res1.length] = cmp;
          src = src.slice(cmp.length);
          if ((cmp = p_media_query.exec(src))) {
            res2[res2.length] = cmp.slice(1);
            cmp = cmp[0];
            res1[res1.length] = cmp;
            src = src.slice(cmp.length);
            continue;
          }
          break;
        }
      }
      return [].concat(res1.join(''), res2);
    }
    return null;
  };
  //
  var o_media_query_list_cache = { };
  var f_create_media_query_list = function (stringData) {
    var queries = p_media_query_list.exec(stringData);
    if (!queries) {
      throw new Error('malformed Media Queries');
    }
    var fn = o_media_query_list_cache[stringData];
    if ('function' === typeof fn) {
      return fn;
    }
    return o_media_query_list_cache[stringData] = function (view, mediaType) {
      var queryCount = queries.length;
      var i;
      for (i = 1; i < queryCount; i++) {
        var exprs = queries[i];
        var exprCount = exprs.length;
        var j;
        if (exprs[1].call(this, mediaType)) {
          var bool = true;
          for (j = 2; j < exprCount; j++) {
            if (!exprs[j].call(this, view)) {
              bool = false;
              break;
            }
          }
          if (exprs[0].call(this, bool)) {
            return true;
          }
        }
      }
      return false;
    };
  };
  this._media_query_list = f_create_media_query_list;
  //
  // shortcut methods
  var f_matches_media_interactive = function (queries, view, thisArg) {
    return f_create_media_query_list(queries).call(thisArg, view, 'interactive');
  };
  this.matchesWindow = f_matches_media_interactive;
  var f_apply_selectors = function (selectors, root, thisArg) {
    selectors = f_create_selectors_group(selectors);
    if (! thisArg) {
      thisArg = { };
    }
    thisArg.all = [ ].concat(thisArg.all || root);
    var result = [ ];
    var es = root.getElementsByTagName('*');
    var I = es.length;
    var i;
    var r;
    for (i = 0; i < I; i++) {
      if ((r = selectors.call(thisArg, es[i]))) {
        r = r[0];
        result[result.length] = r[1] || r[0];
      }
    }
    return result;
  };
  this.applySelectors = f_apply_selectors;
};
  • 初出:2011-07-07/08/09/10/11/12、修正 2011-08-12
目安箱バナー