From f505bcd80bec3880afae5b76cc11c9991179b36b Mon Sep 17 00:00:00 2001 From: Balthasar Teuscher Date: Wed, 20 May 2020 18:10:29 +0200 Subject: [PATCH] Proper pseudo block handling --- src/MapfileStyleParser.ts | 6 +- src/mapfile2js/mapfileTypes.ts | 9 +- src/mapfile2js/parse/checkBlockEndSum.ts | 2 +- src/mapfile2js/parse/checkBlockKey.ts | 2 +- src/mapfile2js/parse/checkComment.ts | 2 +- src/mapfile2js/parse/checkKeyValue.spec.ts | 2 +- src/mapfile2js/parse/checkKeyValue.ts | 2 +- src/mapfile2js/parse/determineDepth.ts | 2 +- src/mapfile2js/parse/parseBlockKey.ts | 34 +++---- src/mapfile2js/parse/resolveSymbolset.ts | 2 +- src/mapfile2js/parseMapfile.spec.ts | 15 +++ src/mapfile2js/{parse.ts => parseMapfile.ts} | 98 +++++++++++++------- 12 files changed, 108 insertions(+), 68 deletions(-) create mode 100644 src/mapfile2js/parseMapfile.spec.ts rename src/mapfile2js/{parse.ts => parseMapfile.ts} (58%) diff --git a/src/MapfileStyleParser.ts b/src/MapfileStyleParser.ts index c69d88a..f8bb15f 100644 --- a/src/MapfileStyleParser.ts +++ b/src/MapfileStyleParser.ts @@ -1,4 +1,4 @@ -import { parseMapfile } from './mapfile2js/parse'; +import { parseMapfile } from './mapfile2js/parseMapfile'; import { rgbToHex } from './Useful'; import { StyleParser, @@ -377,8 +377,8 @@ export class MapfileStyleParser implements StyleParser { const symbolType = mapfileStyle.symbol.type.toLowerCase(); switch (symbolType) { case 'ellipse': { - const xy = mapfileStyle.symbol.points[0].split(' '); - if (xy[0] === xy[1]) { + const xy = mapfileStyle.symbol.points.split(' '); + if (xy[0] === xy[1] && xy.length === 2) { markSymbolizer.wellKnownName = 'Circle' as WellKnownName; } break; diff --git a/src/mapfile2js/mapfileTypes.ts b/src/mapfile2js/mapfileTypes.ts index 34edb6b..b5ea9e8 100644 --- a/src/mapfile2js/mapfileTypes.ts +++ b/src/mapfile2js/mapfileTypes.ts @@ -110,7 +110,7 @@ export interface MapfileClass { } interface MapFileFeature { - points: MapFilePoint[]; + points: string; } export interface MapfileLabel { @@ -144,7 +144,10 @@ export interface MapfileStyle { symbol: MapfileSymbol; outlinecolor: string; outlinewidth: number; - pattern: string; + /** + * Used to define a dash pattern for line work (lines, polygon outlines, hatch lines, …). + */ + pattern: string; color: string; opacity: number; angle: number; @@ -155,5 +158,3 @@ export interface MapfileStyle { interface MapFileLeader { style: MapfileStyle; } - -interface MapFilePoint {} diff --git a/src/mapfile2js/parse/checkBlockEndSum.ts b/src/mapfile2js/parse/checkBlockEndSum.ts index f6fbfe9..37375c5 100644 --- a/src/mapfile2js/parse/checkBlockEndSum.ts +++ b/src/mapfile2js/parse/checkBlockEndSum.ts @@ -1,4 +1,4 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; /** * diff --git a/src/mapfile2js/parse/checkBlockKey.ts b/src/mapfile2js/parse/checkBlockKey.ts index 774cc6c..69abbf6 100644 --- a/src/mapfile2js/parse/checkBlockKey.ts +++ b/src/mapfile2js/parse/checkBlockKey.ts @@ -1,4 +1,4 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; /** * diff --git a/src/mapfile2js/parse/checkComment.ts b/src/mapfile2js/parse/checkComment.ts index 5b954e7..0d6fbb7 100644 --- a/src/mapfile2js/parse/checkComment.ts +++ b/src/mapfile2js/parse/checkComment.ts @@ -1,4 +1,4 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; const regExpHexColor = new RegExp('["\']#[0-9a-f]{6,8}["\']|["\']#[0-9a-f]{3}["\']', 'gi'); diff --git a/src/mapfile2js/parse/checkKeyValue.spec.ts b/src/mapfile2js/parse/checkKeyValue.spec.ts index be9e6e5..59923f4 100644 --- a/src/mapfile2js/parse/checkKeyValue.spec.ts +++ b/src/mapfile2js/parse/checkKeyValue.spec.ts @@ -1,5 +1,5 @@ import { checkKeyValue} from './checkKeyValue'; -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; describe('checkKeyValue', () => { diff --git a/src/mapfile2js/parse/checkKeyValue.ts b/src/mapfile2js/parse/checkKeyValue.ts index 6851a39..c630ac1 100644 --- a/src/mapfile2js/parse/checkKeyValue.ts +++ b/src/mapfile2js/parse/checkKeyValue.ts @@ -1,4 +1,4 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; function removeQuotes(str: string): string { if (/^['"].+['"]$/.test(str)) { diff --git a/src/mapfile2js/parse/determineDepth.ts b/src/mapfile2js/parse/determineDepth.ts index 0113798..528398c 100644 --- a/src/mapfile2js/parse/determineDepth.ts +++ b/src/mapfile2js/parse/determineDepth.ts @@ -1,4 +1,4 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; /** * Determines the depth of every line of mapfile. diff --git a/src/mapfile2js/parse/parseBlockKey.ts b/src/mapfile2js/parse/parseBlockKey.ts index b038ca2..7fa2430 100644 --- a/src/mapfile2js/parse/parseBlockKey.ts +++ b/src/mapfile2js/parse/parseBlockKey.ts @@ -1,26 +1,24 @@ -import { LineObject } from '../parse'; +import { LineObject } from '../parseMapfile'; // there can be multiple layer, class and style block siblings -const multiBlockKeys = { layer: 'layers', class: 'classes', label: 'labels', style: 'styles', symbol: 'symbols' }; -// some blocks are actually just an array -const arrayBlockKeys = ['points']; +const multiBlockKeys = { + layer: 'layers', + class: 'classes', + label: 'labels', + style: 'styles', + symbol: 'symbols', +}; /** * Parse block keys. * @param {LineObject} lineObject Line Object - * @param {number} index Current lines index * @param {array} lines Array of line strings * @param {array} blocks Block stack */ -export function parseBlockKey(lineObject: LineObject, index: number, currentBlock: any): object | undefined { - // can not handle block lines +export function parseBlockKey(lineObject: LineObject, currentBlock: any): object | undefined { + // handle block lines if (lineObject.isBlockLine) { - console.error(`Not able to deal with block line yet! Block line [${index + 1}]: ${lineObject.content}`); - return; - } - - // work around projection imitating a block - if (lineObject.key.toUpperCase() === 'PROJECTION') { + console.error(`Not able to deal with the following Block line: ${lineObject.content}`); return; } @@ -28,21 +26,15 @@ export function parseBlockKey(lineObject: LineObject, index: number, currentBloc if (lineObject.key in multiBlockKeys) { const pluralKey = multiBlockKeys[lineObject.key]; if (!(pluralKey in currentBlock)) { - // add list - currentBlock[pluralKey] = []; + currentBlock[pluralKey] = []; // add list } // add block to list currentBlock[pluralKey].push({}); - // return new block return currentBlock[pluralKey][currentBlock[pluralKey].length - 1]; - } else if (arrayBlockKeys.includes(lineObject.key)) { - // create array - currentBlock[lineObject.key] = []; - return currentBlock[lineObject.key]; } else { // check for duplicate block key - if (lineObject.key in currentBlock) { + if (lineObject.key in currentBlock || multiBlockKeys[lineObject.key] in currentBlock) { console.error(`Overwriting block! Add '${lineObject.key}' to multi block keys!`); } // create block diff --git a/src/mapfile2js/parse/resolveSymbolset.ts b/src/mapfile2js/parse/resolveSymbolset.ts index e3c70a5..70cd6dd 100644 --- a/src/mapfile2js/parse/resolveSymbolset.ts +++ b/src/mapfile2js/parse/resolveSymbolset.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { parseSymbolset } from '../parse'; +import { parseSymbolset } from '../parseMapfile'; import { MapfileSymbol, Mapfile } from '../mapfileTypes'; let mapfileSymbols: Array; diff --git a/src/mapfile2js/parseMapfile.spec.ts b/src/mapfile2js/parseMapfile.spec.ts new file mode 100644 index 0000000..fdd03ad --- /dev/null +++ b/src/mapfile2js/parseMapfile.spec.ts @@ -0,0 +1,15 @@ +import * as fs from 'fs'; + +import { parseMapfile } from "./parseMapfile"; + +describe('parseMapfile', () => { + it('is defined', () => { + expect(parseMapfile).toBeDefined(); + }); + // it('can parse this', () => { + // const pathToMapfile = 'data/mapfiles/ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill.map' + // const mapfile = parseMapfile(fs.readFileSync(pathToMapfile, 'utf8')); + // //console.log(JSON.stringify(mapfile, null, 2)); + // expect(mapfile).toEqual(expect.anything()); + // }); +}); \ No newline at end of file diff --git a/src/mapfile2js/parse.ts b/src/mapfile2js/parseMapfile.ts similarity index 58% rename from src/mapfile2js/parse.ts rename to src/mapfile2js/parseMapfile.ts index e382e39..aadd99e 100644 --- a/src/mapfile2js/parse.ts +++ b/src/mapfile2js/parseMapfile.ts @@ -18,6 +18,26 @@ export interface LineObject { depth: number; } +// some blocks are actually a key value pair +const pseudoBlockKeys = ['projection', 'pattern', 'points']; + +/** + * 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. @@ -26,55 +46,71 @@ export interface LineObject { */ function parseContent(content: string): object { const result = {}; - const lineObjects: Array = []; // stack to keep track of blocks const blocks: Array = [result]; - // replace windows line breaks with linux line breaks - content = content.replace(/[\r\n]/g, '\n'); - // split content into lines - const lines = content.split('\n'); + 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) => { - // line object - let lineObject: LineObject = { content: line.trim() } as any; // ommit empty lines and comments - if (lineObject.content === '' || lineObject.content.startsWith('#')) { + if (line === '' || line.startsWith('#')) { return; } - // 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)); + // line object + const lineObject = parseLine(line); // store lineobjects lineObjects.push(lineObject); // current block - const currentBlock: any = blocks[blocks.length - 1]; + 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 keys if (lineObject.isBlockKey) { - const newBlock = parseBlockKey(lineObject, index, currentBlock); + const newBlock = parseBlockKey(lineObject, currentBlock); if (newBlock) { blocks.push(newBlock as any); } return; } + // handle block end - if (lineObject.key.toUpperCase() === 'END' && !('projection' in currentBlock)) { - // pop current block - blocks.pop(); - return; - } - // work around projection imitating a block - if (lineObject.key.toLowerCase().includes('init=')) { - currentBlock.projection = lineObject.key.trim().replace(/"/g, ''); + if (lineObject.key === 'end') { + if (pseudoBlockKey) { + pseudoBlockKey = undefined; + } else { + blocks.pop(); + } return; } - // some blocks are actually just an array + + // handle array blocks if (Array.isArray(currentBlock)) { currentBlock.push(lineObject.contentWithoutComment); return; } + // insert key value pair if (lineObject.key in currentBlock) { console.warn(`Duplicate key on line [${index + 1}]: ${lineObject.content}`); @@ -90,34 +126,30 @@ function parseContent(content: string): object { 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 }; - + 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); - + return result.symbolset as MapfileSymbolset; }