Lutz Roeder 5 лет назад
Родитель
Сommit
4eec2f6099
14 измененных файлов с 616 добавлено и 319 удалено
  1. 1 1
      setup.py
  2. 3 13
      source/armnn.js
  3. 6 2
      source/darknet.js
  4. 4 2
      source/flatbuffers.js
  5. 1 0
      source/index.html
  6. 530 0
      source/json.js
  7. 21 243
      source/keras.js
  8. 6 21
      source/mxnet.js
  9. 6 2
      source/openvino.js
  10. 6 9
      source/tf.js
  11. 3 13
      source/tflite.js
  12. 9 6
      source/tnn.js
  13. 18 7
      source/view.js
  14. 2 0
      test/models.js

+ 1 - 1
setup.py

@@ -90,7 +90,7 @@ setuptools.setup(
     package_data={
         'netron': [ 
             'favicon.ico', 'icon.png',
-            'base.js', 'protobuf.js', 'flatbuffers.js',
+            'base.js', 'json.js', 'protobuf.js', 'flatbuffers.js',
             'numpy.js', 'pickle.js', 'hdf5.js', 'bson.js',
             'zip.js', 'tar.js', 'gzip.js',
             'armnn.js', 'armnn-metadata.json', 'armnn-schema.js',

+ 3 - 13
source/armnn.js

@@ -11,18 +11,8 @@ armnn.ModelFactory = class {
             return true;
         }
         if (extension === 'json') {
-            const contains = (buffer, text, length) => {
-                length = (length ? Math.min(buffer.length, length) : buffer.length) - text.length;
-                const match = Array.from(text).map((c) => c.charCodeAt(0));
-                for (let i = 0; i < length; i++) {
-                    if (match.every((c, index) => buffer[i + index] === c)) {
-                        return true;
-                    }
-                }
-                return false;
-            };
-            const buffer = context.buffer;
-            if (contains(buffer, '"layers"') && contains(buffer, '"layer_type"')) {
+            const tags = context.tags('json');
+            if (tags.has('layers') && tags.has('inputIds') && tags.has('outputIds')) {
                 return true;
             }
         }
@@ -43,7 +33,7 @@ armnn.ModelFactory = class {
                         break;
                     }
                     case 'json': {
-                        const reader = new flatbuffers.TextReader(context.text);
+                        const reader = new flatbuffers.TextReader(context.buffer);
                         model = armnn.schema.SerializedGraph.createText(reader);
                         break;
                     }

+ 6 - 2
source/darknet.js

@@ -31,9 +31,13 @@ darknet.ModelFactory = class {
             parts.pop();
             const basename = parts.join('.');
             return context.request(basename + '.weights', null).then((weights) => {
-                return this._openModel(metadata, identifier, context.text, weights);
+                const decoder = new TextDecoder();
+                const text = decoder.decode(context.buffer);
+                return this._openModel(metadata, identifier, text, weights);
             }).catch(() => {
-                return this._openModel(metadata, identifier, context.text, null);
+                const decoder = new TextDecoder();
+                const text = decoder.decode(context.buffer);
+                return this._openModel(metadata, identifier, text, null);
             });
         });
     }

+ 4 - 2
source/flatbuffers.js

@@ -2,6 +2,7 @@
 /* jshint esversion: 6 */
 
 var flatbuffers = {};
+var json = json || require('./json');
 
 flatbuffers.get = (name) => {
     flatbuffers._map = flatbuffers._map || new Map();
@@ -294,8 +295,9 @@ flatbuffers.Reader = class {
 
 flatbuffers.TextReader = class {
 
-    constructor(text) {
-        this._root = JSON.parse(text);
+    constructor(buffer) {
+        const reader = json.TextReader.create(buffer);
+        this._root = reader.read();
     }
 
     get root() {

+ 1 - 0
source/index.html

@@ -291,6 +291,7 @@ body { overflow: hidden; margin: 0; width: 100vw; height: 100vh; font-family: -a
 <script type="text/javascript" src="marked.min.js"></script>
 <script type="text/javascript" src="pako.min.js"></script>
 <script type="text/javascript" src="base.js"></script>
+<script type="text/javascript" src="json.js"></script>
 <script type="text/javascript" src="protobuf.js"></script>
 <script type="text/javascript" src="flatbuffers.js"></script>
 <script type="text/javascript" src="zip.js"></script>

+ 530 - 0
source/json.js

@@ -0,0 +1,530 @@
+/* jshint esversion: 6 */
+
+var json = json || {};
+
+json.TextReader = class {
+
+    constructor(buffer) {
+        this._buffer = buffer;
+    }
+
+    static create(buffer) {
+        return new json.TextReader(buffer);
+    }
+
+    read() {
+        const decoder = json.TextDecoder.create(this._buffer);
+        const stack = [];
+        this._decoder = decoder;
+        this._escape = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
+        this._position = 0;
+        this._char = decoder.decode();
+        this._whitespace();
+        let obj = undefined;
+        let first = true;
+        for (;;) {
+            if (Array.isArray(obj)) {
+                this._whitespace();
+                let c = this._char;
+                if (c === ']') {
+                    this._next();
+                    this._whitespace();
+                    if (stack.length > 0) {
+                        obj = stack.pop();
+                        first = false;
+                        continue;
+                    }
+                    if (this._char !== undefined) {
+                        this._unexpected();
+                    }
+                    return obj;
+                }
+                if (!first) {
+                    if (this._char !== ',') {
+                        this._unexpected();
+                    }
+                    this._next();
+                    this._whitespace();
+                    c = this._char;
+                }
+                first = false;
+                switch (c) {
+                    case '{': {
+                        this._next();
+                        stack.push(obj);
+                        const item = {};
+                        obj.push(item);
+                        obj = item;
+                        first = true;
+                        break;
+                    }
+                    case '[': {
+                        this._next();
+                        stack.push(obj);
+                        const item = [];
+                        obj.push(item);
+                        obj = item;
+                        first = true;
+                        break;
+                    }
+                    default: {
+                        obj.push(c === '"' ? this._string() : this._literal());
+                        break;
+                    }
+                }
+            }
+            else if (obj instanceof Object) {
+                this._whitespace();
+                let c = this._char;
+                if (c === '}') {
+                    this._next();
+                    this._whitespace();
+                    if (stack.length > 0) {
+                        obj = stack.pop();
+                        first = false;
+                        continue;
+                    }
+                    if (this._char !== undefined) {
+                        this._unexpected();
+                    }
+                    return obj;
+                }
+                if (!first) {
+                    if (this._char !== ',') {
+                        this._unexpected();
+                    }
+                    this._next();
+                    this._whitespace();
+                    c = this._char;
+                }
+                first = false;
+                if (c === '"') {
+                    const key = this._string();
+                    this._whitespace();
+                    if (this._char !== ':') {
+                        this._unexpected();
+                    }
+                    this._next();
+                    this._whitespace();
+                    c = this._char;
+                    switch (c) {
+                        case '{': {
+                            this._next();
+                            stack.push(obj);
+                            const value = {};
+                            obj[key] = value;
+                            obj = value;
+                            first = true;
+                            break;
+                        }
+                        case '[': {
+                            this._next();
+                            stack.push(obj);
+                            const value = [];
+                            obj[key] = value;
+                            obj = value;
+                            first = true;
+                            break;
+                        }
+                        default: {
+                            obj[key] = c === '"' ? this._string() : this._literal();
+                            break;
+                        }
+                    }
+                    this._whitespace();
+                    continue;
+                }
+                this._unexpected();
+            }
+            else {
+                const c = this._char;
+                switch (c) {
+                    case '{': {
+                        this._next();
+                        obj = {};
+                        first = true;
+                        break;
+                    }
+                    case '[': {
+                        this._next();
+                        obj = [];
+                        first = true;
+                        break;
+                    }
+                    default: {
+                        const value = c === '"' ? this._string() : c >= '0' && c <= '9' ? this._number() : this._literal();
+                        if (this._char !== undefined) {
+                            this._unexpected();
+                        }
+                        return value;
+                    }
+                }
+                this._whitespace();
+            }
+        }
+    }
+
+    _next() {
+        if (this._char === undefined) {
+            this._unexpected();
+        }
+        this._position = this._decoder.position;
+        this._char = this._decoder.decode();
+    }
+
+    _whitespace() {
+        while (this._char === ' ' || this._char === '\n' || this._char === '\r' || this._char === '\t') {
+            this._next();
+        }
+    }
+
+    _literal() {
+        const c = this._char;
+        if (c >= '0' && c <= '9') {
+            return this._number();
+        }
+        switch (c) {
+            case 't': this._expect('true'); return true;
+            case 'f': this._expect('false'); return false;
+            case 'n': this._expect('null'); return null;
+            case 'N': this._expect('NaN'); return NaN;
+            case 'I': this._expect('Infinity'); return Infinity;
+            case '-': return this._number();
+        }
+        this._unexpected();
+    }
+
+    _number() {
+        let value = '';
+        if (this._char === '-') {
+            value = '-';
+            this._next();
+        }
+        if (this._char === 'I') {
+            this._expect('Infinity');
+            return -Infinity;
+        }
+        const c = this._char;
+        if (c < '0' || c > '9') {
+            this._unexpected();
+        }
+        value += c;
+        this._next();
+        if (c === '0') {
+            const n = this._char;
+            if (n >= '0' && n <= '9') {
+                this._unexpected();
+            }
+        }
+        while (this._char >= '0' && this._char <= '9') {
+            value += this._char;
+            this._next();
+        }
+        if (this._char === '.') {
+            value += '.';
+            this._next();
+            const n = this._char;
+            if (n < '0' || n > '9') {
+                this._unexpected();
+            }
+            while (this._char >= '0' && this._char <= '9') {
+                value += this._char;
+                this._next();
+            }
+        }
+        if (this._char === 'e' || this._char === 'E') {
+            value += this._char;
+            this._next();
+            const s = this._char;
+            if (s === '-' || s === '+') {
+                value += this._char;
+                this._next();
+            }
+            const c = this._char;
+            if (c < '0' || c > '9') {
+                this._unexpected();
+            }
+            value += this._char;
+            this._next();
+            while (this._char >= '0' && this._char <= '9') {
+                value += this._char;
+                this._next();
+            }
+        }
+        return +value;
+    }
+
+    _string() {
+        let value = '';
+        this._next();
+        while (this._char != '"') {
+            if (this._char === '\\') {
+                this._next();
+                if (this._char === 'u') {
+                    this._next();
+                    let uffff = 0;
+                    for (let i = 0; i < 4; i ++) {
+                        const hex = parseInt(this._char, 16);
+                        if (!isFinite(hex)) {
+                            this._unexpected();
+                        }
+                        this._next();
+                        uffff = uffff * 16 + hex;
+                    }
+                    value += String.fromCharCode(uffff);
+                }
+                else if (this._escape[this._char]) {
+                    value += this._escape[this._char];
+                    this._next();
+                }
+                else {
+                    this._unexpected();
+                }
+            }
+            else if (this._char < ' ') {
+                this._unexpected();
+            }
+            else {
+                value += this._char;
+                this._next();
+            }
+        }
+        this._next();
+        return value;
+    }
+
+    _expect(text) {
+        for (let i = 0; i < text.length; i++) {
+            if (text[i] !== this._char) {
+                this._unexpected();
+            }
+            this._next();
+        }
+    }
+
+    _unexpected() {
+        let c = this._char;
+        if (c === undefined) {
+            throw new json.Error('Unexpected end of JSON input.');
+        }
+        else if (c === '"') {
+            c = 'string';
+        }
+        else if ((c >= '0' && c <= '9') || c === '-') {
+            c = 'number';
+        }
+        else {
+            if (c < ' ' || c > '\x7F') {
+                const name = Object.keys(this._escape).filter((key) => this._escape[key] === c);
+                c = (name.length === 1) ? '\\' + name : '\\u' + ('000' + c.charCodeAt(0).toString(16)).slice(-4);
+            }
+            c = "token '" + c + "'";
+        }
+        throw new json.Error('Unexpected ' + c + this._location());
+    }
+
+    _location() {
+        let line = 1;
+        let column = 1;
+        this._decoder.position = 0;
+        let c;
+        do {
+            if (this._decoder.position === this.position) {
+                return ' at ' + line.toString() + ':' + column.toString() + '.';
+            }
+            c = this._decoder.decode();
+            if (c === '\n') {
+                line++;
+                column = 0;
+            }
+            else {
+                column++;
+            }
+        }
+        while (c !== undefined);
+        return ' at ' + line.toString() + ':' + column.toString() + '.';
+    }
+};
+
+json.TextDecoder = class {
+
+    static create(buffer) {
+        const length = buffer.length;
+        if (typeof buffer === 'string') {
+            return new json.TextDecoder.String(buffer);
+        }
+        if (length >= 3 && buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
+            return new json.TextDecoder.Utf8(buffer, 3);
+        }
+        if (length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
+            return new json.TextDecoder.Utf16LE(buffer, 2);
+        }
+        if (length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
+            return new json.TextDecoder.Utf16BE(buffer, 2);
+        }
+        if (length >= 4 && buffer[0] === 0x00 && buffer[1] === 0x00 && buffer[2] === 0xfe && buffer[3] === 0xff) {
+            throw new json.Error("Unsupported UTF-32 big-endian encoding.");
+        }
+        if (length >= 4 && buffer[0] === 0xff && buffer[1] === 0xfe && buffer[2] === 0x00 && buffer[3] === 0x00) {
+            throw new json.Error("Unsupported UTF-32 little-endian encoding.");
+        }
+        if (length >= 5 && buffer[0] === 0x2B && buffer[1] === 0x2F && buffer[2] === 0x76 && buffer[3] === 0x38 && buffer[4] === 0x2D) {
+            throw new json.Error("Unsupported UTF-7 encoding.");
+        }
+        if (length >= 4 && buffer[0] === 0x2B && buffer[1] === 0x2F && buffer[2] === 0x76 && (buffer[3] === 0x38 || buffer[3] === 0x39 || buffer[3] === 0x2B || buffer[3] === 0x2F)) {
+            throw new json.Error("Unsupported UTF-7 encoding.");
+        }
+        if (length >= 4 && buffer[0] === 0x84 && buffer[1] === 0x31 && buffer[2] === 0x95 && buffer[3] === 0x33) {
+            throw new json.Error("Unsupported GB-18030 encoding.");
+        }
+        if (length > 4 && (length % 2) == 0 && (buffer[0] === 0x00 || buffer[1] === 0x00 || buffer[2] === 0x00 || buffer[3] === 0x00)) {
+            const lo = new Uint32Array(256);
+            const hi = new Uint32Array(256);
+            for (let i = 0; i < length; i += 2) {
+                lo[buffer[i]]++;
+                hi[buffer[i + 1]]++;
+            }
+            if (lo[0x00] === 0 && (hi[0x00] / (length >> 1)) > 0.5) {
+                return new json.TextDecoder.Utf16LE(buffer, 0);
+            }
+            if (hi[0x00] === 0 && (lo[0x00] / (length >> 1)) > 0.5) {
+                return new json.TextDecoder.Utf16BE(buffer, 0);
+            }
+        }
+        return new json.TextDecoder.Utf8(buffer, 0);
+    }
+};
+
+json.TextDecoder.String = class {
+
+    constructor(buffer) {
+        this.buffer = buffer;
+        this.position = 0;
+        this.length = buffer.length;
+    }
+
+    decode() {
+        return this.position < this.length ? this.buffer[this.position++] : undefined;
+    }
+};
+
+json.TextDecoder.Utf8 = class {
+
+    constructor(buffer, position) {
+        this.position = position || 0;
+        this.buffer = buffer;
+    }
+
+    decode() {
+        const c = this.buffer[this.position];
+        if (c === undefined) {
+            return c;
+        }
+        this.position++;
+        if (c < 0x80) {
+            return String.fromCodePoint(c);
+        }
+        if (c >= 0xC2 && c <= 0xDF) {
+            if (this.buffer[this.position] !== undefined) {
+                const c2 = this.buffer[this.position];
+                this.position++;
+                return String.fromCharCode(((c & 0x1F) << 6) | (c2 & 0x3F));
+            }
+        }
+        if (c >= 0xE0 && c <= 0xEF) {
+            if (this.buffer[this.position + 1] !== undefined) {
+                const c2 = this.buffer[this.position];
+                if ((c !== 0xE0 || c2 >= 0xA0) && (c !== 0xED || c2 <= 0x9f)) {
+                    const c3 = this.buffer[this.position + 1];
+                    if (c3 >= 0x80 && c3 < 0xFB) {
+                        this.position += 2;
+                        return String.fromCharCode(((c & 0x0F) << 12) | ((c2 & 0x3F) << 6) | ((c3 & 0x3F) << 0));
+                    }
+                }
+            }
+        }
+        if (c >= 0xF0 && c <= 0xF4) {
+            if (this.buffer[this.position + 2] !== undefined) {
+                const c2 = this.buffer[this.position];
+                if ((c !== 0xF0 || c2 >= 0x90) && (c !== 0xF4 || c2 <= 0x8f)) {
+                    const c3 = this.buffer[this.position + 1];
+                    if (c3 >= 0x80 && c3 < 0xFB) {
+                        const c4 = this.buffer[this.position + 2];
+                        this.position += 3;
+                        return String.fromCodePoint(((c & 0x07) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F));
+                    }
+                }
+            }
+        }
+        return String.fromCharCode(0xfffd);
+    }
+};
+
+json.TextDecoder.Utf16LE = class {
+
+    constructor(buffer, position) {
+        this.buffer = buffer;
+        this.position = position || 0;
+        this.length = buffer.length;
+    }
+
+    decode() {
+        if (this.position + 1 < this.length) {
+            const c = this.buffer[this.position++] | (this.buffer[this.position++] << 8);
+            if ((c & 0xF800) !== 0xD800) {
+                return String.fromCharCode(c);
+            }
+            if ((c & 0xFC00) !== 0xD800) {
+                if (this._position + 1 < this._length) {
+                    const c2 = this._buffer[this._position++] | (this._buffer[this._position++] << 8);
+                    if ((c2 & 0xFC00) === 0xDC00) {
+                        return String.fromCodePoint(((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000);
+                    }
+                }
+            }
+            return String.fromCharCode(0xfffd);
+        }
+        return undefined;
+    }
+};
+
+json.TextDecoder.Utf16BE = class {
+
+    constructor(buffer, position) {
+        this.buffer = buffer;
+        this.position = position || 0;
+        this.length = buffer.length;
+    }
+
+    decode() {
+        if (this.position + 1 < this.length) {
+            const c = (this.buffer[this.position++] << 8) | this.buffer[this.position++];
+            if ((c & 0xF800) !== 0xD800) {
+                return String.fromCharCode(c);
+            }
+            if ((c & 0xFC00) !== 0xD800) {
+                if (this._position + 1 < this._length) {
+                    const c2 = (this._buffer[this._position++] << 8) | this._buffer[this._position++];
+                    if ((c2 & 0xFC00) === 0xDC00) {
+                        return String.fromCodePoint(((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000);
+                    }
+                }
+            }
+            return String.fromCharCode(0xfffd);
+        }
+        return undefined;
+    }
+};
+
+json.Error = class extends Error {
+
+    constructor(message) {
+        super(message);
+        this.name = 'JSON Error';
+    }
+};
+
+if (typeof module !== 'undefined' && typeof module.exports === 'object') {
+    module.exports.TextReader = json.TextReader;
+    module.exports.TextDecoder = json.TextDecoder;
+}

+ 21 - 243
source/keras.js

@@ -1,6 +1,7 @@
 /* jshint esversion: 6 */
 
 var keras = keras || {};
+var json = json || require('./json');
 
 keras.ModelFactory = class {
 
@@ -13,38 +14,21 @@ keras.ModelFactory = class {
             return buffer && buffer.length > signature.length && signature.every((v, i) => v === buffer[i]);
         }
         if (extension == 'json' && !identifier.endsWith('-symbol.json')) {
-            const contains = (buffer, text, length) => {
-                length = (length ? Math.min(buffer.length, length) : buffer.length) - text.length;
-                const match = Array.from(text).map((c) => c.charCodeAt(0));
-                for (let i = 0; i < length; i++) {
-                    if (match.every((c, index) => buffer[i + index] === c)) {
-                        return true;
-                    }
-                }
+            const tags = context.tags('json');
+            if (tags.has('mxnet_version')) {
                 return false;
-            };
-            if (!contains(context.buffer, '"mxnet_version":')) {
-                try {
-                    let root = keras.JsonParser.parse(context.text);
-                    if (root && root.nodes && root.arg_nodes && root.heads) {
-                        return false;
-                    }
-                    if (root && root.modelTopology) {
-                        root = root.modelTopology;
-                    }
-                    if (root && root.model_config) {
-                        root = root.model_config;
-                    }
-                    if (root && root.class_name) {
-                        return true;
-                    }
-                    if (root && Array.isArray(root) && root.every((manifest) => Array.isArray(manifest.weights) && Array.isArray(manifest.paths))) {
-                        return true;
-                    }
-                }
-                catch (err) {
-                    // continue regardless of error
-                }
+            }
+            if (tags.has('nodes') && tags.has('arg_nodes') && tags.has('heads')) {
+                return false;
+            }
+            if (tags.has('modelTopology')) {
+                return true;
+            }
+            if (tags.has('model_config') || (tags.has('class_name') && tags.has('config'))) {
+                return true;
+            }
+            if (tags.has('[].weights') && tags.has('[].paths')) {
+                return true;
             }
         }
         return false;
@@ -71,9 +55,10 @@ keras.ModelFactory = class {
                         const file = new hdf5.File(context.buffer);
                         rootGroup = file.rootGroup;
                         if (rootGroup.attribute('model_config') || rootGroup.attribute('layer_names')) {
-                            const json = rootGroup.attribute('model_config');
-                            if (json) {
-                                model_config = keras.JsonParser.parse(json);
+                            const model_config_json = rootGroup.attribute('model_config');
+                            if (model_config_json) {
+                                const reader = json.TextReader.create(model_config_json);
+                                model_config = reader.read();
                             }
                             backend = rootGroup.attribute('backend') || '';
                             const version = rootGroup.attribute('keras_version') || '';
@@ -191,7 +176,8 @@ keras.ModelFactory = class {
                         break;
                     }
                     case 'json': {
-                        const root = keras.JsonParser.parse(context.text);
+                        const reader = json.TextReader.create(context.buffer);
+                        const root = reader.read();
                         if (root && Array.isArray(root) && root.every((manifest) => Array.isArray(manifest.weights) && Array.isArray(manifest.paths))) {
                             format = 'TensorFlow.js Weights';
                             rootGroup = {};
@@ -1162,214 +1148,6 @@ keras.Group = class {
     }
 };
 
-keras.JsonParser = class {
-
-    static parse(text) {
-        if (text && (text.indexOf('NaN') !== -1 || text.indexOf('Infinity') !== -1)) {
-            try {
-                return JSON.parse(text);
-            }
-            catch (err) {
-                try {
-                    return new keras.JsonParser(text)._read();
-                }
-                catch (err) {
-                    // continue regardless of error
-                }
-            }
-        }
-        return JSON.parse(text);
-    }
-
-    constructor(text) {
-        this._text = text;
-        this._position = 0;
-        this._ch = ' ';
-        this._escape = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
-    }
-
-    _read() {
-        const result = this._value();
-        this._whitespace();
-        if (this._ch) {
-            this._error("Syntax error");
-        }
-        return result;
-    }
-
-    _next() {
-        return this._ch = this._text.charAt(this._position++);
-    }
-
-    _expect(text) {
-        for (let i = 0; i < text.length; i++) {
-            if (text[i] !== this._ch) {
-                this._error("Expected '" + text[i] + "' instead of '" + this._ch + "'");
-            }
-            this._ch = this._text.charAt(this._position++);
-        }
-    }
-
-    _whitespace() {
-        while (this._ch && this._ch <= ' ') {
-            this._next();
-        }
-    }
-
-    _number() {
-        let value = '';
-        if (this._ch === '-') {
-            value = '-';
-            this._expect('-');
-        }
-        if (this._ch === 'I') {
-            this._expect('Infinity');
-            return -Infinity;
-        }
-        while (this._ch >= '0' && this._ch <= '9') {
-            value += this._ch;
-            this._next();
-        }
-        if (this._ch === '.') {
-            value += '.';
-            while (this._next() && this._ch >= '0' && this._ch <= '9') {
-                value += this._ch;
-            }
-        }
-        if (this._ch === 'e' || this._ch === 'E') {
-            value += this._ch;
-            this._next();
-            if (this._ch === '-' || this._ch === '+') {
-                value += this._ch;
-                this._next();
-            }
-            while (this._ch >= '0' && this._ch <= '9') {
-                value += this._ch;
-                this._next();
-            }
-        }
-        return +value;
-    }
-
-    _string() {
-        let hex;
-        let i;
-        let value = '';
-        let uffff;
-        if (this._ch === '"') {
-            while (this._next()) {
-                if (this._ch === '"') {
-                    this._next();
-                    return value;
-                }
-                if (this._ch === '\\') {
-                    this._next();
-                    if (this._ch === 'u') {
-                        uffff = 0;
-                        for (i = 0; i < 4; i ++) {
-                            hex = parseInt(this._next(), 16);
-                            if (!isFinite(hex)) {
-                                break;
-                            }
-                            uffff = uffff * 16 + hex;
-                        }
-                        value += String.fromCharCode(uffff);
-                    }
-                    else if (this._escape[this._ch]) {
-                        value += this._escape[this._ch];
-                    }
-                    else {
-                        break;
-                    }
-                }
-                else {
-                    value += this._ch;
-                }
-            }
-        }
-        this._error("Invalid string");
-    }
-
-    _literal() {
-        switch (this._ch) {
-            case 't': this._expect('true'); return true;
-            case 'f': this._expect('false'); return false;
-            case 'n': this._expect('null'); return null;
-            case 'N': this._expect('NaN'); return NaN;
-            case 'I': this._expect('Infinity'); return Infinity;
-        }
-        this._error("Unexpected '" + this._ch + "'");
-    }
-
-    _array() {
-        const arr = [];
-        if (this._ch === '[') {
-            this._expect('[');
-            this._whitespace();
-            if (this._ch === ']') {
-                this._expect(']');
-                return arr;
-            }
-            while (this._ch) {
-                arr.push(this._value());
-                this._whitespace();
-                if (this._ch === ']') {
-                    this._expect(']');
-                    return arr;
-                }
-                this._expect(',');
-                this._whitespace();
-            }
-        }
-        this._error("Invalid array");
-    }
-
-    _object() {
-        let key;
-        const obj = {};
-        if (this._ch === '{') {
-            this._expect('{');
-            this._whitespace();
-            if (this._ch === '}') {
-                this._expect('}');
-                return obj;   // empty object
-            }
-            while (this._ch) {
-                key = this._string();
-                this._whitespace();
-                this._expect(':');
-                if (Object.prototype.hasOwnProperty.call(obj, key)) {
-                    this._error('Duplicate key "' + key + '"');
-                }
-                obj[key] = this._value();
-                this._whitespace();
-                if (this._ch === '}') {
-                    this._expect('}');
-                    return obj;
-                }
-                this._expect(',');
-                this._whitespace();
-            }
-        }
-        this._error("Invalid object");
-    }
-
-    _value() {
-        this._whitespace();
-        switch (this._ch) {
-            case '{': return this._object();
-            case '[': return this._array();
-            case '"': return this._string();
-            case '-': return this._number();
-            default:  return this._ch >= '0' && this._ch <= '9' ? this._number() : this._literal();
-        }
-    }
-
-    _error(message) {
-        throw new Error(message + ' at ' + this._position + '.');
-    }
-};
-
 keras.Weights = class {
 
     constructor() {

+ 6 - 21
source/mxnet.js

@@ -1,6 +1,7 @@
 /* jshint esversion: 6 */
 
 var mxnet = mxnet || {};
+var json = json || require('./json');
 var zip = zip || require('./zip');
 var ndarray = ndarray || {};
 
@@ -15,26 +16,9 @@ mxnet.ModelFactory = class {
             }
         }
         else if (extension == 'json') {
-            const contains = (buffer, text, length) => {
-                length = (length ? Math.min(buffer.length, length) : buffer.length) - text.length;
-                const match = Array.from(text).map((c) => c.charCodeAt(0));
-                for (let i = 0; i < length; i++) {
-                    if (match.every((c, index) => buffer[i + index] === c)) {
-                        return true;
-                    }
-                }
-                return false;
-            };
-            if (contains(context.buffer, '"nodes":')) {
-                try {
-                    const symbol = JSON.parse(context.text);
-                    if (symbol && symbol.nodes && symbol.arg_nodes && symbol.heads) {
-                        return true;
-                    }
-                }
-                catch (err) {
-                    // continue regardless of error
-                }
+            const tags = context.tags('json');
+            if (tags.has('nodes') && tags.has('arg_nodes') && tags.has('heads')) {
+                return true;
             }
         }
         else if (extension == 'params') {
@@ -57,7 +41,8 @@ mxnet.ModelFactory = class {
         switch (extension) {
             case 'json':
                 try {
-                    symbol = JSON.parse(context.text);
+                    const reader = json.TextReader.create(context.buffer);
+                    symbol = reader.read();
                     if (symbol && symbol.nodes && symbol.nodes.some((node) => node && node.op == 'tvm_op')) {
                         format  = 'TVM';
                     }

+ 6 - 2
source/openvino.js

@@ -52,9 +52,13 @@ openvino.ModelFactory = class {
         switch (extension) {
             case 'xml':
                 return context.request(identifier.substring(0, identifier.length - 4) + '.bin', null).then((bin) => {
-                    return this._openModel(identifier, host, context.text, bin);
+                    const decoder = new TextDecoder('utf-8');
+                    const xml = decoder.decode(context.buffer);
+                    return this._openModel(identifier, host, xml, bin);
                 }).catch(() => {
-                    return this._openModel(identifier, host, context.text, null);
+                    const decoder = new TextDecoder('utf-8');
+                    const xml = decoder.decode(context.buffer);
+                    return this._openModel(identifier, host, xml, null);
                 });
             case 'bin':
                 return context.request(identifier.substring(0, identifier.length - 4) + '.xml', 'utf-8').then((xml) => {

+ 6 - 9
source/tf.js

@@ -4,6 +4,7 @@
 
 var tf = tf || {};
 var base = base || require('./base');
+var json = json || require('./json');
 var protobuf = protobuf || require('./protobuf');
 
 tf.ModelFactory = class {
@@ -63,14 +64,9 @@ tf.ModelFactory = class {
             }
         }
         if (extension === 'json') {
-            try {
-                const root = JSON.parse(context.text);
-                if (root && root.format && root.format === 'graph-model' && root.modelTopology) {
-                    return true;
-                }
-            }
-            catch (err) {
-                // continue regardless of error
+            const tags = context.tags('json');
+            if (tags.has('format') && tags.get('format') === 'graph-model' && tags.has('modelTopology')) {
+                return true;
             }
         }
         if (extension === 'index' || extension === 'ckpt') {
@@ -100,7 +96,8 @@ tf.ModelFactory = class {
                 }
                 case 'json': {
                     try {
-                        const root = JSON.parse(context.text);
+                        const reader = json.TextReader.create(context.buffer);
+                        const root = reader.read();
                         const graph_def = new tf.proto.GraphDef();
                         const meta_graph = new tf.proto.MetaGraphDef();
                         meta_graph.graph_def = graph_def;

+ 3 - 13
source/tflite.js

@@ -15,18 +15,8 @@ tflite.ModelFactory = class {
             }
         }
         if (extension === 'json') {
-            const contains = (buffer, text, length) => {
-                length = (length ? Math.min(buffer.length, length) : buffer.length) - text.length;
-                const match = Array.from(text).map((c) => c.charCodeAt(0));
-                for (let i = 0; i < length; i++) {
-                    if (match.every((c, index) => buffer[i + index] === c)) {
-                        return true;
-                    }
-                }
-                return false;
-            };
-            const buffer = context.buffer;
-            if (contains(buffer, '"subgraphs"') && contains(buffer, '"operator_codes"')) {
+            const tags = context.tags('json');
+            if (tags.has('subgraphs') && tags.has('operator_codes')) {
                 return true;
             }
         }
@@ -50,7 +40,7 @@ tflite.ModelFactory = class {
                             return new tflite.Model(metadata, model);
                         }
                         case 'json': {
-                            const reader = new flatbuffers.TextReader(context.text);
+                            const reader = new flatbuffers.TextReader(context.buffer);
                             const model = tflite.schema.Model.createText(reader);
                             return new tflite.Model(metadata, model);
                         }

+ 9 - 6
source/tnn.js

@@ -7,7 +7,8 @@ tnn.ModelFactory = class {
     match(context) {
         const identifier = context.identifier.toLowerCase();
         if (identifier.endsWith('.tnnproto')) {
-            let text = context.text;
+            const decoder = new TextDecoder('utf-8');
+            let text = decoder.decode(context.buffer);
             text = text.substring(0, Math.min(text.length, 128));
             const line = text.split('\n').shift().trim();
             if (line.startsWith('"') && line.endsWith('"')) {
@@ -35,9 +36,9 @@ tnn.ModelFactory = class {
             if (identifier.endsWith('.tnnproto')) {
                 const tnnmodel = context.identifier.substring(0, context.identifier.length - 9) + '.tnnmodel';
                 return context.request(tnnmodel, null).then((tnnmodel) => {
-                    return new tnn.Model(metadata, context.text, tnnmodel);
+                    return new tnn.Model(metadata, context.buffer, tnnmodel);
                 }).catch(() => {
-                    return new tnn.Model(metadata, context.text, null);
+                    return new tnn.Model(metadata, context.buffer, null);
                 }).catch((error) => {
                     const message = error && error.message ? error.message : error.toString();
                     throw new tnn.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
@@ -45,8 +46,8 @@ tnn.ModelFactory = class {
             }
             else if (identifier.endsWith('.tnnmodel')) {
                 const tnnproto = context.identifier.substring(0, context.identifier.length - 9) + '.tnnproto';
-                return context.request(tnnproto, 'utf-8').then((text) => {
-                    return new tnn.Model(metadata, text, context.buffer);
+                return context.request(tnnproto, null).then((buffer) => {
+                    return new tnn.Model(metadata, buffer, context.buffer);
                 }).catch((error) => {
                     const message = error && error.message ? error.message : error.toString();
                     throw new tnn.Error(message.replace(/\.$/, '') + " in '" + identifier + "'.");
@@ -626,7 +627,9 @@ tnn.Metadata = class {
 
 tnn.TextProtoReader = class {
 
-    constructor(text) {
+    constructor(buffer) {
+        const decoder = new TextDecoder();
+        const text = decoder.decode(buffer);
         const split = (line, delimiter, trim, ignore_blank) => {
             return line.split(delimiter).map((v) => trim ? v.trim() : v).filter((v) => !ignore_blank || v);
         };

+ 18 - 7
source/view.js

@@ -6,6 +6,7 @@ var base = base || require('./base');
 var zip = zip || require('./zip');
 var gzip = gzip || require('./gzip');
 var tar = tar || require('./tar');
+var json = json || require('./json');
 var protobuf = protobuf || require('./protobuf');
 
 var d3 = d3 || require('d3');
@@ -1032,13 +1033,6 @@ class ModelContext {
         return this._context.buffer;
     }
 
-    get text() {
-        if (!this._text) {
-            this._text = new TextDecoder('utf-8', { fatal: true }).decode(this.buffer);
-        }
-        return this._text;
-    }
-
     entries(extension) {
         let entries = this._entries.get(extension);
         if (!entries) {
@@ -1128,6 +1122,23 @@ class ModelContext {
                         }
                         break;
                     }
+                    case 'json': {
+                        const reader = json.TextReader.create(this.buffer);
+                        const obj = reader.read();
+                        if (!Array.isArray(obj)) {
+                            for (const key in obj) {
+                                const value = obj[key];
+                                tags.set(key, value !== Object(value) ? value : true);
+                            }
+                        }
+                        else {
+                            for (const item of obj) {
+                                for (const key in item) {
+                                    tags.set('[].' + key, true);
+                                }
+                            }
+                        }
+                    }
                 }
             }
             catch (error) {

+ 2 - 0
test/models.js

@@ -10,6 +10,7 @@ const child_process = require('child_process');
 const http = require('http');
 const https = require('https');
 const url = require('url');
+const json = require('../source/json');
 const protobuf = require('../source/protobuf');
 const flatbuffers = require('../source/flatbuffers');
 const sidebar = require('../source/view-sidebar.js');
@@ -22,6 +23,7 @@ const xmldom = require('xmldom');
 
 global.Int64 = base.Int64;
 global.Uint64 = base.Uint64;
+global.json = json;
 global.protobuf = protobuf;
 global.flatbuffers = flatbuffers;
 global.DOMParser = xmldom.DOMParser;