mirror of
https://github.com/michaelrsweet/mxml.git
synced 2024-11-24 11:25:30 +00:00
Add markdown support for HTML/EPUB/DOCSET output (Issue #194)
This commit is contained in:
parent
4e6310962b
commit
c6bda8e144
25
Makefile.in
25
Makefile.in
@ -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
144
doc/body.md
Normal 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);
|
@ -21,7 +21,6 @@ pre {
|
||||
pre.example {
|
||||
background: white;
|
||||
border: dotted thin #999999;
|
||||
margin-left: 36pt;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
907
mmd.c
Normal 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
99
mmd.h
Normal 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
448
mxmldoc.c
@ -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(" ", out);
|
||||
else if (!strcmp(text, "(r)"))
|
||||
fputs("®", out);
|
||||
else if (!strcmp(text, "(tm)"))
|
||||
fputs("™", 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...
|
||||
|
Loading…
Reference in New Issue
Block a user