LDR Full Feed

By hatos david Last update Jul 14, 2008 — Installed 463 times.

Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

// ==UserScript==
// @name        LDR Full Feed
// @namespace   http://d.hatena.ne.jp/Constellation/
// @include     http://reader.livedoor.com/reader/*
// @include     http://fastladder.com/reader/*
// @description loading full entry on LDR and Fastladder
// @version     0.0.18
// @resource    orange  http://utatane.tea.googlepages.com/orange.gif
// @resource    blue    http://utatane.tea.googlepages.com/blue.gif
// @author      Constellation
// ==/UserScript==

(function(w){

// == [CSS] =========================================================
const CSS = <><![CDATA[
.gm_fullfeed_loading, .gm_fullfeed_loading a{color : green !important;}
.gm_fullfeed_loading .item_body a{color : palegreen !important;}
.gm_fullfeed_loading{background-color : Honeydew !important;}
.gm_fullfeed_icon{cursor : pointer ;}
]]></>.toString();

// == [Config] ======================================================

const VERSION = '0.0.18'

const ICON = 'orange' // or blue

const KEY = 'g';
const GET_SITEINFO_KEY = 'G';
const GET_ALL = true;
const GET_ALL_KEY = 'u';

const ADCHECKER = /(^AD:|^PR:)/;
const LOADING_MOTION = true;

const REMOVE_SCRIPT = true;
const REMOVE_H2TAG = true;
const REMOVE_IFRAME = true;

const OPEN = false; //SITEINFOになかった場合にそのエントリを開くかどうか
const ITEMFILTER = true;
const AUTO_SEARCH = true;
const EXTRACT_TEXT = false;
const WIDGET = true;
const CLICKABLE = true;

const USE_AUTOPAGERIZE_SITEINFO = true;
const AUTOPAGER = true;

const DEBUG = false;

const SITEINFO_IMPORT_URLS = [
{
  name:'Wedata',
  format:'JSON',
  url: 'http://wedata.net/databases/LDRFullFeed/items.json'
},
{
  name:'Microformats URL List',
  format:'HTML',
  url: 'http://constellation.jottit.com/microformats_url_list'
},

//{format:'HTML', url: 'http://constellation.jottit.com/siteinfo'},
//{format:'HTML', url: 'http://constellation.jottit.com/test'},
];

const AUTOPAGERIZE_SITEINFO_IMPORT_URLS = [
{
  name:'Wedata AutoPagerize',
  format:'JSON',
  url: 'http://wedata.net/databases/AutoPagerize/items.json'
}
];
// == [SITE_INFO] ===================================================

const SITE_INFO = [
];

const MICROFORMATS = [
  {
      name : 'hAtom-Content',
      xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " hentry ")]//*[contains(concat(" ",normalize-space(@class)," "), " entry-content ")]',
  },
  {
      name : 'hAtom',
      xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " hentry ")]',
  },
  {
      name : 'xFolk',
      xpath: '//*[contains(concat(" ",@class," "), " xfolkentry ")]//*[contains(concat(" ",normalize-space(@class)," "), " description ")]',
  },
  {
      name : 'AutoPagerize(Microformats)',
      xpath: '//*[contains(concat(" ",normalize-space(@class)," "), " autopagerize_page_element ")]',
  },
]

const AUTOPAGERIZE_MICROFORMAT = {
    name:         'autopagerize_microformat',
    url:          '.*',
    nextLink:     '//a[@rel="next"] | //link[@rel="next"]',
    insertBefore: '//*[contains(@class, "autopagerize_insert_before")]',
    pageElement:  '//*[contains(@class, "autopagerize_page_element")]',
}
// == [Cache Phase] =================================================

const PHASE = [
  {type:'SBM'                            },
  {type:'INDIVIDUAL',         sub:'IND'  },
  {type:'INDIV_MICROFORMATS'             },
  {type:'SUBGENERAL',         sub:'SUB'  },
  {type:'GENERAL',            sub:'GEN'  },
  {type:'MICROFORMATS',       sub:'MIC'  }
];


// == [Application] =================================================

// [FullFeed]
var FullFeed = function(info, c){
  log(info, c)
  this.data = c;
  this.info = info;
  this.type = 'FullFeed';

  this.requestURL = this.data.itemURL;
  var bodyXPath = 'id("item_body_' + this.data.id + '")/div[@class="body"]';
  this.data.item_body = $X(bodyXPath, document)[0];
  this.state = 'wait';
  this.mime = 'text/html; charset=' + (this.info.enc || document.characterSet);
  this.entry = [];

  this.request();
};

FullFeed.prototype.request = function(){
  if (!this.requestURL) return;
  this.state = 'request';
  var self = this;
  var opt = {
        method: 'get',
        url: this.requestURL,
        overrideMimeType: this.mime,
        headers: {
          'User-Agent': navigator.userAgent + ' Greasemonkey (LDR Full Feed ' + VERSION + ')',
        },
        onerror: function(){
          self.error.call(self, 'FullFeed Request Error');
        },
        onload: function(res){
          self.load[self.type].call(self, res)
        },
  };
  message('Loading '+this.type+' ...');
  if(w.hasClass(this.data.container, 'gm_fullfeed_loaded'))
    w.toggleClass(this.data.container, 'gm_fullfeed_loaded');
  w.toggleClass(this.data.container, 'gm_fullfeed_loading');
  window.setTimeout(GM_xmlhttpRequest, 0, opt);
}

FullFeed.prototype.load = {
  FullFeed :function(res) {
    this.state = 'loading';
    var text = res.responseText;
    var self = this;

    try {
      text = text.replace(/(<[^>]+?[\s"'])on(?:(?:un)?load|(?:dbl)?click|mouse(?:down|up|over|move|out)|key(?:press|down|up)|focus|blur|submit|reset|select|change)\s*=\s*(?:"(?:\\"|[^"])*"?|'(\\'|[^'])*'?|[^\s>]+(?=[\s>]|<\w))(?=[^>]*?>|<\w|\s*$)/gi,
    "$1");
      if (REMOVE_IFRAME)  text = text.replace(/<iframe(?:\s[^>]+?)?>[\S\s]*?<\/iframe\s*>/gi, "");
      var htmldoc = parseHTML(text);
      removeXSSRisk(htmldoc);
      if(res.finalUrl){
        this.requestURL = res.finalUrl;
        relativeToAbsolutePath(htmldoc, this.requestURL);
      } else {
        relativeToAbsolutePath(htmldoc, this.requestURL);
      }
    } catch(e) {
      return this.error('HTML Parse Error');
    }
  
    time('FULLFEED: DocumentFilterTime: ');
    FullFeed.documentFilters.forEach(function(f) {
      f(htmldoc, this.requestURL, this.info);
    },this);
    timeEnd('FULLFEED: DocumentFilterTime: ');


    if(USE_AUTOPAGERIZE_SITEINFO || AUTOPAGER)
      this.apList = cache.info.autopagerize
        .filter(function({url:aUrl}){
          return new RegExp(aUrl).test(this.requestURL);
        },this)
        .sort(function(a, b){ return (b.url.length - a.url.length) });

    if(this.info.microformats){
      log('FULLFEED: Microformats')
      this.entry = getElementsByMicroformats(htmldoc)
    }

    if(this.entry.length == 0){
      try{
        this.entry = $X(this.info.xpath, htmldoc, Array);
      } catch(e) {
        return this.error('Something is wrong with this XPath');
      }
    }

    if(USE_AUTOPAGERIZE_SITEINFO && this.entry.length == 0){
      log(this.apList)
      this.apList.some(function(i){
        if(i.name=='hAtom' || i.name=='autopagerize_microformat') return false;
        try {
          var entry = $X(i.pageElement, htmldoc, Array);
        } catch(e) { return false }
        if(entry.length>0){
          this.entry = entry;
          log('FULLFEED: AutoPagerize Siteinfo');
          return true;
        }
        else return false;
      },this);
    }

    if(AUTO_SEARCH && this.entry.length == 0){
      log('FULLFEED: Auto Search');
      this.entry = searchEntry(htmldoc);
    }

    if(EXTRACT_TEXT && this.entry.length == 0){
      log('FULLFEED: Extract Text');
      this.entry = extractText(htmldoc);
    }

    if (this.entry.length > 0) {
      if(AUTOPAGER) this.searchAutoPagerData(htmldoc);
      log(this.entry);
      this.removeEntry();
      time('FULLFEED: FilterTime: ');
      FullFeed.filters.forEach(function(f) { f(this.entry, this.requestURL) },this);
      timeEnd('FULLFEED: FilterTime: ');

      this.addEntry();

      this.requestEnd();
    } else return this.error('This SITE_INFO is unmatched to this entry');
  },
  AutoPager: function(res){
    this.state = 'loading';
    var text = res.responseText;
    var self = this;
    try {
      text = text.replace(/(<[^>]+?[\s"'])on(?:(?:un)?load|(?:dbl)?click|mouse(?:down|up|over|move|out)|key(?:press|down|up)|focus|blur|submit|reset|select|change)\s*=\s*(?:"(?:\\"|[^"])*"?|'(\\'|[^'])*'?|[^\s>]+(?=[\s>]|<\w))(?=[^>]*?>|<\w|\s*$)/gi,
    "$1");
      if (REMOVE_IFRAME)  text = text.replace(/<iframe(?:\s[^>]+?)?>[\S\s]*?<\/iframe\s*>/gi, "");
      var htmldoc = parseHTML(text);
      removeXSSRisk(htmldoc);
      if(res.finalUrl){
        this.requestURL = res.finalUrl;
        relativeToAbsolutePath(htmldoc, this.requestURL);
      } else {
        relativeToAbsolutePath(htmldoc, this.requestURL);
      }
    } catch(e) {
      return this.error('HTML Parse Error');
    }

    time('FULLFEED: DocumentFilterTime: ');
    FullFeed.documentFilters.forEach(function(f) {
      f(htmldoc, this.requestURL, this.info);
    },this);
    timeEnd('FULLFEED: DocumentFilterTime: ');

    try {
      this.entry = $X(this.ap.pageElement, htmldoc, Array);
      this.nextLink = $X(this.ap.nextLink, htmldoc);
    } catch(e) {
      this.enable = false;
    }

    if (this.entry.length > 0) {
      if(AUTOPAGER) this.searchAutoPagerData(htmldoc);
      var df = document.createDocumentFragment();
      this.entry.forEach(function(i){ df.appendChild(i) });
      this.entry = Array.slice(df.childNodes);
      log(this.entry);

      time('FULLFEED: FilterTime: ');
      FullFeed.filters.forEach(function(f) { f(this.entry, this.requestURL) },this);
      timeEnd('FULLFEED: FilterTime: ');

      this.createPager();
      this.addEntry();

      this.requestEnd();
    }
    else return this.error('This SITE_INFO is unmatched to this entry');
  }
}

FullFeed.prototype.createPager = function(){
  var pager = document.createRange();
  var p = pager.createContextualFragment('<hr/><p class="gm_fullfeed_pager">page <a class="gm_fullfeed_link" href="'+this.requestURL+'">'+(++this.pageNum || (this.pageNum=2))+'</a></p>');
  this.data.item_body.appendChild(p);
  pager.detach();
}

FullFeed.prototype.requestEnd = function(){
  this.state = 'loaded';
  message('Loading '+this.type+' ...Done');
  if(AUTOPAGER && !FullFeed.fullfeed['_'+this.data.id]) FullFeed.fullfeed['_'+this.data.id] = this;
  w.addClass(this.data.container, 'gm_fullfeed_loaded');
  w.toggleClass(this.data.container, 'gm_fullfeed_loading');
  w.toggleClass(this.data.container, this.requestURL);
}

FullFeed.prototype.error = function(e){
  this.state = 'error';
  message('Error: ' + e);
  w.addClass(this.data.container, 'gm_fullfeed_error');
  w.toggleClass(this.data.container, 'gm_fullfeed_loading');
}

FullFeed.prototype.removeEntry = function(){
  while (this.data.item_body.firstChild) {
    this.data.item_body.removeChild(this.data.item_body.firstChild);
  }
}

FullFeed.prototype.addEntry = function(){
  var df = document.createDocumentFragment();
  this.entry.forEach(function(i){
    try {
      i = document.adoptNode(i, true);
    }catch(e){
      i = document.importNode(i, true);
    }
    df.appendChild(i);
  });
  this.data.item_body.appendChild(df);
}

FullFeed.prototype.AutoPager = function (){
  if (!this.enable){
    if(this.pageNum>0) return message("cannot AutoPage");
    else return message('This entry has been already loaded.');
  }
  var nextLink = this.nextLink.getAttribute('href') ||
    this.nextLink.getAttribute('action') ||
    this.nextLink.getAttribute('value');
  nextLink = rel2abs(nextLink, this.requestURL);
  this.requestURL = nextLink;
  this.type = 'AutoPager';
  this.request();
}

FullFeed.prototype.searchAutoPagerData = function (htmldoc){
  this.enable = false;
  if(this.apList.length>0){
    var nextLink;
    if(!this.ap){
      if( this.apList.some(function(i){
        if((nextLink = $X(i.nextLink, htmldoc)[0]) &&
          ($X(i.pageElement, htmldoc, Array).length>0)){
            this.ap = i;
            this.enable = true;
            return true;
        }
        return false;
      },this)){
        this.nextLink = nextLink;
      }
    } else {
      if(nextLink = $X(this.ap.nextLink, htmldoc)[0]){
        this.enable = true;
        this.nextLink = nextLink;
      }
    }
  }
}

FullFeed.register = function(){

  if(AUTOPAGER){
    FullFeed.fullfeed = {};
    w.register_hook('BEFORE_PRINTFEED',function(){
      FullFeed.fullfeed = null;
      FullFeed.fullfeed = {};
    });
  }
  if(!WIDGET) return;
  var icon_data = GM_getResourceURL(ICON);
  var description = "\u5168\u6587\u53d6\u5f97\u3067\u304d\u308b\u3088\uff01";
  w.entry_widgets.add('gm_fullfeed_widget', function(feed, item){
    if (cache.pattern.test(item.link) || cache.pattern.test(feed.channel.link)) {
      if(CLICKABLE) return [
        '<img class="gm_fullfeed_icon_disable" id="gm_fullfeed_widget_'+item.id+'" src="'+icon_data+'">'
      ].join('');
      else return [
        '<img src="'+icon_data+'">'
      ].join('');
    }
  }, description);

  if(!CLICKABLE) return;
  var tmp = [];
  w.register_hook("AFTER_PRINTFEED", function(feed){
    addListener();
    if(!w.State.writer || w.State.writer.complete){
      return;
    }
    watchWriter(feed);
  });

  w.register_hook("BEFORE_PRINTFEED", function(feed){
    removeListener();
  });

  function watchWriter(feed){
    w.State.writer.watch("complete", function(key, oldVal, newVal){
      w.State.writer2.watch("complete", function(key, oldVal, newVal){
        if(! w.State.writer.complete){
          watchWriter(feed);
          return newVal;
        }
        addListener();
        return newVal;
      });
      return newVal;
    })
  }

  function addListener(){
    $X('id("right_body")//img[contains(concat(" ",@class," ")," gm_fullfeed_icon_disable ")]', document)
    .forEach(function(element){
      w.removeClass(element, 'gm_fullfeed_icon_disable');
      w.addClass(element, 'gm_fullfeed_icon');
      element.addEventListener('click', getEntryByPressButton, true);
      tmp.push(element);
    });
  }

  function removeListener(){
    while(tmp.length){
      try{
        tmp.pop().removeEventListener('click', getEntryByPressButton, true);
      }catch(e){}
    }
  }

  function getEntryByPressButton (e){
    var re = /gm_fullfeed_widget_(\d+)/;
    var id = this.id.match(re)[1];
    if(this.className && this.className == 'gm_fullfeed_icon')
      init(id);
  }

}


// API
FullFeed.documentFilters = [];

FullFeed.filters= [];

FullFeed.itemFilters= [];

window.FullFeed = {
  VERSION : VERSION,
  addItemFilter : function(f){ FullFeed.itemFilters.push(f); },
  addFilter : function(f){ FullFeed.filters.push(f); },
  addDocumentFilter : function(f){ FullFeed.documentFilters.push(f); },
};


// [Filters]

// Filter: Add TargetAttr
window.FullFeed.addFilter(function(nodes, url){
    nodes.forEach(function(e){
      var anchors = $X('descendant-or-self::a', e);
      if(anchors)
        anchors.forEach(function(i){ i.target = '_blank' });
    });
});

// Filter: Remove Script and H2 tags
// iframeはどうも要素を作成した時点で読みにいくようなので、textから正規表現で削除
// なので、SITEINFOはIFRAMEを基準に作成しないでいただけるとありがたい。
if(REMOVE_SCRIPT || REMOVE_H2TAG )
window.FullFeed.addFilter(function(nodes, url){
  filter(nodes, function(e){
    var n = e.nodeName;
    if(REMOVE_SCRIPT && n.indexOf('SCRIPT') == 0) return false;
    if(REMOVE_H2TAG && n.indexOf('H2') == 0) return false;
    return true;
  });
  nodes.forEach(function(e){
    $X('descendant::*[self::script or self::h2]', e)
    .forEach(function(i){
      i.parentNode.removeChild(i);
    });
  });
});

// Filter: Remove Particular Class
// LDR 自体が使っているclassを取り除く。とりあえずmoreだけ。
// ほかにもあれば追加する。
window.FullFeed.addFilter(function(nodes, url){
  nodes.forEach(function(e){
    $X('descendant-or-self::*[contains(concat(" ",@class," ")," more ")]', e)
    .forEach(function(i){
      w.removeClass(i, 'more');
    });
  });
});


// [Cache Manager]

var Cache = function(){
  var self = this;
  this.pattern;
  this.state = 'normal';
  this.getSiteinfo();
  this.rebuildLocalSiteinfo();
  log(this.info);
  if(WIDGET) this.createPattern();
  GM_registerMenuCommand('LDR Full Feed - reset cache', function(){ self.resetSiteinfo.call(self)});
  if(this.state == 'first') this.resetSiteinfo();
}

Cache.prototype.rebuildLocalSiteinfo = function(){
  this.siteinfo = SITE_INFO
    .map(function(i){
      i.urlIndex = -1;
      return i;
  });
}

Cache.prototype.getSiteinfo = function (){
  if(!(this.info = eval(GM_getValue('cache')))){
    log('CACHE: first');
    this.state = 'first';
    var t = {};
    PHASE.forEach(function(i){t[i.type] = []});
    this.info = {
      ldrfullfeed  :  t,
      autopagerize : [AUTOPAGERIZE_MICROFORMAT]
    };
  }
}

Cache.prototype.resetSiteinfo = function(){
  if(this.state == 'loading') return message('Now loading. Please wait!');
  this.state = 'loading';
  this.tmp_ldrfullfeed  = {};
  this.tmp_autopagerize = [AUTOPAGERIZE_MICROFORMAT];

  // identify
  this.id = new Object;
  this.success = 0;
  var id = this.id;

  var self = this;

  message('Resetting cache. Please wait...');

  PHASE.forEach(function(i){
      this.tmp_ldrfullfeed[i.type] = [];
  },this);
  SITEINFO_IMPORT_URLS.forEach(function(obj, index) {
      var name = obj.name || obj.url;
      var opt = {
        method: 'GET',
        url: obj.url,
        headers: {
          'User-Agent': navigator.userAgent + ' Greasemonkey (LDR Full Feed ' + VERSION + ')',
        },
        onload: function(res){
          self.setSiteinfo.call(self, [res, obj, index, id]);
        },
        onerror: function(res){
          self.error.call(self, 'Cache Request Error'+name);
        },
      }
      window.setTimeout(GM_xmlhttpRequest, 0, opt);
  });
  AUTOPAGERIZE_SITEINFO_IMPORT_URLS.forEach(function(obj, index){
      var name = obj.name || obj.url;
      var opt = {
        method: 'GET',
        url: obj.url,
        headers: {
          'User-Agent': navigator.userAgent + ' Greasemonkey (LDR Full Feed ' + VERSION + ')',
        },
        onload: function(res){
          self.setAutoPagerSiteinfo.call(self, [res, obj, index, id]);
        },
        onerror: function(res){
          self.error.call(self, 'Cache Request Error'+name);
        },
      }
      window.setTimeout(GM_xmlhttpRequest, 0, opt);
  });
}

Cache.prototype.setSiteinfo = function([res, obj, index, id]){
  var info = [];
  var name = obj.name || obj.url;
  switch (obj.format.toUpperCase()){
    case 'JSON':
      try {
        info = eval(res.responseText)
          .map(function(i){
            var d = i.data;
            d.name = i.name;
            d.microformats = (d.microformats == 'true')? true: false;
            d.urlIndex = index;
            return d;
          })
          .filter(function(i){ return (this.isValid(i) && i.type)? true : false},this);
      } catch(e) {
        return this.error('Not JSON: '+name);
      }
      break;

    case 'HTML':
      try {
        var doc = parseHTML(res.responseText);
      } catch(e) {
        return this.error('HTML Parse Error: '+name);
      }

      $X('//textarea[contains(concat(" ",normalize-space(@class)," "), " ldrfullfeed_data ")]', doc)
      .forEach(function(siteinfo_list){
        var data = this.parseSiteinfo.call(this, [siteinfo_list.value, index]);
        if (data)
          info.push(data);
      },this);

      var charsets = ['utf-8','euc-jp','shift_jis'];
      charsets.forEach(function(charset){
        $X('//ul[contains(concat(" ",normalize-space(@class)," "), " microformats_list ' + charset + ' ")]/li', doc)
        .forEach(function(microformats_data){
          var data = this.parseMicroformats.call(this, [charset, microformats_data, index]);
          if(data)
            info.push(data);
        },this);
      },this);
      break;
  }
  if(this.id === id){
    PHASE.forEach(function(i){
      info.filter(function(d){
            return (d.type.toUpperCase() == i.type
            || (i.sub && d.type.toUpperCase() == i.sub))? true : false 
          })
          .forEach(function(d){
            this.tmp_ldrfullfeed[i.type].push(d) 
          },this);
      this.tmp_ldrfullfeed[i.type].sort(function(a,b){
        return a.urlIndex - b.urlIndex
      });
      log('CACHE: ' + i.type + ':ok');
    },this);
    if(++this.success == SITEINFO_IMPORT_URLS.length+AUTOPAGERIZE_SITEINFO_IMPORT_URLS.length)
      this.requestEnd();
  }
}

Cache.prototype.setAutoPagerSiteinfo = function([res, obj, index, id]){
  var info = [];
  var name = obj.name || obj.url;
  try {
    info = eval(res.responseText)
        .reduce(function(sum,i){
          var d = i.data;
          d.name = i.name;
          sum.push(d);
          return sum;
        },[]);
  } catch(e) {
    return this.error('Not JSON: '+name);
  }
  if(this.id === id){
    info.map(function(i){this.tmp_autopagerize.push(i)},this);

    if(++this.success == SITEINFO_IMPORT_URLS.length+AUTOPAGERIZE_SITEINFO_IMPORT_URLS.length)
      this.requestEnd();
  }
}

Cache.prototype.requestEnd = function(){
  log(name);
  this.info = {
    ldrfullfeed  : this.tmp_ldrfullfeed,
    autopagerize : this.tmp_autopagerize
  };
  GM_setValue('cache', this.info.toSource());
  log(this.info);
  if(WIDGET) this.createPattern();
  message('Resetting cache. Please wait... Done');
  this.state = 'normal';
}

Cache.prototype.error = function(e){
  message('Error: '+e);
  this.state = 'normal';
}

Cache.prototype.parseMicroformats = function([c, li, index]){
  if(!li) return;
  var info = {
    name : "MicroformatsURLList:"+li.textContent,
    url : li.textContent,
    urlIndex : index,
    enc : c,
    microformats : true,
    type : 'INDIV_MICROFORMATS'
  }

  var isValidUrl = function(info){
    try {
      var reg = new RegExp(info.url);
    } catch(e) {
      return false;
    }
    return true;
  }

  return isValidUrl(info) ? info : null;
}

Cache.prototype.parseSiteinfo = function([text, index]){
  var lines = text.split(/[\r\n]+/);
  var reg = /(^[^:]*?):(.*)$/;
  var trimspace = function(str){
    return str.replace(/^\s*/, '').replace(/\s*$/, '');
  }
  var info = {};
  lines.forEach(function(line) {
    if (reg.test(line)) {
      info[RegExp.$1] = trimspace(RegExp.$2);
    }
  });

  info.microformats = (info.microformats && info.microformats == 'true')? true: false;

  return this.isValid(info) ? info : null;
}

Cache.prototype.createPattern = function(){
  var exps = [];
  var reg;

  this.siteinfo.forEach(function(i){
    exps.push(i.url);
  });

  for each (var i in this.info.ldrfullfeed) {
    i.forEach(function(info) {
      exps.push(info.url);
    });
  }
  // AutoPagerizeの情報にはcharset情報がないので。
  /*
  this.info.autopagerize.forEach(function(i){
    exps.push(i.url);
  });
  */
  reg = new RegExp (exps.join('|'));
  this.pattern = reg;
}

Cache.prototype.isValid = function(info) {
  var infoProp = ['url', 'xpath', 'type'];
  if (infoProp.some(function(i){
    if (!info[i]){
      if (i != 'xpath' || !info.microformats){
        return true;
      }
    }
  })) return false;

  try{
    new RegExp(info.url);
  } catch(e) {
    return false;
  }
  return true;
}

// [Register LDR]

/* data format
 *
 *   itemURL
 *   feedURL
 *   id
 *   title
 *   container
 *   title
 *   item           <-- unsafe item
 *   found
 *
 *   create safe item
 */
var getData = function(id){
  if(!id) var id = w.get_active_item(true).id;
  if(!id) return;
  var feed = w.get_active_feed();

  this.item = w.get_item_info(id);
  this.itemURL = this.item.link;
  this.feedURL = feed.channel.link;
  this.id = this.item.id;
  this.container = w.$('item_' + this.id);
  this.title = this.item.title;
  this.found = false;

};

var launchFullFeed = function(list, c) {
  if (typeof list.some != "function") return;
    list.some(function(i) {
      var reg = new RegExp(i.url);
      if (reg.test(c.itemURL) || reg.test(c.feedURL)) {
        c.found = true;
        new FullFeed(i, c);
        return true;
      } else {
        return false;
      }
    });
}

var loadCurrentEntry = function(){
  init();
};

var loadAllEntries = function(){
  var items = w.get_active_feed().items;
  if (items && items.length > 0)
  items.forEach(function(item){ init(item.id)});
};

var init = function(id){
  var c = (id) ? new getData(id) : new getData();
  if(!c) return;
  if(ITEMFILTER){
    FullFeed.itemFilters.forEach(function(f) {
      f(c);
    });
  }

  if(ADCHECKER.test(c.title))
    return message('This entry is advertisement');
  if(w.hasClass(c.container, 'gm_fullfeed_loaded')){
    if(AUTOPAGER && FullFeed.fullfeed['_'+c.id]){
      FullFeed.fullfeed['_'+c.id].AutoPager();
      return;
    }
    else return message('This entry has been already loaded.');
  }
  if(w.hasClass(c.container, 'gm_fullfeed_loading'))
    return message('Now loadig...');

  if(!c.item.fullfeed){
    launchFullFeed(cache.siteinfo, c);
    log('PHASE: LOCAL SITEINFO');
    if(!c.found && !PHASE.some(function(i){
        log('PHASE: ' + i.type);
        launchFullFeed(cache.info.ldrfullfeed[i.type], c);
        return c.found;
    })){
      message('This entry is not listed on SITE_INFO');
      if (OPEN) GM_openInTab(c.itemURL) || message('Cannot popup');
    }
  }
};

var cache = new Cache();

if(LOADING_MOTION) addStyle(CSS, 'gm_fullfeed_style');

var timer = setTimeout(function() {
  if (timer) clearTimeout(timer);
  if (typeof w.Keybind != 'undefined' && typeof w.entry_widgets != 'undefined') {
    w.Keybind.add(KEY, function(){
      loadCurrentEntry();
    });

    if(GET_ALL){
    w.Keybind.add(GET_ALL_KEY, function(){
      loadAllEntries();
    });
    }

    w.Keybind.add(GET_SITEINFO_KEY, function() {
      cache.resetSiteinfo();
    });

    if(WIDGET) FullFeed.register();
  } else {
    timer = setTimeout(arguments.callee, 100);
  }
});

// == [Utility Functions] ===========================================

function message (mes){
  w.message(mes);
}

function getElementsByMicroformats (htmldoc) {
  var t;
  MICROFORMATS.some(function(i){
    t = $X(i.xpath, htmldoc)
    if(t.length>0){
      log('FULLFEED: Microformats :' + i.name);
      return true;
    }
    else return false;
  });
  return t;
}

function removeXSSRisk (htmldoc){
  var attr = "allowscriptaccess";
    $X("descendant-or-self::embed", htmldoc)
      .forEach(function(elm){
      if(!elm.hasAttribute(attr)) return;
      elm.setAttribute(attr, "never");
    });
    $X("descendant-or-self::param", htmldoc)
      .forEach(function(elm){
      if(!elm.getAttribute("name") || elm.getAttribute("name").toLowerCase().indexOf(attr) < 0) return;
      elm.setAttribute("value", "never");
    });
}

function extractText (htmldoc) {
  var div = document.createElement('div');
  $X('(descendant-or-self::text()[../self::*[self::div or self::table or self::td or self::th or self::tr or self::dt or self::dd or self::font or self::strong or self::ul or self::li]]|descendant-or-self::img|descendant-or-self::a)', htmldoc)
    .map(function(i){
      log(i.parentNode.nodeName);
      if(i.nodeName == 'IMG')
        return i;
      else if(i.nodeName == 'A')
        return i;
      else{
        i.nodeValue = i.nodeValue+'\n'
        return i;
      }
    })
    .forEach(function(i){
      div.appendChild(i);
    });
  div.innerHTML = div.innerHTML
    .replace(/(?:(\r\n|\r|\n)\s*)+/g,'<br>$1');
  return [div];
}

function searchEntry(htmldoc) {
  var max = 0;
  var entry = [];
  var data;
  var xpath = [
      '(//div|//td|//table|//tbody)',
      '[(..//h2) or (.//h3) or (.//h4) or (.//h5) or (.//h6) or (..//*[contains(concat(@id,@class,""),"title")])]',
      // '[(.|.//*|ancestor-or-self::*)contains(concat(@id,@class,""),"entry")) or (contains(concat(@id,@class,""),"section")) or (contains(concat(@id,@class,""),"content")) or (contains(concat(@id,@class,""),"main")) or (contains(concat(@id,@class,""),"day")) or (contains(concat(@id,@class,""),"article"))]]',
      '[not(.//form|ancestor-or-self::form)]',
      '[not((.|.//*|ancestor-or-self::*)contains(concat(" ",@class," ")," robots-nocontent ")])]',
      '[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"side")])]',
      '[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"navi")])]',
      '[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"footer")])]',
      '[not((.|.//*|ancestor-or-self::*)starts-with(concat(@id,@class,""),"header")])]',
      '[not(.//script|ancestor-or-self::script)]',
].join('');
  try {
    var elms = $X(xpath, htmldoc);
    if(elms.length == 0) return entry;
    elms.forEach(function(e){
      // var n = e.getElementsByTagName('br').length;
      var n = e.textContent.length;
      if(max < n){
        max = n;
        data = e;
      }
    });
    entry.push(data);
    return entry;
  }catch (e){
    return [];
  }
}

// written by id:Yuichirou
function relativeToAbsolutePath(htmldoc, base) {
  var o = {
    top : base.match("^https?://[^/]+")[0],
    current : base.replace(/\/[^\/]+$/, '/'),
  }

  $X("descendant-or-self::a", htmldoc)
    .forEach(function(elm) {
    if(elm.getAttribute("href")) elm.href = rel2abs(elm.getAttribute("href"), o);
  });
  $X("descendant-or-self::img", htmldoc)
    .forEach(function(elm) {
    if(elm.getAttribute("src")) elm.src = rel2abs(elm.getAttribute("src"), o);
  });
  $X("descendant-or-self::embed", htmldoc)
    .forEach(function(elm) {
    if(elm.getAttribute("src")) elm.src = rel2abs(elm.getAttribute("src"), o);
  });
  $X("descendant-or-self::object", htmldoc)
    .forEach(function(elm) {
    if(elm.getAttribute("data")) elm.data = rel2abs(elm.getAttribute("data"), o);
  });
}

function rel2abs(url, base) {
  if(typeof(base) == 'object'){
    var top = base.top;
    var current = base.current;
  }else{
    var top = base.match("^https?://[^/]+")[0];
    var current = base.replace(/\/[^\/]+$/, '/');
  }
  if (url.match("^https?://")) {
    return url;
  } else if (url.indexOf("/") == 0) {
    return top + url;
  } else {
    if(url.indexOf(".") == 0){
      while(url.indexOf(".") == 0){
        if(url.substring(0, 3) == "../")
          current = current.replace(/\/[^\/]+\/$/,"/");
        url = url.replace(/^\.+\//,"")
      }
    }
    return current + url;
  }
}

// $X (c) id:cho45
// $X(exp);
// $X(exp, context);
// $X(exp, type);
// $X(exp, context, type);
function $X (exp, context, type /* want type */) {
    if (typeof context == "function") {
        type    = context;
        context = null;
    }
    if (!context) context = document;
    var exp = (context.ownerDocument || context).createExpression(exp, function (prefix) {
        var o = document.createNSResolver(context).lookupNamespaceURI(prefix);
        if (o) return o;
        return (document.contentType == "application/xhtml+xml") ? "http://www.w3.org/1999/xhtml" : "";
    });

    switch (type) {
        case String:
            return exp.evaluate(
                context,
                XPathResult.STRING_TYPE,
                null
            ).stringValue;
        case Number:
            return exp.evaluate(
                context,
                XPathResult.NUMBER_TYPE,
                null
            ).numberValue;
        case Boolean:
            return exp.evaluate(
                context,
                XPathResult.BOOLEAN_TYPE,
                null
            ).booleanValue;
        case Array:
            var result = exp.evaluate(
                context,
                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                null
            );
            var ret = [];
            for (var i = 0, len = result.snapshotLength; i < len; i++) {
                ret.push(result.snapshotItem(i));
            }
            return ret;
        case undefined:
            var result = exp.evaluate(context, XPathResult.ANY_TYPE, null);
            switch (result.resultType) {
                case XPathResult.STRING_TYPE : return result.stringValue;
                case XPathResult.NUMBER_TYPE : return result.numberValue;
                case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
                case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: {
                    // not ensure the order.
                    var ret = [];
                    var i = null;
                    while (i = result.iterateNext()) {
                        ret.push(i);
                    }
                    return ret;
                }
            }
            return null;
        default:
            throw(TypeError("$X: specified type is not valid type."));
    }
}

// copied from LDR-Prefav (c) id:brazil
function filter(a, f) {
	for (var i = a.length; i --> 0; f(a[i]) || a.splice(i, 1));
}

// copied from Pagerization (c) id:ofk
function parseHTML(str) {
  str = str.replace(/^[\s\S]*?<html(?:\s[^>]+?)?>|<\/html\s*>[\S\s]*$/ig, '');
  var res = document.implementation.createDocument(null, 'html', null);
  var range = document.createRange();
  range.setStartAfter(document.body);
  var fragment = range.createContextualFragment(str);
  try {
    fragment = res.adoptNode(fragment); //for Firefox3 beta4
  } catch (e) {
    fragment = res.importNode(fragment, true);
  }
  res.documentElement.appendChild(fragment);
  return res;
}

function pathToURL(url, path) {
    var s
    if (path.match(/^\//)) { // absolute?
        s = url.replace(/^([a-z]+:\/\/.*?)\/.*$/, '$1')
    } else if ( path.match(/^\?/) ) {
        s = url.replace(/([^#]+?)(\?.*)?(#.*)?$/, '$1')
    } else {
        s = url.replace(/^(.*\/).*$/, '$1')
    }
    return s + path
}

// copied from LDRize (c) id:snj14
function addStyle(css,id){ // GM_addStyle is slow
	var link = document.createElement('link');
	link.rel = 'stylesheet';
	link.href = 'data:text/css,' + escape(css);
	document.documentElement.childNodes[0].appendChild(link);
}

// %o %s %i
function log() {if(console && DEBUG) console.log.apply(console, Array.slice(arguments));}
function group() {if(console && DEBUG) console.group.apply(console, Array.slice(arguments))}
function groupEnd() {if(console &&DEBUG) console.groupEnd();}

function time(name) {if(console.time && DEBUG) console.time.apply(console, [arguments[0]])}
function timeEnd(name) {if(console.timeEnd && DEBUG) console.timeEnd.apply(console, [arguments[0]])}

})(this.unsafeWindow || this);