From b12f11af03393bcb15e2e1c6e9a2693c145583d6 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Wed, 13 Sep 2017 21:01:31 -0700
Subject: [PATCH] renamed table to category, added File and Block classes,
 typed category and column objects

---
 dist/molio.esm.js                             | 91 +++++++++++++-----
 dist/molio.js                                 |  1 -
 package.json                                  | 13 ++-
 rollup.config.js                              | 18 ++--
 src/index.d.ts                                |  3 +
 src/index.ts                                  |  3 +
 src/reader/gro.ts                             | 94 +++++++++----------
 src/reader/spec/gro.spec.ts                   |  6 +-
 src/relational/block.ts                       | 24 +++++
 src/relational/category.ts                    | 61 ++++++++++++
 src/relational/file.ts                        | 10 ++
 src/relational/table.ts                       | 36 -------
 src/relational/text-block.ts                  | 39 ++++++++
 .../{text-table.ts => text-category.ts}       | 13 +--
 src/relational/text-column.ts                 |  4 +-
 src/relational/text-file.ts                   | 17 ++++
 src/script.ts                                 | 34 ++++---
 17 files changed, 310 insertions(+), 157 deletions(-)
 create mode 100644 src/relational/block.ts
 create mode 100644 src/relational/category.ts
 create mode 100644 src/relational/file.ts
 delete mode 100644 src/relational/table.ts
 create mode 100644 src/relational/text-block.ts
 rename src/relational/{text-table.ts => text-category.ts} (90%)
 create mode 100644 src/relational/text-file.ts

diff --git a/dist/molio.esm.js b/dist/molio.esm.js
index 913109a81..cce873658 100644
--- a/dist/molio.esm.js
+++ b/dist/molio.esm.js
@@ -72,6 +72,12 @@ function parseFloat(str, start, end) {
     return neg * ret;
 }
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
 /**
  * Eat everything until a newline occurs.
  */
@@ -150,6 +156,13 @@ function skipWhitespace(state) {
     return prev;
 }
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
 var Tokens;
 (function (Tokens) {
     function resize(tokens) {
@@ -182,6 +195,12 @@ var Tokens;
     Tokens.create = create;
 })(Tokens || (Tokens = {}));
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
 /**
  * Represents a column that is not present.
  */
@@ -219,6 +238,13 @@ var ShortStringPool;
     ShortStringPool.get = get;
 })(ShortStringPool || (ShortStringPool = {}));
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
 /**
  * Represents a single column.
  */
@@ -328,10 +354,17 @@ var CifColumn = (function (TextColumn) {
     return CifColumn;
 }(TextColumn));
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
 /**
- * Represents a table backed by a string.
+ * Represents a category backed by a string.
  */
