-
Notifications
You must be signed in to change notification settings - Fork 2
Getting Started
Spiff needs for you to have two things - a binary file to parse, and a description of the file format. The event dispatching mechanism in Spiff means that you can actually do whatever you want with the data (see TreeBuildingEventListener), but in most cases you'll probably also want some classes that will ultimately represent the contents of the file. The assumption throughout this guide is that you are wanting to bind the contents of the file into some class structure.
File formats are described in an .adf file (adf = Arbitrary Data Format - the extension is a convention rather than a necessity). Let's take a look at mapping a relatively simple file format - a 24-bit Bitmap (.bmp) file. You can read more about the .bmp file format at wikipedia
Here's the first 14 bytes of a bmp file
.setorder LITTLE_ENDIAN
.group(BITMAPFILEHEADER) {
# expect to find the string 'BM' at the start
string('BM', US-ASCII) bfType
int bfSize
short bfReserved1
short bfReserved2
int bfOffBits
}
The most basic thing you can do is to specify a list of datatypes, in the order in which they occur in the file, followed by a name to give to that field. Names for the fields will be used to find corresponding fields in bound classes, so identifiers use the java convention (alphanumeric starting with an alpha).
Strings come in three flavours: fixed length, null terminated, and literals. In this case, we've used a literal - that is, we expect the string to have a fixed value ("BM" - 0x424D in hex). We've also explicitly specified that it will be encoded as US-ASCII. The encoding is an optional parameter, if it wasn't there the string would be decoded using the default encoding for your platform. The file includes a comment, preceded with a #. Comments can appear on their own line or at the end of a line, but multiline comments are not supported.
The whole of the header is wrapped in a group instruction. In an adf file, instructions that control the flow of execution, as opposed to reading data from the file, start with a period ("."). The group instruction is used to partition the data into logical objects. Usually, but not necessarily, a group will correspond to an object. In this case, you can imagine that we have a Bitmap class (not explicitly declared in the adf file - we'll get to that), which is composed of a BitmapFileHeader class, and that BitmapFileHeader class has 5 fields, corresponding to the pieces of data defined within it.
Note that at the very top of the file, we use a .setorder instruction to specify that bytes in this file format will be little endian. The .setorder instruction can be used at any point in the file to switch endianness, but in most (sane) cases, you'll just want to specify it at the top. If we don't specify it, the default is BIG_ENDIAN.
For the Bitmap file, the next 50 bytes are pretty much the same as :
.group(BITMAPINFOHEADER) {
int biSize
int biWidth
int biHeight
short biPlanes
short biBitCount
int biCompression
int biSizeImage
int biXPelsPerMeter
int biYPelsPerMeter
int biClrUsed
int biClrImportant
}
##Flow of execution Most binary file formats also require some sort of logic. Spiff allows you to specify conditions and other instructions in amongst the datatypes to control the flow of execution. Instructions in adf files always start with a period ('.'). Blocks (such as conditionals or loops) are surrounded with curly braces ('{}'). Remember though that the "flow of execution" describes simply the order in which instructions are executed - the byte position of the "pointer" in the file only moves forward, unless you use a .jump or .skip to move it backwards (although why you'd want to parse the same data twice is up to you...)
Conditionals can be achieved with .if and .else . These work just like you'd expect them to:
.if(bfType == 1) {
short theDataIfBfTypeIs1
} .else {
int theDataisBfTypeIsNot1
}
Expressions passed as arguments can reference values from earlier instructions simply by using the identifier, and can also reference the byte position in the file of an earlier instruction by prefixing the name with an ampersand. For more detail on what you can specify in expressions, see ExpressionLanguage
Loops can be done using the .repeat instruction. Repeat takes an argument specifying how many times to repeat:
.repeat(bitmapWidth*bitmapHeight) {
int pixelData
}
Many file formats include pieces of data that are reserved for future use, or are simply bits of the file that you don't care about. You can change position in the file with the .skip and .jump instructions (no .hop though). The .jump instruction moves to an absolute position:
int nextDataPosition
.jump nextDataPosition
whereas the .skip instruction moves the "pointer" relative to the current position:
short numberOfUnusedBytes
.skip numberOfUnusedBytes
##Setting Default Options There are two instructions that can be specified in the .adf file that determine how subsequent instructions work. The instructions can appear anywhere in the file, and will change the default option whenever the flow of execution passes through them.
The .setorder instruction determines the endianness of the data. Valid values are LITTLE-ENDIAN and BIG-ENDIAN
.setorder BIG-ENDIAN
The .setencoding instruction sets a default encoding for the file. This encoding will be used for all string datatypes, unless specified otherwise. Valid values are UTF-8, UTF-16, UTF-16LE and US-ASCII
.setencoding UTF-16
string aStringThatWillBeDecodedUsingUTF-16
string(US-ASCII) aStringThatWillBeDecodedUsingUSASCII
string anotherStringThatWillBeDecodedUsingUTF-16
.setencoding UTF-8
string thisStringWillNowBeDecodedUsingUTF-8