A Python library to create, parse, modify, and format MapServer Mapfiles.

  • Python 2 and 3 compatible
  • Pure Python - no MapServer dependencies
  • Open Source License (MIT)

An online formatter demonstrating the libraries capabilities can be found at:

A presentation on mappyfile was given at FOSS4G Europe 2017 - slides are available to download here.


What is mappyfile?

mappyfile takes a Mapfile as input and parses it into an Abstract syntax tree (AST) using lark a Python parsing library. mappyfile can then transform the AST into a dictionary structure, containing keys and values of dicts, and lists familiar to Python programmers. This structure can be edited directly. Alternatively new objects can be added by parsing further Mapfile text and inserting into the dictionary structure. mappyfile also includes a “pretty printer” to export this dictionary structure back to a Mapfile, with keyword formatting and indentation.

mappyfile assumes knowledge of the Mapfile format - a domain specific language (DSL) used by MapServer to generate map images. mappyfile is a possible alternative to using MapScript. The definitions of these (from the MapServer glossary) are shown below:

  • Mapfile is the declarative language that MapServer uses to define data connections, map styling, templating, and server directives. Its format is xml-like and hierarchical, with closing END tags, but the format is not xml.
  • MapScript is an alternative to the CGI application of mapserv that allows you to program the MapServer object API in many languages.

The diagram below shows the different elements of mappyfile, and how they are used to modify a Mapfile:



  • Easily generate development, staging, and production Mapfiles from the same source Mapfile
  • Create Mapfiles for different datasets from a single Mapfile
  • Create, manipulate, and test Mapfiles from within Python

The current alternative to building applications with MapServer is to use MapScript. This approach has a number of issues that resulted in the development of mappyfile:

  • When running on Windows any Python libraries using C/C++ require them to be built with the MS C/C++ VS2008 compiler, this means no applications using MapScript can take advantage of performance improvements in the MS C/C++ 2015 compiler
  • You need to create an empty log file or MapServer won’t open the map (or get “msSetErrorFile(): General error message. Failed to open MS_ERRORFILE” errors)
  • MapScript is not available through PyPI - the last version was uploaded in 2010 - UPDATE mapscript wheels for MapServer 7.2 on Windows are now available
  • It is necessary to set the working directory so that MapServer includes are found (this also applies to mappyfile, but there is no need to os.chdir and change the working directory for your script or application)
  • The MapScript API is not particularly “Pythonic”

One key difference is that mappyfile only deals with text, so you cannot retrieve features or connect to databases through layers as you can with MapScript. mappyfile’s approach is to build a Mapfile that then uses the mapserv program to handle these requirements. This design was influenced by Sean Gillies, the MapScript maintainer for several years (until 2006). A couple of his last blog posts on MapScript make a strong case for working with Mapfiles rather than MapScript:

“Cease, or at the very least, minimize your use of MapServer’s various language bindings. Instead, embrace MapServer’s domain-specific language (DSL) and write more of the declarative cartographic scripts known as mapfiles. Use the mapserv (or shp2img) program to compile these scripts into images. This is the path to happiness and prosperity.”

Sean Gillies - Stop using MapScript

A later post listed the benefits of this approach:

“the instructions encoded in a MapServer mapfile comprise a domain-specific language.. to embrace the map language is to benefit from simplicity, usability, and portability.”

Sean Gillies - Declarative Maps

The concept of the Mapfile as a DSL has been implemented a few times. A Python Mapfile builder written by Allan Doyle used an XML approach.

More recently the Node module node-mapserv provides support for declarative mapfile programming. As the author notes:

node-mapserv is not MapScript for Node. Instead it provides a simple declarative API for rendering mapserver mapfiles..most of what can be accomplished imperatively using mapscript can be done declaratively by custom generating new mapfiles and tweaking existing mapfiles

As an interesting footnote the MapScript “bindings” are available in several different languages thanks to SWIG which creates wrapper code for C. SWIG was developed by David Beazley, who then later built PLY on which mappyfile was originally based. PLY is an implementation of lex and yacc parsing tools for Python - the tools MapServer itself uses to parse Mapfiles in C.

