Add markdown support for HTML/EPUB/DOCSET output (Issue #194)
parent
4e6310962b
commit
c6bda8e144
@ -0,0 +1,144 @@ |
|||||||
|
--- |
||||||
|
title: Mini-XML API Reference |
||||||
|
author: Michael R Sweet |
||||||
|
copyright: Copyright (c) 2003-2017, All Rights Reserved. |
||||||
|
docversion: 2.11 |
||||||
|
... |
||||||
|
|
||||||
|
# Introduction |
||||||
|
|
||||||
|
Mini-XML is a small XML parsing library that you can use to read XML data files |
||||||
|
or strings in your application without requiring large non-standard libraries. |
||||||
|
Mini-XML provides the following functionality: |
||||||
|
|
||||||
|
- Reading of UTF-8 and UTF-16 and writing of UTF-8 encoded XML files and |
||||||
|
strings. |
||||||
|
- Data is stored in a linked-list tree structure, preserving the XML data |
||||||
|
hierarchy. |
||||||
|
- SAX (streamed) reading of XML files and strings to minimize memory usage. |
||||||
|
- Supports arbitrary element names, attributes, and attribute values with no |
||||||
|
preset limits, just available memory. |
||||||
|
- Supports integer, real, opaque ("cdata"), and text data types in "leaf" |
||||||
|
nodes. |
||||||
|
- Functions for creating and managing trees of data. |
||||||
|
- "Find" and "walk" functions for easily locating and navigating trees of |
||||||
|
data. |
||||||
|
|
||||||
|
Mini-XML doesn't do validation or other types of processing on the data based |
||||||
|
upon schema files or other sources of definition information. |
||||||
|
|
||||||
|
# Using Mini-XML |
||||||
|
|
||||||
|
Mini-XML provides a single header file which you include: |
||||||
|
|
||||||
|
#include <mxml.h> |
||||||
|
|
||||||
|
Nodes are defined by the `mxml_node_t` structure; the "type" member defines the |
||||||
|
node type (element, integer, opaque, real, or text) which determines which value |
||||||
|
you want to look at in the "value" union. New nodes can be created using the |
||||||
|
`mxmlNewElement`, `mxmlNewInteger`, `mxmlNewOpaque`, `mxmlNewReal`, and |
||||||
|
`mxmlNewText` functions. Only elements can have child nodes, and the top node |
||||||
|
must be an element, usually "?xml". |
||||||
|
|
||||||
|
You load an XML file using the `mxmlLoadFile` function: |
||||||
|
|
||||||
|
FILE *fp; |
||||||
|
mxml_node_t *tree; |
||||||
|
|
||||||
|
fp = fopen("filename.xml", "r"); |
||||||
|
tree = mxmlLoadFile(NULL, fp, MXML_NO_CALLBACK); |
||||||
|
fclose(fp); |
||||||
|
|
||||||
|
Similarly, you save an XML file using the `mxmlSaveFile` function: |
||||||
|
|
||||||
|
FILE *fp; |
||||||
|
mxml_node_t *tree; |
||||||
|
|
||||||
|
fp = fopen("filename.xml", "w"); |
||||||
|
mxmlSaveFile(tree, fp, MXML_NO_CALLBACK); |
||||||
|
fclose(fp); |
||||||
|
|
||||||
|
The `mxmlLoadString`, `mxmlSaveAllocString`, and `mxmlSaveString` functions |
||||||
|
load XML node trees from and save XML node trees to strings: |
||||||
|
|
||||||
|
char buffer[8192]; |
||||||
|
char *ptr; |
||||||
|
mxml_node_t *tree; |
||||||
|
|
||||||
|
... |
||||||
|
tree = mxmlLoadString(NULL, buffer, MXML_NO_CALLBACK); |
||||||
|
|
||||||
|
... |
||||||
|
mxmlSaveString(tree, buffer, sizeof(buffer), |
||||||
|
MXML_NO_CALLBACK); |
||||||
|
|
||||||
|
... |
||||||
|
ptr = mxmlSaveAllocString(tree, MXML_NO_CALLBACK); |
||||||
|
|
||||||
|
You can find a named element/node using the `mxmlFindElement` function: |
||||||
|
|
||||||
|
mxml_node_t *node = mxmlFindElement(tree, tree, "name", |
||||||
|
"attr", "value", |
||||||
|
MXML_DESCEND); |
||||||
|
|
||||||
|
The "name", "attr", and "value" arguments can be passed as `NULL` to act as |
||||||
|
wildcards, e.g.: |
||||||
|
|
||||||
|
/* Find the first "a" element */ |
||||||
|
node = mxmlFindElement(tree, tree, "a", NULL, NULL, |
||||||
|
MXML_DESCEND); |
||||||
|
|
||||||
|
/* Find the first "a" element with "href" attribute */ |
||||||
|
node = mxmlFindElement(tree, tree, "a", "href", NULL, |
||||||
|
MXML_DESCEND); |
||||||
|
|
||||||
|
/* Find the first "a" element with "href" to a URL */ |
||||||
|
node = mxmlFindElement(tree, tree, "a", "href", |
||||||
|
"http://www.easysw.com/~mike/mxml/", |
||||||
|
MXML_DESCEND); |
||||||
|
|
||||||
|
/* Find the first element with a "src" attribute*/ |
||||||
|
node = mxmlFindElement(tree, tree, NULL, "src", NULL, |
||||||
|
MXML_DESCEND); |
||||||
|
|
||||||
|
/* Find the first element with a "src" = "foo.jpg" */ |
||||||
|
node = mxmlFindElement(tree, tree, NULL, "src", |
||||||
|
"foo.jpg", MXML_DESCEND); |
||||||
|
|
||||||
|
You can also iterate with the same function: |
||||||
|
|
||||||
|
mxml_node_t *node; |
||||||
|
|
||||||
|
for (node = mxmlFindElement(tree, tree, "name", NULL, |
||||||
|
NULL, MXML_DESCEND); |
||||||
|
node != NULL; |
||||||
|
node = mxmlFindElement(node, tree, "name", NULL, |
||||||
|
NULL, MXML_DESCEND)) |
||||||
|
{ |
||||||
|
... do something ... |
||||||
|
} |
||||||
|
|
||||||
|
The `mxmlFindPath` function finds the (first) value node under a specific |
||||||
|
element using a "path": |
||||||
|
|
||||||
|
mxml_node_t *value = mxmlFindPath(tree, "path/to/*/foo/bar"); |
||||||
|
|
||||||
|
The `mxmlGetInteger`, `mxmlGetOpaque`, `mxmlGetReal`, and `mxmlGetText` |
||||||
|
functions retrieve the value from a node: |
||||||
|
|
||||||
|
mxml_node_t *node; |
||||||
|
|
||||||
|
int intvalue = mxmlGetInteger(node); |
||||||
|
|
||||||
|
const char *opaquevalue = mxmlGetOpaque(node); |
||||||
|
|
||||||
|
double realvalue = mxmlGetReal(node); |
||||||
|
|
||||||
|
int whitespacevalue; |
||||||
|
const char *textvalue = mxmlGetText(node, &whitespacevalue); |
||||||
|
|
||||||
|
Finally, once you are done with the XML data, use the `mxmlDelete` function to |
||||||
|
recursively free the memory that is used for a particular node or the entire |
||||||
|
tree: |
||||||
|
|
||||||
|
mxmlDelete(tree); |
@ -0,0 +1,907 @@ |
|||||||
|
/*
|
||||||
|
* Implementation of miniature markdown library. |
||||||
|
* |
||||||
|
* https://github.com/michaelrsweet/mmd
|
||||||
|
* |
||||||
|
* Copyright 2017 by Michael R Sweet. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are met: |
||||||
|
* |
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, |
||||||
|
* this list of conditions and the following disclaimer. |
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
* this list of conditions and the following disclaimer in the documentation |
||||||
|
* and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||||
|
* POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
/*
|
||||||
|
* Include necessary headers... |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "mmd.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <ctype.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Structures... |
||||||
|
*/ |
||||||
|
|
||||||
|
struct _mmd_s |
||||||
|
{ |
||||||
|
mmd_type_t type; /* Node type */ |
||||||
|
int whitespace; /* Leading whitespace? */ |
||||||
|
char *text, /* Text */ |
||||||
|
*url; /* Reference URL (image/link/etc.) */ |
||||||
|
mmd_t *parent, /* Parent node */ |
||||||
|
*first_child, /* First child node */ |
||||||
|
*last_child, /* Last child node */ |
||||||
|
*prev_sibling, /* Previous sibling node */ |
||||||
|
*next_sibling; /* Next sibling node */ |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Local functions... |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
static mmd_t *mmd_add(mmd_t *parent, mmd_type_t type, int whitespace, char *text, char *url); |
||||||
|
static void mmd_free(mmd_t *node); |
||||||
|
static void mmd_parse_inline(mmd_t *parent, char *line); |
||||||
|
static char *mmd_parse_link(char *lineptr, char **text, char **url); |
||||||
|
static void mmd_remove(mmd_t *node); |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdFree()' - Free a markdown tree. |
||||||
|
*/ |
||||||
|
|
||||||
|
void |
||||||
|
mmdFree(mmd_t *node) /* I - First node */ |
||||||
|
{ |
||||||
|
mmd_t *current, /* Current node */ |
||||||
|
*next; /* Next node */ |
||||||
|
|
||||||
|
|
||||||
|
mmd_remove(node); |
||||||
|
|
||||||
|
for (current = node->first_child; current; current = next) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Get the next node... |
||||||
|
*/ |
||||||
|
|
||||||
|
if ((next = current->first_child) != NULL) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Free parent nodes after child nodes have been freed... |
||||||
|
*/ |
||||||
|
|
||||||
|
current->first_child = NULL; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if ((next = current->next_sibling) == NULL) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Next node is the parent, which we'll free as needed... |
||||||
|
*/ |
||||||
|
|
||||||
|
if ((next = current->parent) == node) |
||||||
|
next = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Free child... |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_free(current); |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Then free the memory used by the parent node... |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_free(node); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetFirstChild()' - Return the first child of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - First child or @code NULL@ if none */ |
||||||
|
mmdGetFirstChild(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->first_child : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetLastChild()' - Return the last child of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - Last child or @code NULL@ if none */ |
||||||
|
mmdGetLastChild(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->last_child : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetMetadata()' - Return the metadata for the given keyword. |
||||||
|
*/ |
||||||
|
|
||||||
|
const char * /* O - Value or @code NULL@ if none */ |
||||||
|
mmdGetMetadata(mmd_t *doc, /* I - Document */ |
||||||
|
const char *keyword) /* I - Keyword */ |
||||||
|
{ |
||||||
|
mmd_t *metadata, /* Metadata node */ |
||||||
|
*current; /* Current node */ |
||||||
|
char prefix[256]; /* Prefix string */ |
||||||
|
size_t prefix_len; /* Length of prefix string */ |
||||||
|
const char *value; /* Pointer to value */ |
||||||
|
|
||||||
|
|
||||||
|
if (!doc || (metadata = doc->first_child) == NULL || metadata->type != MMD_TYPE_METADATA) |
||||||
|
return (NULL); |
||||||
|
|
||||||
|
snprintf(prefix, sizeof(prefix), "%s:", keyword); |
||||||
|
prefix_len = strlen(prefix); |
||||||
|
|
||||||
|
for (current = metadata->first_child; current; current = current->next_sibling) |
||||||
|
{ |
||||||
|
if (strncmp(current->text, prefix, prefix_len)) |
||||||
|
continue; |
||||||
|
|
||||||
|
value = current->text + prefix_len; |
||||||
|
while (isspace(*value & 255)) |
||||||
|
value ++; |
||||||
|
|
||||||
|
return (value); |
||||||
|
} |
||||||
|
|
||||||
|
return (NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetNextSibling()' - Return the next sibling of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - Next sibling or @code NULL@ if none */ |
||||||
|
mmdGetNextSibling(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->next_sibling : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetParent()' - Return the parent of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - Parent node or @code NULL@ if none */ |
||||||
|
mmdGetParent(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->parent : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetPrevSibling()' - Return the previous sibling of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - Previous sibling or @code NULL@ if none */ |
||||||
|
mmdGetPrevSibling(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->prev_sibling : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetText()' - Return the text associated with a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
const char * /* O - Text or @code NULL@ if none */ |
||||||
|
mmdGetText(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->text : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetType()' - Return the type of a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_type_t /* O - Type or @code MMD_TYPE_NONE@ if none */ |
||||||
|
mmdGetType(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->type : MMD_TYPE_NONE); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetURL()' - Return the URL associated with a node, if any. |
||||||
|
*/ |
||||||
|
|
||||||
|
const char * /* O - URL or @code NULL@ if none */ |
||||||
|
mmdGetURL(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->url : NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdGetWhitespace()' - Return whether whitespace preceded a node. |
||||||
|
*/ |
||||||
|
|
||||||
|
int /* O - 1 for whitespace, 0 for none */ |
||||||
|
mmdGetWhitespace(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->whitespace : 0); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdIsBlock()' - Return whether the node is a block. |
||||||
|
*/ |
||||||
|
|
||||||
|
int /* O - 1 for block nodes, 0 otherwise */ |
||||||
|
mmdIsBlock(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
return (node ? node->type < MMD_TYPE_NORMAL_TEXT : 0); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmdLoad()' - Load a markdown file into nodes. |
||||||
|
*/ |
||||||
|
|
||||||
|
mmd_t * /* O - First node in markdown */ |
||||||
|
mmdLoad(const char *filename) /* I - File to load */ |
||||||
|
{ |
||||||
|
FILE *fp; /* File */ |
||||||
|
mmd_t *doc, /* Document */ |
||||||
|
*current, /* Current parent block */ |
||||||
|
*block = NULL; /* Current block */ |
||||||
|
mmd_type_t type; /* Type for line */ |
||||||
|
char line[65536], /* Line from file */ |
||||||
|
*lineptr, /* Pointer into line */ |
||||||
|
*lineend; /* End of line */ |
||||||
|
int blank_code = 0; /* Saved indented blank code line */ |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Open the file and create an empty document... |
||||||
|
*/ |
||||||
|
|
||||||
|
if ((fp = fopen(filename, "r")) == NULL) |
||||||
|
return (NULL); |
||||||
|
|
||||||
|
doc = current = mmd_add(NULL, MMD_TYPE_DOCUMENT, 0, NULL, NULL); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Read lines until end-of-file... |
||||||
|
*/ |
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fp)) |
||||||
|
{ |
||||||
|
lineptr = line; |
||||||
|
|
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if ((lineptr - line) >= 4 && !block && (current == doc || current->type == MMD_TYPE_CODE_BLOCK)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Indented code block. |
||||||
|
*/ |
||||||
|
|
||||||
|
if (current == doc) |
||||||
|
current = mmd_add(doc, MMD_TYPE_CODE_BLOCK, 0, NULL, NULL); |
||||||
|
|
||||||
|
if (blank_code) |
||||||
|
mmd_add(current, MMD_TYPE_CODE_TEXT, 0, "\n", NULL); |
||||||
|
|
||||||
|
mmd_add(current, MMD_TYPE_CODE_TEXT, 0, line + 4, NULL); |
||||||
|
|
||||||
|
blank_code = 0; |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (*lineptr == '`' && (!lineptr[1] || lineptr[1] == '`')) |
||||||
|
{ |
||||||
|
if (block) |
||||||
|
{ |
||||||
|
if (block->type == MMD_TYPE_CODE_BLOCK) |
||||||
|
block = NULL; |
||||||
|
else if (block->type == MMD_TYPE_LIST_ITEM) |
||||||
|
block = mmd_add(block, MMD_TYPE_CODE_BLOCK, 0, NULL, NULL); |
||||||
|
else if (block->parent->type == MMD_TYPE_LIST_ITEM) |
||||||
|
block = mmd_add(block->parent, MMD_TYPE_CODE_BLOCK, 0, NULL, NULL); |
||||||
|
else |
||||||
|
block = mmd_add(current, MMD_TYPE_CODE_BLOCK, 0, NULL, NULL); |
||||||
|
} |
||||||
|
else |
||||||
|
block = mmd_add(current, MMD_TYPE_CODE_BLOCK, 0, NULL, NULL); |
||||||
|
|
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
if (block && block->type == MMD_TYPE_CODE_BLOCK) |
||||||
|
{ |
||||||
|
mmd_add(block, MMD_TYPE_CODE_TEXT, 0, line, NULL); |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (!strncmp(lineptr, "---", 3) && doc->first_child == NULL) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Document metadata... |
||||||
|
*/ |
||||||
|
|
||||||
|
block = mmd_add(doc, MMD_TYPE_METADATA, 0, NULL, NULL); |
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), fp)) |
||||||
|
{ |
||||||
|
lineptr = line; |
||||||
|
|
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (!strncmp(line, "---", 3) || !strncmp(line, "...", 3)) |
||||||
|
break; |
||||||
|
|
||||||
|
lineend = lineptr + strlen(lineptr) - 1; |
||||||
|
if (lineend > lineptr && *lineend == '\n') |
||||||
|
*lineend = '\0'; |
||||||
|
|
||||||
|
mmd_add(block, MMD_TYPE_METADATA_TEXT, 0, lineptr, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
block = NULL; |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (!block && (!strncmp(lineptr, "---", 3) || !strncmp(lineptr, "***", 3) || !strncmp(lineptr, "___", 3))) |
||||||
|
{ |
||||||
|
int ch = *lineptr; |
||||||
|
|
||||||
|
lineptr += 3; |
||||||
|
while (*lineptr && (*lineptr == ch || isspace(*lineptr & 255))) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
{ |
||||||
|
block = NULL; |
||||||
|
mmd_add(current, MMD_TYPE_THEMATIC_BREAK, 0, NULL, NULL); |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (*lineptr == '>') |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Block quote. See if the parent of the current node is already a block |
||||||
|
* quote... |
||||||
|
*/ |
||||||
|
|
||||||
|
if (current == doc || current->type != MMD_TYPE_BLOCK_QUOTE) |
||||||
|
current = mmd_add(doc, MMD_TYPE_BLOCK_QUOTE, 0, NULL, NULL); |
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip whitespace after the ">"... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr ++; |
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
} |
||||||
|
else if (current->type == MMD_TYPE_BLOCK_QUOTE) |
||||||
|
current = current->parent; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
{ |
||||||
|
blank_code = current->type == MMD_TYPE_CODE_BLOCK; |
||||||
|
block = NULL; |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (!strcmp(lineptr, "+")) |
||||||
|
{ |
||||||
|
if (block) |
||||||
|
{ |
||||||
|
if (block->type == MMD_TYPE_LIST_ITEM) |
||||||
|
block = mmd_add(block, MMD_TYPE_PARAGRAPH, 0, NULL, NULL); |
||||||
|
else if (block->parent->type == MMD_TYPE_LIST_ITEM) |
||||||
|
block = mmd_add(block->parent, MMD_TYPE_PARAGRAPH, 0, NULL, NULL); |
||||||
|
else |
||||||
|
block = NULL; |
||||||
|
} |
||||||
|
continue; |
||||||
|
} |
||||||
|
else if (block && block->type == MMD_TYPE_PARAGRAPH && (!strncmp(lineptr, "---", 3) || !strncmp(lineptr, "===", 3))) |
||||||
|
{ |
||||||
|
int ch = *lineptr; |
||||||
|
|
||||||
|
lineptr += 3; |
||||||
|
while (*lineptr == ch) |
||||||
|
lineptr ++; |
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
{ |
||||||
|
if (ch == '=') |
||||||
|
block->type = MMD_TYPE_HEADING_1; |
||||||
|
else |
||||||
|
block->type = MMD_TYPE_HEADING_2; |
||||||
|
|
||||||
|
block = NULL; |
||||||
|
continue; |
||||||
|
} |
||||||
|
} |
||||||
|
else if ((*lineptr == '-' || *lineptr == '+' || *lineptr == '*') && isspace(lineptr[1] & 255)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Bulleted list... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr += 2; |
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (current == doc && doc->last_child && doc->last_child->type == MMD_TYPE_UNORDERED_LIST) |
||||||
|
current = doc->last_child; |
||||||
|
else if (current->type != MMD_TYPE_UNORDERED_LIST) |
||||||
|
current = mmd_add(current, MMD_TYPE_UNORDERED_LIST, 0, NULL, NULL); |
||||||
|
|
||||||
|
type = MMD_TYPE_LIST_ITEM; |
||||||
|
block = NULL; |
||||||
|
} |
||||||
|
else if (isdigit(*lineptr & 255)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Ordered list? |
||||||
|
*/ |
||||||
|
|
||||||
|
char *temp = lineptr + 1; |
||||||
|
|
||||||
|
while (isdigit(*temp & 255)) |
||||||
|
temp ++; |
||||||
|
|
||||||
|
if (*temp == '.' && isspace(temp[1] & 255)) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Yes, ordered list. |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr = temp + 2; |
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (current == doc && doc->last_child && doc->last_child->type == MMD_TYPE_ORDERED_LIST) |
||||||
|
current = doc->last_child; |
||||||
|
else if (current->type != MMD_TYPE_ORDERED_LIST) |
||||||
|
current = mmd_add(current, MMD_TYPE_ORDERED_LIST, 0, NULL, NULL); |
||||||
|
|
||||||
|
type = MMD_TYPE_LIST_ITEM; |
||||||
|
block = NULL; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* No, just a regular paragraph... |
||||||
|
*/ |
||||||
|
|
||||||
|
type = block ? block->type : MMD_TYPE_PARAGRAPH; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (*lineptr == '#') |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Heading, count the number of '#' for the heading level... |
||||||
|
*/ |
||||||
|
|
||||||
|
char *temp = lineptr + 1; |
||||||
|
|
||||||
|
while (*temp == '#') |
||||||
|
temp ++; |
||||||
|
|
||||||
|
if ((temp - lineptr) <= 6) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Heading 1-6... |
||||||
|
*/ |
||||||
|
|
||||||
|
type = MMD_TYPE_HEADING_1 + (temp - lineptr - 1); |
||||||
|
block = NULL; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Skip whitespace after "#"... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr = temp; |
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
/*
|
||||||
|
* Strip trailing "#" characters... |
||||||
|
*/ |
||||||
|
|
||||||
|
for (temp = lineptr + strlen(lineptr) - 1; temp > lineptr && *temp == '#'; temp --) |
||||||
|
*temp = '\0'; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* More than 6 #'s, just treat as a paragraph... |
||||||
|
*/ |
||||||
|
|
||||||
|
type = MMD_TYPE_PARAGRAPH; |
||||||
|
} |
||||||
|
} |
||||||
|
else if (!block) |
||||||
|
{ |
||||||
|
type = MMD_TYPE_PARAGRAPH; |
||||||
|
|
||||||
|
if (lineptr == line) |
||||||
|
current = doc; |
||||||
|
} |
||||||
|
else |
||||||
|
type = block->type; |
||||||
|
|
||||||
|
if (!block || block->type != type) |
||||||
|
{ |
||||||
|
if (current->type == MMD_TYPE_CODE_BLOCK) |
||||||
|
current = doc; |
||||||
|
|
||||||
|
block = mmd_add(current, type, 0, NULL, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
mmd_parse_inline(block, lineptr); |
||||||
|
} |
||||||
|
|
||||||
|
fclose(fp); |
||||||
|
|
||||||
|
return (doc); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmd_add()' - Add a new markdown node. |
||||||
|
*/ |
||||||
|
|
||||||
|
static mmd_t * /* O - New node */ |
||||||
|
mmd_add(mmd_t *parent, /* I - Parent node */ |
||||||
|
mmd_type_t type, /* I - Node type */ |
||||||
|
int whitespace, /* I - 1 if whitespace precedes this node */ |
||||||
|
char *text, /* I - Text, if any */ |
||||||
|
char *url) /* I - URL, if any */ |
||||||
|
{ |
||||||
|
mmd_t *temp; /* New node */ |
||||||
|
|
||||||
|
|
||||||
|
if ((temp = calloc(1, sizeof(mmd_t))) != NULL) |
||||||
|
{ |
||||||
|
if (parent) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Add node to the parent... |
||||||
|
*/ |
||||||
|
|
||||||
|
temp->parent = parent; |
||||||
|
|
||||||
|
if (parent->last_child) |
||||||
|
{ |
||||||
|
parent->last_child->next_sibling = temp; |
||||||
|
temp->prev_sibling = parent->last_child; |
||||||
|
parent->last_child = temp; |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
parent->first_child = parent->last_child = temp; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy the node values... |
||||||
|
*/ |
||||||
|
|
||||||
|
temp->type = type; |
||||||
|
temp->whitespace = whitespace; |
||||||
|
|
||||||
|
if (text) |
||||||
|
temp->text = strdup(text); |
||||||
|
|
||||||
|
if (url) |
||||||
|
temp->url = strdup(url); |
||||||
|
} |
||||||
|
|
||||||
|
return (temp); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmd_free()' - Free memory used by a node. |
||||||
|
*/ |
||||||
|
|
||||||
|
static void |
||||||
|
mmd_free(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
if (node->text) |
||||||
|
free(node->text); |
||||||
|
|
||||||
|
if (node->url) |
||||||
|
free(node->url); |
||||||
|
|
||||||
|
free(node); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmd_parse_inline()' - Parse inline formatting. |
||||||
|
*/ |
||||||
|
|
||||||
|
static void |
||||||
|
mmd_parse_inline(mmd_t *parent, /* I - Parent node */ |
||||||
|
char *line) /* I - Line from file */ |
||||||
|
{ |
||||||
|
mmd_type_t type; /* Current node type */ |
||||||
|
int whitespace; /* Whitespace precedes? */ |
||||||
|
char *lineptr, /* Pointer into line */ |
||||||
|
*text, /* Text fragment in line */ |
||||||
|
*url; /* URL in link */ |
||||||
|
|
||||||
|
|
||||||
|
whitespace = parent->last_child != NULL; |
||||||
|
|
||||||
|
for (lineptr = line, text = NULL, type = MMD_TYPE_NORMAL_TEXT; *lineptr; lineptr ++) |
||||||
|
{ |
||||||
|
if (isspace(*lineptr & 255)) |
||||||
|
{ |
||||||
|
if (text) |
||||||
|
{ |
||||||
|
*lineptr = '\0'; |
||||||
|
mmd_add(parent, type, whitespace, text, NULL); |
||||||
|
text = NULL; |
||||||
|
} |
||||||
|
|
||||||
|
whitespace = 1; |
||||||
|
} |
||||||
|
else if (!text) |
||||||
|
{ |
||||||
|
if (*lineptr == '\\' && lineptr[1]) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Escaped character... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr ++; |
||||||
|
text = lineptr; |
||||||
|
} |
||||||
|
else if (*lineptr == '!' && lineptr[1] == '[') |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Image... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr = mmd_parse_link(lineptr + 1, &text, &url); |
||||||
|
|
||||||
|
if (url) |
||||||
|
mmd_add(parent, MMD_TYPE_IMAGE, whitespace, text, url); |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
return; |
||||||
|
|
||||||
|
text = url = NULL; |
||||||
|
whitespace = 0; |
||||||
|
lineptr --; |
||||||
|
} |
||||||
|
else if (*lineptr == '[') |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Link... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr = mmd_parse_link(lineptr, &text, &url); |
||||||
|
|
||||||
|
if (text) |
||||||
|
mmd_add(parent, MMD_TYPE_LINKED_TEXT, whitespace, text, url); |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
return; |
||||||
|
|
||||||
|
text = url = NULL; |
||||||
|
whitespace = 0; |
||||||
|
lineptr --; |
||||||
|
} |
||||||
|
else if (*lineptr == '<' && strchr(lineptr + 1, '>')) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Autolink... |
||||||
|
*/ |
||||||
|
|
||||||
|
url = lineptr + 1; |
||||||
|
lineptr = strchr(lineptr + 1, '>'); |
||||||
|
|
||||||
|
*lineptr++ = '\0'; |
||||||
|
|
||||||
|
mmd_add(parent, MMD_TYPE_LINKED_TEXT, whitespace, url, url); |
||||||
|
|
||||||
|
text = url = NULL; |
||||||
|
whitespace = 0; |
||||||
|
lineptr --; |
||||||
|
} |
||||||
|
else if (*lineptr == '`') |
||||||
|
{ |
||||||
|
type = MMD_TYPE_CODE_TEXT; |
||||||
|
text = lineptr + 1; |
||||||
|
lineptr ++; |
||||||
|
} |
||||||
|
else if (*lineptr == '*') |
||||||
|
{ |
||||||
|
if (lineptr[1] == '*' && !isspace(lineptr[2] & 255)) |
||||||
|
{ |
||||||
|
type = MMD_TYPE_STRONG_TEXT; |
||||||
|
lineptr += 2; |
||||||
|
} |
||||||
|
else if (!isspace(lineptr[1] & 255)) |
||||||
|
{ |
||||||
|
type = MMD_TYPE_EMPHASIZED_TEXT; |
||||||
|
lineptr ++; |
||||||
|
} |
||||||
|
else |
||||||
|
type = MMD_TYPE_NORMAL_TEXT; |
||||||
|
} |
||||||
|
else |
||||||
|
type = MMD_TYPE_NORMAL_TEXT; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
break; |
||||||
|
|
||||||
|
text = lineptr; |
||||||
|
} |
||||||
|
else if (*lineptr == '*' && type != MMD_TYPE_NORMAL_TEXT) |
||||||
|
{ |
||||||
|
*lineptr = '\0'; |
||||||
|
if (lineptr[1] == '*') |
||||||
|
{ |
||||||
|
lineptr ++; |
||||||
|
*lineptr = '\0'; |
||||||
|
} |
||||||
|
|
||||||
|
mmd_add(parent, type, whitespace, text, NULL); |
||||||
|
|
||||||
|
text = NULL; |
||||||
|
whitespace = 0; |
||||||
|
} |
||||||
|
else if (*lineptr == '`' && type == MMD_TYPE_CODE_TEXT) |
||||||
|
{ |
||||||
|
*lineptr = '\0'; |
||||||
|
mmd_add(parent, type, whitespace, text, NULL); |
||||||
|
|
||||||
|
text = NULL; |
||||||
|
whitespace = 0; |
||||||
|
} |
||||||
|
else if (*lineptr == '\\' && lineptr[1]) |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Escaped character... |
||||||
|
*/ |
||||||
|
|
||||||
|
memmove(lineptr, lineptr + 1, strlen(lineptr)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (text) |
||||||
|
mmd_add(parent, type, whitespace, text, NULL); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmd_parse_link()' - Parse a link. |
||||||
|
*/ |
||||||
|
|
||||||
|
static char * /* O - End of link text */ |
||||||
|
mmd_parse_link(char *lineptr, /* I - Pointer into line */ |
||||||
|
char **text, /* O - Text */ |
||||||
|
char **url) /* O - URL */ |
||||||
|
{ |
||||||
|
lineptr ++; /* skip "[" */ |
||||||
|
|
||||||
|
*text = lineptr; |
||||||
|
*url = NULL; |
||||||
|
|
||||||
|
while (*lineptr && *lineptr != ']') |
||||||
|
{ |
||||||
|
if (*lineptr == '\"') |
||||||
|
{ |
||||||
|
lineptr ++; |
||||||
|
while (*lineptr && *lineptr != '\"') |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
return (lineptr); |
||||||
|
} |
||||||
|
|
||||||
|
lineptr ++; |
||||||
|
} |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
return (lineptr); |
||||||
|
|
||||||
|
*lineptr++ = '\0'; |
||||||
|
|
||||||
|
while (isspace(*lineptr & 255)) |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (*lineptr == '(') |
||||||
|
{ |
||||||
|
/*
|
||||||
|
* Get URL... |
||||||
|
*/ |
||||||
|
|
||||||
|
lineptr ++; |
||||||
|
*url = lineptr; |
||||||
|
|
||||||
|
while (*lineptr && *lineptr != ')') |
||||||
|
{ |
||||||
|
if (isspace(*lineptr & 255)) |
||||||
|
*lineptr = '\0'; |
||||||
|
else if (*lineptr == '\"') |
||||||
|
{ |
||||||
|
lineptr ++; |
||||||
|
while (*lineptr && *lineptr != '\"') |
||||||
|
lineptr ++; |
||||||
|
|
||||||
|
if (!*lineptr) |
||||||
|
return (lineptr); |
||||||
|
} |
||||||
|
|
||||||
|
lineptr ++; |
||||||
|
} |
||||||
|
|
||||||
|
*lineptr++ = '\0'; |
||||||
|
} |
||||||
|
|
||||||
|
return (lineptr); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 'mmd_remove()' - Remove a node from its parent. |
||||||
|
*/ |
||||||
|
|
||||||
|
static void |
||||||
|
mmd_remove(mmd_t *node) /* I - Node */ |
||||||
|
{ |
||||||
|
if (node->parent) |
||||||
|
{ |
||||||
|
if (node->prev_sibling) |
||||||
|
node->prev_sibling->next_sibling = node->next_sibling; |
||||||
|
else |
||||||
|
node->parent->first_child = node->next_sibling; |
||||||
|
|
||||||
|
if (node->next_sibling) |
||||||
|
node->next_sibling->prev_sibling = node->prev_sibling; |
||||||
|
else |
||||||
|
node->parent->last_child = node->prev_sibling; |
||||||
|
|
||||||
|
node->parent = NULL; |
||||||
|
node->prev_sibling = NULL; |
||||||
|
node->next_sibling = NULL; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
/*
|
||||||
|
* Header file for miniature markdown library. |
||||||
|
* |
||||||
|
* https://github.com/michaelrsweet/mmd
|
||||||
|
* |
||||||
|
* Copyright 2017 by Michael R Sweet. |
||||||
|
* |
||||||
|
* Redistribution and use in source and binary forms, with or without |
||||||
|
* modification, are permitted provided that the following conditions are met: |
||||||
|
* |
||||||
|
* 1. Redistributions of source code must retain the above copyright notice, |
||||||
|
* this list of conditions and the following disclaimer. |
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice, |
||||||
|
* this list of conditions and the following disclaimer in the documentation |
||||||
|
* and/or other materials provided with the distribution. |
||||||
|
* |
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
||||||
|
* POSSIBILITY OF SUCH DAMAGE. |
||||||
|
*/ |
||||||
|
|
||||||
|
#ifndef MMD_H |
||||||
|
# define MMD_H |
||||||
|
|
||||||
|
/*
|
||||||
|
* Include necessary headers... |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants... |
||||||
|
*/ |
||||||
|
|
||||||
|
typedef enum mmd_type_e |
||||||
|
{ |
||||||
|
MMD_TYPE_NONE = -1, |
||||||
|
MMD_TYPE_DOCUMENT, |
||||||
|
MMD_TYPE_METADATA, |
||||||
|
MMD_TYPE_BLOCK_QUOTE, |
||||||
|
MMD_TYPE_ORDERED_LIST, |
||||||
|
MMD_TYPE_UNORDERED_LIST, |
||||||
|
MMD_TYPE_LIST_ITEM, |
||||||
|
MMD_TYPE_HEADING_1 = 10, |
||||||
|
MMD_TYPE_HEADING_2, |
||||||
|
MMD_TYPE_HEADING_3, |
||||||
|
MMD_TYPE_HEADING_4, |
||||||
|
MMD_TYPE_HEADING_5, |
||||||
|
MMD_TYPE_HEADING_6, |
||||||
|
MMD_TYPE_PARAGRAPH, |
||||||
|
MMD_TYPE_CODE_BLOCK, |
||||||
|
MMD_TYPE_THEMATIC_BREAK, |
||||||
|
MMD_TYPE_NORMAL_TEXT = 100, |
||||||
|
MMD_TYPE_EMPHASIZED_TEXT, |
||||||
|
MMD_TYPE_STRONG_TEXT, |
||||||
|
MMD_TYPE_STRUCK_TEXT, |
||||||
|
MMD_TYPE_LINKED_TEXT, |
||||||
|
MMD_TYPE_CODE_TEXT, |
||||||
|
MMD_TYPE_IMAGE, |
||||||
|
MMD_TYPE_HARD_BREAK, |
||||||
|
MMD_TYPE_SOFT_BREAK, |
||||||
|
MMD_TYPE_METADATA_TEXT |
||||||
|
} mmd_type_t; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types... |
||||||
|
*/ |
||||||
|
|
||||||
|
typedef struct _mmd_s mmd_t; |
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Functions... |
||||||
|
*/ |
||||||
|
|
||||||
|
extern void mmdFree(mmd_t *node); |
||||||
|
extern mmd_t *mmdGetFirstChild(mmd_t *node); |
||||||
|
extern mmd_t *mmdGetLastChild(mmd_t *node); |
||||||
|
extern const char *mmdGetMetadata(mmd_t *doc, const char *keyword); |
||||||
|
extern mmd_t *mmdGetNextSibling(mmd_t *node); |
||||||
|
extern mmd_t *mmdGetParent(mmd_t *node); |
||||||
|
extern mmd_t *mmdGetPrevSibling(mmd_t *node); |
||||||
|
extern const char *mmdGetText(mmd_t *node); |
||||||
|
extern mmd_type_t mmdGetType(mmd_t *node); |
||||||
|
extern const char *mmdGetURL(mmd_t *node); |
||||||
|
extern int mmdGetWhitespace(mmd_t *node); |
||||||
|
extern int mmdIsBlock(mmd_t *node); |
||||||
|
extern mmd_t *mmdLoad(const char *filename); |
||||||
|
|
||||||
|
#endif /* !MMD_H */ |
Loading…
Reference in new issue