/
parseMapfile.ts
157 lines (138 loc) · 4.63 KB
/
parseMapfile.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
import { checkComment } from './parse/checkComment';
import { checkKeyValue } from './parse/checkKeyValue';
import { checkBlockKey } from './parse/checkBlockKey';
import { parseBlockKey } from './parse/parseBlockKey';
import { checkBlockEndSum } from './parse/checkBlockEndSum';
import { determineDepth } from './parse/determineDepth';
import { resolveSymbolset } from './parse/resolveSymbolset';
import { Mapfile, MapfileSymbolset } from './mapfileTypes';
// some blocks are actually a key value pair
const pseudoBlockKeys = ['projection', 'pattern', 'points'];
// some keys are reused to specify an array of values
const listKeys = ['formatoption', 'include', 'processing'];
/**
* Object representation of a Mapfile line.
*/
export interface LineObject {
content: string;
comment: string;
contentWithoutComment: string;
key: string;
value: string;
isBlockKey: boolean;
isBlockLine: boolean;
depth: number;
}
/**
* Parses a Mapfile line into a JavaScript object.
* @param {string} line Content of a Mapfile
* @returns {object} the parsed object
*/
function parseLine(line: string): LineObject {
let lineObject: LineObject = { content: line } as any;
// check included comments
lineObject = Object.assign(lineObject, checkComment(lineObject));
// check key value
lineObject = Object.assign(lineObject, checkKeyValue(lineObject));
// check block key
lineObject = Object.assign(lineObject, checkBlockKey(lineObject));
return lineObject;
}
/**
* Parses the Mapfile content into a JavaScript object.
* @param {string} content Content of a Mapfile
* @returns {object} the parsed object
*/
function parseContent(content: string): object {
const result = {};
const lineObjects: Array<LineObject> = [];
// stack to keep track of blocks
const blocks: Array<any> = [result];
let pseudoBlockKey: any;
// split content into trimmed lines like Van Damme
const lines = content.split(/\s*(?:\r\n?|\n)\s*/g);
// iterate over lines
lines.forEach((line, index) => {
// ommit empty lines and comments
if (line === '' || line.startsWith('#')) {
return;
}
// line object
const lineObject = parseLine(line);
// store lineobjects
lineObjects.push(lineObject);
// current block
const currentBlock = blocks[blocks.length - 1];
// handle pseudo blocks
if (pseudoBlockKeys.includes(lineObject.key)) {
if (lineObject.value) {
currentBlock[lineObject.key] = lineObject.value.replace(/\s*END$/i, '');
if (lineObject.value.match(/\s*END$/i)) {
return;
}
}
pseudoBlockKey = lineObject.key;
return;
}
if (pseudoBlockKey && lineObject.key !== 'end') {
const value = lineObject.contentWithoutComment.replace(/"/g, '');
if (currentBlock[pseudoBlockKey]) {
currentBlock[pseudoBlockKey] = `${currentBlock[pseudoBlockKey]} ${value}`;
} else {
currentBlock[pseudoBlockKey] = value;
}
return;
}
// handle block & list keys
if (lineObject.isBlockKey || listKeys.includes(lineObject.key)) {
const newBlock = parseBlockKey(lineObject, currentBlock);
if (newBlock) {
blocks.push(newBlock as any);
}
return;
}
// handle block end
if (lineObject.key === 'end') {
if (pseudoBlockKey) {
pseudoBlockKey = undefined;
} else {
blocks.pop();
}
return;
}
// insert key value pair
if (lineObject.key in currentBlock) {
console.warn(`Duplicate key on line [${index + 1}]: ${lineObject.content}`);
console.error('Overwriting existing key! consider an array!');
}
currentBlock[lineObject.key] = lineObject.value;
});
// basic syntax checks
checkBlockEndSum(lineObjects);
determineDepth(lineObjects);
return result;
}
/**
* Parses a MapServer Mapfile to a JavaScript object.
* @param {string} content Content of a MapServer Mapfile
* @returns {Mapfile} the parsed Mapfile
*/
export function parseMapfile(content: string): Mapfile {
let result = parseContent(content);
// add map bock for consistency if not exists
result = 'map' in result ? result : { map: result };
// resolve symbolset
const mapfile = resolveSymbolset(result as Mapfile);
return mapfile;
}
/**
* Parses a MapServer Symbolsetfile to a JavaScript object.
* @param {string} content Content of a MapServer Mapfile
* @returns {MapfileSymbolset} the parsed Symbolset
*/
export function parseSymbolset(content: string): MapfileSymbolset {
const result: any = parseContent(content);
// A Mapfile symbolset begins with SYMBOLSET and ends with END
console.assert('symbolset' in result);
return result.symbolset as MapfileSymbolset;
}