Code Examples

This section details the basic use of the mappyfile library. For all functionality and examples see the mappyfile API documentation.

Accessing Values

# open will accept a filename (mappyfile.loads will accept a string)
mapfile ="./docs/examples/")

# print the map name
print(mapfile["name"]) # "MyMap"
# access layers
layers = mapfile["layers"]
layer2 = layers[1] # access by index

# access classes in a layer
classes = layer2["classes"]

for c in classes:


# load will accept a filename (loads will accept a string)
mapfile ="./docs/examples/")

# search for a layer by name
layer = mappyfile.find(mapfile['layers'], 'name', 'sea')
print(layer['name']) # "sea"
# search for all layers in a group
for layer in mappyfile.findall(mapfile['layers'], 'group', 'my_group'):

Modifying Values

# update the map name
mapfile["name"] = "MyNewMap"

# update a layer name
layers = mapfile["layers"]
layer = layers[0]
layer["name"] = "MyLayer"

# update the error file path in the map config section
# note key names can be lower or upper case

mapfile["config"]["ms_errorfile"] = "/ms4w/tmp/ms_error.txt"

# update the web metadata settings

mapfile["web"]["metadata"]["wms_format"] = "image/png"
print(mappyfile.dumps(mapfile["web"])) # print out just the WEB section

# alternatively we can parse the Mapfile syntax and load it directly

s = """
        'wms_enable_request' '*'
        'wms_feature_info_mime_type' 'text/html'
        'wms_format' 'image/jpg'

metadata = mappyfile.loads(s)
mapfile["web"]["metadata"] = metadata

Adding Items

Adding a new layer:

layers = mapfile["layers"]

new_layer_string = """
    NAME 'land'
    DATA '../data/vector/naturalearth/ne_110m_land'
            COLOR 107 208 107
            OUTLINECOLOR 2 2 2
            WIDTH 1

new_layer = mappyfile.loads(new_layer_string)
layers.insert(0, new_layer) # can insert the new layer at any index

Adding a new class to a layer:

# find a layer using its name
layer = mappyfile.find(mapfile["layers"], "name", "sea")

