fast-plist
Version:
A fast PLIST parser
456 lines (455 loc) • 13.4 kB
JavaScript
/*---------------------------------------------------------
* 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(/&|<|>|"|'/g, function (_) {
switch (_) {
case '&': return '&';
case '<': return '<';
case '>': return '>';
case '"': return '"';
case ''': 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;
}