Language Support

Language Support

Although intended for British Heraldry DrawShield does have some support for different Blazon Languages and I have added rudimentary support for some very simple French blazons which hopefully will provide a framework for later expansion.

Important Parsing Concepts

There are some quirks to parsing which you should be aware of.

Input Preparation

The first stage of processing is to remove everything that looks like an HTML element or HTML entities. This is a security measure, we only deal with plain unicode text.

Tokens

The input Blazon string is next broken down into tokens - tokens are largely separated by spaces or punctuation characters, alhtough some punctuation is significant so may also become tokens.

Tokens are stored as an Array of Token objects. The original unicode text is stored in the $text property, however for the purposes of parsing we create an additional property $match, this is all lower case and has all accented characters reduced to their unaccented form. This is important as it means that all comparisons are done with this form.

<

Lexemes

An important consideration is that, unlike most programming languages we have an intermediate level of parsing. The parser does not search for specific tokens it looks for lexemes, a word I have defined as "unit of meaning". A lexeme encompasses the ideas that in heraldry there are many variant spellings, and several tokens may be combined into a single unit of meaning. Consider the following partial input strings:

  • fleur-de-lys
  • fleur-de-lis
  • fleurs-de-lys
  • fleur de lys

The idea of the lexeme is that it "collapses" all these different forms into a single value for use by the parser (in this particular case it is "fleur-de-lys"). So the parser does not have to worry about variations in spelling or the fact that a heraldic term is made up of more than one word, it can just look for a particular type of lexeme.

We also group lexemes into categories, so for example the category of "colours". If the parser thinks that a colour should be present at this point it does not have to search every possible colour name (including variations) it can just use the class function findLex with the name of the category and it will be told if a valid colour exists at that point in the input.

Parsing

Parsing is carried out by the various protected functions of the <Grammar object which is extended by each language model. The Parser can be considered as a recursive descent parser, i.e. a shield is composed of a tincture, followed by any number of objects. A tincture is either a colour, fur treatment or divsions. An object is either a charge or an ordinary... and so on until we reach the level of individual lexemes. The process involves looking for a category of lexeme that is permitted at the current point in parsing, and if it exists making a note of it and moving on to search for the next. If at any point we fail to find a permmitted category of lexeme we simply return null to the calling function.

If all of the expected lexeme categories are found then the function should create a fragment of the Abstract Syntax Tree (AST) in XML format and return that to the calling function. The calling function can then incorporate that AST fragment into its own fragment of thee tree (typically by appending it as a child) and then return that to its own caller. In this way the top level function will be able to return a complete AST representing the whole of the blazon which can be passed to the renderer for drawing.

Backtracking

It may be that we start to find one or more expected lexemes and then are unable to find the next one. This might be due to an error in the blazon or because the blazon is valid but ambiguous - for example if we are looking for an ordinary we find a quantity (like "a"), which is expected but then no ordinary name follows (it might be a charge perhaps).

At this point we have already "consumed" the number so need to "put it back" into the input queue so that another function can consider it. To allow for this, if we know that we going to be considerng more than one lexeme we can call the grammar object method $phrase = self::newPhrase(). This will "memorise" the current position and also start recording the text content of every lexeme that we consume.

If we come across a parsing issue and want to backtrack, discarding everything that we have found then we can call the grammar object method <self::discardPhrase($phrase) passing it the phrase object we created earlier. This will reset the input point to how it was at the original call.

Creating AST Fragments

To create an AST fragment that the parsing function can return to its caller we can use the schema method <makeNodeFromLexeme() passing it the element name we want to use (typically one of the const values defined in the BlazonML object) and also (optionally) passing in the text of the lexemes we have consumed in parsing by also passing the result of the function $phrase->creat(). This is not necessary but it helps traceability for debugging.

Format of the Lexeme Files

