XpmLoader改良

昨日id:bellbind:20050806:p1のXpmLoader関数群をメソッド化し、さらに非同期で表示もできるように改造してみた。

// functions for backward compatibility
function loadXpm(url) {
  var loader = new XpmLoader(true);
  loader.load(url);
}

function loadXpmAt(url, parentId) {
  var loader = new XpmLoader(true);
  loader.loadAt(url, parentId);
}

// XpmLoader prototype
function XpmLoader(isAsync) {
  this.document = document;
  this.window = window;
  this.isAsync = isAsync;
}

XpmLoader.prototype.load = function (url) {
  this.document.write("<div id='" + url + "'></div>");
  this.loadAt(url, url);
};

XpmLoader.prototype.loadAt = function (url, parentId) {
  var xmlhttp = this.createXmlHttp();
  xmlhttp.open("GET", url, true);
  var loader = this;
  xmlhttp.onreadystatechange = function () {
    loader.handleResponse(xmlhttp, parentId);
  };
  xmlhttp.send(null);
};

XpmLoader.prototype.createXmlHttp = function (){
  if (this.window.ActiveXObject) {
    return new this.window.ActiveXObject("Microsoft.XMLHTTP");
  } else if (this.window.XMLHttpRequest) {
    return new this.window.XMLHttpRequest();
  } else {
    return null;
  }
};

XpmLoader.prototype.handleResponse = function (xmlhttp, parentId) {
  if (xmlhttp.readyState == 4) {
    if (xmlhttp.status == 200) {
      this.writeImage(parentId, xmlhttp.responseText);
      //this.dumpError(parentId, xmlhttp.responseText);
    }
  }
};

XpmLoader.prototype.dumpError = function (parentId, message) {
  var textNode = this.document.createTextNode(message);
  this.document.getElementById(parentId).appendChild(textNode);
};

XpmLoader.prototype.writeImage = function (parentId, source) {
  xpmWriter = new XpmLoader.Writer(parentId, source, this.document, this.window, this.isAsync);
  xpmWriter.write();
};


// XpmLoader.Witer prototype
XpmLoader.Writer = function (parentId, source, document, window, isAsync) {
  this.src = source;
  this.parentId = parentId;
  this.document = document;
  this.window = window;
  this.isAsync = isAsync;
  this.infoLinePattern = new RegExp();
  this.infoLinePattern.compile('"([^"]+)"');
};

XpmLoader.Writer.prototype.dumpError = function (message) {
  var textNode = this.document.createTextNode(message);
  this.document.getElementById(this.parentId).appendChild(textNode);
};

XpmLoader.Writer.prototype.write = function () {
  try {
    this.parseMetadata();
    this.parseColorLines();
    this.parseDataLines();
  } catch (error) {
    this.dumpError(error);
  }
};

XpmLoader.Writer.prototype.getLine = function(errorMessage) {
  var result = this.src.match(this.infoLinePattern);
  if (result !== null) {
    var line = result[1];
    this.src = this.src.substring(result.index + result[0].length);
    return line;
  } else {
    throw errorMessage;
  } 
};

XpmLoader.Writer.prototype.parseMetadata = function () {
  var line = this.getLine("Could not read metadata line.");
  var nums = line.split(" ");
  this.width = parseInt(nums[0]);
  this.height = parseInt(nums[1]);
  this.colorNum = parseInt(nums[2]);
  this.colorSize = parseInt(nums[3]);
  this.colors = new Array(this.colorNum);
};

XpmLoader.Writer.prototype.parseColorLines = function () {
  var colorNo = 0;
  while (colorNo < this.colorNum) {
    this.parseColorLine();
    colorNo += 1;
  }
};
  
XpmLoader.Writer.prototype.parseColorLine = function () {
  var line = this.getLine("Could not read color line.");
  var colorId = line.substring(0, this.colorSize);
  var color = line.substring(this.colorSize + 1 + 2);
  this.colors[colorId] = color;
};

XpmLoader.Writer.prototype.parseDataLines = function () {
  this.lineNo = 0;
  this.imageRoot = this.createImageRoot();
  this.document.getElementById(this.parentId).appendChild(this.imageRoot);
  
  if (this.isAsync) {
    this.parseDataLineAsync();
  } else {
    while (this.lineNo < this.height) {
      this.parseDataLine();
      this.lineNo += 1;
    }
  }
};

XpmLoader.Writer.prototype.parseDataLineAsync = function () {
  try {
    if (this.lineNo < this.height) {
      this.parseDataLine();
      this.lineNo += 1;
      var writer = this;
      this.window.setTimeout(function () {writer.parseDataLineAsync();}, 1);
    }
  } catch (error) {
    this.dumpError(error);
  }
};

XpmLoader.Writer.prototype.parseDataLine = function () {
  var line = this.getLine("Could not read data line.");
  var imageLine = this.addImageLine(this.imageRoot);
  for (var i = 0;  i < this.width; i += 1) {
    var colorId = line.substring(i * this.colorSize, (i + 1) * this.colorSize);
    var color = this.colors[colorId];
    this.addImageCell(imageLine, color);
  }
};

XpmLoader.Writer.prototype.createImageRoot = function () {
  var table = this.document.createElement("TABLE");
  table.border = "0";
  table.cellPadding = "0";
  table.cellSpacing = "0";
  return table;
};

XpmLoader.Writer.prototype.addImageLine = function (imageRoot) {
  var tr = imageRoot.insertRow(imageRoot.rows.length);
  return tr;
};

XpmLoader.Writer.prototype.addImageCell = function (imageLine, color) {
  var cell = imageLine.insertCell(imageLine.cells.length);
  cell.bgColor = color;
  
  var table = this.document.createElement("TABLE");
  table.border = "0";
  table.cellPadding = "0";
  table.cellSpacing = "0";
  var tr = table.insertRow(table.rows.length);
  var td = tr.insertCell(tr.cells.length);
  td.width = "1";
  td.height = "1";
  
  cell.appendChild(table);
  return cell;
};

メソッド化した場合は、JavaScriptでも例外処理は便利な機構かもしれない。
メソッド化したほうが副作用として処理が多少早くなるみたい。これはグローバル名前空間にアクセスしないからだと思う。

※Opera8でも動くようRegExpの使い方を修正した
※ JSLintチェックに通した