// Add two functions `can_contain` and `can_be_contained` to the | |
// node type metaobject. (This is just an example. It's possible | |
// to split the work between these two functions differently.) | |
struct cmark_ext_node_type { | |
cmark_syntax_extension *extension; | |
char *name; | |
void (*free)(cmark_node *node); | |
// Used to check document structure. | |
int (*can_contain)(cmark_node *node, cmark_node *child); | |
// Note that this function has a node type parameter. | |
// `parent_type` must not be `CMARK_NODE_EXT`. | |
int (*can_be_contained)(cmark_node *node, cmark_node_type parent_type); | |
}; | |
// Expose `can_be_contained`. | |
int | |
cmark_node_can_be_contained(cmark_node *node, cmark_node_type parent_type) { | |
if (node->type == CMARK_NODE_EXT) { | |
return node->as.ext.type->can_be_contained(node, parent_type); | |
} | |
else { | |
// This is only needed for extension nodes, but it could do | |
// something useful for core nodes, too. | |
... | |
} | |
} | |
// Now cmark's internal `S_can_contain` function can handle extension | |
// nodes like this: | |
static bool | |
S_can_contain(cmark_node *node, cmark_node *child) { | |
if (node->type == CMARK_NODE_EXT) { | |
return node->as.ext.type->can_contain(node, child); | |
} | |
if (child->type == CMARK_NODE_EXT) { | |
return child->as.ext.type->can_be_contained(child, node->type); | |
} | |
// Both `node` and `child` aren't extension nodes. | |
// Continue as usual. | |
... | |
} | |
// A "table" node would implement these functions like this. | |
static int | |
S_table_can_contain(cmark_node *table, cmark_node *child) { | |
// This is just an example, but I think it's a good idea | |
// to use something similar to XML's namespace URIs to | |
// identify extension nodes. | |
const char *child_type_uri = child_get_node_type_string(child); | |
// Only allow table rows. | |
if (strcmp(child_type_uri, "http://myext.com/table-row") == 0) { | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} | |
static int | |
S_table_can_be_contained(cmark_node *table, cmark_node_type parent_type) { | |
return parent_type == CMARK_NODE_DOCUMENT | |
|| parent_type == CMARK_NODE_BLOCKQUOTE | |
|| parent_type == CMARK_NODE_LIST | |
|| ...; | |
} | |
// A "table cell" node would implement these functions like this. | |
static int | |
S_table_cell_can_contain(cmark_node *cell, cmark_node *child) { | |
cmark_node_type child_type = cmark_node_get_type(child); | |
if (child_type == CMARK_NODE_EXT) { | |
// Two extension nodes. | |
// | |
// HERE'S THE TRICK: `can_contain` calls `can_be_contained` | |
// with a standard node type. This means that table cells | |
// can contain the same node types as documents, i. e. all | |
// top-level block types. | |
// | |
// This makes it possible to handle nodes from separate | |
// extensions that don't know about each other. | |
return cmark_node_can_be_contained(child, CMARK_NODE_DOCUMENT); | |
} | |
return child_type == CMARK_NODE_PARAGRAPH | |
|| child_type == CMARK_NODE_BLOCK_QUOTE | |
|| child_type == CMARK_NODE_LIST | |
|| ...; | |
} | |
static int | |
S_table_cell_can_be_contained(cmark_node *cell, cmark_node_type parent_type) { | |
// Table cells can't be contained in non-extension nodes. | |
return 0; | |
} | |
// To understand how this works, this is the call chain when testing | |
// whether a table cell can contain a table. | |
// | |
// S_can_contain(table_cell, table); | |
// S_table_call_can_contain(table_cell, table); | |
// S_table_can_be_contained(table, CMARK_NODE_DOCUMENT); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment