A :cpp:class:`FSTR::Map` is analogous to the Wiring :cpp:class:`HashMap` class, allowing content to be indexed using a key value.
The Map contains an array of :cpp:class:`FSTR::MapPair` structures:
struct MapPair<KeyType, ContentType> {
KeyType key_;
ContentType* content_;
};
KeyType
can be any simple type such as char
, int
, float
, enum
etc.
It may also be a String
Object (or, more precisely, String*
).
ContentType
can be any Object type (String, Array, Vector or Map).
This allows hierarchical structures to be created.
Here's a basic example using integer keys:
#include <FlashString/Map.hpp>
IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");
DEFINE_FSTR_MAP(intmap, int, FSTR::String,
{35, &content1},
{180, &content2}
);
You should generally use :c:func:`IMPORT_FSTR_LOCAL` when referencing imported objects. If you need global access to imported data as well, then use :c:func:`IMPORT_FSTR`.
Note
Older toolchains (generally GCC earlier than version 6) will fail to compile with error: the value of 'FS_content1' is not usable in a constant expression.
You can work around this as follows:
IMPORT_FSTR(content1, PROJECT_DIR "/files/index.html"); // Make this a global reference
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");
DEFINE_FSTR_MAP(intmap, int, FSTR::String,
{35, &FSTR_DATA_NAME(FS_content1).as<FSTR::String>()}, // Cast the actual content
{180, &content2}
);
We can now do this:
void printValue(int key)
{
auto value = intmap[key];
if(value) {
Serial.printf("Found '%u' in map, containing %u chars\n", value.key(), value.content().length());
Serial.println(value.printer());
} else {
Serial.printf("Couldn't find '%u' in map\n", key);
}
}
Both the key and the content are stored as Strings:
#include <FlashString/Map.hpp>
DEFINE_FSTR_LOCAL(key1, "index.html");
DEFINE_FSTR_LOCAL(key2, "favicon.ico");
IMPORT_FSTR_LOCAL(content1, PROJECT_DIR "/files/index.html");
IMPORT_FSTR_LOCAL(content2, PROJECT_DIR "/files/favicon.html");
DEFINE_FSTR_MAP(fileMap, FlashString, FlashString,
{&key1, &content1},
{&key2, &content2},
);
We can now do this:
void onFile(HttpRequest& request, HttpResponse& response)
{
String fileName = request.uri.getRelativePath();
auto& value = fileMap[fileName];
if(value) {
// Found
Serial.printf("Found '%s' in fileMap\n", String(value.key()).c_str());
auto stream = new FlashMemoryStream(value);
response.sendDataStream(stream, ContentType::fromFullFileName(fileName));
} else {
Serial.printf("File '%s' not found\n", fileName.c_str());
}
}
Note
As with Vector<String>
, Map<String, ...>
lookups are by default case-insensitive.
If you require a case-sensitive lookup, use the indexOf
method with ignoreCase = false
.
The macro in the first example above produces a structure like this:
constexpr const struct {
ObjectBase object;
MapPair<int, String> data[2];
} __fstr__intmap PROGMEM = {
{16},
{35, &content1},
{180, &content2},
};
const Map<int, String>& intmap = __fstr__intmap.object.as<Map<int, String>>();
Note: FSTR::
namespace qualifier omitted for clarity.
Usually, each MapPair is 8 bytes, but if the key is a double or int64 it would be 12 bytes.
.. doxygengroup:: fstr_map
:content-only:
.. doxygenclass:: FSTR::Map
:members:
.. doxygenclass:: FSTR::MapPair
:members: