scratch->padded = 0;
break;
case BLOCK_META:
- print("<meta>\n");
- //token_tree_describe(t, source);
- print("</meta>\n");
break;
case BLOCK_PARA:
case BLOCK_DEF_CITATION:
mmd_export_token_tree_html(out, source, t->child, offset, scratch);
}
break;
+ case PAIR_BRACKET_VARIABLE:
+ print_token(t);
+ break;
case PAIR_CRITIC_ADD:
// Ignore if we're rejecting
if (scratch->extensions & EXT_CRITIC_REJECT)
}
+void mmd_start_complete_html(DString * out, const char * source, scratch_pad * scratch) {
+ print("<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\"/>\n");
+
+ // Iterate over metadata keys
+ meta * m;
+
+ for (m = scratch->meta_hash; m != NULL; m = m->hh.next) {
+ if (strcmp(m->key, "baseheaderlevel") == 0) {
+ } else if (strcmp(m->key, "bibtex") == 0) {
+ } else if (strcmp(m->key, "css") == 0) {
+ print("\t<link type=\"text/css\" rel=\"stylesheet\" href=\"");
+ mmd_print_string_html(out, m->value, false);
+ print("\"/>\n");
+ } else if (strcmp(m->key, "htmlfooter") == 0) {
+ } else if (strcmp(m->key, "htmlheader") == 0) {
+ } else if (strcmp(m->key, "htmlheaderlevel") == 0) {
+ } else if (strcmp(m->key, "lang") == 0) {
+ } else if (strcmp(m->key, "latexfooter") == 0) {
+ } else if (strcmp(m->key, "latexinput") == 0) {
+ } else if (strcmp(m->key, "latexmode") == 0) {
+ } else if (strcmp(m->key, "mmdfooter") == 0) {
+ } else if (strcmp(m->key, "mmdheader") == 0) {
+ } else if (strcmp(m->key, "quoteslanguage") == 0) {
+ } else if (strcmp(m->key, "title") == 0) {
+ print("\t<title>");
+ mmd_print_string_html(out, m->value, false);
+ print("</title>\n");
+ } else if (strcmp(m->key, "transcludebase") == 0) {
+ } else if (strcmp(m->key, "xhtmlheader") == 0) {
+ } else if (strcmp(m->key, "xhtmlheaderlevel") == 0) {
+ } else {
+ print("\t<meta name=\"");
+ mmd_print_string_html(out, m->key, false);
+ print("\" content=\"");
+ mmd_print_string_html(out, m->value, false);
+ print("\"/>\n");
+ }
+ }
+
+ print("</head>\n<body>\n\n");
+}
+
+
+void mmd_end_complete_html(DString * out, const char * source, scratch_pad * scratch) {
+ print("\n\n</body>\n</html>\n");
+}
+
+
void mmd_export_token_tree_html_raw(DString * out, const char * source, token * t, size_t offset, scratch_pad * scratch) {
while (t != NULL) {
if (scratch->skip_token) {
void mmd_export_citation_list_html(DString * out, const char * source, scratch_pad * scratch);
void mmd_export_footnote_list_html(DString * out, const char * source, scratch_pad * scratch);
+void mmd_start_complete_html(DString * out, const char * source, scratch_pad * scratch);
+void mmd_end_complete_html(DString * out, const char * source, scratch_pad * scratch);
+
#endif
#define kBUFFERSIZE 4096 // How many bytes to read at a time
// argtable structs
-struct arg_lit *a_help, *a_version, *a_compatibility, *a_nolabels, *a_batch, *a_accept, *a_reject;
+struct arg_lit *a_help, *a_version, *a_compatibility, *a_nolabels, *a_batch, *a_accept, *a_reject, *a_full, *a_snippet;
struct arg_str *a_format, *a_lang;
struct arg_file *a_file, *a_o;
struct arg_end *a_end;
a_batch = arg_lit0("b", "batch", "process each file separately"),
a_compatibility = arg_lit0("c", "compatibility", "Markdown compatibility mode"),
+ a_full = arg_lit0("f", "full", "force a complete document"),
+ a_snippet = arg_lit0("s", "snippet", "force a snippet"),
a_rem2 = arg_rem("", ""),
extensions &= ~(EXT_CRITIC_REJECT | EXT_CRITIC_ACCEPT);
}
+ if (a_full->count > 0) {
+ // Force complete document
+ extensions |= EXT_COMPLETE;
+ }
+
+ if (a_snippet->count > 0) {
+ // Force snippet
+ extensions |= EXT_SNIPPET;
+ }
+
if (a_format->count > 0) {
if (strcmp(a_format->sval[0], "html") == 0)
format = FORMAT_HTML;
e->footnote_stack = stack_new(0);
e->header_stack = stack_new(0);
e->link_stack = stack_new(0);
+ e->metadata_stack = stack_new(0);
e->pairings1 = token_pair_engine_new();
e->pairings2 = token_pair_engine_new();
footnote_free(stack_pop(e->footnote_stack));
}
stack_free(e->footnote_stack);
-
+
+ // Metadata needs to be freed
+ while (e->metadata_stack->size) {
+ meta_free(stack_pop(e->metadata_stack));
+ }
+ stack_free(e->metadata_stack);
+
free(e);
}
while (t != NULL) {
switch (t->type) {
+ case BLOCK_META:
+ // Do we treat this like metadata?
+ if (!(e->extensions & EXT_COMPATIBILITY) &&
+ !(e->extensions & EXT_NO_METADATA))
+ return;
+ // This is not metadata
+ t->type = BLOCK_PARA;
case DOC_START_TOKEN:
case BLOCK_BLOCKQUOTE:
case BLOCK_H1:
case BLOCK_LIST_ENUMERATED_LOOSE:
case BLOCK_LIST_ITEM:
case BLOCK_LIST_ITEM_TIGHT:
- case BLOCK_META:
case BLOCK_PARA:
case BLOCK_TABLE:
// Assign child tokens of blocks
}
}
+
void recursive_parse_blockquote(mmd_engine * e, token * block) {
// Strip blockquote markers (if present)
strip_quote_markers_from_block(e, block);
}
-void strip_line_tokens_from_block(token * block) {
+void metadata_stack_describe(mmd_engine * e) {
+ meta * m;
+
+ for (int i = 0; i < e->metadata_stack->size; ++i)
+ {
+ m = stack_peek_index(e->metadata_stack, i);
+ fprintf(stderr, "'%s': '%s'\n", m->key, m->value);
+ }
+}
+
+
+void strip_line_tokens_from_metadata(mmd_engine * e, token * metadata) {
+ token * l = metadata->child;
+ char * source = e->dstr->str;
+
+ meta * m = NULL;
+ size_t start, len;
+
+ DString * d = d_string_new("");
+
+ while (l) {
+ switch (l->type) {
+ case LINE_META:
+ if (m) {
+ meta_set_value(m, d->str);
+ d_string_erase(d, 0, -1);
+ }
+ len = scan_meta_key(&source[l->start]);
+ m = meta_new(source, l->start, len);
+ start = l->start + len + 1;
+ len = l->start + l->len - start - 1;
+ d_string_append_c_array(d, &source[start], len);
+ stack_push(e->metadata_stack, m);
+ break;
+ case LINE_INDENTED_TAB:
+ case LINE_INDENTED_SPACE:
+ while (l->len && char_is_whitespace(source[l->start])) {
+ l->start++;
+ l->len--;
+ }
+ case LINE_PLAIN:
+ d_string_append_c(d, '\n');
+ d_string_append_c_array(d, &source[l->start], l->len);
+ break;
+ default:
+ fprintf(stderr, "ERROR!\n");
+ token_describe(l, NULL);
+ break;
+ }
+
+ l = l->next;
+ }
+
+ // Finish last line
+ if (m) {
+ meta_set_value(m, d->str);
+ }
+
+ d_string_free(d, true);
+}
+
+
+void strip_line_tokens_from_block(mmd_engine * e, token * block) {
if ((block == NULL) || (block->child == NULL))
return;
token * l = block->child;
- // Strip trailing empty lines from indented code blocks
- if (block->type == BLOCK_CODE_INDENTED) {
- while (l->tail->type == LINE_EMPTY)
- token_remove_last_child(block);
+ // Custom actions
+ switch (block->type) {
+ case BLOCK_META:
+ // Handle metadata differently
+ return strip_line_tokens_from_metadata(e, block);
+ case BLOCK_CODE_INDENTED:
+ // Strip trailing empty lines from indented code blocks
+ while (l->tail->type == LINE_EMPTY)
+ token_remove_last_child(block);
+ break;
}
token * children = NULL;
token * temp;
-
// Move contents of line directly into the parent block
while (l != NULL) {
switch (l->type) {
stack * header_stack;
stack * footnote_stack;
stack * link_stack;
+ stack * metadata_stack;
short language;
short quotes_lang;
/// Expose routines to lemon parser
void recursive_parse_list_item(mmd_engine * e, token * block);
void recursive_parse_blockquote(mmd_engine * e, token * block);
-void strip_line_tokens_from_block(token * block);
+void strip_line_tokens_from_block(mmd_engine * e, token * block);
void is_para_html(mmd_engine * e, token * block);
break;
case 1: /* blocks ::= blocks block */
{
- strip_line_tokens_from_block(yymsp[0].minor.yy0);
+ strip_line_tokens_from_block(engine, yymsp[0].minor.yy0);
if (yymsp[-1].minor.yy0 == NULL) { yymsp[-1].minor.yy0 = yymsp[0].minor.yy0; yymsp[0].minor.yy0 = NULL;}
yylhsminor.yy0 = yymsp[-1].minor.yy0;
token_chain_append(yylhsminor.yy0, yymsp[0].minor.yy0);
break;
case 2: /* blocks ::= block */
{
- strip_line_tokens_from_block(yymsp[0].minor.yy0);
+ strip_line_tokens_from_block(engine, yymsp[0].minor.yy0);
#ifndef NDEBUG
fprintf(stderr, "First block %d\n", yymsp[0].minor.yy0->type);
#endif
blocks(A) ::= blocks(B) block(C).
{
- strip_line_tokens_from_block(C);
+ strip_line_tokens_from_block(engine, C);
if (B == NULL) { B = C; C = NULL;}
A = B;
token_chain_append(A, C);
}
blocks(A) ::= block(B).
{
- strip_line_tokens_from_block(B);
+ strip_line_tokens_from_block(engine, B);
#ifndef NDEBUG
fprintf(stderr, "First block %d\n", B->type);
#endif
-/* Generated by re2c 0.14.3 on Tue Jan 24 17:14:02 2017 */
+/* Generated by re2c 0.14.3 on Sat Jan 28 15:02:52 2017 */
/**
MultiMarkdown 6 -- Lightweight markup processor to produce HTML, LaTeX, and more.
}
+size_t scan_meta_key(const char * c) {
+ const char * marker = NULL;
+ const char * start = c;
+
+
+{
+ char yych;
+ yych = *c;
+ switch (yych) {
+ case '\n': goto yy636;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z': goto yy637;
+ default: goto yy639;
+ }
+yy636:
+ { return 0; }
+yy637:
+ ++c;
+ yych = *c;
+ goto yy641;
+yy638:
+ { return (size_t)( c - start ); }
+yy639:
+ yych = *++c;
+ goto yy636;
+yy640:
+ ++c;
+ yych = *c;
+yy641:
+ switch (yych) {
+ case ' ':
+ case '!':
+ case '"':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '(':
+ case ')':
+ case '*':
+ case '+':
+ case ',':
+ case '-':
+ case '.':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ case 'G':
+ case 'H':
+ case 'I':
+ case 'J':
+ case 'K':
+ case 'L':
+ case 'M':
+ case 'N':
+ case 'O':
+ case 'P':
+ case 'Q':
+ case 'R':
+ case 'S':
+ case 'T':
+ case 'U':
+ case 'V':
+ case 'W':
+ case 'X':
+ case 'Y':
+ case 'Z':
+ case '_':
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'g':
+ case 'h':
+ case 'i':
+ case 'j':
+ case 'k':
+ case 'l':
+ case 'm':
+ case 'n':
+ case 'o':
+ case 'p':
+ case 'q':
+ case 'r':
+ case 's':
+ case 't':
+ case 'u':
+ case 'v':
+ case 'w':
+ case 'x':
+ case 'y':
+ case 'z': goto yy640;
+ default: goto yy638;
+ }
+}
+
+}
+
#ifdef TEST
void Test_scan_url(CuTest* tc) {
size_t scan_html_block(const char * c);
size_t scan_html_line(const char * c);
size_t scan_key(const char * c);
+size_t scan_meta_key(const char * c);
size_t scan_meta_line(const char * c);
size_t scan_ref_citation(const char * c);
size_t scan_ref_foot(const char * c);
}
+size_t scan_meta_key(const char * c) {
+ const char * marker = NULL;
+ const char * start = c;
+
+/*!re2c
+ meta_key { return (size_t)( c - start ); }
+ .? { return 0; }
+*/
+}
+
#ifdef TEST
void Test_scan_url(CuTest* tc) {
void store_link(scratch_pad * scratch, link * l);
+void store_metadata(scratch_pad * scratch, meta * m);
+
/// Temporary storage while exporting parse tree to output format
scratch_pad * scratch_pad_new(mmd_engine * e) {
p->list_is_tight = false; // Tight vs Loose list
p->skip_token = 0; // Skip over next n tokens
- p->link_hash = NULL; // Store defined links in a hash
+ p->extensions = e->extensions;
+ p->quotes_lang = e->quotes_lang;
+ p->language = e->language;
+ // Store links in a hash for rapid retrieval when exporting
+ p->link_hash = NULL;
link * l;
for (int i = 0; i < e->link_stack->size; ++i)
store_link(p, l);
}
+ // Store footnotes in a hash for rapid retrieval when exporting
p->used_footnotes = stack_new(0); // Store footnotes as we use them
p->inline_footnotes_to_free = stack_new(0); // Inline footnotes need to be freed
p->footnote_being_printed = 0;
store_footnote(p, f);
}
+ // Store citations in a hash for rapid retrieval when exporting
p->used_citations = stack_new(0);
p->inline_citations_to_free = stack_new(0);
p->citation_being_printed = 0;
store_citation(p, f);
}
+ // Store links in a hash for rapid retrieval when exporting
+ p->meta_hash = NULL;
+ meta * m;
- p->extensions = e->extensions;
- p->quotes_lang = e->quotes_lang;
- p->language = e->language;
+ for (int i = 0; i < e->metadata_stack->size; ++i)
+ {
+ m = stack_peek_index(e->metadata_stack, i);
+
+ store_metadata(p, m);
+ }
}
return p;
}
stack_free(scratch->inline_citations_to_free);
+ // Free metadata hash
+ meta * m, * m_tmp;
+
+ HASH_ITER(hh, scratch->meta_hash, m, m_tmp) {
+ HASH_DEL(scratch->meta_hash, m); // Remove item from hash
+ // Don't free meta pointer since it is freed with the mmd_engine
+ //meta_free(m);
+ }
free(scratch);
}
}
+void store_metadata(scratch_pad * scratch, meta * m) {
+ meta * temp;
+
+ // Store by `key`
+ HASH_FIND_STR(scratch->meta_hash, m->key, temp);
+
+ if (!temp) {
+ HASH_ADD_KEYPTR(hh, scratch->meta_hash, m->key, strlen(m->key), m);
+ }
+}
+
+
void link_free(link * l) {
free(l->label_text);
free(l->clean_text);
}
+meta * meta_new(const char * source, size_t key_start, size_t len) {
+ meta * m = malloc(sizeof(meta));
+ char * key;
+
+ if (m) {
+ key = strndup(&source[key_start], len);
+ m->key = label_from_string(key);
+ free(key);
+ m->value = NULL;
+ }
+
+ return m;
+}
+
+
+void meta_set_value(meta * m, const char * value) {
+ if (value) {
+ if (m->value)
+ free(m->value);
+
+ m->value = clean_string(value, false);
+ }
+}
+
+
+void meta_free(meta * m) {
+ free(m->key);
+ free(m->value);
+
+ free(m);
+}
+
+
+/// Find metadata based on key
+meta * extract_meta_from_stack(scratch_pad * scratch, const char * target) {
+ char * key = clean_string(target, true);
+
+ meta * temp = NULL;
+
+ HASH_FIND_STR(scratch->meta_hash, key, temp);
+
+ free(key);
+
+ return temp;
+}
+
+
bool definition_extract(mmd_engine * e, token ** remainder) {
char * source = e->dstr->str;
token * label = NULL;
}
}
+
+/// Parse metadata
+void process_metadata_stack(mmd_engine * e, scratch_pad * scratch) {
+ if ((scratch->extensions & EXT_NO_METADATA) ||
+ (scratch->extensions & EXT_COMPATIBILITY))
+ return;
+
+ meta * m;
+
+ for (int i = 0; i < e->metadata_stack->size; ++i)
+ {
+ // Check for certain metadata keys
+ m = stack_peek_index(e->metadata_stack, i);
+
+ // Certain keys do not force complete documents
+ if (!(scratch->extensions & EXT_COMPLETE) &&
+ !(scratch->extensions & EXT_SNIPPET)) {
+ if ((strcmp(m->key, "baseheaderlevel") != 0) &&
+ (strcmp(m->key, "xhtmlheaderlevel") != 0) &&
+ (strcmp(m->key, "htmlheaderlevel") != 0) &&
+ (strcmp(m->key, "latexheaderlevel") != 0) &&
+ (strcmp(m->key, "odfheaderlevel") != 0) &&
+ (strcmp(m->key, "xhtmlheader") != 0) &&
+ (strcmp(m->key, "htmlheader") != 0) &&
+ (strcmp(m->key, "quoteslanguage") != 0)) {
+ // We found a key that is not in the list, so
+ // Force a complete document
+ scratch->extensions |= EXT_COMPLETE;
+ }
+ }
+ }
+}
+
+
void mmd_export_token_tree(DString * out, mmd_engine * e, short format) {
// Process potential reference definitions
// Create scratch pad
scratch_pad * scratch = scratch_pad_new(e);
+ // Process metadata
+ process_metadata_stack(e, scratch);
+
+
switch (format) {
case FORMAT_HTML:
+ if (scratch->extensions & EXT_COMPLETE)
+ mmd_start_complete_html(out, e->dstr->str, scratch);
+
mmd_export_token_tree_html(out, e->dstr->str, e->root, 0, scratch);
mmd_export_footnote_list_html(out, e->dstr->str, scratch);
mmd_export_citation_list_html(out, e->dstr->str, scratch);
+
+ if (scratch->extensions & EXT_COMPLETE)
+ mmd_end_complete_html(out, e->dstr->str, scratch);
+
break;
}
typedef struct {
struct link * link_hash;
+ struct meta * meta_hash;
unsigned long extensions;
short padded; //!< How many empty lines at end output buffer
typedef struct fn_holder fn_holder;
+struct meta {
+ char * key;
+ char * value;
+ UT_hash_handle hh;
+};
+
+typedef struct meta meta;
+
/// Temporary storage while exporting parse tree to output format
scratch_pad * scratch_pad_new(mmd_engine * e);
void footnote_from_bracket(const char * source, scratch_pad * scratch, token * t, short * num);
void citation_from_bracket(const char * source, scratch_pad * scratch, token * t, short * num);
+meta * meta_new(const char * source, size_t start, size_t len);
+void meta_set_value(meta * m, const char * value);
+void meta_free(meta * m);
#endif
--- /dev/null
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <title>*foo* "bar"</title>
+ <link type="text/css" rel="stylesheet" href="http://foo.com/bar.css"/>
+ <meta name="foo" content="bar foo bar foo bar"/>
+</head>
+<body>
+
+<p>foo: bar</p>
+
+</body>
+</html>
--- /dev/null
+<p>title: <em>foo</em> "bar"
+css: http://foo.com/bar.css
+foo: bar
+foo bar
+foo bar
+foo: <em>bar</em></p>
+
+<p>foo: bar</p>
--- /dev/null
+title: *foo* "bar"
+css: http://foo.com/bar.css
+foo: bar
+foo bar
+ foo bar
+foo: *bar*
+
+foo: bar