Add markdown support for HTML/EPUB/DOCSET output (Issue #194)

This commit is contained in:
Michael Sweet 2017-04-11 15:37:38 -04:00
parent 4e6310962b
commit c6bda8e144
10 changed files with 1594 additions and 76 deletions

View File

@ -82,7 +82,7 @@ DOCFILES = doc/mxml.html doc/mxmldoc.xsd README.md COPYING CHANGES.md
PUBLIBOBJS = mxml-attr.o mxml-entity.o mxml-file.o mxml-get.o \
mxml-index.o mxml-node.o mxml-search.o mxml-set.o
LIBOBJS = $(PUBLIBOBJS) mxml-private.o mxml-string.o
OBJS = mxmldoc.o testmxml.o zipc.o $(LIBOBJS)
OBJS = mmd.o mxmldoc.o testmxml.o zipc.o $(LIBOBJS)
ALLTARGETS = $(LIBMXML) mxmldoc testmxml mxml.xml @MXML_EPUB@
CROSSTARGETS = $(LIBMXML) mxmldoc
TARGETS = $(@TARGETS@)
@ -293,16 +293,17 @@ libmxml.1.dylib: $(LIBOBJS)
# mxmldoc
#
mxmldoc: $(LIBMXML) mxmldoc.o @ZIPC@
mxmldoc: $(LIBMXML) mxmldoc.o mmd.o @ZIPC@
echo Linking $@...
$(CC) -L. $(LDFLAGS) -o $@ mxmldoc.o @ZIPC@ -lmxml $(LIBS)
$(CC) -L. $(LDFLAGS) -o $@ mxmldoc.o mmd.o @ZIPC@ -lmxml $(LIBS)
mxmldoc-static: libmxml.a mxmldoc.o @ZIPC@
mxmldoc-static: libmxml.a mxmldoc.o mmd.o @ZIPC@
echo Linking $@...
$(CC) $(LDFLAGS) -o $@ mxmldoc.o @ZIPC@ libmxml.a $(LIBS)
$(CC) $(LDFLAGS) -o $@ mxmldoc.o mmd.o @ZIPC@ libmxml.a $(LIBS)
mxmldoc.o: mxml.h zipc.h
mxmldoc.o: mxml.h zipc.h mmd.h
zipc.o: zipc.h
mmd.o: mmd.h
#
@ -354,18 +355,14 @@ mxml.xml: mxmldoc-static mxml.h $(PUBLIBOBJS:.o=.c)
--title "Mini-XML API Reference" \
mxml.xml mxml.h $(PUBLIBOBJS:.o=.c) >doc/reference.html
./mxmldoc-static --man mxml --title "Mini-XML API" \
--intro doc/intro.man --footer doc/footer.man \
--author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
--body doc/body.man --footer doc/footer.man \
mxml.xml >doc/mxml.man
if test "x`uname`" = xDarwin; then \
./mxmldoc-static --docset org.msweet.mxml.docset \
--docversion @VERSION@ --feedname org.msweet.mxml \
--feedurl https://michaelrsweet.github.io/mxml/org.msweet.mxml.atom \
--header doc/docset.header --intro doc/docset.intro \
--css doc/docset.css --author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
--title "Mini-XML API Reference" \
--header doc/docset.header --body doc/body.md \
--css doc/docset.css \
mxml.xml || exit 1; \
$(RM) org.msweet.mxml.atom; \
xcrun docsetutil package --output org.msweet.mxml.xar \
@ -382,7 +379,7 @@ mxml.xml: mxmldoc-static mxml.h $(PUBLIBOBJS:.o=.c)
mxml.epub: mxml.xml
echo Generating EPUB API documentation...
./mxmldoc-static --header doc/docset.header \
--intro doc/docset.intro --css doc/docset.css \
--body doc/body.md \
--docversion @VERSION@ --author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
--title "Mini-XML API Reference" \

144
doc/body.md Normal file
View File

@ -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);

View File