Potentially we need hundreds or even thousands of lexemes to address the whole of a blazon language. To save time and space we define lexemes in a compact form in a plain text file. We can use as many files as we like, just place them in the folder /parser/{language}/lexemes. You can use any file naming convention that you like but it must have a .txt extension.

Comments

You can document your files by using comments - any line starting with a '#' character is ignored, as are blank lines and (for reasons we will see below) any line that does NOT contain a '=' character.

Category Names

Categories can be given whatever names you like; a category is introduced by a line starting with '=' and immediately followed by the category name.

In my code I have also created a const value for the category name in the file /parser/{language}/lexicon.inc as part of the languageDB object. You don't have to do this, you can just use the name of the category in a string but I have chosen to use constants to avoid spelling errors as unknown categories are silently ignored.

Category Members

Every line following a category title is assumed to be a member of that category. Category members consist of two parts separated (without spaces) by a '=' sign. To the left of this sign is a space separated set of regular expressions (note that a plain string is just a very simple regular expression!). To the right of the '=' is what the code calls the keyterm and is the "canonical" name of the item which will be used by the rendering in locating the appropriate SVG resource to draw it.

<

Following on from our example above we can define it as:

    =charge
    fleurs? de l[yi]s=fleur-de-lys
  

It is NOT an error if more than one entry has the same keyword (that is another way to handle variant spellings). It is also NOT an error if the same patterns exist in different categories - a cross can be both a charge and an ordinary for example.

Although we don't need to know it for the purposes of parsing, the renderer will look at the keyword "fleur-de-lys" and associated that with the SVG file /charge/fleur-dey-lys.svg. This also means that it is NOT an error to use a keyword that the renderer does not understand - the renderer will report the unkown keyword and replace it with something else as a placeholder. In this way we can write a parser to handle a wider range of blazons and add the ability to actually draw those blazons at a later date.

Additional Features

Automatic Keyword

You can miss out the keyword, in which case the pattern will be used for the keyword. Obviously you should only use this if the pattern is just a single, simple word. Consider this extract:

=fur
ermine=
erminois=
erminites=
pean=
potent=
vair=
  

Optional Patterns

Special flag before patterns - if you include the character '?' immediately before a pattern the program will consider that whole word to be optional. From the same category as above we also have:

tapiss?ee? ?of ?wheat=tapisse
  

In this case both the "of" and "wheat" words are optional.

Charge Sub-categories

Our example charge above was actually a little simplistic as we need to manage hundreds of charges so we put charges into sub-categories. Consider for example:

=charge
=charge/insect/
abeilles?=bee
=charge/eagle/
aigles?=eagle
aiglets?=alerion
alerions?=alerion
=charge/ship/
ancres?=anchor
  
<

This list of charges contains three sub-categories. This helps the renderer find the appropriate resources more easily and also allows resources to share names, for example a "fountain" can be an architectural feature or a special type of roundel so can appear in both sub-categories.

Testing Changes

At present there is no support on the website page for selecting a different blazon language but we can do so when calling drawshield from the command line. There are two ways of doing this.

The debug.inc File

If a file called debug.inc exists and drawshield is called from the command line then the file will be included in the processing after the default values have been set. In this way you can override the default options with anything that you like. Any PHP code can be included but most usefully you can set members of the global $options array. Here is an example debug.inc file:

<
$options['blazon'] = <<< BLAZON
d'azur une bande d'or
BLAZON;

$options['language'] = 'french';
  

We can then run drawshield from the command line with:

    php drawshield.php
  

And the output SVG will be sent to standard output. You can also add the line <$options[asFile] = true; to have the file saved as "shield.svg";

Using Command Line Arguments

You can also just append your blazon to the command line, after setting whatever options you need. For example:

    php drawshield.php --language=french --asFile=1 "d'azur une bande d'or" 
  

You do not need to always enclose the blazon in double quotes as the program will concatenate the remaining arguments but you will need to escape any characters that are special to the shell. Full details of the command line processing options can be found here.


Please Help!

Gallery

Test Me

flashcard image