Skip to content

Commit

Permalink
Proper pseudo block handling
Browse files Browse the repository at this point in the history
  • Loading branch information
b4l committed May 20, 2020
1 parent 080c403 commit f505bcd
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 68 deletions.
6 changes: 3 additions & 3 deletions src/MapfileStyleParser.ts
@@ -1,4 +1,4 @@
import { parseMapfile } from './mapfile2js/parse';
import { parseMapfile } from './mapfile2js/parseMapfile';
import { rgbToHex } from './Useful';
import {
StyleParser,
Expand Down Expand Up @@ -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;
Expand Down
9 changes: 5 additions & 4 deletions src/mapfile2js/mapfileTypes.ts
Expand Up @@ -110,7 +110,7 @@ export interface MapfileClass {
}

interface MapFileFeature {
points: MapFilePoint[];
points: string;
}

export interface MapfileLabel {
Expand Down Expand Up @@ -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;
Expand All @@ -155,5 +158,3 @@ export interface MapfileStyle {
interface MapFileLeader {
style: MapfileStyle;
}

interface MapFilePoint {}
2 changes: 1 addition & 1 deletion src/mapfile2js/parse/checkBlockEndSum.ts
@@ -1,4 +1,4 @@
import { LineObject } from '../parse';
import { LineObject } from '../parseMapfile';

/**
*
Expand Down
2 changes: 1 addition & 1 deletion src/mapfile2js/parse/checkBlockKey.ts
@@ -1,4 +1,4 @@
import { LineObject } from '../parse';
import { LineObject } from '../parseMapfile';

/**
*
Expand Down
2 changes: 1 addition & 1 deletion 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');

Expand Down
2 changes: 1 addition & 1 deletion src/mapfile2js/parse/checkKeyValue.spec.ts
@@ -1,5 +1,5 @@
import { checkKeyValue} from './checkKeyValue';
import { LineObject } from '../parse';
import { LineObject } from '../parseMapfile';


describe('checkKeyValue', () => {
Expand Down
2 changes: 1 addition & 1 deletion 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)) {
Expand Down
2 changes: 1 addition & 1 deletion 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.
Expand Down
34 changes: 13 additions & 21 deletions src/mapfile2js/parse/parseBlockKey.ts
@@ -1,48 +1,40 @@
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;
}

// handle multi block keys
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
Expand Down
2 changes: 1 addition & 1 deletion 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<MapfileSymbol>;
Expand Down
15 changes: 15 additions & 0 deletions 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());
// });
});
98 changes: 65 additions & 33 deletions src/mapfile2js/parse.ts → src/mapfile2js/parseMapfile.ts
Expand Up @@ -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.
Expand All @@ -26,55 +46,71 @@ export interface LineObject {
*/
function parseContent(content: string): object {
const result = {};

const lineObjects: Array<LineObject> = [];
// stack to keep track of blocks
const blocks: Array<any> = [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}`);
Expand All @@ -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;
}

0 comments on commit f505bcd

Please sign in to comment.