@ -21,7 +21,6 @@ pre {
pre.example {
background: white;
border: dotted thin #999999;
margin-left: 36pt;
padding: 10px;
}

View File

@ -1,4 +1,4 @@
.TH mxml 3 "Mini-XML API" "04/08/17" "Mini-XML API"
.TH mxml 3 "Mini-XML API" "04/11/17" "Mini-XML API"
.SH NAME
mxml \- Mini-XML API
.SH INCLUDE FILE

View File

@ -11,7 +11,7 @@
.\"
.\" https://michaelrsweet.github.io/mxml
.\"
.TH mxmldoc 1 "Mini-XML" "6 April 2017" "Michael Sweet"
.TH mxmldoc 1 "Mini-XML" "11 April 2017" "Michael R Sweet"
.SH NAME
mxmldoc \- mini-xml documentation generator
.SH SYNOPSIS
@ -25,14 +25,14 @@ mxmldoc \- mini-xml documentation generator
.B mxmldoc
[ \-\-author
.I author
] [ \-\-body
.I bodyfile
] [ \-\-copyright
.I copyright
] [ \-\-footer
.I footerfile
] [ \-\-header
.I headerfile
] [ \-\-intro
.I introfile
] [ \-\-section
.I section
] [ \-\-title
@ -49,6 +49,8 @@ mxmldoc \- mini-xml documentation generator
.I directory.docset
[ \-\-author
.I author
] [ \-\-body
.I bodyfile
] [ \-\-copyright
.I copyright
] [ \-\-docversion
@ -61,8 +63,6 @@ mxmldoc \- mini-xml documentation generator
.I footerfile
] [ \-\-header
.I headerfile
] [ \-\-intro
.I introfile
] [ \-\-section
.I section
] [ \-\-title
@ -87,14 +87,14 @@ mxmldoc \- mini-xml documentation generator
.I basename
[ \-\-author
.I author
] [ \-\-body
.I bodyfile
] [ \-\-copyright
.I copyright
] [ \-\-footer
.I footerfile
] [ \-\-header
.I headerfile
] [ \-\-intro
.I introfile
] [ \-\-section
.I section
] [ \-\-title
@ -108,14 +108,14 @@ mxmldoc \- mini-xml documentation generator
.B mxmldoc
[ \-\-author
.I author
] [ \-\-body
.I bodyfile
] [ \-\-copyright
.I copyright
] [ \-\-footer
.I footerfile
] [ \-\-header
.I headerfile
] [ \-\-intro
.I introfile
] \-\-man
.I manpage
[ \-\-section
@ -134,6 +134,8 @@ mxmldoc \- mini-xml documentation generator
.I filename.epub
[ \-\-author
.I author
] [ \-\-body
.I bodyfile
] [ \-\-copyright
.I copyright
] [ \-\-docversion
@ -146,8 +148,6 @@ mxmldoc \- mini-xml documentation generator
.I footerfile
] [ \-\-header
.I headerfile
] [ \-\-intro
.I introfile
] [ \-\-section
.I section
] [ \-\-title
@ -180,6 +180,10 @@ Guide which is available at "http://www.cups.org/doc/spec-cmp.html".
.br
Specifies the name of the documentation author.
.TP 5
\-\-body bodyfile
.br
Inserts the specified file between the table of contents and references.
.TP 5
\-\-copyright "copyright text"
.br
Specifies the copyright text to use.
@ -219,10 +223,6 @@ one for the body.
.br
Inserts the specified file at the top of the output documentation.
.TP 5
\-\-intro introfile
.br
Inserts the specified file before the table of contents.
.TP 5
\-\-man manpage
.br
Generated a man page instead of HTML documentation.

View File

@ -9,7 +9,7 @@
<meta name="version" content="2.11">
<style type="text/css"><!--
body, p, h1, h2, h3, h4 {
font-family: "lucida grande", geneva, helvetica, arial, sans-serif;
font-family: sans-serif;
}
div.body h1 {
font-size: 250%;
@ -85,10 +85,18 @@ div.contents ul.contents {
}
.variable {
}
code, p.code, pre, ul.code li {
font-family: monaco, courier, monospace;
p code, li code, p.code, pre, ul.code li {
background: rgba(127,127,127,0.1);
border: thin dotted gray;
font-family: monospace;
font-size: 90%;
}
p.code, pre, ul.code li {
padding: 10px;
}
p code, li code {
padding: 2px 5px;
}
a:link, a:visited {
text-decoration: none;
}

907
mmd.c Normal file
View File

@ -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;
}
}

99
mmd.h Normal file
View File

@ -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 */

448
mxmldoc.c
View File

@ -20,6 +20,7 @@
#include "config.h"
#include "mxml.h"
#include "mmd.h"
#include <time.h>
#include <sys/stat.h>
#ifndef WIN32
@ -162,13 +163,17 @@ typedef struct
static void add_toc(toc_t *toc, int level, const char *anchor, const char *title);
static mxml_node_t *add_variable(mxml_node_t *parent, const char *name, mxml_node_t *type);
static toc_t *build_toc(mxml_node_t *doc, const char *introfile);
static toc_t *build_toc(mxml_node_t *doc, const char *bodyfile, mmd_t *body);
static mxml_node_t *find_public(mxml_node_t *node, mxml_node_t *top, const char *element, const char *name);
static void free_toc(toc_t *toc);
static char *get_comment_info(mxml_node_t *description);
static char *get_iso_date(time_t t);
static char *get_text(mxml_node_t *node, char *buffer, int buflen);
static int is_markdown(const char *filename);
static mxml_type_t load_cb(mxml_node_t *node);
static const char *markdown_anchor(const char *text);
static void markdown_write_block(FILE *out, mmd_t *parent, int mode);
static void markdown_write_inline(FILE *out, mmd_t *node, int mode);
static mxml_node_t *new_documentation(mxml_node_t **mxmldoc);
static int remove_directory(const char *path);
static void safe_strcpy(char *dst, const char *src);
@ -178,19 +183,19 @@ static void update_comment(mxml_node_t *parent, mxml_node_t *comment);
static void usage(const char *option);
static void write_description(FILE *out, mxml_node_t *description, const char *element, int summary);
#ifdef __APPLE__
static void write_docset(const char *docset, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *feedname, const char *feedurl, const char *cssfile, const char *headerfile, const char *introfile, mxml_node_t *doc, const char *footerfile);
static void write_docset(const char *docset, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *feedname, const char *feedurl, const char *cssfile, const char *headerfile, const char *bodyfile, mmd_t *body, mxml_node_t *doc, const char *footerfile);
#endif /* __APPLE__ */
static void write_element(FILE *out, mxml_node_t *doc, mxml_node_t *element, int mode);
#ifdef HAVE_ZLIB_H
static void write_epub(const char *epubfile, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *cssfile, const char *coverimage, const char *headerfile, const char *introfile, mxml_node_t *doc, const char *footerfile);
static void write_epub(const char *epubfile, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *cssfile, const char *coverimage, const char *headerfile, const char *bodyfile, mmd_t *body, mxml_node_t *doc, const char *footerfile);
#endif /* HAVE_ZLIB_H */
static void write_file(FILE *out, const char *file, int mode);
static void write_function(FILE *out, int mode, mxml_node_t *doc, mxml_node_t *function, int level);
static void write_html(const char *framefile, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *cssfile, const char *coverimage, const char *headerfile, const char *introfile, mxml_node_t *doc, const char *footerfile);
static void write_html_body(FILE *out, int mode, const char *introfile, mxml_node_t *doc);
static void write_html(const char *framefile, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *cssfile, const char *coverimage, const char *headerfile, const char *bodyfile, mmd_t *body, mxml_node_t *doc, const char *footerfile);
static void write_html_body(FILE *out, int mode, const char *bodyfile, mmd_t *body, mxml_node_t *doc);
static void write_html_head(FILE *out, int mode, const char *section, const char *title, const char *author, const char *copyright, const char *docversion, const char *cssfile);
static void write_html_toc(FILE *out, const char *title, toc_t *toc, const char *filename, const char *target);
static void write_man(const char *man_name, const char *section, const char *title, const char *author, const char *copyright, const char *headerfile, const char *introfile, mxml_node_t *doc, const char *footerfile);
static void write_man(const char *man_name, const char *section, const char *title, const char *author, const char *copyright, const char *headerfile, const char *bodyfile, mmd_t *body, mxml_node_t *doc, const char *footerfile);
static void write_scu(FILE *out, int mode, mxml_node_t *doc, mxml_node_t *scut);
static void write_string(FILE *out, const char *s, int mode);
static void write_tokens(FILE *out, mxml_node_t *doc, const char *path);
@ -221,13 +226,14 @@ main(int argc, /* I - Number of command-line args */
*footerfile = NULL, /* Footer file */
*framefile = NULL, /* Framed HTML basename */
*headerfile = NULL, /* Header file */
*introfile = NULL, /* Introduction file */
*bodyfile = NULL, /* Body file */
*coverimage = NULL, /* Cover image file */
*name = NULL, /* Name of manpage */
*path = NULL, /* Path to help file for tokens */
*section = NULL, /* Section/keywords of documentation */
*title = NULL, /* Title of documentation */
*xmlfile = NULL; /* XML file */
mmd_t *body; /* Body markdown file, if any */
int mode = OUTPUT_HTML, /* Output mode */
update = 0; /* Updated XML file */
@ -402,15 +408,15 @@ main(int argc, /* I - Number of command-line args */
else
usage(NULL);
}
else if (!strcmp(argv[i], "--intro") && !introfile)
else if ((!strcmp(argv[i], "--body") || !strcmp(argv[i], "--intro")) && !bodyfile)
{
/*
* Set intro file...
* Set body file...
*/
i ++;
if (i < argc)
introfile = argv[i];
bodyfile = argv[i];
else
usage(NULL);
}
@ -601,6 +607,39 @@ main(int argc, /* I - Number of command-line args */
}
}
/*
* Load the body file and collect the default metadata values, if present.
*/
if (is_markdown(bodyfile))
body = mmdLoad(bodyfile);
else
body = NULL;
if (!title)
title = mmdGetMetadata(body, "title");
if (!title)
title = "Documentation";
if (!author)
author = mmdGetMetadata(body, "author");
if (!author)
author = "Unknown";
if (!copyright)
copyright = mmdGetMetadata(body, "copyright");
if (!copyright)
copyright = "Unknown";
if (!docversion)
docversion = mmdGetMetadata(body, "version");
if (!docversion)
docversion = "0.0";
/*
* Write output...
*/
switch (mode)
{
case OUTPUT_DOCSET :
@ -609,7 +648,7 @@ main(int argc, /* I - Number of command-line args */
*/
#ifdef __APPLE__
write_docset(docset, section, title ? title : "Documentation", author ? author : "Unknown", copyright ? copyright : "Unknown", docversion ? docversion : "0.0", feedname, feedurl, cssfile, headerfile, introfile, mxmldoc, footerfile);
write_docset(docset, section, title, author, copyright, docversion, feedname, feedurl, cssfile, headerfile, bodyfile, body, mxmldoc, footerfile);
#else
fputs("mxmldoc: Sorry, Xcode documentation sets can only be created on macOS.\n", stderr);
#endif /* __APPLE__ */
@ -621,7 +660,7 @@ main(int argc, /* I - Number of command-line args */
*/
#ifdef HAVE_ZLIB_H
write_epub(epubfile, section, title ? title : "Documentation", author ? author : "Unknown", copyright ? copyright : "Unknown", docversion ? docversion : "0.0", cssfile, coverimage, headerfile, introfile, mxmldoc, footerfile);
write_epub(epubfile, section, title, author, copyright, docversion, cssfile, coverimage, headerfile, bodyfile, body, mxmldoc, footerfile);
#else
fputs("mxmldoc: Sorry, not compiled with EPUB support.\n", stderr);
#endif /* HAVE_ZLIB_H */
@ -632,7 +671,7 @@ main(int argc, /* I - Number of command-line args */
* Write HTML documentation...
*/
write_html(framefile, section, title ? title : "Documentation", author ? author : "Unknown", copyright ? copyright : "Unknown", docversion ? docversion : "0.0", cssfile, coverimage, headerfile, introfile, mxmldoc, footerfile);
write_html(framefile, section, title, author, copyright, docversion, cssfile, coverimage, headerfile, bodyfile, body, mxmldoc, footerfile);
break;
case OUTPUT_MAN :
@ -640,7 +679,7 @@ main(int argc, /* I - Number of command-line args */
* Write manpage documentation...
*/
write_man(name, section, title, author ? author : "Unknown", copyright ? copyright : "Unknown", headerfile, introfile, mxmldoc, footerfile);
write_man(name, section, title, author, copyright, headerfile, bodyfile, body, mxmldoc, footerfile);
break;
case OUTPUT_TOKENS :
@ -653,6 +692,9 @@ main(int argc, /* I - Number of command-line args */
break;
}
if (body)
mmdFree(body);
/*
* Delete the tree and return...
*/
@ -824,10 +866,11 @@ add_variable(mxml_node_t *parent, /* I - Parent node */
static toc_t * /* O - Table of contents */
build_toc(mxml_node_t *doc, /* I - Documentation */
const char *introfile) /* I - Introduction file */
const char *bodyfile, /* I - Body file */
mmd_t *body) /* I - Markdown body */
{
toc_t *toc; /* Array of headings */
FILE *fp; /* Intro file */
FILE *fp; /* Body file */
mxml_node_t *function, /* Current function */
*scut, /* Struct/class/union/typedef */
*arg; /* Current argument */
@ -835,17 +878,60 @@ build_toc(mxml_node_t *doc, /* I - Documentation */
/*
* Make an initial allocation of 100 entries...
* Make a new table-of-contents...
*/
if ((toc = calloc(1, sizeof(toc_t))) == NULL)
return (NULL);
/*
* Scan the intro file for headings...
* Scan the body file for headings...
*/
if (introfile && (fp = fopen(introfile, "r")) != NULL)
if (body)
{
mmd_t *node, /* Current node */
*tnode, /* Title node */
*next; /* Next node */
mmd_type_t type; /* Node type */
char title[1024], /* Heading title */
*ptr; /* Pointer into title */
for (node = mmdGetFirstChild(body); node; node = next)
{
type = mmdGetType(node);
if (type == MMD_TYPE_HEADING_1 || type == MMD_TYPE_HEADING_2)
{
title[sizeof(title) - 1] = '\0';
for (tnode = mmdGetFirstChild(node), ptr = title; tnode; tnode = mmdGetNextSibling(tnode))
{
if (mmdGetWhitespace(tnode) && ptr < (title + sizeof(title) - 1))
*ptr++ = ' ';
strncpy(ptr, mmdGetText(tnode), sizeof(title) - (ptr - title) - 1);
ptr += strlen(ptr);
}
add_toc(toc, type - MMD_TYPE_HEADING_1 + 1, markdown_anchor(title), title);
next = NULL;
}
else
next = mmdGetFirstChild(node);
if ((next = mmdGetNextSibling(node)) == NULL)
{
next = mmdGetParent(node);
while (next && mmdGetNextSibling(next) == NULL)
next = mmdGetParent(next);
next = mmdGetNextSibling(next);
}
}
}
else if (bodyfile && (fp = fopen(bodyfile, "r")) != NULL)
{
char line[8192], /* Line from file */
*ptr, /* Pointer in line */
@ -1311,6 +1397,20 @@ get_text(mxml_node_t *node, /* I - Node to get */
}
/*
* 'is_markdown()' - Determine whether a file is markdown text.
*/
static int /* O - 1 if markdown, 0 otherwise */
is_markdown(const char *filename) /* I - File to check */
{
const char *ext = filename ? strstr(filename, ".md") : NULL;
/* Pointer to extension */
return (ext && !ext[3]);
}
/*
* 'load_cb()' - Set the type of child nodes.
*/
@ -1325,6 +1425,252 @@ load_cb(mxml_node_t *node) /* I - Node */
}
/*
* 'markdown_anchor()' - Return the HTML anchor for a given title.
*/
static const char * /* O - HTML anchor */
markdown_anchor(const char *text) /* I - Title text */
{
char *bufptr; /* Pointer into buffer */
static char buffer[1024]; /* Buffer for anchor string */
for (bufptr = buffer; *text && bufptr < (buffer + sizeof(buffer) - 1); text ++)
{
if ((*text >= '0' && *text <= '9') || (*text >= 'a' && *text <= 'z') || (*text >= 'A' && *text <= 'Z') || *text == '.' || *text == '-')
*bufptr++ = *text;
}
*bufptr = '\0';
return (buffer);
}
/*
* 'markdown_write_block()' - Write a markdown block.
*/
static void
markdown_write_block(FILE *out, /* I - Output file */
mmd_t *parent, /* I - Parent node */
int mode) /* I - Output mode */
{
mmd_t *node; /* Current child node */
mmd_type_t type; /* Node type */
type = mmdGetType(parent);
if (mode == OUTPUT_MAN)
{
}
else
{
const char *element; /* Enclosing element, if any */
switch (type)
{
case MMD_TYPE_BLOCK_QUOTE :
element = "blockquote";
break;
case MMD_TYPE_ORDERED_LIST :
element = "ol";
break;
case MMD_TYPE_UNORDERED_LIST :
element = "ul";
break;
case MMD_TYPE_LIST_ITEM :
element = "li";
break;
case MMD_TYPE_HEADING_1 :
element = "h2"; /* Offset since title is H1 for mxmldoc output */
break;
case MMD_TYPE_HEADING_2 :
element = "h3"; /* Offset since title is H1 for mxmldoc output */
break;
case MMD_TYPE_HEADING_3 :
element = "h4"; /* Offset since title is H1 for mxmldoc output */
break;
case MMD_TYPE_HEADING_4 :
element = "h5"; /* Offset since title is H1 for mxmldoc output */
break;
case MMD_TYPE_HEADING_5 :
element = "h6"; /* Offset since title is H1 for mxmldoc output */
break;
case MMD_TYPE_HEADING_6 :
element = "h6";
break;
case MMD_TYPE_PARAGRAPH :
element = "p";
break;
case MMD_TYPE_CODE_BLOCK :
fputs(" <pre><code>", out);
for (node = mmdGetFirstChild(parent); node; node = mmdGetNextSibling(node))
write_string(out, mmdGetText(node), mode);
fputs("</code></pre>\n", out);
return;
case MMD_TYPE_THEMATIC_BREAK :
if (mode == OUTPUT_EPUB)
fputs(" <hr />\n", out);
else
fputs(" <hr>\n", out);
return;
default :
element = NULL;
break;
}
if (type >= MMD_TYPE_HEADING_1 && type <= MMD_TYPE_HEADING_6)
{
/*
* Add an anchor...
*/
fprintf(out, " <%s><a id=\"", element);
for (node = mmdGetFirstChild(parent); node; node = mmdGetNextSibling(node))
fputs(markdown_anchor(mmdGetText(node)), out);
fputs("\">", out);
}
else if (element)
fprintf(out, " <%s>%s", element, type <= MMD_TYPE_UNORDERED_LIST ? "\n" : "");
for (node = mmdGetFirstChild(parent); node; node = mmdGetNextSibling(node))
{
if (mmdIsBlock(node))
markdown_write_block(out, node, mode);
else
markdown_write_inline(out, node, mode);
}
if (type >= MMD_TYPE_HEADING_1 && type <= MMD_TYPE_HEADING_6)
fprintf(out, "</a></%s>\n", element);
else if (element)
fprintf(out, "</%s>\n", element);
}
}
/*
* 'markdown_write_inline()' - Write an inline markdown node.
*/
static void
markdown_write_inline(FILE *out, /* I - Output file */
mmd_t *node, /* I - Node to write */
int mode) /* I - Output mode */
{
const char *text, /* Text to write */
*url; /* URL to write */
if (mmdGetWhitespace(node))
fputc(' ', out);
text = mmdGetText(node);
url = mmdGetURL(node);
if (mode == OUTPUT_MAN)
{
}
else
{
const char *element; /* Encoding element, if any */
switch (mmdGetType(node))
{
case MMD_TYPE_EMPHASIZED_TEXT :
element = "em";
break;
case MMD_TYPE_STRONG_TEXT :
element = "strong";
break;
case MMD_TYPE_STRUCK_TEXT :
element = "del";
break;
case MMD_TYPE_LINKED_TEXT :
element = "a";
break;
case MMD_TYPE_CODE_TEXT :
element = "code";
break;
case MMD_TYPE_IMAGE :
fputs("<img src=\"", out);
write_string(out, url, mode);
fputs("\" alt=\"", out);
write_string(out, text, mode);
if (mode == OUTPUT_EPUB)
fputs("\" />", out);
else
fputs("\">", out);
return;
case MMD_TYPE_HARD_BREAK :
if (mode == OUTPUT_EPUB)
fputs("<br />\n", out);
else
fputs("<br>\n", out);
return;
case MMD_TYPE_SOFT_BREAK :
if (mode == OUTPUT_EPUB)
fputs("<wbr />", out);
else
fputs("<wbr>", out);
return;
case MMD_TYPE_METADATA_TEXT :
return;
default :
element = NULL;
break;
}
if (url)
{
if (!strcmp(url, "@"))
fprintf(out, "<a href=\"#%s\">", markdown_anchor(text));
else
fprintf(out, "<a href=\"%s\">", url);
}
else if (element)
fprintf(out, "<%s>", element);
if (!strcmp(text, "(c)"))
fputs("&#160;", out);
else if (!strcmp(text, "(r)"))
fputs("&#174;", out);
else if (!strcmp(text, "(tm)"))
fputs("&#8482;", out);
else
write_string(out, text, mode);
if (element)
fprintf(out, "</%s>", element);
}
}
/*
* 'new_documentation()' - Create a new documentation tree.
*/
@ -3157,6 +3503,9 @@ usage(const char *option) /* I - Unknown option */
puts("Usage: mxmldoc [options] [filename.xml] [source files] >filename.html");
puts("Options:");
puts(" --author name Set author name");
puts(" --body bodyfile Set body file (markdown supported)");
puts(" --copyright text Set copyright text");
puts(" --css filename.css Set CSS stylesheet file");
puts(" --docset bundleid.docset Generate documentation set");
puts(" --docversion version Set documentation version");
@ -3166,7 +3515,6 @@ usage(const char *option) /* I - Unknown option */
puts(" --footer footerfile Set footer file");
puts(" --framed basename Generate framed HTML to basename*.html");
puts(" --header headerfile Set header file");
puts(" --intro introfile Set introduction file");
puts(" --man name Generate man page");
puts(" --no-output Do no generate documentation file");
puts(" --section section Set section name");
@ -3376,7 +3724,8 @@ write_docset(const char *docset, /* I - Documentation set directory */
const char *feedurl, /* I - Feed URL for doc set */
const char *cssfile, /* I - Stylesheet file */
const char *headerfile, /* I - Header file */
const char *introfile, /* I - Intro file */
const char *bodyfile, /* I - Body file */
mmd_t *body, /* I - Markdown body */
mxml_node_t *doc, /* I - XML documentation */
const char *footerfile) /* I - Footer file */
{
@ -3389,7 +3738,7 @@ write_docset(const char *docset, /* I - Documentation set directory */
* Create the table-of-contents entries...
*/
toc = build_toc(doc, introfile);
toc = build_toc(doc, bodyfile, body);
/*
* Create an Xcode documentation set - start by removing any existing
@ -3657,7 +4006,7 @@ write_docset(const char *docset, /* I - Documentation set directory */
fputs(" <div class=\"body\">\n", out);
write_html_body(out, OUTPUT_HTML, introfile, doc);
write_html_body(out, OUTPUT_HTML, bodyfile, body, doc);
/*
* Footer...
@ -3801,7 +4150,8 @@ write_epub(const char *epubfile, /* I - EPUB file (output) */
const char *cssfile, /* I - Stylesheet file */
const char *coverimage, /* I - Cover image file */
const char *headerfile, /* I - Header file */
const char *introfile, /* I - Intro file */
const char *bodyfile, /* I - Body file */
mmd_t *body, /* I - Markdown body */
mxml_node_t *doc, /* I - XML documentation */
const char *footerfile) /* I - Footer file */
{
@ -3900,7 +4250,7 @@ write_epub(const char *epubfile, /* I - EPUB file (output) */
fputs(" <div class=\"body\">\n", fp);
write_html_body(fp, OUTPUT_EPUB, introfile, doc);
write_html_body(fp, OUTPUT_EPUB, bodyfile, body, doc);
/*
* Footer...
@ -4079,7 +4429,7 @@ write_epub(const char *epubfile, /* I - EPUB file (output) */
if ((epubf = zipcCreateFile(epub, "OEBPS/nav.xhtml", 1)) != NULL)
{
toc = build_toc(doc, introfile);
toc = build_toc(doc, bodyfile, body);
zipcFilePrintf(epubf, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<!DOCTYPE html>\n"
@ -4142,8 +4492,8 @@ write_file(FILE *out, /* I - Output file */
const char *file, /* I - File to copy */
int mode) /* I - Output mode */
{
FILE *fp; /* Copy file */
char line[8192]; /* Line from file */
FILE *fp; /* Copy file */
char line[8192]; /* Line from file */
if ((fp = fopen(file, "r")) == NULL)
@ -4338,7 +4688,8 @@ write_html(const char *framefile, /* I - Framed HTML basename */
const char *cssfile, /* I - Stylesheet file */
const char *coverimage, /* I - Cover image file */
const char *headerfile, /* I - Header file */
const char *introfile, /* I - Intro file */
const char *bodyfile, /* I - Body file */
mmd_t *body, /* I - Markdown body */
mxml_node_t *doc, /* I - XML documentation */
const char *footerfile) /* I - Footer file */
{
@ -4352,7 +4703,7 @@ write_html(const char *framefile, /* I - Framed HTML basename */
* Create the table-of-contents entries...
*/
toc = build_toc(doc, introfile);
toc = build_toc(doc, bodyfile, body);
if (framefile)
{
@ -4533,7 +4884,7 @@ write_html(const char *framefile, /* I - Framed HTML basename */
fputs(" <div class=\"body\">\n", out);
write_html_body(out, OUTPUT_HTML, introfile, doc);
write_html_body(out, OUTPUT_HTML, bodyfile, body, doc);
/*
* Footer...
@ -4569,7 +4920,8 @@ static void
write_html_body(
FILE *out, /* I - Output file */
int mode, /* I - HTML or EPUB/XHTML output */
const char *introfile, /* I - Intro file */
const char *bodyfile, /* I - Body file */
mmd_t *body, /* I - Markdown body */
mxml_node_t *doc) /* I - XML documentation */
{
mxml_node_t *function, /* Current function */
@ -4582,11 +4934,13 @@ write_html_body(
/*
* Intro...
* Body...
*/
if (introfile)
write_file(out, introfile, mode);
if (body)
markdown_write_block(out, body, mode);
else if (bodyfile)
write_file(out, bodyfile, mode);
/*
* List of classes...
@ -4906,8 +5260,7 @@ write_html_head(FILE *out, /* I - Output file */
*/
fputs("body, p, h1, h2, h3, h4 {\n"
" font-family: \"lucida grande\", geneva, helvetica, arial, "
"sans-serif;\n"
" font-family: sans-serif;\n"
"}\n"
"div.body h1 {\n"
" font-size: 250%;\n"
@ -4983,10 +5336,18 @@ write_html_head(FILE *out, /* I - Output file */
"}\n"
".variable {\n"
"}\n"
"code, p.code, pre, ul.code li {\n"
" font-family: monaco, courier, monospace;\n"
"p code, li code, p.code, pre, ul.code li {\n"
" background: rgba(127,127,127,0.1);\n"
" border: thin dotted gray;\n"
" font-family: monospace;\n"
" font-size: 90%;\n"
"}\n"
"p.code, pre, ul.code li {\n"
" padding: 10px;\n"
"}\n"
"p code, li code {\n"
" padding: 2px 5px;\n"
"}\n"
"a:link, a:visited {\n"
" text-decoration: none;\n"
"}\n"
@ -5127,7 +5488,8 @@ write_man(const char *man_name, /* I - Name of manpage */
const char *author, /* I - Author's name */
const char *copyright, /* I - Copyright string */
const char *headerfile, /* I - Header file */
const char *introfile, /* I - Intro file */
const char *bodyfile, /* I - Body file */
mmd_t *body, /* I - Markdown body */
mxml_node_t *doc, /* I - XML documentation */
const char *footerfile) /* I - Footer file */
{
@ -5191,8 +5553,10 @@ write_man(const char *man_name, /* I - Name of manpage */
* Intro...
*/
if (introfile)
write_file(stdout, introfile, OUTPUT_MAN);
if (body)
markdown_write_block(stdout, body, OUTPUT_MAN);
else if (bodyfile)
write_file(stdout, bodyfile, OUTPUT_MAN);
/*
* List of classes...