-var TextTable = function TextTable(data, name, columns, tokens) {
+var TextCategory = function TextCategory(data, name, columns, tokens) {
     this.name = name;
     this.indices = tokens.indices;
     this.data = data;
@@ -348,13 +381,13 @@ prototypeAccessors.columnNames.get = function () {
 /**
  * Get a column object that makes accessing data easier.
  */
-TextTable.prototype.getColumn = function getColumn (name) {
+TextCategory.prototype.getColumn = function getColumn (name) {
     var i = this.columnIndices.get(name);
     if (i !== void 0)
         { return new TextColumn(this, this.data, name, i); }
     return UndefinedColumn;
 };
-TextTable.prototype.initColumns = function initColumns (columns) {
+TextCategory.prototype.initColumns = function initColumns (columns) {
         var this$1 = this;
 
     this.columnIndices = new Map();
@@ -365,23 +398,23 @@ TextTable.prototype.initColumns = function initColumns (columns) {
     }
 };
 
-Object.defineProperties( TextTable.prototype, prototypeAccessors );
-var CifTable = (function (TextTable) {
-    function CifTable () {
-        TextTable.apply(this, arguments);
+Object.defineProperties( TextCategory.prototype, prototypeAccessors );
+var CifCategory = (function (TextCategory) {
+    function CifCategory () {
+        TextCategory.apply(this, arguments);
     }
 
-    if ( TextTable ) CifTable.__proto__ = TextTable;
-    CifTable.prototype = Object.create( TextTable && TextTable.prototype );
-    CifTable.prototype.constructor = CifTable;
+    if ( TextCategory ) CifCategory.__proto__ = TextCategory;
+    CifCategory.prototype = Object.create( TextCategory && TextCategory.prototype );
+    CifCategory.prototype.constructor = CifCategory;
 
-    CifTable.prototype.getColumn = function getColumn (name) {
+    CifCategory.prototype.getColumn = function getColumn (name) {
         var i = this.columnIndices.get(name);
         if (i !== void 0)
             { return new CifColumn(this, this.data, name, i); }
         return UndefinedColumn;
     };
-    CifTable.prototype.initColumns = function initColumns (columns) {
+    CifCategory.prototype.initColumns = function initColumns (columns) {
         var this$1 = this;
 
         this.columnIndices = new Map();
@@ -393,8 +426,8 @@ var CifTable = (function (TextTable) {
         }
     };
 
-    return CifTable;
-}(TextTable));
+    return CifCategory;
+}(TextCategory));
 
 /*
  * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
@@ -445,19 +478,19 @@ var GroFile = function GroFile(data) {
 };
 var GroBlock = function GroBlock(data) {
     this.data = data;
-    this.tableMap = new Map();
-    this.tableList = [];
+    this.categoryMap = new Map();
+    this.categoryList = [];
 };
 
-GroBlock.prototype.getTable = function getTable (name) {
-    return this.tableMap.get(name);
+GroBlock.prototype.getCategory = function getCategory (name) {
+    return this.categoryMap.get(name);
 };
 /**
- * Adds a table.
+ * Adds a category.
  */
-GroBlock.prototype.addTable = function addTable (table) {
-    this.tableList[this.tableList.length] = table;
-    this.tableMap.set(table.name, table);
+GroBlock.prototype.addCategory = function addCategory (category) {
+    this.categoryList[this.categoryList.length] = category;
+    this.categoryMap.set(category.name, category);
 };
 function createTokenizer(data) {
     return {
@@ -590,7 +623,7 @@ function handleAtoms(state, block) {
         state.position = valueEnd;
         eatLine(state);
     }
-    block.addTable(new TextTable(state.data, name, columns, tokens));
+    block.addCategory(new TextCategory(state.data, name, columns, tokens));
 }
 /**
  * box vectors (free format, space separated reals), values:
@@ -626,8 +659,8 @@ function parseInternal(data) {
     file.blocks.push(block);
     var headerColumns = ['title', 'timeInPs', 'numberOfAtoms', 'boxX', 'boxY', 'boxZ'];
     var headerTokens = Tokens.create(2 * headerColumns.length);
-    var header = new TextTable(state.data, 'header', headerColumns, headerTokens);
-    block.addTable(header);
+    var header = new TextCategory(state.data, 'header', headerColumns, headerTokens);
+    block.addCategory(header);
     handleTitleString(state, headerTokens);
     handleNumberOfAtoms(state, headerTokens);
     handleAtoms(state, block);
@@ -638,5 +671,11 @@ function parse(data) {
     return parseInternal(data);
 }
 
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
 export { parse as groReader };
 //# sourceMappingURL=molio.esm.js.map
diff --git a/dist/molio.js b/dist/molio.js
index 847442f65..e69de29bb 100644
--- a/dist/molio.js
+++ b/dist/molio.js
@@ -1 +0,0 @@
-!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(t.MOLIO={})}(this,function(t){"use strict";function n(t,n,e){var i=0,r=1;for(45===t.charCodeAt(n)&&(r=-1,n++);n<e;n++){var o=t.charCodeAt(n)-48;if(o>9||o<0)return r*i|0;i=10*i+o|0}return r*i}function e(t,e,i,r){return 43===e.charCodeAt(i)&&i++,t*Math.pow(10,n(e,i,r))}function i(t,n,i){var r=1,o=0,s=0,u=1;for(45===t.charCodeAt(n)&&(r=-1,++n);n<i;){var a=t.charCodeAt(n)-48;if(!(a>=0&&a<10)){if(-2===a){for(++n;n<i;){if(!((a=t.charCodeAt(n)-48)>=0&&a<10))return 53===a||21===a?e(r*(o+s/u),t,n+1,i):r*(o+s/u);s=10*s+a,u*=10,++n}return r*(o+s/u)}if(53===a||21===a)return e(r*o,t,n+1,i);break}o=10*o+a,++n}return r*o}function r(t){for(;t.position<t.length;)switch(t.data.charCodeAt(t.position)){case 10:return t.currentTokenEnd=t.position,++t.position,void++t.currentLineNumber;case 13:return t.currentTokenEnd=t.position,++t.position,++t.currentLineNumber,void(10===t.data.charCodeAt(t.position)&&++t.position);default:++t.position}t.currentTokenEnd=t.position}function o(t){for(;t.position<t.length;)switch(t.data.charCodeAt(t.position)){case 9:case 10:case 13:case 32:return void(t.currentTokenEnd=t.position);default:++t.position}t.currentTokenEnd=t.position}function s(t){for(var n=10;t.position<t.length;){var e=t.data.charCodeAt(t.position);switch(e){case 9:case 32:n=e,++t.position;break;case 10:13!==n&&++t.currentLineNumber,n=e,++t.position;break;case 13:n=e,++t.position,++t.currentLineNumber;break;default:return n}}return n}function u(t){return{data:t,position:0,length:t.length,currentLineNumber:1,currentTokenStart:0,currentTokenEnd:0,numberOfAtoms:0,hasVelocities:!1,numberOfDecimalPlaces:3}}function a(t,n){r(t);for(var e=t.currentTokenStart,i=t.currentTokenEnd,o=t.currentTokenStart,s=e;s<i&&!d(t.data,s);)++s;if(d(t.data,s)){for(var u=s+2;s>e&&c(t.data,s-1);)--s;for(v.add(n,o,s);u<i&&32===t.data.charCodeAt(u);)++u;for(;s>u&&32===t.data.charCodeAt(s-1);)--s;v.add(n,u,i)}else v.add(n,o,s),v.add(n,s,s)}function c(t,n){var e=t.charCodeAt(n);return 32===e||44===e}function d(t,n){var e=t.charCodeAt(n);return(84===e||116===e)&&61===t.charCodeAt(n+1)}function h(t,e){s(t),t.currentTokenStart=t.position,o(t),t.numberOfAtoms=n(t.data,t.currentTokenStart,t.currentTokenEnd),v.add(e,t.currentTokenStart,t.currentTokenEnd),r(t)}function f(t,n){console.log("MOINMOIN");var e=["residueNumber","residueName","atomName","atomNumber","x","y","z"];t.hasVelocities&&e.push("vx","vy","vz");for(var i,o,s,u=[5,5,5,5,8,8,8,8,8,8],a=e.length,c=v.create(2*t.numberOfAtoms*a),d=t.position,h=0;h<t.numberOfAtoms;++h){t.currentTokenStart=t.position,o=t.currentTokenStart;for(var f=0;f<a;++f){for(s=i=o,d=o=i+u[f];s<d&&32===t.data.charCodeAt(s);)++s;for(;d>s&&32===t.data.charCodeAt(d-1);)--d;v.addUnchecked(c,s,d)}t.position=d,r(t)}n.addTable(new k(t.data,"atoms",e,c))}function p(t,n){for(var e=0;e<3;++e)s(t),t.currentTokenStart=t.position,o(t),v.add(n,t.currentTokenStart,t.currentTokenEnd)}function l(t){return w.success(t)}function m(t){var n=u(t),e=new x(t),i=new E(t);e.blocks.push(i);var r=["title","timeInPs","numberOfAtoms","boxX","boxY","boxZ"],o=v.create(2*r.length),s=new k(n.data,"header",r,o);return i.addTable(s),a(n,o),h(n,o),f(n,i),p(n,o),l(e)}var v;!function(t){function n(t){var n=new Int32Array(1.61*t.indices.length|0);n.set(t.indices),t.indices=n,t.indicesLenMinus2=n.length-2|0}t.add=function(t,e,i){t.count>t.indicesLenMinus2&&n(t),t.indices[t.count++]=e,t.indices[t.count++]=i},t.addUnchecked=function(t,n,e){t.indices[t.count++]=n,t.indices[t.count++]=e},t.create=function(t){return{indicesLenMinus2:t-2|0,count:0,indices:new Int32Array(t)}}}(v||(v={}));var g=function(){this.isDefined=!1};g.prototype.getString=function(t){return null},g.prototype.getInteger=function(t){return 0},g.prototype.getFloat=function(t){return 0},g.prototype.getValuePresence=function(t){return 1},g.prototype.areValuesEqual=function(t,n){return!0},g.prototype.stringEquals=function(t,n){return null===n};var b,y=new g;!function(t){t.create=function(){return Object.create(null)},t.get=function(t,n){if(n.length>6)return n;var e=t[n];return void 0!==e?e:(t[n]=n,n)}}(b||(b={}));var C=function(t,n,e,i){this.data=n,this.name=e,this.index=i,this.stringPool=b.create(),this.isDefined=!0,this.indices=t.indices,this.columnCount=t.columnCount};C.prototype.getString=function(t){var n=2*(t*this.columnCount+this.index);return b.get(this.stringPool,this.data.substring(this.indices[n],this.indices[n+1]))},C.prototype.getInteger=function(t){var e=2*(t*this.columnCount+this.index);return n(this.data,this.indices[e],this.indices[e+1])},C.prototype.getFloat=function(t){var n=2*(t*this.columnCount+this.index);return i(this.data,this.indices[n],this.indices[n+1])},C.prototype.stringEquals=function(t,n){var e=this,i=2*(t*this.columnCount+this.index),r=this.indices[i],o=n.length;if(o!==this.indices[i+1]-r)return!1;for(var s=0;s<o;s++)if(e.data.charCodeAt(s+r)!==n.charCodeAt(s))return!1;return!0},C.prototype.areValuesEqual=function(t,n){var e=this,i=2*(t*this.columnCount+this.index),r=2*(n*this.columnCount+this.index),o=this.indices[i],s=this.indices[r],u=this.indices[i+1]-o;if(u!==this.indices[r+1]-s)return!1;for(var a=0;a<u;a++)if(e.data.charCodeAt(a+o)!==e.data.charCodeAt(a+s))return!1;return!0},C.prototype.getValuePresence=function(t){var n=2*(t*this.columnCount+this.index);return this.indices[n]===this.indices[n+1]?1:0};var A=function(t){function n(){t.apply(this,arguments)}return t&&(n.__proto__=t),n.prototype=Object.create(t&&t.prototype),n.prototype.constructor=n,n.prototype.getString=function(n){var e=t.prototype.getString.call(this,n);return"."===e||"?"===e?null:e},n.prototype.getValuePresence=function(t){var n=2*(t*this.columnCount+this.index),e=this.indices[n];if(this.indices[n+1]-e!=1)return 0;var i=this.data.charCodeAt(e);return 46===i?1:63===i?2:0},n}(C),k=function(t,n,e,i){this.name=n,this.indices=i.indices,this.data=t,this.columnCount=e.length,this.rowCount=i.count/2/e.length|0,this.initColumns(e)},T={columnNames:{}};T.columnNames.get=function(){return this.columnNameList},k.prototype.getColumn=function(t){var n=this.columnIndices.get(t);return void 0!==n?new C(this,this.data,t,n):y},k.prototype.initColumns=function(t){var n=this;this.columnIndices=new Map,this.columnNameList=[];for(var e=0;e<t.length;e++)n.columnIndices.set(t[e],e),n.columnNameList.push(t[e])},Object.defineProperties(k.prototype,T);var w;!function(t){function n(){t.apply(this,arguments)}t&&(n.__proto__=t),n.prototype=Object.create(t&&t.prototype),n.prototype.constructor=n,n.prototype.getColumn=function(t){var n=this.columnIndices.get(t);return void 0!==n?new A(this,this.data,t,n):y},n.prototype.initColumns=function(t){var n=this;this.columnIndices=new Map,this.columnNameList=[];for(var e=0;e<t.length;e++){var i=t[e].substr(n.name.length+1);n.columnIndices.set(i,e),n.columnNameList.push(i)}}}(k);!function(t){t.error=function(t,n){return void 0===n&&(n=-1),new L(t,n)},t.success=function(t,n){return void 0===n&&(n=[]),new N(t,n)}}(w||(w={}));var L=function(t,n){this.message=t,this.line=n,this.isError=!0};L.prototype.toString=function(){return this.line>=0?"[Line "+this.line+"] "+this.message:this.message};var N=function(t,n){this.result=t,this.warnings=n,this.isError=!1},x=function(t){this.blocks=[],this.data=t},E=function(t){this.data=t,this.tableMap=new Map,this.tableList=[]};E.prototype.getTable=function(t){return this.tableMap.get(t)},E.prototype.addTable=function(t){this.tableList[this.tableList.length]=t,this.tableMap.set(t.name,t)},t.groReader=function(t){return m(t)},Object.defineProperty(t,"__esModule",{value:!0})});
diff --git a/package.json b/package.json
index 639b0d963..c1de25f15 100644
--- a/package.json
+++ b/package.json
@@ -23,19 +23,18 @@
   "license": "MIT",
   "devDependencies": {
     "@types/jest": "latest",
-    "@types/node": "^8.0.25",
-    "jest": "^20.0.4",
-    "rollup": "^0.49.2",
+    "@types/node": "^8.0.28",
+    "jest": "^21.1.0",
+    "rollup": "^0.49.3",
     "rollup-plugin-buble": "^0.15.0",
-    "rollup-plugin-commonjs": "^8.2.0",
+    "rollup-plugin-commonjs": "^8.2.1",
     "rollup-plugin-json": "^2.3.0",
     "rollup-plugin-node-resolve": "^3.0.0",
     "rollup-watch": "^4.3.1",
-    "ts-jest": "^20.0.14",
+    "ts-jest": "^21.0.0",
     "tslint": "^5.7.0",
     "typescript": "^2.5.1",
-    "uglify-js": "^3.0.28",
-    "webpack": "^3.5.5"
+    "uglify-js": "^3.1.0"
   },
   "dependencies": {}
 }
diff --git a/rollup.config.js b/rollup.config.js
index 29e505326..577c39346 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -1,4 +1,4 @@
-import buble from 'rollup-plugin-buble';
+// import buble from 'rollup-plugin-buble';
 import json from 'rollup-plugin-json';
 import resolve from 'rollup-plugin-node-resolve';
 import commonjs from 'rollup-plugin-commonjs';
@@ -16,21 +16,21 @@ export default {
     }),
     commonjs(),
     json(),
-    buble()
+    // buble()
   ],
   output: [
     {
       file: "build/js/molio.dev.js",
       format: 'umd',
       name: 'MOLIO',
-      sourcemap: true
+      sourcemap: false
     },
-    {
-      file: "build/js/molio.esm.js",
-      format: 'es',
-      sourcemap: true
-    }
+    // {
+    //   file: "build/js/molio.esm.js",
+    //   format: 'es',
+    //   sourcemap: false
+    // }
   ],
   external: external,
-  sourcemap: true
+  sourcemap: false
 };
\ No newline at end of file
diff --git a/src/index.d.ts b/src/index.d.ts
index f4e7f3c9a..8e857ee33 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -6,4 +6,7 @@
 
 export { ParserResult, ParserError, ParserSuccess } from './parser'
 
+export { Category } from './relational/category'
+export { Column } from './relational/column'
+
 export { parse as groReader } from './reader/gro'
diff --git a/src/index.ts b/src/index.ts
index cbcdecf1e..3a7b1225e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,4 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+export { Category } from './relational/category'
+export { Column } from './relational/column'
+
 export { parse as groReader } from './reader/gro'
diff --git a/src/reader/gro.ts b/src/reader/gro.ts
index ede2ac342..d148f3ebc 100644
--- a/src/reader/gro.ts
+++ b/src/reader/gro.ts
@@ -9,7 +9,9 @@ import { eatLine, eatValue, skipWhitespace } from '../utils/helper'
 import { Tokens } from '../utils/tokens'
 import { TokenizerState } from '../utils/tokenizer-state'
 
-import { TextTable } from '../relational/text-table'
+import { TextFile } from '../relational/text-file'
+import { TextBlock } from '../relational/text-block'
+import { TextCategory } from '../relational/text-category'
 
 import { ParserResult } from '../parser'
 
@@ -17,53 +19,40 @@ import { ParserResult } from '../parser'
  * http://manual.gromacs.org/current/online/gro.html
  */
 
-export interface GroFile {
-    data: string;
-    blocks: GroBlock[];
+export const GroCategories = {
+    'header': '',
+    'atoms': ''
 }
 
-export interface GroBlock {
-    getTable(name: string): TextTable
-    addTable(table: TextTable): void
-}
-
-export class GroFile implements GroFile {
-    data: string;
-    blocks: GroBlock[] = [];
+// type GroCategories = keyof typeof GroCategories
 
-    constructor(data: string) {
-        this.data = data;
-    }
+export const GroAtomBasicColumns = {
+    'residueNumber': '',
+    'residueName': '',
+    'atomName': '',
+    'atomNumber': '',
+    'x': '',
+    'y': '',
+    'z': ''
 }
-
-export class GroBlock implements GroBlock {
-    private tableMap: Map<string, TextTable>;
-    private tableList: TextTable[];
-
-    data: string;
-
-    /**
-     * Gets a table by its name.
-     */
-    getTable(name: string) {
-        return this.tableMap.get(name);
-    }
-
-    /**
-     * Adds a table.
-     */
-    addTable(table: TextTable) {
-        this.tableList[this.tableList.length] = table;
-        this.tableMap.set(table.name, table);
-    }
-
-    constructor(data: string) {
-        this.data = data;
-
-        this.tableMap = new Map()
-        this.tableList = []
-    }
+export type GroAtomBasicColumns = keyof typeof GroAtomBasicColumns
+
+export const GroAtomVelocityColumns = Object.assign({
+    'vx': '',
+    'vy': '',
+    'vz': ''
+}, GroAtomBasicColumns)
+export type GroAtomVelocityColumns = keyof typeof GroAtomVelocityColumns
+
+export const GroHeaderColumns = {
+    'title': '',
+    'timeInPs': '',
+    'numberOfAtoms': '',
+    'boxX': '',
+    'boxY': '',
+    'boxZ': ''
 }
+export type GroHeaderColumns = keyof typeof GroHeaderColumns
 
 export interface GroState extends TokenizerState {
     numberOfAtoms: number
@@ -181,7 +170,7 @@ function handleNumberOfAtoms (state: GroState, tokens: Tokens) {
  *     position (in nm, x y z in 3 columns, each 8 positions with 3 decimal places)
  *     velocity (in nm/ps (or km/s), x y z in 3 columns, each 8 positions with 4 decimal places)
  */
-function handleAtoms (state: GroState, block: GroBlock) {
+function handleAtoms (state: GroState, block: TextBlock) {
     console.log('MOINMOIN')
     const name = 'atoms'
 
@@ -218,7 +207,7 @@ function handleAtoms (state: GroState, block: GroBlock) {
         eatLine(state)
     }
 
-    block.addTable(new TextTable(state.data, name, columns, tokens));
+    block.addCategory(new TextCategory(state.data, name, columns, tokens));
 }
 
 /**
@@ -241,27 +230,28 @@ function handleBoxVectors (state: GroState, tokens: Tokens) {
  * Creates an error result.
  */
 // function error(line: number, message: string) {
-//     return ParserResult.error<GroFile>(message, line);
+//     return ParserResult.error<TextFile>(message, line);
 // }
 
 /**
  * Creates a data result.
  */
-function result(data: GroFile) {
+function result(data: TextFile) {
     return ParserResult.success(data);
 }
 
-function parseInternal(data: string): ParserResult<GroFile> {
+function parseInternal(data: string): ParserResult<TextFile> {
     const state = createTokenizer(data)
-    const file = new GroFile(data)
+    const file = new TextFile(data)
+    file.blocks
 
-    let block = new GroBlock(data)
+    let block = new TextBlock(data)
     file.blocks.push(block)
 
     const headerColumns = ['title', 'timeInPs', 'numberOfAtoms', 'boxX', 'boxY', 'boxZ']
     const headerTokens = Tokens.create(2 * headerColumns.length)
-    let header = new TextTable(state.data, 'header', headerColumns, headerTokens)
-    block.addTable(header)
+    let header = new TextCategory(state.data, 'header', headerColumns, headerTokens)
+    block.addCategory(header)
 
     handleTitleString(state, headerTokens)
     handleNumberOfAtoms(state, headerTokens)
diff --git a/src/reader/spec/gro.spec.ts b/src/reader/spec/gro.spec.ts
index db611f56f..0071a447c 100644
--- a/src/reader/spec/gro.spec.ts
+++ b/src/reader/spec/gro.spec.ts
@@ -5,7 +5,7 @@
  */
 
 import { parse } from '../gro'
-// import { Table } from '../../relational/table'
+// import { Category } from '../../relational/category'
 
 const groString = `MD of 2 waters, t= 4.2
     6
@@ -33,7 +33,7 @@ describe('gro reader', () => {
         } else {
             const groFile = parsed.result
 
-            const header = groFile.blocks[0].getTable('header')
+            const header = groFile.blocks[0].getCategory('header')
             if (header) {
                 expect(header.columnNames).toEqual(['title', 'timeInPs', 'numberOfAtoms', 'boxX', 'boxY', 'boxZ'])
 
@@ -58,7 +58,7 @@ describe('gro reader', () => {
         } else {
             const groFile = parsed.result
 
-            const header = groFile.blocks[0].getTable('header')
+            const header = groFile.blocks[0].getCategory('header')
             if (header) {
                 expect(header.columnNames).toEqual(['title', 'timeInPs', 'numberOfAtoms', 'boxX', 'boxY', 'boxZ'])
 
diff --git a/src/relational/block.ts b/src/relational/block.ts
new file mode 100644
index 000000000..d8c212bff
--- /dev/null
+++ b/src/relational/block.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Category, UndefinedCategory } from './category'
+
+export abstract class Block<T> {
+    abstract getCategory(name: string): T|undefined
+    abstract addCategory(category: T): void
+
+    getCategoriesFromSchema<T extends object> (schema: T) {
+        return BlockCategories(this, schema)
+    }
+}
+
+export type BlockCategories<Categories extends string> = { readonly [name in Categories]: Category }
+export function BlockCategories<T extends object>(block: Block<any> | undefined, categories: T): BlockCategories<keyof T> {
+    const ret = Object.create(null);
+    if (!block) for (const c of Object.keys(categories)) ret[c] = UndefinedCategory;
+    else for (const c of Object.keys(categories)) ret[c] = block.getCategory(c);
+    return ret;
+}
diff --git a/src/relational/category.ts b/src/relational/category.ts
new file mode 100644
index 000000000..0d387b49d
--- /dev/null
+++ b/src/relational/category.ts
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * from https://github.com/dsehnal/CIFTools.js
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Column, UndefinedColumn } from './column'
+
+/**
+ * Represents a tabular category with multiple fields represented as columns.
+ *
+ * Example:
+ * _category.field1
+ * _category.field2
+ * ...
+ */
+export abstract class Category {
+    name: string;
+    rowCount: number;
+    columnCount: number;
+    columnNames: string[];
+
+    /**
+     * If a field with the given name is not present, returns UndefinedColumn.
+     *
+     * Columns are accessed by their field name only, i.e.
+     * _category.field is accessed by
+     * category.getColumn('field')
+     *
+     * Note that columns are created on demand and there is some computational
+     * cost when creating a new column. Therefore, if you need to reuse a column,
+     * it is a good idea to cache it.
+     */
+    abstract getColumn(name: string): Column;
+
+    getColumnsFromSchema<T extends object> (schema: T) {
+        return CategoryColumns(this, schema)
+    }
+}
+
+/**
+ * Represents a category that is not present.
+ */
+class _UndefinedCategory extends Category {  // tslint:disable-line:class-name
+    name: ''
+    rowCount = 0
+    columnCount = 0
+    columnNames = []
+    getColumn(name: string) { return UndefinedColumn }
+}
+export const UndefinedCategory = new _UndefinedCategory() as Category;
+
+
+export type CategoryColumns<Columns extends string> = { readonly [name in Columns]: Column }
+export function CategoryColumns<T extends object>(category: Category | undefined, columns: T): CategoryColumns<keyof T> {
+    const ret = Object.create(null);
+    if (!category) for (const c of Object.keys(columns)) ret[c] = UndefinedColumn;
+    else for (const c of Object.keys(columns)) ret[c] = category.getColumn(c);
+    return ret;
+}
diff --git a/src/relational/file.ts b/src/relational/file.ts
new file mode 100644
index 000000000..80bf53205
--- /dev/null
+++ b/src/relational/file.ts
@@ -0,0 +1,10 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export interface File<T> {
+    blocks: T[];
+}
+
diff --git a/src/relational/table.ts b/src/relational/table.ts
deleted file mode 100644
index ebf6f88f7..000000000
--- a/src/relational/table.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
- *
- * from https://github.com/dsehnal/CIFTools.js
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { Column } from './column'
-
-/**
- * Represents that CIF category with multiple fields represented as columns.
- *
- * Example:
- * _category.field1
- * _category.field2
- * ...
- */
-export interface Table {
-    name: string;
-    rowCount: number;
-    columnCount: number;
-    columnNames: string[];
-
-    /**
-     * If a field with the given name is not present, returns UndefinedColumn.
-     *
-     * Columns are accessed by their field name only, i.e.
-     * _category.field is accessed by
-     * category.getColumn('field')
-     *
-     * Note that columns are created on demand and there is some computational
-     * cost when creating a new column. Therefore, if you need to reuse a column,
-     * it is a good idea to cache it.
-     */
-    getColumn(name: string): Column;
-}
diff --git a/src/relational/text-block.ts b/src/relational/text-block.ts
new file mode 100644
index 000000000..c4ef35b76
--- /dev/null
+++ b/src/relational/text-block.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Block } from './block'
+import { TextCategory } from './text-category'
+
+export class TextBlock extends Block<TextCategory> {
+    private categoryMap: Map<string, TextCategory>;
+    private categoryList: TextCategory[];
+
+    data: string;
+
+    /**
+     * Gets a category by its name.
+     */
+    getCategory(name: string) {
+        return this.categoryMap.get(name);
+    }
+
+    /**
+     * Adds a category.
+     */
+    addCategory(category: TextCategory) {
+        this.categoryList[this.categoryList.length] = category;
+        this.categoryMap.set(category.name, category);
+    }
+
+    constructor(data: string) {
+        super()
+
+        this.data = data;
+
+        this.categoryMap = new Map()
+        this.categoryList = []
+    }
+}
\ No newline at end of file
diff --git a/src/relational/text-table.ts b/src/relational/text-category.ts
similarity index 90%
rename from src/relational/text-table.ts
rename to src/relational/text-category.ts
index c4ca29a8c..f550213df 100644
--- a/src/relational/text-table.ts
+++ b/src/relational/text-category.ts
@@ -6,16 +6,16 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Table } from './table'
+import { Category } from './category'
 import { UndefinedColumn } from './column'
 import { TextColumn, CifColumn } from './text-column'
 
 import { Tokens } from '../utils/tokens'
 
 /**
- * Represents a table backed by a string.
+ * Represents a category backed by a string.
  */
-export class TextTable implements Table {
+export class TextCategory extends Category {
     protected data: string;
     protected columnNameList: string[];
     protected columnIndices: Map<string, number>;
@@ -66,8 +66,9 @@ export class TextTable implements Table {
         }
     }
 
-    constructor(
-        data: string, name: string, columns: string[], tokens: Tokens) {
+    constructor(data: string, name: string, columns: string[], tokens: Tokens) {
+        super()
+
         this.name = name;
         this.indices = tokens.indices;
         this.data = data;
@@ -79,7 +80,7 @@ export class TextTable implements Table {
     }
 }
 
-export class CifTable extends TextTable {
+export class CifCategory extends TextCategory {
     getColumn(name: string): CifColumn {
         let i = this.columnIndices.get(name);
         if (i !== void 0) return new CifColumn(this, this.data, name, i);
diff --git a/src/relational/text-column.ts b/src/relational/text-column.ts
index d6cb87c79..7c04c09d6 100644
--- a/src/relational/text-column.ts
+++ b/src/relational/text-column.ts
@@ -8,7 +8,7 @@
 
 import { Column } from './column'
 import { ValuePresence } from './constants'
-import { TextTable } from './text-table'
+import { TextCategory } from './text-category'
 
 import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../utils/number-parser'
 import { ShortStringPool } from '../utils/short-string-pool'
@@ -89,7 +89,7 @@ export class TextColumn implements Column {
         return ValuePresence.Present
     }
 
-    constructor(table: TextTable, protected data: string, public name: string, public index: number) {
+    constructor(table: TextCategory, protected data: string, public name: string, public index: number) {
         this.indices = table.indices;
         this.columnCount = table.columnCount;
     }
diff --git a/src/relational/text-file.ts b/src/relational/text-file.ts
new file mode 100644
index 000000000..6058531cc
--- /dev/null
+++ b/src/relational/text-file.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { File } from './file'
+import { TextBlock } from './text-block'
+
+export class TextFile implements File<TextBlock> {
+    data: string;
+    blocks: TextBlock[] = [];
+
+    constructor(data: string) {
+        this.data = data;
+    }
+}
diff --git a/src/script.ts b/src/script.ts
index d5520faea..013e07921 100644
--- a/src/script.ts
+++ b/src/script.ts
@@ -7,17 +7,17 @@
 // import * as util from 'util'
 import * as fs from 'fs'
 
-import { parse } from './reader/gro'
-import { Table } from './relational/table'
+import { parse, GroCategories, GroAtomBasicColumns } from './reader/gro'
+import { Category } from './relational/category'
 
 const file = '1crn.gro'
 // const file = 'water.gro'
 // const file = 'test.gro'
 // const file = 'md_1u19_trj.gro'
 
-function getFloatArray(table: Table, name: string) {
-    const column = table.getColumn(name)
-    const n = table.rowCount
+function getFloatArray(category: Category, name: string) {
+    const column = category.getColumn(name)
+    const n = category.rowCount
     const array = new Float32Array(n)
     for (let i = 0; i < n; ++i) {
         array[i] = column.getFloat(i)
@@ -25,9 +25,9 @@ function getFloatArray(table: Table, name: string) {
     return array
 }
 
-function getIntArray(table: Table, name: string) {
-    const column = table.getColumn(name)
-    const n = table.rowCount
+function getIntArray(category: Category, name: string) {
+    const column = category.getColumn(name)
+    const n = category.rowCount
     const array = new Int32Array(n)
     for (let i = 0; i < n; ++i) {
         array[i] = column.getInteger(i)
@@ -48,8 +48,10 @@ fs.readFile(`./examples/${file}`, 'utf8', function (err,data) {
         console.log(parsed)
     } else {
         const groFile = parsed.result
+        const categories = groFile.blocks[0].getCategoriesFromSchema(GroCategories)
 
-        const header = groFile.blocks[0].getTable('header')
+        // const header = groFile.blocks[0].getCategory('header')
+        const header = categories.header
         if (header) {
             console.log(header.columnNames)
 
@@ -63,15 +65,17 @@ fs.readFile(`./examples/${file}`, 'utf8', function (err,data) {
             console.error('no header')
         }
 
-        const atoms = groFile.blocks[0].getTable('atoms')
+        const atoms = categories.atoms
         if (atoms) {
             console.log(atoms.columnNames)
 
-            console.log(`'${atoms.getColumn('residueNumber').getString(1)}'`)
-            console.log(`'${atoms.getColumn('residueName').getString(1)}'`)
-            console.log(`'${atoms.getColumn('atomName').getString(1)}'`)
-            console.log(atoms.getColumn('z').getFloat(1))
-            console.log(`'${atoms.getColumn('z').getString(1)}'`)
+            const columns = atoms.getColumnsFromSchema(GroAtomBasicColumns)
+
+            console.log(`'${columns.residueNumber.getString(1)}'`)
+            console.log(`'${columns.residueName.getString(1)}'`)
+            console.log(`'${columns.atomName.getString(1)}'`)
+            console.log(columns.z.getFloat(1))
+            console.log(`'${columns.z.getString(1)}'`)
 
             const n = atoms.rowCount
             console.log('rowCount', n)
-- 
GitLab