' + g.html +
+ const g = grid(s.rows, s.cols);
+ html += '
' + (s.note ? '
' + esc(s.note) + '
' : '') + g.html +
(g.cut ? '
показаны первые ' + MAX_ROWS + ' строк и ' + MAX_COLS + ' столбцов
' : '') + '
';
});
return html;
@@ -387,5 +387,142 @@
text.split(/\r\n|\r|\n|\x07/).filter(l => l.trim()).map(l => '
' + esc(l) + '
').join('') + '
';
}
- window.DocView = {docx, xlsx, ods, odt, csv, doc, bindTabs};
+ // ---------- sqlite ----------
+ // Reads the database file itself: the b-tree of sqlite_master for the
+ // tables, then each table's b-tree for its first rows. No SQL engine.
+ function sqlite(buf) {
+ const b = new Uint8Array(buf);
+ if (new TextDecoder().decode(b.subarray(0, 15)) !== 'SQLite format 3') throw new Error('не база SQLite');
+ const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
+ let ps = dv.getUint16(16);
+ if (ps === 1) ps = 65536;
+ const usable = ps - b[20];
+ const enc = dv.getUint32(56);
+ const dec = new TextDecoder(enc === 2 ? 'utf-16le' : enc === 3 ? 'utf-16be' : 'utf-8');
+ const page = n => (n - 1) * ps;
+
+ function varint(o) {
+ let v = 0;
+ for (let i = 0; i < 9; i++) {
+ const c = b[o + i];
+ if (i === 8) return [v * 256 + c, o + 9];
+ v = v * 128 + (c & 0x7f);
+ if (!(c & 0x80)) return [v, o + i + 1];
+ }
+ }
+ // A cell's payload, gathered from its overflow pages when it spills.
+ function payload(o, size) {
+ const max = usable - 35;
+ if (size <= max) return b.subarray(o, o + size);
+ const min = Math.floor((usable - 12) * 32 / 255) - 23;
+ let local = min + (size - min) % (usable - 4);
+ if (local > max) local = min;
+ const out = new Uint8Array(size);
+ out.set(b.subarray(o, o + local));
+ let at = local, next = dv.getUint32(o + local);
+ for (let guard = 0; next && at < size && guard < 1e6; guard++) {
+ const po = page(next), len = Math.min(usable - 4, size - at);
+ out.set(b.subarray(po + 4, po + 4 + len), at);
+ at += len;
+ next = dv.getUint32(po);
+ }
+ return out;
+ }
+ function record(p) {
+ const pv = new DataView(p.buffer, p.byteOffset, p.byteLength);
+ let [hlen, o] = vi(p, 0);
+ const types = [];
+ while (o < hlen) { const r = vi(p, o); types.push(r[0]); o = r[1]; }
+ let d = hlen;
+ return types.map(t => {
+ let v = null;
+ if (t >= 1 && t <= 6) {
+ const n = [0, 1, 2, 3, 4, 6, 8][t];
+ let x = 0;
+ for (let i = 0; i < n; i++) x = x * 256 + p[d + i];
+ if (p[d] & 0x80) x -= Math.pow(2, 8 * n);
+ v = n === 8 && Math.abs(x) > Number.MAX_SAFE_INTEGER ? pv.getBigInt64(d).toString() : x;
+ d += n;
+ } else if (t === 7) { v = pv.getFloat64(d); d += 8; }
+ else if (t === 8) v = 0;
+ else if (t === 9) v = 1;
+ else if (t >= 12 && t % 2 === 0) { const n = (t - 12) / 2; v = '[blob ' + n + ' B]'; d += n; }
+ else if (t >= 13) { const n = (t - 13) / 2; v = dec.decode(p.subarray(d, d + n)); d += n; }
+ return v;
+ });
+ }
+ // varint over a payload buffer rather than the file
+ function vi(p, o) {
+ let v = 0;
+ for (let i = 0; i < 9; i++) {
+ const c = p[o + i];
+ if (i === 8) return [v * 256 + c, o + 9];
+ v = v * 128 + (c & 0x7f);
+ if (!(c & 0x80)) return [v, o + i + 1];
+ }
+ }
+ // Walks a table b-tree in rowid order, up to limit rows; also counts all.
+ function table(root, limit) {
+ const rows = [];
+ let count = 0;
+ const walk = (n, depth) => {
+ if (depth > 40) return;
+ const h = page(n) + (n === 1 ? 100 : 0);
+ const type = b[h], cells = dv.getUint16(h + 3);
+ const ptrs = h + (type === 0x05 ? 12 : 8);
+ if (type === 0x0d) {
+ count += cells;
+ for (let i = 0; i < cells && rows.length < limit; i++) {
+ let o = page(n) + dv.getUint16(ptrs + i * 2), size, rowid;
+ [size, o] = varint(o);
+ [rowid, o] = varint(o);
+ rows.push({rowid, values: record(payload(o, size))});
+ }
+ } else if (type === 0x05) {
+ for (let i = 0; i < cells; i++) walk(dv.getUint32(page(n) + dv.getUint16(ptrs + i * 2)), depth + 1);
+ walk(dv.getUint32(h + 8), depth + 1);
+ }
+ };
+ walk(root, 0);
+ return {rows, count};
+ }
+ // Column names out of CREATE TABLE; an INTEGER PRIMARY KEY is the rowid.
+ function columns(sql) {
+ const m = /\(([\s\S]*)\)/.exec(sql || '');
+ if (!m) return {names: [], rowidCol: -1};
+ const parts = [];
+ let depth = 0, cur = '';
+ for (const c of m[1]) {
+ if (c === '(') depth++;
+ if (c === ')') depth--;
+ if (c === ',' && !depth) { parts.push(cur); cur = ''; } else cur += c;
+ }
+ parts.push(cur);
+ const names = [];
+ let rowidCol = -1;
+ for (const p of parts) {
+ const t = p.trim();
+ if (!t || /^(constraint|primary\s+key|unique|check|foreign\s+key)\b/i.test(t)) continue;
+ const nm = /^("(?:[^"]|"")+"|`[^`]+`|\[[^\]]+\]|\S+)/.exec(t)[1].replace(/^["`\[]|["`\]]$/g, '');
+ if (/^\S+\s+integer\s+primary\s+key\b/i.test(t.replace(/^("(?:[^"]|"")+"|`[^`]+`|\[[^\]]+\])/, 'x'))) rowidCol = names.length;
+ names.push(nm);
+ }
+ return {names, rowidCol};
+ }
+
+ const master = table(1, 100000).rows.map(r => ({type: r.values[0], name: r.values[1], root: r.values[3], sql: r.values[4] || ''}));
+ const list = [];
+ for (const t of master.filter(x => x.type === 'table' && x.root).sort((a, c) => String(a.name).localeCompare(String(c.name)))) {
+ if (/without\s+rowid/i.test(t.sql)) { list.push({name: t.name, rows: [], note: 'таблица WITHOUT ROWID - её строки здесь не показываются'}); continue; }
+ const {names, rowidCol} = columns(t.sql);
+ let data;
+ try { data = table(t.root, MAX_ROWS); } catch (e) { list.push({name: t.name, rows: [], note: 'не удалось прочитать: ' + e.message}); continue; }
+ const rows = data.rows.map(r => { const v = r.values.slice(); if (rowidCol >= 0 && v[rowidCol] == null) v[rowidCol] = r.rowid; return v; });
+ list.push({name: t.name, cols: names, rows, note: data.count + ' ' + (data.count % 10 === 1 && data.count % 100 !== 11 ? 'строка' : data.count % 10 >= 2 && data.count % 10 <= 4 && (data.count % 100 < 12 || data.count % 100 > 14) ? 'строки' : 'строк') + (data.count > rows.length ? ', показаны первые ' + rows.length : '')});
+ }
+ if (!list.length) return '