diff --git a/package.json b/package.json
index 31ad572ad994ae22ff0b852798a91c6d15b35d76..ae74e31e7843cfcee96df6862a932c6d13e567e9 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
     "test": "./node_modules/.bin/jest",
     "dist": "./node_modules/.bin/uglifyjs build/js/molio.dev.js -cm > dist/molio.js && cp build/js/molio.esm.js dist/molio.esm.js",
     "script": "./node_modules/.bin/rollup build/js/src/script.js -e fs -f cjs -o build/js/script.js",
-    "runscript": "npm run script && node build/js/script.js"
+    "runscript": "npm run script && node build/js/script.js",
+    "download-dics": "./node_modules/.bin/download -o build/dics http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic && ./node_modules/.bin/download -o build/dics http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_ddl.dic"
   },
   "jest": {
     "moduleFileExtensions": [
@@ -29,7 +30,8 @@
   "license": "MIT",
   "devDependencies": {
     "@types/jest": "^21.1.2",
-    "@types/node": "^8.0.32",
+    "@types/node": "^8.0.34",
+    "download-cli": "^1.0.5",
     "jest": "^21.2.1",
     "rollup": "^0.50.0",
     "rollup-plugin-buble": "^0.16.0",
@@ -37,10 +39,11 @@
     "rollup-plugin-json": "^2.3.0",
     "rollup-plugin-node-resolve": "^3.0.0",
     "rollup-watch": "^4.3.1",
-    "ts-jest": "^21.0.1",
+    "ts-jest": "^21.1.2",
     "tslint": "^5.7.0",
     "typescript": "^2.5.3",
-    "uglify-js": "^3.1.3"
+    "uglify-js": "^3.1.3",
+    "util.promisify": "^1.0.0"
   },
   "dependencies": {}
 }
diff --git a/src/reader/cif/schema/utils.ts b/src/reader/cif/schema/utils.ts
index 42093d8391390b99cc3a7f6e5efb2161dd362af1..94d68bb2c7b464432cca2c374f2655f2e8fe2df7 100644
--- a/src/reader/cif/schema/utils.ts
+++ b/src/reader/cif/schema/utils.ts
@@ -1,6 +1,6 @@
 
 // import dic from './dic'
-import { Field, Category } from '../schema'
+import { Field, Block, Category } from '../schema'
 import * as Data from '../data-model'
 
 const pooledStr = Field.pooledStr()
@@ -8,7 +8,7 @@ const str = Field.str()
 const int = Field.int()
 const float = Field.float()
 
-function getFieldType (type: string) {
+export function getFieldType (type: string) {
     switch (type) {
         case 'code':
         case 'ucode':
@@ -63,38 +63,102 @@ function getFieldType (type: string) {
     return str
 }
 
+type SafeFrameCategories = { [category: string]: Data.SafeFrame }
+type SafeFrameLinks = { [k: string]: string }
+
+interface SafeFrameData {
+    categories: SafeFrameCategories
+    links: SafeFrameLinks
+}
+
+// get field from given or linked category
+function getField ( category: string, field: string, d: Data.SafeFrame, ctx: SafeFrameData): Data.Field|undefined {
+    const { categories, links } = ctx
+
+    const cat = d.categories[category]
+    if (cat) {
+        return cat.getField(field)
+    } else {
+        if (d.header in links) {
+            return getField(category, field, categories[links[d.header]], ctx)
+        } else {
+            // console.log(`no links found for '${d.header}'`)
+        }
+    }
+}
+
+// function getEnums (d: Data.SafeFrame, ctx: SafeFrameData): string[]|undefined {
+//     const value = getField('_item_enumeration', 'value', d, ctx)
+//     if (value) {
+//         const enums: string[] = []
+//         for (let i = 0; i < value.rowCount; ++i) {
+//             enums.push(value.str(i))
+//             // console.log(value.str(i))
+//         }
+//         return enums
+//     } else {
+//         // console.log(`item_enumeration.value not found for '${d.header}'`)
+//     }
+// }
+
+function getCode (d: Data.SafeFrame, ctx: SafeFrameData): string|undefined {
+    const code = getField('_item_type', 'code', d, ctx)
+    if (code) {
+        let c = code.str(0)
+        // if (c === 'ucode') {
+        //     const enums = getEnums(d, ctx)
+        //     if (enums) c += `: ${enums.join('|')}`
+        // }
+        return c
+    } else {
+        console.log(`item_type.code not found for '${d.header}'`)
+    }
+}
+
 export function getSchema (dic: Data.Block) {  // todo Block needs to be specialized with safe frames as well
-    const schema: { [category: string]: Category.Schema } = {}
+    const schema: Block.Schema = {}  // { [category: string]: Category.Schema } = {}
 
+    const categories: SafeFrameCategories = {}
+    const links: SafeFrameLinks = {}
     dic.saveFrames.forEach(d => {
-        if (d.header[0] !== '_') {
-            schema[d.header] = {}
-        } else {
-            const categoryName = d.header.substring(1, d.header.indexOf('.'))
-            const itemName = d.header.substring(d.header.indexOf('.') + 1)
-            let fields
-            if (categoryName in schema) {
-                fields = schema[categoryName]
-            } else {
-                fields = {}
-                schema[categoryName] = fields
-            }
-            // console.log(util.inspect(d.categories, {showHidden: false, depth: 1}))
-            const item_type = d.categories['_item_type']
-            if (item_type) {
-                const code = item_type.getField('code')
-                if (code) {
-                    fields[itemName] = getFieldType(code.str(0))
-                } else {
-                    console.log(`item_type.code not found for '${d.header}'`)
+        if (d.header[0] !== '_') return
+        categories[d.header] = d
+        const item_linked = d.categories['_item_linked']
+        if (item_linked) {
+            const child_name = item_linked.getField('child_name')
+            const parent_name = item_linked.getField('parent_name')
+            if (child_name && parent_name) {
+                for (let i = 0; i < item_linked.rowCount; ++i) {
+                    const childName = child_name.str(i)
+                    const parentName = parent_name.str(i)
+                    if (childName in links && links[childName] !== parentName) {
+                        console.log(`${childName} linked to ${links[childName]}, ignoring link to ${parentName}`)
+                    }
+                    links[childName] = parentName
                 }
-            } else {
-                // TODO check for _item_linked.parent_name and use its type
-                console.log(`item_type not found for '${d.header}'`)
             }
+        }
+    })
 
+    Object.keys(categories).forEach(fullName => {
+        const d = categories[fullName]
+        const categoryName = d.header.substring(1, d.header.indexOf('.'))
+        const itemName = d.header.substring(d.header.indexOf('.') + 1)
+        let fields
+        if (categoryName in schema) {
+            fields = schema[categoryName]
+        } else {
+            fields = {}
+            schema[categoryName] = fields
+        }
+
+        const code = getCode(d, { categories, links })
+        if (code) {
+            fields[itemName] = getFieldType(code)
+        } else {
+            console.log(`could not determine code for '${d.header}'`)
         }
     })
 
-    return schema
+    return schema as Block.Instance<any>
 }
diff --git a/src/script.ts b/src/script.ts
index 06868a492c54fa9971cfd113a919448c5a9051f8..4f5615a1225f9a77ac08b37cb9e05e8c35f85840 100644
--- a/src/script.ts
+++ b/src/script.ts
@@ -8,9 +8,13 @@
 import * as util from 'util'
 import * as fs from 'fs'
 
+require('util.promisify').shim();
+const readFileAsync = util.promisify(fs.readFile);
+
 import Gro from './reader/gro/parser'
 import CIF from './reader/cif/index'
 
+import { apply as applySchema } from './reader/cif/schema'
 import { getSchema } from './reader/cif/schema/utils'
 
 const file = '1crn.gro'
@@ -76,13 +80,9 @@ async function runGro(input: string) {
     console.log(residueNumber.length, residueNumber[0], residueNumber[residueNumber.length - 1])
 }
 
-export function _gro() {
-    fs.readFile(`./examples/${file}`, 'utf8', function (err, input) {
-        if (err) {
-            return console.log(err);
-        }
-        runGro(input)
-    });
+export async function _gro() {
+    const input = await readFileAsync(`./examples/${file}`, 'utf8')
+    runGro(input)
 }
 
 // _gro()
@@ -108,35 +108,38 @@ async function runCIF(input: string | Uint8Array) {
     console.log(mmcif.atom_site.Cartn_x.value(0));
     console.log(mmcif.entity.type.toArray());
     console.log(mmcif.pdbx_struct_oper_list.matrix.value(0));
+
+    const schema = await _dic()
+    if (schema) {
+        const mmcif2 = applySchema(schema, data)
+        // console.log(util.inspect(mmcif2.atom_site, {showHidden: false, depth: 3}))
+        console.log(mmcif2.atom_site.Cartn_x.value(0));
+        console.log(mmcif2.entity.type.toArray());
+        // console.log(mmcif2.pdbx_struct_oper_list.matrix.value(0)); // TODO
+    } else {
+        console.log('error getting mmcif schema from dic')
+    }
 }
 
-export function _cif() {
+export async function _cif() {
     let path = `./examples/1cbs_updated.cif`;
-    path = '../test/3j3q.cif'  // lets have a relative path for big test files
-    fs.readFile(path, 'utf8', function (err, input) {
-        if (err) {
-            return console.log(err);
-        }
-        console.log('------------------');
-        console.log('Text CIF:');
-        runCIF(input);
-    });
+    // path = '../test/3j3q.cif'  // lets have a relative path for big test files
+    const input = await readFileAsync(path, 'utf8')
+    console.log('------------------');
+    console.log('Text CIF:');
+    runCIF(input);
 
     path = `./examples/1cbs_full.bcif`;
     // const path = 'c:/test/quick/3j3q.cif';
-    fs.readFile(path, function (err, input) {
-        if (err) {
-            return console.log(err);
-        }
-        console.log('------------------');
-        console.log('BinaryCIF:');
-        const data = new Uint8Array(input.byteLength);
-        for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
-        runCIF(input);
-    });
+    const input2 = await readFileAsync(path)
+    console.log('------------------');
+    console.log('BinaryCIF:');
+    const data = new Uint8Array(input2.byteLength);
+    for (let i = 0; i < input2.byteLength; i++) data[i] = input2[i];
+    runCIF(input2);
 }
 
-// _cif();
+_cif();
 
 async function runDic(input: string | Uint8Array) {
     console.time('parseDic');
@@ -151,23 +154,21 @@ async function runDic(input: string | Uint8Array) {
     }
 
     const schema = getSchema(parsed.result.blocks[0])
-    // console.log(util.inspect(schema, {showHidden: false, depth: 1}))
-    console.log(util.inspect(Object.keys(schema).length, {showHidden: false, depth: 1}))
+    console.log(util.inspect(schema, {showHidden: false, depth: 3}))
+    // console.log(util.inspect(Object.keys(schema).length, {showHidden: false, depth: 1}))
+
+    return schema
 }
 
-export function _dic() {
-    let path = '../test/mmcif_pdbx_v50.dic'
-    fs.readFile(path, 'utf8', function (err, input) {
-        if (err) {
-            return console.log(err);
-        }
-        console.log('------------------');
-        console.log('Text DIC:');
-        runDic(input);
-    });
+export async function _dic() {
+    let path = './build/dics/mmcif_pdbx_v50.dic'
+    const input = await readFileAsync(path, 'utf8')
+    console.log('------------------');
+    console.log('Text DIC:');
+    return runDic(input);
 }
 
-_dic();
+// _dic();
 
 import Computation from './utils/computation'
 const comp = Computation.create(async ctx => {