JavaScriptでテキストデータの読み込み / magicien 

JavaScriptでXファイルを読み込んだときに使った方法。

バイナリデータの読み込みについて書きかけの状態ですが、今回はテキストデータの読み込みについて。
実行速度よりも実装のしやすさを優先するなら、Ginがおすすめ。
実際の使い方としてはこんな感じ。
var xfileGrammar;
function start()
{
  xfileGrammar = new Gin.Grammar({
  XFile:         / XFileHeader:header (XObjectLong)* /,
  XFileHeader:   / 'xof ':xof VersionNumber FormatType:showValue FloatSize:showValue /,
  VersionNumber: / <\d\d>:showValue <\d\d>:showValue /,
  FormatType:    / 'txt ' | 'bin ' | 'tzip' | 'bzip' /,
  FloatSize:     / '0064' | '0032' /,
 
  XObject:       / Template | Animation | AnimationKey | AnimationOptions | AnimationSet | AnimTicksPerSecond | Boolean | Boolean2d | ColorRGB | ColorRGBA | Coords2d | DeclData | EffectDWorld | EffectFloats | EffectInstance | EffectParamDWorld | EffectParamFloats | EffectString | FaceAdjacency | FloatKeys | Frame | FrameTransformMatrix | FVFData | Header | IndexedColor | Material | Matrix4x4 | MeshFaceWraps | MeshFace | MeshMaterialList | MeshNormals | MeshTextureCoords | MeshVertexColors | Mesh | Patch | PatchMesh9 | PatchMesh | PMAttributeRange | PMInfo | PMVSplitRecord | SkinWeights | TextureFilename | TimedFloatKeys | Vector | VertexDuplicationIndices | VertexElement | XSkinMeshHeader /,
  XObjectLong:   / Template | AnimationLong | AnimationKeyLong | AnimationOptionsLong | AnimationSetLong | AnimTicksPerSecondLong | BooleanLong | Boolean2dLong | ColorRGBLong | ColorRGBALong | Coords2dLong | DeclDataLong | EffectDWorldLong | EffectFloatsLong | EffectInstanceLong | EffectParamDWorldLong | EffectParamFloatsLong | EffectStringLong | FaceAdjacencyLong | FloatKeysLong | FrameLong | FrameTransformMatrixLong | FVFDataLong | HeaderLong | IndexedColorLong | MaterialLong | Matrix4x4Long | MeshFaceWrapsLong | MeshFaceLong | MeshMaterialListLong | MeshNormalsLong | MeshTextureCoordsLong | MeshVertexColorsLong | MeshLong | PatchLong | PatchMeshLong | PatchMesh9Long | PMAttributeRangeLong | PMInfoLong | PMVSplitRecordLong | SkinWeightsLong | TextureFilenameLong | TimedFloatKeysLong | VectorLong | VertexDuplicationIndicesLong | VertexElementLong | XSkinMeshHeaderLong /,
 
  XString:       / <[0-9A-Za-z]*> /,
  Identifier:    / XString /,
  Name:          / XString /,
  UUID:          / '<' (<[0-9A-Za-z]|->)* '>' /,
  Member:        / ('array' TemplateType Name '[' ($INT | XString) ']') | (TemplateType Name) /,
  TemplateType:  / XString /,
  XArray:        / '[' $INT ']' /,
 
  XObjectTemplate:       / Identifier:identifier Name:name '{' UUID ('[...]' | ('[' Name ']') | (Member ';'))* '}' /,
  Template:             / 'template':template  Name '{' UUID ('[...]' | ('[' Name ']') | (Member ';'))* '}' /,
 
  IntValue:             / $INT ';' ::first /,
  IntArray:             / $INT (',' $INT)* (',')? ';' /,
 
  FloatValue:           / $REAL ';' ::first /,
  FloatArray:           / $REAL (',' $REAL)* (',')? ';' /,
 
  Header:               / HeaderLong | (HeaderValue ';') /,
  HeaderLong:           / 'Header' (Name)? '{' (UUID)? HeaderValue '}' /,
  HeaderValue:          / IntValue:major IntValue:minor IntValue:flags /,
 
  Animation:            / AnimationLong | ( AnimationValue ';') /,
  AnimationLong:        / 'Animation' (Name)? '{' (UUID)? AnimationValue '}' /,
  AnimationValue:       / $ANY /,
 
  AnimationKey:         / AnimationKeyLong | ( AnimationKeyValue ';') /,
  AnimationKeyLong:     / 'AnimationKey' (Name)? '{' (UUID)? AnimationKeyValue '}' /,
  AnimationKeyValue:    / $ANY /,
 
  AnimationOptions:      / AnimationOptionsLong | ( AnimationOptionsValue ';') /,
  AnimationOptionsLong:  / 'AnimationOptions' (Name)? '{' (UUID)? AnimationOptionsValue '}' /,
  AnimationOptionsValue: / $ANY /,
 
  AnimationSet:         / AnimationSetLong | ( AnimationSetValue ';') /,
  AnimationSetLong:     / 'AnimationSet' (Name)? '{' (UUID)? AnimationSetValue '}' /,
  AnimationSetValue:    / $ANY /,
 
  AnimTicksPerSecond:      / AnimTicksPerSecondLong | ( AnimTicksPerSecondValue ';') /,
  AnimTicksPerSecondLong:  / 'AnimTicksPerSecond' (Name)? '{' (UUID)? AnimTicksPerSecondValue '}' /,
  AnimTicksPerSecondValue: / $ANY /,
 
  Boolean:              / BooleanLong | ( BooleanValue ';') /,
  BooleanLong:          / 'Boolean' (Name)? '{' (UUID)? BooleanValue '}' /,
  BooleanValue:         / $ANY /,
 
  Boolean2d:            / Boolean2dLong | ( Boolean2dValue ';') /,
  Boolean2dLong:        / 'Boolean2d' (Name)? '{' (UUID)? Boolean2dValue '}' /,
  Boolean2dValue:       / $ANY /,
 
  ColorRGB:             / ColorRGBLong | (ColorRGBValue ';') /,
  ColorRGBLong:         / 'ColorRGB' (Name)? '{' (UUID)? ColorRGBValue '}' /,
  ColorRGBValue:        / FloatValue:red FloatValue:green FloatValue:blue /,
 
  ColorRGBA:            / ColorRGBALong | (ColorRGBAValue ';') /,
  ColorRGBALong:        / 'ColorRGBA' (Name)? '{' (UUID)? ColorRGBAValue '}' /,
  ColorRGBAValue:       / FloatValue:red FloatValue:green FloatValue:blue FloatValue:alpha /,
 
  Coords2d:             / Coords2dLong | (Coords2dValue ';') /,
  Coords2dLong:         / 'Coords2d' (Name)? '{' (UUID)? Coords2dValue '}' /,
  Coords2dArray:        / Coords2dValue (',' Coords2dValue)* (',')? ';' /,
  Coords2dValue:        / FloatValue:u FloatValue:v /,
(中略)
  XSkinMeshHeader:      / XSkinMeshHeaderLong | ( XSkinMeshHeaderValue ';') /,
  XSkinMeshHeaderLong:  / 'XSkinMeshHeader' (Name)? '{' (UUID)? XSkinMeshHeaderValue '}' /,
  XSkinMeshHeaderValue: / $ANY /,
}, "XFile", Gin.SPACE);

function XFileHandler() { this._s = [] };
XFileHandler.prototype = {
  first: function(v){ return v[0]; },
  header: function(v){ alert("version: " + v[1]); },
  identifier: function(v){ alert("identifier:" + v[0]); },
  xof: function(v){ alert("xof"); },
  showValue: function(v){ alert("value:" + v); },
  name: function(v){ alert("name:" + v); },
(中略)
  // Mesh
  mesh:      function(v){ alert("Mesh start"); },
  nVertices: function(v){ alert("nVertices: " + v); },
  vertices:  function(v){},
  nFaces:    function(v){ alert("nFaces: " + v); },
  faces:     function(v){},
(中略)
};
 
var match;
function parseXFile(request)
{
  var text = request.responseText;
  var handler = new XFileHandler();
  match = xfileGrammar.parse(text, handler);
  if(match && match.full) {
    alert("OK");
  }else{
     alert("NG");
  }
}

var url = "xfile.x";
var myAjax = new Ajax.Request(
  url,
  {
    method: 'get',
    onComplete: parseXFile
  });
}
パースしたいデータの構文が分かっているなら、それをそのまま書いてやれば簡単にパーサが実装できる。
速度を優先するなら、各ファイル形式に合わせて自分で実装すべし。
xファイルのパーサはこんな感じで作りました。
var XParser = Class.create({
  _text: null,
(中略)
  parse: function(text){
    this._text = text;
    if(!this._obj){
      this._obj = new XModel();
    }
    this._offset = 0;
    this._err = 0;

    if(!this.XFileHeader(this._obj)){
      alert("header format error");
      this._err = 1;
      return null;
    }
    while(this.XObjectLong(this._obj)){}
    if(this._err){
      alert("obj format error:" + this._err);
      return null;
    }
    this.splitFaceNormals();

    return this._obj;
  },

  moveIndex: function(len) {
    this._text = this._text.substring(len);
    this._offset += len;
  },

  getString: function(pattern) {
    this.skip();
    var str = this._text.match(pattern);
    if(str != null){
      this.moveIndex(str[0].length);
      return str[0];
    }
    return null;
  },

  /* matching patterns */
  skip: function() {
    var i=0;
    var code;
    while(1){
      code = this._text.charCodeAt(i);
      if(code != 32 && (code < 9 || code > 13)){
        break;
      }
      i++;
    }
    if(i>0){
      this.moveIndex(i);
    }
  },

  _integerPattern: new RegExp(/^(-|\+)?\d+;?/),
  getInteger: function() {
    var str = this.getString(this._integerPattern);
    var val = parseInt(str);
    return val;
  },

  _floatPattern: new RegExp(/^(-|\+)?(\d)*\.(\d)*;?/),
  getFloat: function() {
    var str = this.getString(this._floatPattern);
    var val = parseFloat(str);
    return val;
  },
(中略)
  XObjectLong: function(parent){
    var id = this.getWord();
    if(id == null){
      return null;
    }
    switch(id){
      case "template":
        return this.Template(parent);
      case "Header":
        return this.Header(parent);
      case "Mesh":
        return this.Mesh(parent);
      case "MeshMaterialList":
        return this.MeshMaterialList(parent);
      case "MeshNormals":
        return this.MeshNormals(parent);
      case "MeshTextureCoords":
        return this.MeshTextureCoords(parent);
      case "MeshVertexColors":
        return this.MeshVertexColors(parent);

      default:
        alert("unknown type:" + id);
        break;
    }
    return false;
  },
(中略)
  Mesh: function(parent) {
    this.getLeftBrace();

    // vertices
    var nVertices = this.getInteger();
    var skinArray = $A();
    for(var i=0; i<nVertices; i++){
      var skin = new Skin();
      var pos = new DHVector3();
      pos.x = this.getFloat();
      pos.y = this.getFloat();
      pos.z = -this.getFloat();
      skin.position = pos;

      skin.boneIndex[0] = 0;
      skin.boneIndex[1] = -1;
      skin.boneIndex[2] = -1;
      skin.boneIndex[3] = -1;

      skin.skinWeight[0] = 1;
      skin.skinWeight[1] = 0;
      skin.skinWeight[2] = 0;
      skin.skinWeight[3] = 0;

      skinArray.push(skin);

      this.getCommaOrSemicolon();
    }
    this._obj.skinArray = skinArray;
    this._obj.dynamicSkinOffset = -1;

    // faces
    var nFaces = this.getInteger();
    var faces = $A();
    for(var i=0; i<nFaces; i++){
      var face = this.getIntegerArray();
      faces.push(face);
    }
    this._indices = faces;
    this.getRightBrace();

    return true;
  },
(中略)
}

2012/10/05(Fri) 01:21:39