new_class_string = """
    NAME 'highlights'
        COLOR 107 208 107
        OUTLINECOLOR 2 2 2
        WIDTH 1

new_class = mappyfile.loads(new_class_string)
layer["classes"].insert(1, new_class) # can insert the new class at any index

Development Roadmap

Future development plans, leading to a v1.0 release include:

  • Setup an easy way to plug in “linters” to check various Mapfile settings and rules (e.g. configured correctly for WFS)
  • Create a Jupyter Notebook demonstrating mappyfile usage
  • Add a plugins page to the docs
  • Add an example of creating Mapfiles using YAML
  • Create a new prune function to remove redundant default settings from a Mapfile


0.9.7 03/04/2022

  • Fix “”ResourceWarning: unclosed”” when reading mapfile.lark in Python 3.10
  • #151 - Updates for COMPOSITE blocks
  • #150 - Unknown COMPOP “SOFT-LIGHT” and error with several
    lines with COMPFILTER with validate

0.9.6 29/03/2022

  • Schema fixes for GRID LABELFORMAT and set max versions for MAP DATAPATTERN and TEMPLATEPATTERN

0.9.5 01/03/2022

  • #147 - Create list objects for containers when modifying dicts
  • #146 - Add COMPOSITE validation
  • #145 - layers.insert fails with dict error
  • #144 - Invalid value in COMPOSITE - ‘compfilter’
  • #140 - New feature: group complex types at the end

0.9.4 22/02/2022

  • #137 - Checking mapfile dict properties creates invalid empty dictionaries
  • #119 - STYLE GEOMTRANSFORM ‘labelcenter’
  • #143 - Automate schema building
  • #142 - Allow newer versions of jsonschema for py3
  • #141 - Update and fix Continuous Integration
  • #139 - Feature: align values in column
  • #138 - Update schema based on new Mapfile validation rules

0.9.3 13/12/2021

  • Adds a new mappyfile.create function to allow creation of Mapfile objects with default values
  • Update the Mapfile schema to include default values for keywords

0.9.2 28/08/2021

  • Add the “idw” to LAYER` ``CONNECIONTYPE
  • Correct “minVersion” of LABEL EXPRESSION
  • Add validation to LEGEND LABELS
  • Add correct validation for MAP LEGEND and OUTPUTFORMAT
  • Add “maxVersion” to WEB LOG
  • #120 - Expression list element with apostrophe throws error
  • #118 - LABEL -> FONT and LABEL -> POSITION gives errors in validate when attributes are used
  • #114 - Style OFFSET: mixed attribute and numerical value fail to parse

0.9.1 23/12/2020

  • Allow any version of lark-parser > 0.9 to be used
  • Fixes for requirements for Python 2.7
  • #115 - Fix for issue #109 (OFFSET numeric and attribute pairs)
  • #109 - Style OFFSET: mixed attribute and numerical value fail to parse

0.9.0 14/07/2020

  • Schemas updated to include minVersion and maxVersion metadata to define which Mapfile keywords are valid for different versions of MapServer
  • A new schema command line tool to export Mapfile schemas for different versions of MapServer
  • Allow Mapfile validation based on a specific version of MapServer
  • Add better error message when incorrect dicts are passed to printer
  • Add py38 to continuous integration testing
  • Add command line scripts to continuous integration testing
  • Fix CONNECTIONOPTIONS formatted output
  • Update to lark-parser 0.9.0
  • #109 - Add validation based on MapServer version
  • #96 - Unquoted Unicode strings cause parsing errors
  • #102 - Added support for accented-latin in unquoted strings (Issue #96) - thanks @erezsh
  • #97 - Allow for negative expressions
  • #101 - Fix for issue #97 (unary negation) - thanks @erezsh
  • #85 - Coding of NOT logical expression
  • #100 - Allowing non-bracketed NOT expression (Issue #85) - thanks @erezsh

0.8.4 11/01/2020

  • Update to lark-parser 0.7.8
  • #95 - Allow Mapfile input from io.StringIO as well as from a file - thanks @ianturton for pull request
  • #93 - fix to ensure Mapfiles are closed after reading
  • #89 - List expressions with spaces in the attributes fail to parse - thanks @ianturton for fix

0.8.3 06/10/2019

  • Update to lark-parser 0.7.7
  • Update to jsonref 0.2
  • Add automated releases to GitHub using Appveyor
  • Add automated releases to PyPI using Appveyor
  • Add missing CLASS properties to JSON schema
  • Additional tests for CaseInsensitiveOrderedDict and EXPRESSIONs
  • #37 - LIKE not recognised in FILTER - thanks @ianturton for fix
  • #87 - JSON schema add join tag- thanks @hugbe8 for fix

0.8.2 29/03/2019

  • #74 - Map files containing Unicode can fail in mappyfile.load with python2.7 thanks @ianturton
  • #73 - Deepcopy not working (Python3 >=3.5) - thanks @guardeivid
  • Add support for CLUSTER keyword along with schema changes and tests

0.8.1 27/02/2019

  • Fix comments on root objects in a MapFile
  • Fix issues with duplicated METADATA keys and comments
  • Fix ReadTheDocs build
  • Add more sample MapFiles for testing to the project

0.8.0 24/02/2019

  • Update code to work with Lark 0.6.6 (see #71)
  • New end_comment option for pprint - Add a comment with the block type at each closing END statement e.g. END # MAP (see request #69)
  • Add **kwargs to main API to allow greater flexibility with plugins
  • Fix DeprecationWarnings relating to Python 3.7.2 (thanks @tigerfoot for the report)
  • Tested use with new jsonschema 3.0.0 release

0.7.6 (13/10/2018)

  • Deprecated write function removed from the API and codebase
  • Update OFFSET validation to allow attribute bindings - see
  • #68 - Support pickling of DefaultOrderedDict in Python3
  • #67 - Fix deprecation warnings for grammar regular expressions in Python 3.6
  • #65 - Handle hexadecimal color translucence

0.7.5 (14/09/2018)

  • Save tokens for value lists
  • Update README and fix example code

0.7.4 (07/09/2018)

  • Support for modulus operator
  • Allow custom transformers to be used with kwargs

0.7.3 (23/08/2018)

  • Two new CLI programs - format and validate
  • Update of Lark parser to 0.6.4 (fixes some validation line number issues)
  • Improvements to validation log messages
  • Normalise include paths

0.7.2 (24/07/2018)

  • Update of Lark parser to 0.6.2 and associated changes - thanks @erezsh
  • mappyfile.findall returns a list rather than a generator
  • SYMBOLSET files now supported (both parsing and transforming)
  • #63 - Set the PROJECTION value correctly for single strings
  • #61 - Remove quotes in mappyfile.findall()

0.7.1 (10/07/2018)

  • Breaking Change utils.dictfind renamed utils.findkey
  • new dictionary update function - allowing for easier creation of Mapfiles using YAML
  • allow any custom hidden metadata tags of the form __property__ to be used in dicts for custom processing
  • Schema validation updates including RANGEITEM and CLUSTER
  • Appveyor builds added
  • #56 Can’t parse expressions with a : in them
  • #54 fix windows cwd name issue in includes - thanks @ianturton

0.7.0 (04/04/2018)

  • Finalise validation API
  • Finalised Mapfile comments API
  • New dictfind function
  • Allow non-string function parameters in expressions
  • Use of CaseInsensitiveOrderedDict throughout transformer
  • UTF comments
  • JSONSchema updates and fixes

0.6.2 (24/02/2018)

  • Breaking Change - the mappyfile.load method now accepts a file-like object rather than a filename to match the usage in other Python libraries. A new method allows opening directly with a filename.
  • New preserve comments feature - experimental
  • Add basic plugin system
  • Updates to schema docs (fixes for POSITION, AUTO, and added new default values)
  • Fix issue with comments on INCLUDE lines
  • #50 Allow END keyword for GEOTRANSFORM parameter
  • #49 Allow non-ASCII characters in parser
  • #47 Add in missing expression operators - divide, multiply, and power.

0.6.1 (06/02/2018)

  • Fixes to

0.6.0 (17/01/2018)

  • Extensive refactoring of grammar and transformer
  • Removal of Earley grammar
  • Whitespace ignored when parsing
  • JSON schema fixes
  • #45 Set fixed dependency ranges
  • Experimental - inclusion of token positions
  • Experimental - inclusion of validation comments

0.5.1 (05/01/2018)

  • #45 Remove unnecessary parser keyword`

0.5.0 (01/11/2017)

  • Add in jsonschema and validation class
  • #44 Includes should be relative to Mapfile`

0.4.3 (28/08/2017)

  • #36 Create a unique logger for mappyfile logger`
  • #35 Add support for missing arithmetic expressions and run flake8 within tox` - thanks @loicgrasser
  • #33 Fix max recursion limit count` - thanks @loicgrasser

0.4.0 (18/08/2017)

  • Add a LALR grammar and parser, now a 8k line Mapfile is now parsed 12x faster
  • Add a experimental validator module using jsonschema
  • #30 Flake8 support` - thanks @loicgrasser
  • #28 Add support for relative path for nested include` - thanks @loicgrasser
  • #25 Expression grammar not allowing !`


  • Revert back to a single grammar, but add linebreaks before all END keywords to keep acceptable performance


  • Add in alternative grammar that allows for no line breaks between composites, and fall back to this if parsing fails (otherwise most use cases suffer a 3x performance hit)


  • Allow multiple composites to be parsed directly (e.g. CLASS..END CLASS..END)
  • Allow direct parsing of the METADATA and VALIDATION blocks
  • UTF-8 checks when opening a Mapfile
  • #23 Alternative NE and EQ comparisons not defined`
  • #22 Handle AUTO Projection setting`
  • #21 INCLUDES throw error when no cwd set`
  • #20 Only the first FORMATOPTION is kept after transform`
  • #19 IMAGEMODE FEATURE throws parsing error`
  • #18 CONFIG keyword not capitalised`

Older Releases