UNPKG

fast-plist

Version:
456 lines (455 loc) 13.4 kB
/*--------------------------------------------------------- * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ 'use strict'; exports.__esModule = true; exports.parse = exports.parseWithLocation = void 0; function parseWithLocation(content, filename, locationKeyName) { return _parse(content, filename, locationKeyName); } exports.parseWithLocation = parseWithLocation; /** * A very fast plist parser */ function parse(content) { return _parse(content, null, null); } exports.parse = parse; function _parse(content, filename, locationKeyName) { var len = content.length; var pos = 0; var line = 1; var char = 0; // Skip UTF8 BOM if (len > 0 && content.charCodeAt(0) === 65279 /* ChCode.BOM */) { pos = 1; } function advancePosBy(by) { if (locationKeyName === null) { pos = pos + by; } else { while (by > 0) { var chCode = content.charCodeAt(pos); if (chCode === 10 /* ChCode.LINE_FEED */) { pos++; line++; char = 0; } else { pos++; char++; } by--; } } } function advancePosTo(to) { if (locationKeyName === null) { pos = to; } else { advancePosBy(to - pos); } } function skipWhitespace() { while (pos < len) { var chCode = content.charCodeAt(pos); if (chCode !== 32 /* ChCode.SPACE */ && chCode !== 9 /* ChCode.TAB */ && chCode !== 13 /* ChCode.CARRIAGE_RETURN */ && chCode !== 10 /* ChCode.LINE_FEED */) { break; } advancePosBy(1); } } function advanceIfStartsWith(str) { if (content.substr(pos, str.length) === str) { advancePosBy(str.length); return true; } return false; } function advanceUntil(str) { var nextOccurence = content.indexOf(str, pos); if (nextOccurence !== -1) { advancePosTo(nextOccurence + str.length); } else { // EOF advancePosTo(len); } } function captureUntil(str) { var nextOccurence = content.indexOf(str, pos); if (nextOccurence !== -1) { var r = content.substring(pos, nextOccurence); advancePosTo(nextOccurence + str.length); return r; } else { // EOF var r = content.substr(pos); advancePosTo(len); return r; } } var state = 0 /* State.ROOT_STATE */; var cur = null; var stateStack = []; var objStack = []; var curKey = null; function pushState(newState, newCur) { stateStack.push(state); objStack.push(cur); state = newState; cur = newCur; } function popState() { if (stateStack.length === 0) { return fail('illegal state stack'); } state = stateStack.pop(); cur = objStack.pop(); } function fail(msg) { throw new Error('Near offset ' + pos + ': ' + msg + ' ~~~' + content.substr(pos, 50) + '~~~'); } var dictState = { enterDict: function () { if (curKey === null) { return fail('missing <key>'); } var newDict = {}; if (locationKeyName !== null) { newDict[locationKeyName] = { filename: filename, line: line, char: char }; } cur[curKey] = newDict; curKey = null; pushState(1 /* State.DICT_STATE */, newDict); }, enterArray: function () { if (curKey === null) { return fail('missing <key>'); } var newArr = []; cur[curKey] = newArr; curKey = null; pushState(2 /* State.ARR_STATE */, newArr); } }; var arrState = { enterDict: function () { var newDict = {}; if (locationKeyName !== null) { newDict[locationKeyName] = { filename: filename, line: line, char: char }; } cur.push(newDict); pushState(1 /* State.DICT_STATE */, newDict); }, enterArray: function () { var newArr = []; cur.push(newArr); pushState(2 /* State.ARR_STATE */, newArr); } }; function enterDict() { if (state === 1 /* State.DICT_STATE */) { dictState.enterDict(); } else if (state === 2 /* State.ARR_STATE */) { arrState.enterDict(); } else { // ROOT_STATE cur = {}; if (locationKeyName !== null) { cur[locationKeyName] = { filename: filename, line: line, char: char }; } pushState(1 /* State.DICT_STATE */, cur); } } function leaveDict() { if (state === 1 /* State.DICT_STATE */) { popState(); } else if (state === 2 /* State.ARR_STATE */) { return fail('unexpected </dict>'); } else { // ROOT_STATE return fail('unexpected </dict>'); } } function enterArray() { if (state === 1 /* State.DICT_STATE */) { dictState.enterArray(); } else if (state === 2 /* State.ARR_STATE */) { arrState.enterArray(); } else { // ROOT_STATE cur = []; pushState(2 /* State.ARR_STATE */, cur); } } function leaveArray() { if (state === 1 /* State.DICT_STATE */) { return fail('unexpected </array>'); } else if (state === 2 /* State.ARR_STATE */) { popState(); } else { // ROOT_STATE return fail('unexpected </array>'); } } function acceptKey(val) { if (state === 1 /* State.DICT_STATE */) { if (curKey !== null) { return fail('too many <key>'); } curKey = val; } else if (state === 2 /* State.ARR_STATE */) { return fail('unexpected <key>'); } else { // ROOT_STATE return fail('unexpected <key>'); } } function acceptString(val) { if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function acceptReal(val) { if (isNaN(val)) { return fail('cannot parse float'); } if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function acceptInteger(val) { if (isNaN(val)) { return fail('cannot parse integer'); } if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function acceptDate(val) { if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function acceptData(val) { if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function acceptBool(val) { if (state === 1 /* State.DICT_STATE */) { if (curKey === null) { return fail('missing <key>'); } cur[curKey] = val; curKey = null; } else if (state === 2 /* State.ARR_STATE */) { cur.push(val); } else { // ROOT_STATE cur = val; } } function escapeVal(str) { return str.replace(/&#([0-9]+);/g, function (_, m0) { return String.fromCodePoint(parseInt(m0, 10)); }).replace(/&#x([0-9a-f]+);/g, function (_, m0) { return String.fromCodePoint(parseInt(m0, 16)); }).replace(/&amp;|&lt;|&gt;|&quot;|&apos;/g, function (_) { switch (_) { case '&amp;': return '&'; case '&lt;': return '<'; case '&gt;': return '>'; case '&quot;': return '"'; case '&apos;': return '\''; } return _; }); } function parseOpenTag() { var r = captureUntil('>'); var isClosed = false; if (r.charCodeAt(r.length - 1) === 47 /* ChCode.SLASH */) { isClosed = true; r = r.substring(0, r.length - 1); } return { name: r.trim(), isClosed: isClosed }; } function parseTagValue(tag) { if (tag.isClosed) { return ''; } var val = captureUntil('</'); advanceUntil('>'); return escapeVal(val); } while (pos < len) { skipWhitespace(); if (pos >= len) { break; } var chCode = content.charCodeAt(pos); advancePosBy(1); if (chCode !== 60 /* ChCode.LESS_THAN */) { return fail('expected <'); } if (pos >= len) { return fail('unexpected end of input'); } var peekChCode = content.charCodeAt(pos); if (peekChCode === 63 /* ChCode.QUESTION_MARK */) { advancePosBy(1); advanceUntil('?>'); continue; } if (peekChCode === 33 /* ChCode.EXCLAMATION_MARK */) { advancePosBy(1); if (advanceIfStartsWith('--')) { advanceUntil('-->'); continue; } advanceUntil('>'); continue; } if (peekChCode === 47 /* ChCode.SLASH */) { advancePosBy(1); skipWhitespace(); if (advanceIfStartsWith('plist')) { advanceUntil('>'); continue; } if (advanceIfStartsWith('dict')) { advanceUntil('>'); leaveDict(); continue; } if (advanceIfStartsWith('array')) { advanceUntil('>'); leaveArray(); continue; } return fail('unexpected closed tag'); } var tag = parseOpenTag(); switch (tag.name) { case 'dict': enterDict(); if (tag.isClosed) { leaveDict(); } continue; case 'array': enterArray(); if (tag.isClosed) { leaveArray(); } continue; case 'key': acceptKey(parseTagValue(tag)); continue; case 'string': acceptString(parseTagValue(tag)); continue; case 'real': acceptReal(parseFloat(parseTagValue(tag))); continue; case 'integer': acceptInteger(parseInt(parseTagValue(tag), 10)); continue; case 'date': acceptDate(new Date(parseTagValue(tag))); continue; case 'data': acceptData(parseTagValue(tag)); continue; case 'true': parseTagValue(tag); acceptBool(true); continue; case 'false': parseTagValue(tag); acceptBool(false); continue; } if (/^plist/.test(tag.name)) { continue; } return fail('unexpected opened tag ' + tag.name); } return cur; }