For the purposes of the decoder functions of this API, an object is a container that holds
zero or more attributes of any type, each of which can be referenced by name. An
array is a container that holds zero or more values that are referenced by position.
Unlike arrays in C, an array here can use a different data type for each element. When
the JSON encoding is used, individual PPS attributes can themselves be objects or arrays,
which can be nested.
The PPS decoder functions allow both PPS- and JSON-encoded data to be parsed. Once a data
string has been parsed, the pps_decoder_t structure maintains a
representation of this data as a tree. Immediately upon parsing the data, you are
positioned at the root of the tree. Using the PPS decoder functions you can:
- extract simple types such as numbers or strings that are located at the current level
- move into more deeply nested objects or arrays by calling
pps_decoder_push()
- return to an outer level by calling
pps_decoder_pop()
The decoder is always positioned within some object or array, either at one of its
elements or off the end at a special element of type PPS_TYPE_NONE. Many
of the decoder functions take a name argument that indicates the PPS attribute or object
property name to look for. If the name is NULL, the current element is used.
When extracting data from arrays, the name must always be NULL because array elements don't
have names. When you successfully extract a data element, for example by calling
pps_decoder_get_int()
or after you call
pps_decoder_pop(), the position automatically
moves to the next element. So, for example, you could extract all elements of an array of
numbers using code like this:
while ( pps_decoder_type(decoder, NULL) != PPS_TYPE_NONE ) {
pps_decoder_get_double(decoder, NULL, &values[i++]);
}
Let's look at a complete example, using the following PPS data:
@gps
city::Ottawa
speed:n:65.412
position:json:{"latitude":45.6512,"longitude":-75.9041}
To extract this data, you might use code like this:
const char *city;
double lat, lon, speed;
pps_decoder_t decoder;
pps_decoder_initialize(&decoder, NULL);
pps_decoder_parse_pps_str(&decoder, buffer);
pps_decoder_push(&decoder, NULL);
pps_decoder_get_double(&decoder, "speed", &speed);
pps_decoder_get_string(&decoder, "city", &city);
pps_decoder_push(&decoder, "position");
pps_decoder_get_double(&decoder, "latitude", &lat);
pps_decoder_get_double(&decoder, "longitude", &lon);
pps_decoder_pop(&decoder);
pps_decoder_pop(&decoder);
if ( pps_decoder_status(&decoder, false) == PPS_DECODER_OK ) {
. . .
}
pps_decoder_cleanup(&decoder);
Let's take a look at each of these function calls:
- pps_decoder_initialize()
- Initializes the pps_decoder_t structure from an unknown state.
You can skip the subsequent call to pps_decoder_parse_pps_str()
and just pass the data to be parsed in this call, but typically you'll be parsing multiple buffers of PPS
data, so you'll need both steps.
- pps_decoder_parse_pps_str()
- Parses the PPS data into the decoder's internal data
structures. This process modifies the buffer that's passed in, and the internal structure
uses pointers into this buffer, so the contents of the buffer must remain valid
and unchanged until the parsing results are no longer needed.
- pps_decoder_push()
- Causes the decoder to descend into the
gps object to allow the values of the attributes to be extracted. In this particularly
simple situation, this step might seem unnecessary. But if, for example, the data came from reading the
.all special object, which returns multiple objects, it's important to indicate
which particular object you're extracting data from.
- pps_decoder_get_double(),
pps_decoder_get_string()
- Extract data from the PPS object, in this case the speed and city attributes.
Note that the order of these calls doesn't have to match the order of the attributes in the original PPS data.
- pps_decoder_push(), pps_decoder_get_double(), pps_decoder_pop()
- In this case, the latitude and longitude are both contained in a single
JSON-encoded PPS attribute. Therefore, before extracting latitude and longitude,
we have to descend into the object they're contained in by calling pps_decoder_push(). Having
pushed into the object, we can extract the two values just as if they had been PPS attributes. After the position
data has been extracted, we then call pps_decoder_pop() to go back a level in case we need to
extract more PPS attributes.
- pps_decoder_pop()
- Performs the reverse action of the initial
pps_decoder_push() and pops out of an object (in this case the gps object)
to its parent. Calling pps_decoder_pop() in this case isn't really required, but
if we had read .all and there were multiple objects contained in the data,
we would need this call to advance to the next object.
- pps_decoder_status()
- Returns the status of the decoder object.
If this is PPS_DECODER_OK, then all parsing and data extraction occurred successfully.
The preceding calls have the effect that if they fail, they will update the decoder status,
so that you can perform a series of operations and check the status at the end, rather than checking at each stage.
In this case, the second parameter to pps_decoder_status() is false,
meaning that the status shouldn't be reset to PPS_DECODER_OK when the function returns.
Parsing more data using pps_decoder_parse_pps_str() also has the effect of resetting
the status (unless it fails).
- pps_decoder_cleanup()
- Once you've finished with a decoder object,
pps_decoder_cleanup() frees any memory that was allocated. You need to
call this only when you no longer require the decoder; if you have more data to parse, you can simply
call pps_decoder_parse_pps_str() again.