Nick Gammon said:
No, there isn't. I must admit that writing XML schemas is not one of my strengths.
I'm not totally sure what a schema is, except to guess that you validate the XML against a schema in order to determine whether it conforms to specifications (eg. a field which should contain a number actually contains a number).
What would the use of it be? Perhaps, if you are hand-writing a plugin you might want to make sure that the fields in the XML will be correctly processed?
You can probably get a good idea of the element names from: https://github.com/nickgammon/mushclient/blob/master/xml/xml_load_world.cpp
The options in the world file are validated against a table here: https://github.com/nickgammon/mushclient/blob/master/scriptingoptions.cpp
That would be a good place to see the allowable ranges for various elements.
For example:
{"spam_line_count", 20, O(m_iSpamLineCount), 5, 500 },
{"speed_walk_delay", 0, O(m_iSpeedWalkDelay), 0, 30000, OPT_FIX_SPEEDWALK_DELAY},
So the element spam_line_count is a number, and its allowable range is 5 to 500.
Basically, a schema describes the structure of a document. It not only specifies the elements and attributes of the document but can also specify things like the number of occurrences of an attribute or element, the minimum and maximum of the element, a pattern to verify/validate that element, etc. And though they can be used for validation, you can also pass them to something like CodeSynthesis XSD to generate serialization/deserialization code from them.
There are two ways I know of to write schemas. One way is to use XML itself, although this is annoying and cumbersome, or you can use something like RelaxNG. As an example, I've tried to whip up a basic schema of all the possible elements/attributes, so using RelaxNG's compact representation, I can do something like:
muclient = element muclient {
plugin?,
include_*,
world?,
triggers?,
aliases?,
timers?,
macros?,
variables?,
colours?,
keypad?,
printing?
}
world = element world {
attribute world_file_version { xsd:long }?,
attribute muclient_version { xsd:token }?,
attribute date_saved { xsd:date }?,
attribute accept_chat_connections { xsd:boolean }?,
attribute alternative_inverse { xsd:boolean }?,
attribute alt_arrow_recalls_partial { xsd:boolean }?,
# ...
The '?' in the above means optional (zero or one), the '*' means zero or more. If I had wanted to make one of these required, I would omit either a '*' or '?'. ("include" is a keyword in RelaxNG's compact form, hence the _ after it.) The "attribute" part is split into two parts: the name and the type. So attribute world_file_version { xsd:long }?,
means that there is an attribute named "world_file_version" of type "xsd:long", where "xsd:long" is the type "long" within the XML schema definition language specification. I'm still learning this myself; I've never written a schema before, so I'm still learning how to, e.g., place constraints on elements and such in RNC. But it's really neat, and XML isn't the only language that's schematized.
I've already drafted a basic schema for the format, both because I'm working on a project of my own but in case someone else wants it. I'm just uncertain if I've made elements required that shouldn't be required, or optional where they should be required, little things like that; looking through the code makes it difficult to tell what's required and what isn't, so I've just opted for the safest rout (assume that pretty much everything can be optional, worry about that not being the case later). It makes the code a bit annoying to use, but the generate code lets me do something like:
#include "mushclient.h" // mushclient.h is the schema file, can obviously rename it
#include <format>
#include <iostream>
// Assume we've already read the file into an istream called input_stream
// Parse it
const auto doc = muclient_(input_stream);
if (!doc) {
// Parse failed
}
// Now, we can access the world:
const auto world = doc->world();
if (!world) { // world isn't present
// Do something
}
const auto name = world->name(); // This is probably required, so I should make it so
std::cout << std::format("World name is {}\n", name);
For each attribute in the schema, the code generator generates a few methods: two for retrieving the attribute (one read-only, one read-write), and up to three setters (two make copies, one doesn't, and for optional attributes, one of those setters takes an std::optional<T> as a parameter, and only sets it if the optional isn't std::nullopt). So not only can you parse your documents but you can also build them from the ground up and serialize them. |