text: Exhibit.Scraper = function(elmt, uiContext, settings) {    
      if (!settings.scraperInput) {
          SimileAjax.Debug.warn('Scraper not given an input element!');
          return;
      }
      
      var input = this._input = SimileAjax.jQuery('#' + settings.scraperInput);
  
      input.val('');
      input.attr('disabled', false);
  
      var elmt = this._elmt = SimileAjax.jQuery(elmt);
      this._uiContext = uiContext;
      this._settings = settings;
      
      elmt.attr('href', 'javascript:');
      
      var scraper = this;
      elmt.click(function() { scraper.activate() });
  }
  
  Exhibit.Scraper._settingSpecs = {
      "scraperInput":  { type: "text" },
      "itemType":  { type: "text", defaultValue: "item" },
      "inputType": { type: "text", defaultValue: "auto" },
      "scraperService":  { type: "text", defaultValue: "http://valinor.mit.edu/sostler/scraper.cgi" }
  };
  
  Exhibit.UI.generateCreationMethods(Exhibit.Scraper);
  Exhibit.UI.registerComponent('scraper', Exhibit.Scraper);
  
  Exhibit.Scraper.prototype.activate = function() {
      var input = this._input.val();
      if (this._settings.inputType == 'auto') {
          if (input.startsWith('http://')) {
              this.scrapeURL(input);
          } else {
              this.scrapeText(input);
          }
      } else if (this._settings.inputType == 'text') {
          this.scrapeText(input);        
      } else if (this._settings.inputType.toLowerCase() == 'url') {
          this.scrapeURL(input);
      } else {
          SimileAjax.Debug.warn('Unknown scraper input type ' + this._settings.inputType);
      }
  }
  
  Exhibit.Scraper.prototype.disableUI = function() {
      this._input.attr('disabled', true);
      this._elmt.removeAttr('href');
      this._elmt.css('color', 'AAA');
      this._elmt.unbind();
  }
  
  Exhibit.Scraper.prototype.enableUI = function() {
      var scraper = this;
      this._input.attr('disabled', false);
      this._elmt.attr('href', 'javascript:');
      this._elmt.css('color', '');
      SimileAjax.jQuery(this._elmt).click(function() { scraper.activate() });
  }
  
  Exhibit.Scraper.prototype.scrapeURL = function(url) {
      this.disableUI();
      var scraper = this;
      
      var success = function(resp) {
          var status = resp.status;
          if (status == 'ok') {
              scraper.scrapePageSource(resp.obj);
          } else if (status == 'error') {
              alert("Error using scraper service!\n\n" + resp.obj);
          } else {
              alert('Unknown response from scraper service:\n\n' + status);
          }
          scraper.enableUI();
      }
  
      this.disableUI();
      
      SimileAjax.jQuery.ajax({
          url: this._settings.scraperService,
          dataType: 'jsonp',
          jsonp: 'callback',
          data: { url: url },
          success: success
      });
  }
  
  Exhibit.Scraper.prototype.scrapeText = function(text) {
      var title = null;
      var item = Exhibit.ScraperBackend.extractItemFromText(
          text, this._settings.itemType, this._uiContext.getDatabase());
  
      Exhibit.ItemCreator.makeNewItemBox(this._uiContext, item);
  }
  
  Exhibit.Scraper.prototype.scrapePageSource = function(pageSource) {
      var text = Exhibit.ScraperBackend.getTextFromPageSource(pageSource);
      
      var title = Exhibit.ScraperBackend.getTitleFromPageSource(pageSource);
      var item = Exhibit.ScraperBackend.extractItemFromText(
          text, this._settings.itemType, this._uiContext.getDatabase());
      
      Exhibit.ItemCreator.makeNewItemBox(this._uiContext, item, { title: title });
  }
  
  // The backend performs content extraction
  Exhibit.ScraperBackend = {};
  
  Exhibit.ScraperBackend.getTitleFromPageSource = function(pageSource) {
      var div = document.createElement('div');
      div.innerHTML = pageSource.replace(/\s+/g, ' ');
      
      var dom = SimileAjax.jQuery(div);
      var title = dom.find('title').text();
      
      return title;
  }
  
  // returns contents of all children textnodes of node argument
  // from JavaScript: the Definitive Guide, by David Flanagan
  Exhibit.ScraperBackend.getTextContents = function(node) {
      function getStrings(n, strings) {
          if (n.nodeType == 3) {
              strings.push(n.data);
          } else if (n.nodeType == 1) {
              for (var m = n.firstChild; m != null; m = m.nextSibling) {
                  getStrings(m, strings)
              }
          }
      }
      var strings = [];
      getStrings(node, strings);
      return strings.join('');
  }
  
  Exhibit.ScraperBackend.getTextFromPageSource = function(pageSource) {
      var div = document.createElement('div');
      div.innerHTML = pageSource.replace(/\s+/g, ' ');
      
      // we ignore contents of style/script tags
      var children = div.childNodes;
      for (i=0; i < children.length; i++) {
          var node = children[i];
          if (node.nodeName.toLowerCase() == 'style' || node.nodeName.toLowerCase() == 'script') {
              div.removeChild(node);
          }
      }
      
      return Exhibit.ScraperBackend.getTextContents(div);
  }
  
  Exhibit.ScraperBackend.findMostCommon = function(substrings, text) {
      var maxCount = 0; // if none have more than 0, return null
      var maxSubstring = null;
      
      function countSubstrings(str, text) {
          str = str.toLowerCase();
          var count = 0;
          var index = null;
  
          while ((index = text.indexOf(str, index)) != -1) {
              count++;
              index += 1;
          }
          return count;
      }
      
      for (var i=0; i < substrings.length; i++) {
          var s = substrings[i];
          var count = countSubstrings(s, text);
          if (count > maxCount) {
              maxCount = count;
              maxSubstring = s;
          }
      };
      return maxSubstring;
  }
  
  Exhibit.ScraperBackend.extractItemFromText = function(text, itemType, db) {
      var item = { type: itemType };    
      var typeSet = new Exhibit.Set();
      typeSet.add(itemType);
      
      var subjects = db.getSubjectsUnion(typeSet, 'type');
  
      text = text.toLowerCase(); // ignore cases
      
      db.getAllProperties().forEach(function(prop) {
          var itemVals = db.getObjectsUnion(subjects, prop).toArray();
          var mostCommonItemValue = Exhibit.ScraperBackend.findMostCommon(itemVals, text);
          
          if (mostCommonItemValue) {
              item[prop] = mostCommonItemValue;
          }
      });
     
      return item;
  }
  