mirror of
https://github.com/lxsang/silk.git
synced 2025-07-26 10:49:45 +02:00
Add C based module + lua based API code (WIP)
This commit is contained in:
20
modules/3rd/jsmn/LICENSE
Executable file
20
modules/3rd/jsmn/LICENSE
Executable file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2010 Serge A. Zaitsev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
167
modules/3rd/jsmn/README.md
Executable file
167
modules/3rd/jsmn/README.md
Executable file
@ -0,0 +1,167 @@
|
||||
|
||||
JSMN
|
||||
====
|
||||
|
||||
jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be
|
||||
easily integrated into resource-limited or embedded projects.
|
||||
|
||||
You can find more information about JSON format at [json.org][1]
|
||||
|
||||
Library sources are available at https://github.com/zserge/jsmn
|
||||
|
||||
The web page with some information about jsmn can be found at
|
||||
[http://zserge.com/jsmn.html][2]
|
||||
|
||||
Philosophy
|
||||
----------
|
||||
|
||||
Most JSON parsers offer you a bunch of functions to load JSON data, parse it
|
||||
and extract any value by its name. jsmn proves that checking the correctness of
|
||||
every JSON packet or allocating temporary objects to store parsed JSON fields
|
||||
often is an overkill.
|
||||
|
||||
JSON format itself is extremely simple, so why should we complicate it?
|
||||
|
||||
jsmn is designed to be **robust** (it should work fine even with erroneous
|
||||
data), **fast** (it should parse data on the fly), **portable** (no superfluous
|
||||
dependencies or non-standard C extensions). And of course, **simplicity** is a
|
||||
key feature - simple code style, simple algorithm, simple integration into
|
||||
other projects.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* compatible with C89
|
||||
* no dependencies (even libc!)
|
||||
* highly portable (tested on x86/amd64, ARM, AVR)
|
||||
* about 200 lines of code
|
||||
* extremely small code footprint
|
||||
* API contains only 2 functions
|
||||
* no dynamic memory allocation
|
||||
* incremental single-pass parsing
|
||||
* library code is covered with unit-tests
|
||||
|
||||
Design
|
||||
------
|
||||
|
||||
The rudimentary jsmn object is a **token**. Let's consider a JSON string:
|
||||
|
||||
'{ "name" : "Jack", "age" : 27 }'
|
||||
|
||||
It holds the following tokens:
|
||||
|
||||
* Object: `{ "name" : "Jack", "age" : 27}` (the whole object)
|
||||
* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values)
|
||||
* Number: `27`
|
||||
|
||||
In jsmn, tokens do not hold any data, but point to token boundaries in JSON
|
||||
string instead. In the example above jsmn will create tokens like: Object
|
||||
[0..31], String [3..7], String [12..16], String [20..23], Number [27..29].
|
||||
|
||||
Every jsmn token has a type, which indicates the type of corresponding JSON
|
||||
token. jsmn supports the following token types:
|
||||
|
||||
* Object - a container of key-value pairs, e.g.:
|
||||
`{ "foo":"bar", "x":0.3 }`
|
||||
* Array - a sequence of values, e.g.:
|
||||
`[ 1, 2, 3 ]`
|
||||
* String - a quoted sequence of chars, e.g.: `"foo"`
|
||||
* Primitive - a number, a boolean (`true`, `false`) or `null`
|
||||
|
||||
Besides start/end positions, jsmn tokens for complex types (like arrays
|
||||
or objects) also contain a number of child items, so you can easily follow
|
||||
object hierarchy.
|
||||
|
||||
This approach provides enough information for parsing any JSON data and makes
|
||||
it possible to use zero-copy techniques.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
To clone the repository you should have Git installed. Just run:
|
||||
|
||||
$ git clone https://github.com/zserge/jsmn
|
||||
|
||||
Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in
|
||||
the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside.
|
||||
|
||||
To build the library, run `make`. It is also recommended to run `make test`.
|
||||
Let me know, if some tests fail.
|
||||
|
||||
If build was successful, you should get a `libjsmn.a` library.
|
||||
The header file you should include is called `"jsmn.h"`.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
Token types are described by `jsmntype_t`:
|
||||
|
||||
typedef enum {
|
||||
JSMN_UNDEFINED = 0,
|
||||
JSMN_OBJECT = 1,
|
||||
JSMN_ARRAY = 2,
|
||||
JSMN_STRING = 3,
|
||||
JSMN_PRIMITIVE = 4
|
||||
} jsmntype_t;
|
||||
|
||||
**Note:** Unlike JSON data types, primitive tokens are not divided into
|
||||
numbers, booleans and null, because one can easily tell the type using the
|
||||
first character:
|
||||
|
||||
* <code>'t', 'f'</code> - boolean
|
||||
* <code>'n'</code> - null
|
||||
* <code>'-', '0'..'9'</code> - number
|
||||
|
||||
Token is an object of `jsmntok_t` type:
|
||||
|
||||
typedef struct {
|
||||
jsmntype_t type; // Token type
|
||||
int start; // Token start position
|
||||
int end; // Token end position
|
||||
int size; // Number of child (nested) tokens
|
||||
} jsmntok_t;
|
||||
|
||||
**Note:** string tokens point to the first character after
|
||||
the opening quote and the previous symbol before final quote. This was made
|
||||
to simplify string extraction from JSON data.
|
||||
|
||||
All job is done by `jsmn_parser` object. You can initialize a new parser using:
|
||||
|
||||
jsmn_parser parser;
|
||||
jsmntok_t tokens[10];
|
||||
|
||||
jsmn_init(&parser);
|
||||
|
||||
// js - pointer to JSON string
|
||||
// tokens - an array of tokens available
|
||||
// 10 - number of tokens available
|
||||
jsmn_parse(&parser, js, strlen(js), tokens, 10);
|
||||
|
||||
This will create a parser, and then it tries to parse up to 10 JSON tokens from
|
||||
the `js` string.
|
||||
|
||||
A non-negative return value of `jsmn_parse` is the number of tokens actually
|
||||
used by the parser.
|
||||
Passing NULL instead of the tokens array would not store parsing results, but
|
||||
instead the function will return the value of tokens needed to parse the given
|
||||
string. This can be useful if you don't know yet how many tokens to allocate.
|
||||
|
||||
If something goes wrong, you will get an error. Error will be one of these:
|
||||
|
||||
* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted
|
||||
* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large
|
||||
* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data
|
||||
|
||||
If you get `JSON_ERROR_NOMEM`, you can re-allocate more tokens and call
|
||||
`jsmn_parse` once more. If you read json data from the stream, you can
|
||||
periodically call `jsmn_parse` and check if return value is `JSON_ERROR_PART`.
|
||||
You will get this error until you reach the end of JSON data.
|
||||
|
||||
Other info
|
||||
----------
|
||||
|
||||
This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php),
|
||||
so feel free to integrate it in your commercial products.
|
||||
|
||||
[1]: http://www.json.org/
|
||||
[2]: http://zserge.com/jsmn.html
|
126
modules/3rd/jsmn/example/jsondump.c
Executable file
126
modules/3rd/jsmn/example/jsondump.c
Executable file
@ -0,0 +1,126 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "../jsmn.h"
|
||||
|
||||
/* Function realloc_it() is a wrapper function for standart realloc()
|
||||
* with one difference - it frees old memory pointer in case of realloc
|
||||
* failure. Thus, DO NOT use old data pointer in anyway after call to
|
||||
* realloc_it(). If your code has some kind of fallback algorithm if
|
||||
* memory can't be re-allocated - use standart realloc() instead.
|
||||
*/
|
||||
static inline void *realloc_it(void *ptrmem, size_t size) {
|
||||
void *p = realloc(ptrmem, size);
|
||||
if (!p) {
|
||||
free (ptrmem);
|
||||
fprintf(stderr, "realloc(): errno=%d\n", errno);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* An example of reading JSON from stdin and printing its content to stdout.
|
||||
* The output looks like YAML, but I'm not sure if it's really compatible.
|
||||
*/
|
||||
|
||||
static int dump(const char *js, jsmntok_t *t, size_t count, int indent) {
|
||||
int i, j, k;
|
||||
if (count == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (t->type == JSMN_PRIMITIVE) {
|
||||
printf("%.*s", t->end - t->start, js+t->start);
|
||||
return 1;
|
||||
} else if (t->type == JSMN_STRING) {
|
||||
printf("'%.*s'", t->end - t->start, js+t->start);
|
||||
return 1;
|
||||
} else if (t->type == JSMN_OBJECT) {
|
||||
printf("\n");
|
||||
j = 0;
|
||||
for (i = 0; i < t->size; i++) {
|
||||
for (k = 0; k < indent; k++) printf(" ");
|
||||
j += dump(js, t+1+j, count-j, indent+1);
|
||||
printf(": ");
|
||||
j += dump(js, t+1+j, count-j, indent+1);
|
||||
printf("\n");
|
||||
}
|
||||
return j+1;
|
||||
} else if (t->type == JSMN_ARRAY) {
|
||||
j = 0;
|
||||
printf("\n");
|
||||
for (i = 0; i < t->size; i++) {
|
||||
for (k = 0; k < indent-1; k++) printf(" ");
|
||||
printf(" - ");
|
||||
j += dump(js, t+1+j, count-j, indent+1);
|
||||
printf("\n");
|
||||
}
|
||||
return j+1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int r;
|
||||
int eof_expected = 0;
|
||||
char *js = NULL;
|
||||
size_t jslen = 0;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
jsmn_parser p;
|
||||
jsmntok_t *tok;
|
||||
size_t tokcount = 2;
|
||||
|
||||
/* Prepare parser */
|
||||
jsmn_init(&p);
|
||||
|
||||
/* Allocate some tokens as a start */
|
||||
tok = malloc(sizeof(*tok) * tokcount);
|
||||
if (tok == NULL) {
|
||||
fprintf(stderr, "malloc(): errno=%d\n", errno);
|
||||
return 3;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
/* Read another chunk */
|
||||
r = fread(buf, 1, sizeof(buf), stdin);
|
||||
if (r < 0) {
|
||||
fprintf(stderr, "fread(): %d, errno=%d\n", r, errno);
|
||||
return 1;
|
||||
}
|
||||
if (r == 0) {
|
||||
if (eof_expected != 0) {
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "fread(): unexpected EOF\n");
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
js = realloc_it(js, jslen + r + 1);
|
||||
if (js == NULL) {
|
||||
return 3;
|
||||
}
|
||||
strncpy(js + jslen, buf, r);
|
||||
jslen = jslen + r;
|
||||
|
||||
again:
|
||||
r = jsmn_parse(&p, js, jslen, tok, tokcount);
|
||||
if (r < 0) {
|
||||
if (r == JSMN_ERROR_NOMEM) {
|
||||
tokcount = tokcount * 2;
|
||||
tok = realloc_it(tok, sizeof(*tok) * tokcount);
|
||||
if (tok == NULL) {
|
||||
return 3;
|
||||
}
|
||||
goto again;
|
||||
}
|
||||
} else {
|
||||
dump(js, tok, p.toknext, 0);
|
||||
eof_expected = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
76
modules/3rd/jsmn/example/simple.c
Executable file
76
modules/3rd/jsmn/example/simple.c
Executable file
@ -0,0 +1,76 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../jsmn.h"
|
||||
|
||||
/*
|
||||
* A small example of jsmn parsing when JSON structure is known and number of
|
||||
* tokens is predictable.
|
||||
*/
|
||||
|
||||
static const char *JSON_STRING =
|
||||
"{\"user\": \"johndoe\", \"admin\": false, \"uid\": 1000,\n "
|
||||
"\"groups\": [\"users\", \"wheel\", \"audio\", \"video\"]}";
|
||||
|
||||
static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
|
||||
if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
|
||||
strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main() {
|
||||
int i;
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t t[128]; /* We expect no more than 128 tokens */
|
||||
|
||||
jsmn_init(&p);
|
||||
r = jsmn_parse(&p, JSON_STRING, strlen(JSON_STRING), t, sizeof(t)/sizeof(t[0]));
|
||||
if (r < 0) {
|
||||
printf("Failed to parse JSON: %d\n", r);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Assume the top-level element is an object */
|
||||
if (r < 1 || t[0].type != JSMN_OBJECT) {
|
||||
printf("Object expected\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Loop over all keys of the root object */
|
||||
for (i = 1; i < r; i++) {
|
||||
if (jsoneq(JSON_STRING, &t[i], "user") == 0) {
|
||||
/* We may use strndup() to fetch string value */
|
||||
printf("- User: %.*s\n", t[i+1].end-t[i+1].start,
|
||||
JSON_STRING + t[i+1].start);
|
||||
i++;
|
||||
} else if (jsoneq(JSON_STRING, &t[i], "admin") == 0) {
|
||||
/* We may additionally check if the value is either "true" or "false" */
|
||||
printf("- Admin: %.*s\n", t[i+1].end-t[i+1].start,
|
||||
JSON_STRING + t[i+1].start);
|
||||
i++;
|
||||
} else if (jsoneq(JSON_STRING, &t[i], "uid") == 0) {
|
||||
/* We may want to do strtol() here to get numeric value */
|
||||
printf("- UID: %.*s\n", t[i+1].end-t[i+1].start,
|
||||
JSON_STRING + t[i+1].start);
|
||||
i++;
|
||||
} else if (jsoneq(JSON_STRING, &t[i], "groups") == 0) {
|
||||
int j;
|
||||
printf("- Groups:\n");
|
||||
if (t[i+1].type != JSMN_ARRAY) {
|
||||
continue; /* We expect groups to be an array of strings */
|
||||
}
|
||||
for (j = 0; j < t[i+1].size; j++) {
|
||||
jsmntok_t *g = &t[i+j+2];
|
||||
printf(" * %.*s\n", g->end - g->start, JSON_STRING + g->start);
|
||||
}
|
||||
i += t[i+1].size + 1;
|
||||
} else {
|
||||
printf("Unexpected key: %.*s\n", t[i].end-t[i].start,
|
||||
JSON_STRING + t[i].start);
|
||||
}
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
314
modules/3rd/jsmn/jsmn.c
Executable file
314
modules/3rd/jsmn/jsmn.c
Executable file
@ -0,0 +1,314 @@
|
||||
#include "jsmn.h"
|
||||
|
||||
/**
|
||||
* Allocates a fresh unused token from the token pull.
|
||||
*/
|
||||
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
|
||||
jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *tok;
|
||||
if (parser->toknext >= num_tokens) {
|
||||
return NULL;
|
||||
}
|
||||
tok = &tokens[parser->toknext++];
|
||||
tok->start = tok->end = -1;
|
||||
tok->size = 0;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
tok->parent = -1;
|
||||
#endif
|
||||
return tok;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills token type and boundaries.
|
||||
*/
|
||||
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
|
||||
int start, int end) {
|
||||
token->type = type;
|
||||
token->start = start;
|
||||
token->end = end;
|
||||
token->size = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills next available token with JSON primitive.
|
||||
*/
|
||||
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
|
||||
size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *token;
|
||||
int start;
|
||||
|
||||
start = parser->pos;
|
||||
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
switch (js[parser->pos]) {
|
||||
#ifndef JSMN_STRICT
|
||||
/* In strict mode primitive must be followed by "," or "}" or "]" */
|
||||
case ':':
|
||||
#endif
|
||||
case '\t' : case '\r' : case '\n' : case ' ' :
|
||||
case ',' : case ']' : case '}' :
|
||||
goto found;
|
||||
}
|
||||
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
#ifdef JSMN_STRICT
|
||||
/* In strict mode primitive must be followed by a comma/object/array */
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_PART;
|
||||
#endif
|
||||
|
||||
found:
|
||||
if (tokens == NULL) {
|
||||
parser->pos--;
|
||||
return 0;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_NOMEM;
|
||||
}
|
||||
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
parser->pos--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills next token with JSON string.
|
||||
*/
|
||||
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
|
||||
size_t len, jsmntok_t *tokens, size_t num_tokens) {
|
||||
jsmntok_t *token;
|
||||
|
||||
int start = parser->pos;
|
||||
|
||||
parser->pos++;
|
||||
|
||||
/* Skip starting quote */
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
char c = js[parser->pos];
|
||||
|
||||
/* Quote: end of string */
|
||||
if (c == '\"') {
|
||||
if (tokens == NULL) {
|
||||
return 0;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL) {
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_NOMEM;
|
||||
}
|
||||
jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Backslash: Quoted symbol expected */
|
||||
if (c == '\\' && parser->pos + 1 < len) {
|
||||
int i;
|
||||
parser->pos++;
|
||||
switch (js[parser->pos]) {
|
||||
/* Allowed escaped symbols */
|
||||
case '\"': case '/' : case '\\' : case 'b' :
|
||||
case 'f' : case 'r' : case 'n' : case 't' :
|
||||
break;
|
||||
/* Allows escaped symbol \uXXXX */
|
||||
case 'u':
|
||||
parser->pos++;
|
||||
for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
|
||||
/* If it isn't a hex character we have an error */
|
||||
if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
|
||||
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
|
||||
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
parser->pos++;
|
||||
}
|
||||
parser->pos--;
|
||||
break;
|
||||
/* Unexpected symbol */
|
||||
default:
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
parser->pos = start;
|
||||
return JSMN_ERROR_PART;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse JSON string and fill tokens.
|
||||
*/
|
||||
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
|
||||
jsmntok_t *tokens, unsigned int num_tokens) {
|
||||
int r;
|
||||
int i;
|
||||
jsmntok_t *token;
|
||||
int count = parser->toknext;
|
||||
|
||||
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
|
||||
char c;
|
||||
jsmntype_t type;
|
||||
|
||||
c = js[parser->pos];
|
||||
switch (c) {
|
||||
case '{': case '[':
|
||||
count++;
|
||||
if (tokens == NULL) {
|
||||
break;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if (token == NULL)
|
||||
return JSMN_ERROR_NOMEM;
|
||||
if (parser->toksuper != -1) {
|
||||
tokens[parser->toksuper].size++;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
token->parent = parser->toksuper;
|
||||
#endif
|
||||
}
|
||||
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||
token->start = parser->pos;
|
||||
parser->toksuper = parser->toknext - 1;
|
||||
break;
|
||||
case '}': case ']':
|
||||
if (tokens == NULL)
|
||||
break;
|
||||
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
if (parser->toknext < 1) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
token = &tokens[parser->toknext - 1];
|
||||
for (;;) {
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
if (token->type != type) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
token->end = parser->pos + 1;
|
||||
parser->toksuper = token->parent;
|
||||
break;
|
||||
}
|
||||
if (token->parent == -1) {
|
||||
if(token->type != type || parser->toksuper == -1) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
token = &tokens[token->parent];
|
||||
}
|
||||
#else
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
token = &tokens[i];
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
if (token->type != type) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
parser->toksuper = -1;
|
||||
token->end = parser->pos + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Error if unmatched closing bracket */
|
||||
if (i == -1) return JSMN_ERROR_INVAL;
|
||||
for (; i >= 0; i--) {
|
||||
token = &tokens[i];
|
||||
if (token->start != -1 && token->end == -1) {
|
||||
parser->toksuper = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case '\"':
|
||||
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
|
||||
if (r < 0) return r;
|
||||
count++;
|
||||
if (parser->toksuper != -1 && tokens != NULL)
|
||||
tokens[parser->toksuper].size++;
|
||||
break;
|
||||
case '\t' : case '\r' : case '\n' : case ' ':
|
||||
break;
|
||||
case ':':
|
||||
parser->toksuper = parser->toknext - 1;
|
||||
break;
|
||||
case ',':
|
||||
if (tokens != NULL && parser->toksuper != -1 &&
|
||||
tokens[parser->toksuper].type != JSMN_ARRAY &&
|
||||
tokens[parser->toksuper].type != JSMN_OBJECT) {
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
parser->toksuper = tokens[parser->toksuper].parent;
|
||||
#else
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
|
||||
if (tokens[i].start != -1 && tokens[i].end == -1) {
|
||||
parser->toksuper = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
#ifdef JSMN_STRICT
|
||||
/* In strict mode primitives are: numbers and booleans */
|
||||
case '-': case '0': case '1' : case '2': case '3' : case '4':
|
||||
case '5': case '6': case '7' : case '8': case '9':
|
||||
case 't': case 'f': case 'n' :
|
||||
/* And they must not be keys of the object */
|
||||
if (tokens != NULL && parser->toksuper != -1) {
|
||||
jsmntok_t *t = &tokens[parser->toksuper];
|
||||
if (t->type == JSMN_OBJECT ||
|
||||
(t->type == JSMN_STRING && t->size != 0)) {
|
||||
return JSMN_ERROR_INVAL;
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* In non-strict mode every unquoted value is a primitive */
|
||||
default:
|
||||
#endif
|
||||
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
|
||||
if (r < 0) return r;
|
||||
count++;
|
||||
if (parser->toksuper != -1 && tokens != NULL)
|
||||
tokens[parser->toksuper].size++;
|
||||
break;
|
||||
|
||||
#ifdef JSMN_STRICT
|
||||
/* Unexpected char in strict mode */
|
||||
default:
|
||||
return JSMN_ERROR_INVAL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens != NULL) {
|
||||
for (i = parser->toknext - 1; i >= 0; i--) {
|
||||
/* Unmatched opened object or array */
|
||||
if (tokens[i].start != -1 && tokens[i].end == -1) {
|
||||
return JSMN_ERROR_PART;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new parser based over a given buffer with an array of tokens
|
||||
* available.
|
||||
*/
|
||||
void jsmn_init(jsmn_parser *parser) {
|
||||
parser->pos = 0;
|
||||
parser->toknext = 0;
|
||||
parser->toksuper = -1;
|
||||
}
|
||||
|
76
modules/3rd/jsmn/jsmn.h
Executable file
76
modules/3rd/jsmn/jsmn.h
Executable file
@ -0,0 +1,76 @@
|
||||
#ifndef __JSMN_H_
|
||||
#define __JSMN_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* JSON type identifier. Basic types are:
|
||||
* o Object
|
||||
* o Array
|
||||
* o String
|
||||
* o Other primitive: number, boolean (true/false) or null
|
||||
*/
|
||||
typedef enum {
|
||||
JSMN_UNDEFINED = 0,
|
||||
JSMN_OBJECT = 1,
|
||||
JSMN_ARRAY = 2,
|
||||
JSMN_STRING = 3,
|
||||
JSMN_PRIMITIVE = 4
|
||||
} jsmntype_t;
|
||||
|
||||
enum jsmnerr {
|
||||
/* Not enough tokens were provided */
|
||||
JSMN_ERROR_NOMEM = -1,
|
||||
/* Invalid character inside JSON string */
|
||||
JSMN_ERROR_INVAL = -2,
|
||||
/* The string is not a full JSON packet, more bytes expected */
|
||||
JSMN_ERROR_PART = -3
|
||||
};
|
||||
|
||||
/**
|
||||
* JSON token description.
|
||||
* type type (object, array, string etc.)
|
||||
* start start position in JSON data string
|
||||
* end end position in JSON data string
|
||||
*/
|
||||
typedef struct {
|
||||
jsmntype_t type;
|
||||
int start;
|
||||
int end;
|
||||
int size;
|
||||
#ifdef JSMN_PARENT_LINKS
|
||||
int parent;
|
||||
#endif
|
||||
} jsmntok_t;
|
||||
|
||||
/**
|
||||
* JSON parser. Contains an array of token blocks available. Also stores
|
||||
* the string being parsed now and current position in that string
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned int pos; /* offset in the JSON string */
|
||||
unsigned int toknext; /* next token to allocate */
|
||||
int toksuper; /* superior token node, e.g parent object or array */
|
||||
} jsmn_parser;
|
||||
|
||||
/**
|
||||
* Create JSON parser over an array of tokens
|
||||
*/
|
||||
void jsmn_init(jsmn_parser *parser);
|
||||
|
||||
/**
|
||||
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
|
||||
* a single JSON object.
|
||||
*/
|
||||
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
|
||||
jsmntok_t *tokens, unsigned int num_tokens);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __JSMN_H_ */
|
16
modules/3rd/jsmn/library.json
Executable file
16
modules/3rd/jsmn/library.json
Executable file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "jsmn",
|
||||
"keywords": "json",
|
||||
"description": "Minimalistic JSON parser/tokenizer in C. It can be easily integrated into resource-limited or embedded projects",
|
||||
"repository":
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/zserge/jsmn.git"
|
||||
},
|
||||
"frameworks": "*",
|
||||
"platforms": "*",
|
||||
"examples": [
|
||||
"example/*.c"
|
||||
],
|
||||
"exclude": "test"
|
||||
}
|
27
modules/3rd/jsmn/test/test.h
Executable file
27
modules/3rd/jsmn/test/test.h
Executable file
@ -0,0 +1,27 @@
|
||||
#ifndef __TEST_H__
|
||||
#define __TEST_H__
|
||||
|
||||
static int test_passed = 0;
|
||||
static int test_failed = 0;
|
||||
|
||||
/* Terminate current test with error */
|
||||
#define fail() return __LINE__
|
||||
|
||||
/* Successful end of the test case */
|
||||
#define done() return 0
|
||||
|
||||
/* Check single condition */
|
||||
#define check(cond) do { if (!(cond)) fail(); } while (0)
|
||||
|
||||
/* Test runner */
|
||||
static void test(int (*func)(void), const char *name) {
|
||||
int r = func();
|
||||
if (r == 0) {
|
||||
test_passed++;
|
||||
} else {
|
||||
test_failed++;
|
||||
printf("FAILED: %s (at line %d)\n", name, r);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* __TEST_H__ */
|
398
modules/3rd/jsmn/test/tests.c
Executable file
398
modules/3rd/jsmn/test/tests.c
Executable file
@ -0,0 +1,398 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "test.h"
|
||||
#include "testutil.h"
|
||||
|
||||
int test_empty(void) {
|
||||
check(parse("{}", 1, 1,
|
||||
JSMN_OBJECT, 0, 2, 0));
|
||||
check(parse("[]", 1, 1,
|
||||
JSMN_ARRAY, 0, 2, 0));
|
||||
check(parse("[{},{}]", 3, 3,
|
||||
JSMN_ARRAY, 0, 7, 2,
|
||||
JSMN_OBJECT, 1, 3, 0,
|
||||
JSMN_OBJECT, 4, 6, 0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_object(void) {
|
||||
check(parse("{\"a\":0}", 3, 3,
|
||||
JSMN_OBJECT, 0, 7, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_PRIMITIVE, "0"));
|
||||
check(parse("{\"a\":[]}", 3, 3,
|
||||
JSMN_OBJECT, 0, 8, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_ARRAY, 5, 7, 0));
|
||||
check(parse("{\"a\":{},\"b\":{}}", 5, 5,
|
||||
JSMN_OBJECT, -1, -1, 2,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_OBJECT, -1, -1, 0,
|
||||
JSMN_STRING, "b", 1,
|
||||
JSMN_OBJECT, -1, -1, 0));
|
||||
check(parse("{\n \"Day\": 26,\n \"Month\": 9,\n \"Year\": 12\n }", 7, 7,
|
||||
JSMN_OBJECT, -1, -1, 3,
|
||||
JSMN_STRING, "Day", 1,
|
||||
JSMN_PRIMITIVE, "26",
|
||||
JSMN_STRING, "Month", 1,
|
||||
JSMN_PRIMITIVE, "9",
|
||||
JSMN_STRING, "Year", 1,
|
||||
JSMN_PRIMITIVE, "12"));
|
||||
check(parse("{\"a\": 0, \"b\": \"c\"}", 5, 5,
|
||||
JSMN_OBJECT, -1, -1, 2,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_PRIMITIVE, "0",
|
||||
JSMN_STRING, "b", 1,
|
||||
JSMN_STRING, "c", 0));
|
||||
|
||||
#ifdef JSMN_STRICT
|
||||
check(parse("{\"a\"\n0}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{\"a\", 0}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{\"a\": {2}}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{\"a\": {2: 3}}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{\"a\": {\"a\": 2 3}}", JSMN_ERROR_INVAL, 5));
|
||||
/* FIXME */
|
||||
/*check(parse("{\"a\"}", JSMN_ERROR_INVAL, 2));*/
|
||||
/*check(parse("{\"a\": 1, \"b\"}", JSMN_ERROR_INVAL, 4));*/
|
||||
/*check(parse("{\"a\",\"b\":1}", JSMN_ERROR_INVAL, 4));*/
|
||||
/*check(parse("{\"a\":1,}", JSMN_ERROR_INVAL, 4));*/
|
||||
/*check(parse("{\"a\":\"b\":\"c\"}", JSMN_ERROR_INVAL, 4));*/
|
||||
/*check(parse("{,}", JSMN_ERROR_INVAL, 4));*/
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_array(void) {
|
||||
/* FIXME */
|
||||
/*check(parse("[10}", JSMN_ERROR_INVAL, 3));*/
|
||||
/*check(parse("[1,,3]", JSMN_ERROR_INVAL, 3)*/
|
||||
check(parse("[10]", 2, 2,
|
||||
JSMN_ARRAY, -1, -1, 1,
|
||||
JSMN_PRIMITIVE, "10"));
|
||||
check(parse("{\"a\": 1]", JSMN_ERROR_INVAL, 3));
|
||||
/* FIXME */
|
||||
/*check(parse("[\"a\": 1]", JSMN_ERROR_INVAL, 3));*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_primitive(void) {
|
||||
check(parse("{\"boolVar\" : true }", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "boolVar", 1,
|
||||
JSMN_PRIMITIVE, "true"));
|
||||
check(parse("{\"boolVar\" : false }", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "boolVar", 1,
|
||||
JSMN_PRIMITIVE, "false"));
|
||||
check(parse("{\"nullVar\" : null }", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "nullVar", 1,
|
||||
JSMN_PRIMITIVE, "null"));
|
||||
check(parse("{\"intVar\" : 12}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "intVar", 1,
|
||||
JSMN_PRIMITIVE, "12"));
|
||||
check(parse("{\"floatVar\" : 12.345}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "floatVar", 1,
|
||||
JSMN_PRIMITIVE, "12.345"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_string(void) {
|
||||
check(parse("{\"strVar\" : \"hello world\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "strVar", 1,
|
||||
JSMN_STRING, "hello world", 0));
|
||||
check(parse("{\"strVar\" : \"escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "strVar", 1,
|
||||
JSMN_STRING, "escapes: \\/\\r\\n\\t\\b\\f\\\"\\\\", 0));
|
||||
check(parse("{\"strVar\": \"\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "strVar", 1,
|
||||
JSMN_STRING, "", 0));
|
||||
check(parse("{\"a\":\"\\uAbcD\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_STRING, "\\uAbcD", 0));
|
||||
check(parse("{\"a\":\"str\\u0000\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_STRING, "str\\u0000", 0));
|
||||
check(parse("{\"a\":\"\\uFFFFstr\"}", 3, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_STRING, "\\uFFFFstr", 0));
|
||||
check(parse("{\"a\":[\"\\u0280\"]}", 4, 4,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_ARRAY, -1, -1, 1,
|
||||
JSMN_STRING, "\\u0280", 0));
|
||||
|
||||
check(parse("{\"a\":\"str\\uFFGFstr\"}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{\"a\":\"str\\u@FfF\"}", JSMN_ERROR_INVAL, 3));
|
||||
check(parse("{{\"a\":[\"\\u028\"]}", JSMN_ERROR_INVAL, 4));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_partial_string(void) {
|
||||
int i;
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t tok[5];
|
||||
const char *js = "{\"x\": \"va\\\\ue\", \"y\": \"value y\"}";
|
||||
|
||||
jsmn_init(&p);
|
||||
for (i = 1; i <= strlen(js); i++) {
|
||||
r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0]));
|
||||
if (i == strlen(js)) {
|
||||
check(r == 5);
|
||||
check(tokeq(js, tok, 5,
|
||||
JSMN_OBJECT, -1, -1, 2,
|
||||
JSMN_STRING, "x", 1,
|
||||
JSMN_STRING, "va\\\\ue", 0,
|
||||
JSMN_STRING, "y", 1,
|
||||
JSMN_STRING, "value y", 0));
|
||||
} else {
|
||||
check(r == JSMN_ERROR_PART);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_partial_array(void) {
|
||||
#ifdef JSMN_STRICT
|
||||
int r;
|
||||
int i;
|
||||
jsmn_parser p;
|
||||
jsmntok_t tok[10];
|
||||
const char *js = "[ 1, true, [123, \"hello\"]]";
|
||||
|
||||
jsmn_init(&p);
|
||||
for (i = 1; i <= strlen(js); i++) {
|
||||
r = jsmn_parse(&p, js, i, tok, sizeof(tok)/sizeof(tok[0]));
|
||||
if (i == strlen(js)) {
|
||||
check(r == 6);
|
||||
check(tokeq(js, tok, 6,
|
||||
JSMN_ARRAY, -1, -1, 3,
|
||||
JSMN_PRIMITIVE, "1",
|
||||
JSMN_PRIMITIVE, "true",
|
||||
JSMN_ARRAY, -1, -1, 2,
|
||||
JSMN_PRIMITIVE, "123",
|
||||
JSMN_STRING, "hello", 0));
|
||||
} else {
|
||||
check(r == JSMN_ERROR_PART);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_array_nomem(void) {
|
||||
int i;
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t toksmall[10], toklarge[10];
|
||||
const char *js;
|
||||
|
||||
js = " [ 1, true, [123, \"hello\"]]";
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
jsmn_init(&p);
|
||||
memset(toksmall, 0, sizeof(toksmall));
|
||||
memset(toklarge, 0, sizeof(toklarge));
|
||||
r = jsmn_parse(&p, js, strlen(js), toksmall, i);
|
||||
check(r == JSMN_ERROR_NOMEM);
|
||||
|
||||
memcpy(toklarge, toksmall, sizeof(toksmall));
|
||||
|
||||
r = jsmn_parse(&p, js, strlen(js), toklarge, 10);
|
||||
check(r >= 0);
|
||||
check(tokeq(js, toklarge, 4,
|
||||
JSMN_ARRAY, -1, -1, 3,
|
||||
JSMN_PRIMITIVE, "1",
|
||||
JSMN_PRIMITIVE, "true",
|
||||
JSMN_ARRAY, -1, -1, 2,
|
||||
JSMN_PRIMITIVE, "123",
|
||||
JSMN_STRING, "hello", 0));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_unquoted_keys(void) {
|
||||
#ifndef JSMN_STRICT
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t tok[10];
|
||||
const char *js;
|
||||
|
||||
jsmn_init(&p);
|
||||
js = "key1: \"value\"\nkey2 : 123";
|
||||
|
||||
r = jsmn_parse(&p, js, strlen(js), tok, 10);
|
||||
check(r >= 0);
|
||||
check(tokeq(js, tok, 4,
|
||||
JSMN_PRIMITIVE, "key1",
|
||||
JSMN_STRING, "value", 0,
|
||||
JSMN_PRIMITIVE, "key2",
|
||||
JSMN_PRIMITIVE, "123"));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_issue_22(void) {
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t tokens[128];
|
||||
const char *js;
|
||||
|
||||
js = "{ \"height\":10, \"layers\":[ { \"data\":[6,6], \"height\":10, "
|
||||
"\"name\":\"Calque de Tile 1\", \"opacity\":1, \"type\":\"tilelayer\", "
|
||||
"\"visible\":true, \"width\":10, \"x\":0, \"y\":0 }], "
|
||||
"\"orientation\":\"orthogonal\", \"properties\": { }, \"tileheight\":32, "
|
||||
"\"tilesets\":[ { \"firstgid\":1, \"image\":\"..\\/images\\/tiles.png\", "
|
||||
"\"imageheight\":64, \"imagewidth\":160, \"margin\":0, \"name\":\"Tiles\", "
|
||||
"\"properties\":{}, \"spacing\":0, \"tileheight\":32, \"tilewidth\":32 }], "
|
||||
"\"tilewidth\":32, \"version\":1, \"width\":10 }";
|
||||
jsmn_init(&p);
|
||||
r = jsmn_parse(&p, js, strlen(js), tokens, 128);
|
||||
check(r >= 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_issue_27(void) {
|
||||
const char *js =
|
||||
"{ \"name\" : \"Jack\", \"age\" : 27 } { \"name\" : \"Anna\", ";
|
||||
check(parse(js, JSMN_ERROR_PART, 8));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_input_length(void) {
|
||||
const char *js;
|
||||
int r;
|
||||
jsmn_parser p;
|
||||
jsmntok_t tokens[10];
|
||||
|
||||
js = "{\"a\": 0}garbage";
|
||||
|
||||
jsmn_init(&p);
|
||||
r = jsmn_parse(&p, js, 8, tokens, 10);
|
||||
check(r == 3);
|
||||
check(tokeq(js, tokens, 3,
|
||||
JSMN_OBJECT, -1, -1, 1,
|
||||
JSMN_STRING, "a", 1,
|
||||
JSMN_PRIMITIVE, "0"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_count(void) {
|
||||
jsmn_parser p;
|
||||
const char *js;
|
||||
|
||||
js = "{}";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
|
||||
|
||||
js = "[]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 1);
|
||||
|
||||
js = "[[]]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 2);
|
||||
|
||||
js = "[[], []]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
|
||||
|
||||
js = "[[], []]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 3);
|
||||
|
||||
js = "[[], [[]], [[], []]]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
|
||||
|
||||
js = "[\"a\", [[], []]]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
|
||||
|
||||
js = "[[], \"[], [[]]\", [[]]]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 5);
|
||||
|
||||
js = "[1, 2, 3]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 4);
|
||||
|
||||
js = "[1, 2, [3, \"a\"], null]";
|
||||
jsmn_init(&p);
|
||||
check(jsmn_parse(&p, js, strlen(js), NULL, 0) == 7);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int test_nonstrict(void) {
|
||||
#ifndef JSMN_STRICT
|
||||
const char *js;
|
||||
js = "a: 0garbage";
|
||||
check(parse(js, 2, 2,
|
||||
JSMN_PRIMITIVE, "a",
|
||||
JSMN_PRIMITIVE, "0garbage"));
|
||||
|
||||
js = "Day : 26\nMonth : Sep\n\nYear: 12";
|
||||
check(parse(js, 6, 6,
|
||||
JSMN_PRIMITIVE, "Day",
|
||||
JSMN_PRIMITIVE, "26",
|
||||
JSMN_PRIMITIVE, "Month",
|
||||
JSMN_PRIMITIVE, "Sep",
|
||||
JSMN_PRIMITIVE, "Year",
|
||||
JSMN_PRIMITIVE, "12"));
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_unmatched_brackets(void) {
|
||||
const char *js;
|
||||
js = "\"key 1\": 1234}";
|
||||
check(parse(js, JSMN_ERROR_INVAL, 2));
|
||||
js = "{\"key 1\": 1234";
|
||||
check(parse(js, JSMN_ERROR_PART, 3));
|
||||
js = "{\"key 1\": 1234}}";
|
||||
check(parse(js, JSMN_ERROR_INVAL, 3));
|
||||
js = "\"key 1\"}: 1234";
|
||||
check(parse(js, JSMN_ERROR_INVAL, 3));
|
||||
js = "\"key {1\": 1234";
|
||||
check(parse(js, 2, 2,
|
||||
JSMN_STRING, "key {1", 1,
|
||||
JSMN_PRIMITIVE, "1234"));
|
||||
js = "{{\"key 1\": 1234}";
|
||||
check(parse(js, JSMN_ERROR_PART, 4));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
test(test_empty, "test for a empty JSON objects/arrays");
|
||||
test(test_object, "test for a JSON objects");
|
||||
test(test_array, "test for a JSON arrays");
|
||||
test(test_primitive, "test primitive JSON data types");
|
||||
test(test_string, "test string JSON data types");
|
||||
|
||||
test(test_partial_string, "test partial JSON string parsing");
|
||||
test(test_partial_array, "test partial array reading");
|
||||
test(test_array_nomem, "test array reading with a smaller number of tokens");
|
||||
test(test_unquoted_keys, "test unquoted keys (like in JavaScript)");
|
||||
test(test_input_length, "test strings that are not null-terminated");
|
||||
test(test_issue_22, "test issue #22");
|
||||
test(test_issue_27, "test issue #27");
|
||||
test(test_count, "test tokens count estimation");
|
||||
test(test_nonstrict, "test for non-strict mode");
|
||||
test(test_unmatched_brackets, "test for unmatched brackets");
|
||||
printf("\nPASSED: %d\nFAILED: %d\n", test_passed, test_failed);
|
||||
return (test_failed > 0);
|
||||
}
|
94
modules/3rd/jsmn/test/testutil.h
Executable file
94
modules/3rd/jsmn/test/testutil.h
Executable file
@ -0,0 +1,94 @@
|
||||
#ifndef __TEST_UTIL_H__
|
||||
#define __TEST_UTIL_H__
|
||||
|
||||
#include "../jsmn.c"
|
||||
|
||||
static int vtokeq(const char *s, jsmntok_t *t, int numtok, va_list ap) {
|
||||
if (numtok > 0) {
|
||||
int i, start, end, size;
|
||||
int type;
|
||||
char *value;
|
||||
|
||||
size = -1;
|
||||
value = NULL;
|
||||
for (i = 0; i < numtok; i++) {
|
||||
type = va_arg(ap, int);
|
||||
if (type == JSMN_STRING) {
|
||||
value = va_arg(ap, char *);
|
||||
size = va_arg(ap, int);
|
||||
start = end = -1;
|
||||
} else if (type == JSMN_PRIMITIVE) {
|
||||
value = va_arg(ap, char *);
|
||||
start = end = size = -1;
|
||||
} else {
|
||||
start = va_arg(ap, int);
|
||||
end = va_arg(ap, int);
|
||||
size = va_arg(ap, int);
|
||||
value = NULL;
|
||||
}
|
||||
if (t[i].type != type) {
|
||||
printf("token %d type is %d, not %d\n", i, t[i].type, type);
|
||||
return 0;
|
||||
}
|
||||
if (start != -1 && end != -1) {
|
||||
if (t[i].start != start) {
|
||||
printf("token %d start is %d, not %d\n", i, t[i].start, start);
|
||||
return 0;
|
||||
}
|
||||
if (t[i].end != end ) {
|
||||
printf("token %d end is %d, not %d\n", i, t[i].end, end);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (size != -1 && t[i].size != size) {
|
||||
printf("token %d size is %d, not %d\n", i, t[i].size, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (s != NULL && value != NULL) {
|
||||
const char *p = s + t[i].start;
|
||||
if (strlen(value) != t[i].end - t[i].start ||
|
||||
strncmp(p, value, t[i].end - t[i].start) != 0) {
|
||||
printf("token %d value is %.*s, not %s\n", i, t[i].end-t[i].start,
|
||||
s+t[i].start, value);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int tokeq(const char *s, jsmntok_t *tokens, int numtok, ...) {
|
||||
int ok;
|
||||
va_list args;
|
||||
va_start(args, numtok);
|
||||
ok = vtokeq(s, tokens, numtok, args);
|
||||
va_end(args);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int parse(const char *s, int status, int numtok, ...) {
|
||||
int r;
|
||||
int ok = 1;
|
||||
va_list args;
|
||||
jsmn_parser p;
|
||||
jsmntok_t *t = malloc(numtok * sizeof(jsmntok_t));
|
||||
|
||||
jsmn_init(&p);
|
||||
r = jsmn_parse(&p, s, strlen(s), t, numtok);
|
||||
if (r != status) {
|
||||
printf("status is %d, not %d\n", r, status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (status >= 0) {
|
||||
va_start(args, numtok);
|
||||
ok = vtokeq(s, t, numtok, args);
|
||||
va_end(args);
|
||||
}
|
||||
free(t);
|
||||
return ok;
|
||||
}
|
||||
|
||||
#endif /* __TEST_UTIL_H__ */
|
2190
modules/3rd/md4c/entity.c
Normal file
2190
modules/3rd/md4c/entity.c
Normal file
File diff suppressed because it is too large
Load Diff
42
modules/3rd/md4c/entity.h
Normal file
42
modules/3rd/md4c/entity.h
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* MD4C: Markdown parser for C
|
||||
* (http://github.com/mity/md4c)
|
||||
*
|
||||
* Copyright (c) 2016-2019 Martin Mitas
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MD4C_ENTITY_H
|
||||
#define MD4C_ENTITY_H
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
/* Most entities are formed by single Unicode codepoint, few by two codepoints.
|
||||
* Single-codepoint entities have codepoints[1] set to zero. */
|
||||
struct entity {
|
||||
const char* name;
|
||||
unsigned codepoints[2];
|
||||
};
|
||||
|
||||
const struct entity* entity_lookup(const char* name, size_t name_size);
|
||||
|
||||
|
||||
#endif /* MD4C_ENTITY_H */
|
750
modules/3rd/md4c/md4c-html.c
Normal file
750
modules/3rd/md4c/md4c-html.c
Normal file
@ -0,0 +1,750 @@
|
||||
/*
|
||||
* MD4C: Markdown parser for C
|
||||
* (http://github.com/mity/md4c)
|
||||
*
|
||||
* Copyright (c) 2016-2019 Martin Mitas
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "md4c-html.h"
|
||||
#include "entity.h"
|
||||
|
||||
static int hd_cnt[6] = {0, 0, 0, 0, 0, 0};
|
||||
|
||||
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199409L
|
||||
/* C89/90 or old compilers in general may not understand "inline". */
|
||||
#if defined __GNUC__
|
||||
#define inline __inline__
|
||||
#elif defined _MSC_VER
|
||||
#define inline __inline
|
||||
#else
|
||||
#define inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define snprintf _snprintf
|
||||
#endif
|
||||
|
||||
typedef struct MD_HTML_tag MD_HTML;
|
||||
struct MD_HTML_tag
|
||||
{
|
||||
void (*process_output)(const MD_CHAR *, MD_SIZE, void *);
|
||||
void *userdata;
|
||||
unsigned flags;
|
||||
int image_nesting_level;
|
||||
char escape_map[256];
|
||||
};
|
||||
|
||||
#define NEED_HTML_ESC_FLAG 0x1
|
||||
#define NEED_URL_ESC_FLAG 0x2
|
||||
|
||||
/*****************************************
|
||||
*** HTML rendering helper functions ***
|
||||
*****************************************/
|
||||
|
||||
#define ISDIGIT(ch) ('0' <= (ch) && (ch) <= '9')
|
||||
#define ISLOWER(ch) ('a' <= (ch) && (ch) <= 'z')
|
||||
#define ISUPPER(ch) ('A' <= (ch) && (ch) <= 'Z')
|
||||
#define ISALNUM(ch) (ISLOWER(ch) || ISUPPER(ch) || ISDIGIT(ch))
|
||||
|
||||
static inline void
|
||||
render_verbatim(MD_HTML *r, const MD_CHAR *text, MD_SIZE size)
|
||||
{
|
||||
r->process_output(text, size, r->userdata);
|
||||
}
|
||||
|
||||
/* Keep this as a macro. Most compiler should then be smart enough to replace
|
||||
* the strlen() call with a compile-time constant if the string is a C literal. */
|
||||
#define RENDER_VERBATIM(r, verbatim) \
|
||||
render_verbatim((r), (verbatim), (MD_SIZE)(strlen(verbatim)))
|
||||
|
||||
static void
|
||||
render_html_escaped(MD_HTML *r, const MD_CHAR *data, MD_SIZE size)
|
||||
{
|
||||
MD_OFFSET beg = 0;
|
||||
MD_OFFSET off = 0;
|
||||
|
||||
/* Some characters need to be escaped in normal HTML text. */
|
||||
#define NEED_HTML_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_HTML_ESC_FLAG)
|
||||
|
||||
while (1)
|
||||
{
|
||||
/* Optimization: Use some loop unrolling. */
|
||||
while (off + 3 < size && !NEED_HTML_ESC(data[off + 0]) && !NEED_HTML_ESC(data[off + 1]) && !NEED_HTML_ESC(data[off + 2]) && !NEED_HTML_ESC(data[off + 3]))
|
||||
off += 4;
|
||||
while (off < size && !NEED_HTML_ESC(data[off]))
|
||||
off++;
|
||||
|
||||
if (off > beg)
|
||||
render_verbatim(r, data + beg, off - beg);
|
||||
|
||||
if (off < size)
|
||||
{
|
||||
switch (data[off])
|
||||
{
|
||||
case '&':
|
||||
RENDER_VERBATIM(r, "&");
|
||||
break;
|
||||
case '<':
|
||||
RENDER_VERBATIM(r, "<");
|
||||
break;
|
||||
case '>':
|
||||
RENDER_VERBATIM(r, ">");
|
||||
break;
|
||||
case '"':
|
||||
RENDER_VERBATIM(r, """);
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
beg = off;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_url_escaped(MD_HTML *r, const MD_CHAR *data, MD_SIZE size)
|
||||
{
|
||||
static const MD_CHAR hex_chars[] = "0123456789ABCDEF";
|
||||
MD_OFFSET beg = 0;
|
||||
MD_OFFSET off = 0;
|
||||
|
||||
/* Some characters need to be escaped in URL attributes. */
|
||||
#define NEED_URL_ESC(ch) (r->escape_map[(unsigned char)(ch)] & NEED_URL_ESC_FLAG)
|
||||
|
||||
while (1)
|
||||
{
|
||||
while (off < size && !NEED_URL_ESC(data[off]))
|
||||
off++;
|
||||
if (off > beg)
|
||||
render_verbatim(r, data + beg, off - beg);
|
||||
|
||||
if (off < size)
|
||||
{
|
||||
char hex[3];
|
||||
|
||||
switch (data[off])
|
||||
{
|
||||
case '&':
|
||||
RENDER_VERBATIM(r, "&");
|
||||
break;
|
||||
default:
|
||||
hex[0] = '%';
|
||||
hex[1] = hex_chars[((unsigned)data[off] >> 4) & 0xf];
|
||||
hex[2] = hex_chars[((unsigned)data[off] >> 0) & 0xf];
|
||||
render_verbatim(r, hex, 3);
|
||||
break;
|
||||
}
|
||||
off++;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
beg = off;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned
|
||||
hex_val(char ch)
|
||||
{
|
||||
if ('0' <= ch && ch <= '9')
|
||||
return ch - '0';
|
||||
if ('A' <= ch && ch <= 'Z')
|
||||
return ch - 'A' + 10;
|
||||
else
|
||||
return ch - 'a' + 10;
|
||||
}
|
||||
|
||||
static void
|
||||
render_utf8_codepoint(MD_HTML *r, unsigned codepoint,
|
||||
void (*fn_append)(MD_HTML *, const MD_CHAR *, MD_SIZE))
|
||||
{
|
||||
static const MD_CHAR utf8_replacement_char[] = {0xef, 0xbf, 0xbd};
|
||||
|
||||
unsigned char utf8[4];
|
||||
size_t n;
|
||||
|
||||
if (codepoint <= 0x7f)
|
||||
{
|
||||
n = 1;
|
||||
utf8[0] = codepoint;
|
||||
}
|
||||
else if (codepoint <= 0x7ff)
|
||||
{
|
||||
n = 2;
|
||||
utf8[0] = 0xc0 | ((codepoint >> 6) & 0x1f);
|
||||
utf8[1] = 0x80 + ((codepoint >> 0) & 0x3f);
|
||||
}
|
||||
else if (codepoint <= 0xffff)
|
||||
{
|
||||
n = 3;
|
||||
utf8[0] = 0xe0 | ((codepoint >> 12) & 0xf);
|
||||
utf8[1] = 0x80 + ((codepoint >> 6) & 0x3f);
|
||||
utf8[2] = 0x80 + ((codepoint >> 0) & 0x3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = 4;
|
||||
utf8[0] = 0xf0 | ((codepoint >> 18) & 0x7);
|
||||
utf8[1] = 0x80 + ((codepoint >> 12) & 0x3f);
|
||||
utf8[2] = 0x80 + ((codepoint >> 6) & 0x3f);
|
||||
utf8[3] = 0x80 + ((codepoint >> 0) & 0x3f);
|
||||
}
|
||||
|
||||
if (0 < codepoint && codepoint <= 0x10ffff)
|
||||
fn_append(r, (char *)utf8, n);
|
||||
else
|
||||
fn_append(r, utf8_replacement_char, 3);
|
||||
}
|
||||
|
||||
/* Translate entity to its UTF-8 equivalent, or output the verbatim one
|
||||
* if such entity is unknown (or if the translation is disabled). */
|
||||
static void
|
||||
render_entity(MD_HTML *r, const MD_CHAR *text, MD_SIZE size,
|
||||
void (*fn_append)(MD_HTML *, const MD_CHAR *, MD_SIZE))
|
||||
{
|
||||
if (r->flags & MD_HTML_FLAG_VERBATIM_ENTITIES)
|
||||
{
|
||||
render_verbatim(r, text, size);
|
||||
return;
|
||||
}
|
||||
|
||||
/* We assume UTF-8 output is what is desired. */
|
||||
if (size > 3 && text[1] == '#')
|
||||
{
|
||||
unsigned codepoint = 0;
|
||||
|
||||
if (text[2] == 'x' || text[2] == 'X')
|
||||
{
|
||||
/* Hexadecimal entity (e.g. "�")). */
|
||||
MD_SIZE i;
|
||||
for (i = 3; i < size - 1; i++)
|
||||
codepoint = 16 * codepoint + hex_val(text[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Decimal entity (e.g. "&1234;") */
|
||||
MD_SIZE i;
|
||||
for (i = 2; i < size - 1; i++)
|
||||
codepoint = 10 * codepoint + (text[i] - '0');
|
||||
}
|
||||
|
||||
render_utf8_codepoint(r, codepoint, fn_append);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Named entity (e.g. " "). */
|
||||
const struct entity *ent;
|
||||
|
||||
ent = entity_lookup(text, size);
|
||||
if (ent != NULL)
|
||||
{
|
||||
render_utf8_codepoint(r, ent->codepoints[0], fn_append);
|
||||
if (ent->codepoints[1])
|
||||
render_utf8_codepoint(r, ent->codepoints[1], fn_append);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fn_append(r, text, size);
|
||||
}
|
||||
|
||||
static void
|
||||
render_attribute(MD_HTML *r, const MD_ATTRIBUTE *attr,
|
||||
void (*fn_append)(MD_HTML *, const MD_CHAR *, MD_SIZE))
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; attr->substr_offsets[i] < attr->size; i++)
|
||||
{
|
||||
MD_TEXTTYPE type = attr->substr_types[i];
|
||||
MD_OFFSET off = attr->substr_offsets[i];
|
||||
MD_SIZE size = attr->substr_offsets[i + 1] - off;
|
||||
const MD_CHAR *text = attr->text + off;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MD_TEXT_NULLCHAR:
|
||||
render_utf8_codepoint(r, 0x0000, render_verbatim);
|
||||
break;
|
||||
case MD_TEXT_ENTITY:
|
||||
render_entity(r, text, size, fn_append);
|
||||
break;
|
||||
default:
|
||||
fn_append(r, text, size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_ol_block(MD_HTML *r, const MD_BLOCK_OL_DETAIL *det)
|
||||
{
|
||||
char buf[64];
|
||||
|
||||
if (det->start == 1)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<ol>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "<ol start=\"%u\">\n", det->start);
|
||||
RENDER_VERBATIM(r, buf);
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_li_block(MD_HTML *r, const MD_BLOCK_LI_DETAIL *det)
|
||||
{
|
||||
if (det->is_task)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<li class=\"task-list-item\">"
|
||||
"<input type=\"checkbox\" class=\"task-list-item-checkbox\" disabled");
|
||||
if (det->task_mark == 'x' || det->task_mark == 'X')
|
||||
RENDER_VERBATIM(r, " checked");
|
||||
RENDER_VERBATIM(r, ">");
|
||||
}
|
||||
else
|
||||
{
|
||||
RENDER_VERBATIM(r, "<li>");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_code_block(MD_HTML *r, const MD_BLOCK_CODE_DETAIL *det)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<pre><code");
|
||||
|
||||
/* If known, output the HTML 5 attribute class="language-LANGNAME". */
|
||||
if (det->lang.text != NULL)
|
||||
{
|
||||
RENDER_VERBATIM(r, " class=\"language-");
|
||||
render_attribute(r, &det->lang, render_html_escaped);
|
||||
RENDER_VERBATIM(r, "\"");
|
||||
}
|
||||
|
||||
RENDER_VERBATIM(r, ">");
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_td_block(MD_HTML *r, const MD_CHAR *cell_type, const MD_BLOCK_TD_DETAIL *det)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<");
|
||||
RENDER_VERBATIM(r, cell_type);
|
||||
|
||||
switch (det->align)
|
||||
{
|
||||
case MD_ALIGN_LEFT:
|
||||
RENDER_VERBATIM(r, " align=\"left\">");
|
||||
break;
|
||||
case MD_ALIGN_CENTER:
|
||||
RENDER_VERBATIM(r, " align=\"center\">");
|
||||
break;
|
||||
case MD_ALIGN_RIGHT:
|
||||
RENDER_VERBATIM(r, " align=\"right\">");
|
||||
break;
|
||||
default:
|
||||
RENDER_VERBATIM(r, ">");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_a_span(MD_HTML *r, const MD_SPAN_A_DETAIL *det)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<a href=\"");
|
||||
render_attribute(r, &det->href, render_url_escaped);
|
||||
|
||||
if (det->title.text != NULL)
|
||||
{
|
||||
RENDER_VERBATIM(r, "\" title=\"");
|
||||
render_attribute(r, &det->title, render_html_escaped);
|
||||
}
|
||||
|
||||
RENDER_VERBATIM(r, "\">");
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_img_span(MD_HTML *r, const MD_SPAN_IMG_DETAIL *det)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<img src=\"");
|
||||
render_attribute(r, &det->src, render_url_escaped);
|
||||
|
||||
RENDER_VERBATIM(r, "\" alt=\"");
|
||||
|
||||
r->image_nesting_level++;
|
||||
}
|
||||
|
||||
static void
|
||||
render_close_img_span(MD_HTML *r, const MD_SPAN_IMG_DETAIL *det)
|
||||
{
|
||||
if (det->title.text != NULL)
|
||||
{
|
||||
RENDER_VERBATIM(r, "\" title=\"");
|
||||
render_attribute(r, &det->title, render_html_escaped);
|
||||
}
|
||||
|
||||
RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "\" />" : "\">");
|
||||
|
||||
r->image_nesting_level--;
|
||||
}
|
||||
|
||||
static void
|
||||
render_open_wikilink_span(MD_HTML *r, const MD_SPAN_WIKILINK_DETAIL *det)
|
||||
{
|
||||
RENDER_VERBATIM(r, "<x-wikilink data-target=\"");
|
||||
render_attribute(r, &det->target, render_html_escaped);
|
||||
|
||||
RENDER_VERBATIM(r, "\">");
|
||||
}
|
||||
|
||||
/**************************************
|
||||
*** HTML renderer implementation ***
|
||||
**************************************/
|
||||
|
||||
static int
|
||||
enter_block_callback(MD_BLOCKTYPE type, void *detail, void *userdata)
|
||||
{
|
||||
static const MD_CHAR *head[6] = {"<h1>", "<h2>", "<h3>", "<h4>", "<h5>", "<h6>"};
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
char buf[32];
|
||||
switch (type)
|
||||
{
|
||||
case MD_BLOCK_DOC: /* noop */
|
||||
break;
|
||||
case MD_BLOCK_QUOTE:
|
||||
RENDER_VERBATIM(r, "<blockquote>\n");
|
||||
break;
|
||||
case MD_BLOCK_UL:
|
||||
RENDER_VERBATIM(r, "<ul>\n");
|
||||
break;
|
||||
case MD_BLOCK_OL:
|
||||
render_open_ol_block(r, (const MD_BLOCK_OL_DETAIL *)detail);
|
||||
break;
|
||||
case MD_BLOCK_LI:
|
||||
render_open_li_block(r, (const MD_BLOCK_LI_DETAIL *)detail);
|
||||
break;
|
||||
case MD_BLOCK_HR:
|
||||
RENDER_VERBATIM(r, (r->flags & MD_HTML_FLAG_XHTML) ? "<hr />\n" : "<hr>\n");
|
||||
break;
|
||||
case MD_BLOCK_H:
|
||||
hd_cnt[((MD_BLOCK_H_DETAIL *)detail)->level - 1]++;
|
||||
snprintf(buf, 32, "<a id=\"h%d_%d\"></a>", ((MD_BLOCK_H_DETAIL *)detail)->level, hd_cnt[((MD_BLOCK_H_DETAIL *)detail)->level - 1]);
|
||||
RENDER_VERBATIM(r, buf);
|
||||
RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL *)detail)->level - 1]);
|
||||
break;
|
||||
case MD_BLOCK_CODE:
|
||||
render_open_code_block(r, (const MD_BLOCK_CODE_DETAIL *)detail);
|
||||
break;
|
||||
case MD_BLOCK_HTML: /* noop */
|
||||
break;
|
||||
case MD_BLOCK_P:
|
||||
RENDER_VERBATIM(r, "<p>");
|
||||
break;
|
||||
case MD_BLOCK_TABLE:
|
||||
RENDER_VERBATIM(r, "<table>\n");
|
||||
break;
|
||||
case MD_BLOCK_THEAD:
|
||||
RENDER_VERBATIM(r, "<thead>\n");
|
||||
break;
|
||||
case MD_BLOCK_TBODY:
|
||||
RENDER_VERBATIM(r, "<tbody>\n");
|
||||
break;
|
||||
case MD_BLOCK_TR:
|
||||
RENDER_VERBATIM(r, "<tr>\n");
|
||||
break;
|
||||
case MD_BLOCK_TH:
|
||||
render_open_td_block(r, "th", (MD_BLOCK_TD_DETAIL *)detail);
|
||||
break;
|
||||
case MD_BLOCK_TD:
|
||||
render_open_td_block(r, "td", (MD_BLOCK_TD_DETAIL *)detail);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
leave_block_callback(MD_BLOCKTYPE type, void *detail, void *userdata)
|
||||
{
|
||||
static const MD_CHAR *head[6] = {"</h1>\n", "</h2>\n", "</h3>\n", "</h4>\n", "</h5>\n", "</h6>\n"};
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
switch (type)
|
||||
{
|
||||
case MD_BLOCK_DOC: /*noop*/
|
||||
break;
|
||||
case MD_BLOCK_QUOTE:
|
||||
RENDER_VERBATIM(r, "</blockquote>\n");
|
||||
break;
|
||||
case MD_BLOCK_UL:
|
||||
RENDER_VERBATIM(r, "</ul>\n");
|
||||
break;
|
||||
case MD_BLOCK_OL:
|
||||
RENDER_VERBATIM(r, "</ol>\n");
|
||||
break;
|
||||
case MD_BLOCK_LI:
|
||||
RENDER_VERBATIM(r, "</li>\n");
|
||||
break;
|
||||
case MD_BLOCK_HR: /*noop*/
|
||||
break;
|
||||
case MD_BLOCK_H:
|
||||
RENDER_VERBATIM(r, head[((MD_BLOCK_H_DETAIL *)detail)->level - 1]);
|
||||
break;
|
||||
case MD_BLOCK_CODE:
|
||||
RENDER_VERBATIM(r, "</code></pre>\n");
|
||||
break;
|
||||
case MD_BLOCK_HTML: /* noop */
|
||||
break;
|
||||
case MD_BLOCK_P:
|
||||
RENDER_VERBATIM(r, "</p>\n");
|
||||
break;
|
||||
case MD_BLOCK_TABLE:
|
||||
RENDER_VERBATIM(r, "</table>\n");
|
||||
break;
|
||||
case MD_BLOCK_THEAD:
|
||||
RENDER_VERBATIM(r, "</thead>\n");
|
||||
break;
|
||||
case MD_BLOCK_TBODY:
|
||||
RENDER_VERBATIM(r, "</tbody>\n");
|
||||
break;
|
||||
case MD_BLOCK_TR:
|
||||
RENDER_VERBATIM(r, "</tr>\n");
|
||||
break;
|
||||
case MD_BLOCK_TH:
|
||||
RENDER_VERBATIM(r, "</th>\n");
|
||||
break;
|
||||
case MD_BLOCK_TD:
|
||||
RENDER_VERBATIM(r, "</td>\n");
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
enter_span_callback(MD_SPANTYPE type, void *detail, void *userdata)
|
||||
{
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
|
||||
if (r->image_nesting_level > 0)
|
||||
{
|
||||
/* We are inside a Markdown image label. Markdown allows to use any
|
||||
* emphasis and other rich contents in that context similarly as in
|
||||
* any link label.
|
||||
*
|
||||
* However, unlike in the case of links (where that contents becomes
|
||||
* contents of the <a>...</a> tag), in the case of images the contents
|
||||
* is supposed to fall into the attribute alt: <img alt="...">.
|
||||
*
|
||||
* In that context we naturally cannot output nested HTML tags. So lets
|
||||
* suppress them and only output the plain text (i.e. what falls into
|
||||
* text() callback).
|
||||
*
|
||||
* This make-it-a-plain-text approach is the recommended practice by
|
||||
* CommonMark specification (for HTML output).
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MD_SPAN_EM:
|
||||
RENDER_VERBATIM(r, "<em>");
|
||||
break;
|
||||
case MD_SPAN_STRONG:
|
||||
RENDER_VERBATIM(r, "<strong>");
|
||||
break;
|
||||
case MD_SPAN_U:
|
||||
RENDER_VERBATIM(r, "<u>");
|
||||
break;
|
||||
case MD_SPAN_A:
|
||||
render_open_a_span(r, (MD_SPAN_A_DETAIL *)detail);
|
||||
break;
|
||||
case MD_SPAN_IMG:
|
||||
render_open_img_span(r, (MD_SPAN_IMG_DETAIL *)detail);
|
||||
break;
|
||||
case MD_SPAN_CODE:
|
||||
RENDER_VERBATIM(r, "<code>");
|
||||
break;
|
||||
case MD_SPAN_DEL:
|
||||
RENDER_VERBATIM(r, "<del>");
|
||||
break;
|
||||
case MD_SPAN_LATEXMATH:
|
||||
RENDER_VERBATIM(r, "<x-equation>");
|
||||
break;
|
||||
case MD_SPAN_LATEXMATH_DISPLAY:
|
||||
RENDER_VERBATIM(r, "<x-equation type=\"display\">");
|
||||
break;
|
||||
case MD_SPAN_WIKILINK:
|
||||
render_open_wikilink_span(r, (MD_SPAN_WIKILINK_DETAIL *)detail);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
leave_span_callback(MD_SPANTYPE type, void *detail, void *userdata)
|
||||
{
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
|
||||
if (r->image_nesting_level > 0)
|
||||
{
|
||||
/* Ditto as in enter_span_callback(), except we have to allow the
|
||||
* end of the <img> tag. */
|
||||
if (r->image_nesting_level == 1 && type == MD_SPAN_IMG)
|
||||
render_close_img_span(r, (MD_SPAN_IMG_DETAIL *)detail);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MD_SPAN_EM:
|
||||
RENDER_VERBATIM(r, "</em>");
|
||||
break;
|
||||
case MD_SPAN_STRONG:
|
||||
RENDER_VERBATIM(r, "</strong>");
|
||||
break;
|
||||
case MD_SPAN_U:
|
||||
RENDER_VERBATIM(r, "</u>");
|
||||
break;
|
||||
case MD_SPAN_A:
|
||||
RENDER_VERBATIM(r, "</a>");
|
||||
break;
|
||||
case MD_SPAN_IMG: /*noop, handled above*/
|
||||
break;
|
||||
case MD_SPAN_CODE:
|
||||
RENDER_VERBATIM(r, "</code>");
|
||||
break;
|
||||
case MD_SPAN_DEL:
|
||||
RENDER_VERBATIM(r, "</del>");
|
||||
break;
|
||||
case MD_SPAN_LATEXMATH: /*fall through*/
|
||||
case MD_SPAN_LATEXMATH_DISPLAY:
|
||||
RENDER_VERBATIM(r, "</x-equation>");
|
||||
break;
|
||||
case MD_SPAN_WIKILINK:
|
||||
RENDER_VERBATIM(r, "</x-wikilink>");
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
text_callback(MD_TEXTTYPE type, const MD_CHAR *text, MD_SIZE size, void *userdata)
|
||||
{
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case MD_TEXT_NULLCHAR:
|
||||
render_utf8_codepoint(r, 0x0000, render_verbatim);
|
||||
break;
|
||||
case MD_TEXT_BR:
|
||||
RENDER_VERBATIM(r, (r->image_nesting_level == 0
|
||||
? ((r->flags & MD_HTML_FLAG_XHTML) ? "<br />\n" : "<br>\n")
|
||||
: " "));
|
||||
break;
|
||||
case MD_TEXT_SOFTBR:
|
||||
RENDER_VERBATIM(r, (r->image_nesting_level == 0 ? "\n" : " "));
|
||||
break;
|
||||
case MD_TEXT_HTML:
|
||||
render_verbatim(r, text, size);
|
||||
break;
|
||||
case MD_TEXT_ENTITY:
|
||||
render_entity(r, text, size, render_html_escaped);
|
||||
break;
|
||||
default:
|
||||
render_html_escaped(r, text, size);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
debug_log_callback(const char *msg, void *userdata)
|
||||
{
|
||||
MD_HTML *r = (MD_HTML *)userdata;
|
||||
if (r->flags & MD_HTML_FLAG_DEBUG)
|
||||
fprintf(stderr, "MD4C: %s\n", msg);
|
||||
}
|
||||
|
||||
int md_html(const MD_CHAR *input, MD_SIZE input_size,
|
||||
void (*process_output)(const MD_CHAR *, MD_SIZE, void *),
|
||||
void *userdata, unsigned parser_flags, unsigned renderer_flags)
|
||||
{
|
||||
MD_HTML render = {process_output, userdata, renderer_flags, 0, {0}};
|
||||
int i;
|
||||
|
||||
MD_PARSER parser = {
|
||||
0,
|
||||
parser_flags,
|
||||
enter_block_callback,
|
||||
leave_block_callback,
|
||||
enter_span_callback,
|
||||
leave_span_callback,
|
||||
text_callback,
|
||||
debug_log_callback,
|
||||
NULL};
|
||||
|
||||
/* Build map of characters which need escaping. */
|
||||
for (i = 0; i < 256; i++)
|
||||
{
|
||||
unsigned char ch = (unsigned char)i;
|
||||
|
||||
if (strchr("\"&<>", ch) != NULL)
|
||||
render.escape_map[i] |= NEED_HTML_ESC_FLAG;
|
||||
|
||||
if (!ISALNUM(ch) && strchr("-_.+!*(),%#@?=;:/,+$", ch) == NULL)
|
||||
render.escape_map[i] |= NEED_URL_ESC_FLAG;
|
||||
}
|
||||
|
||||
/* Consider skipping UTF-8 byte order mark (BOM). */
|
||||
if (renderer_flags & MD_HTML_FLAG_SKIP_UTF8_BOM && sizeof(MD_CHAR) == 1)
|
||||
{
|
||||
static const MD_CHAR bom[3] = {0xef, 0xbb, 0xbf};
|
||||
if (input_size >= sizeof(bom) && memcmp(input, bom, sizeof(bom)) == 0)
|
||||
{
|
||||
input += sizeof(bom);
|
||||
input_size -= sizeof(bom);
|
||||
}
|
||||
}
|
||||
|
||||
return md_parse(input, input_size, &parser, (void *)&render);
|
||||
}
|
||||
|
||||
void reset_hd_cnt()
|
||||
{
|
||||
for (size_t i = 0; i < 6; i++)
|
||||
{
|
||||
hd_cnt[i] = 0;
|
||||
}
|
||||
}
|
70
modules/3rd/md4c/md4c-html.h
Normal file
70
modules/3rd/md4c/md4c-html.h
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* MD4C: Markdown parser for C
|
||||
* (http://github.com/mity/md4c)
|
||||
*
|
||||
* Copyright (c) 2016-2017 Martin Mitas
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MD4C_HTML_H
|
||||
#define MD4C_HTML_H
|
||||
|
||||
#include "md4c.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/* If set, debug output from md_parse() is sent to stderr. */
|
||||
#define MD_HTML_FLAG_DEBUG 0x0001
|
||||
#define MD_HTML_FLAG_VERBATIM_ENTITIES 0x0002
|
||||
#define MD_HTML_FLAG_SKIP_UTF8_BOM 0x0004
|
||||
#define MD_HTML_FLAG_XHTML 0x0008
|
||||
|
||||
|
||||
/* Render Markdown into HTML.
|
||||
*
|
||||
* Note only contents of <body> tag is generated. Caller must generate
|
||||
* HTML header/footer manually before/after calling md_html().
|
||||
*
|
||||
* Params input and input_size specify the Markdown input.
|
||||
* Callback process_output() gets called with chunks of HTML output.
|
||||
* (Typical implementation may just output the bytes to a file or append to
|
||||
* some buffer).
|
||||
* Param userdata is just propgated back to process_output() callback.
|
||||
* Param parser_flags are flags from md4c.h propagated to md_parse().
|
||||
* Param render_flags is bitmask of MD_HTML_FLAG_xxxx.
|
||||
*
|
||||
* Returns -1 on error (if md_parse() fails.)
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int md_html(const MD_CHAR* input, MD_SIZE input_size,
|
||||
void (*process_output)(const MD_CHAR*, MD_SIZE, void*),
|
||||
void* userdata, unsigned parser_flags, unsigned renderer_flags);
|
||||
|
||||
|
||||
void reset_hd_cnt();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" { */
|
||||
#endif
|
||||
|
||||
#endif /* MD4C_HTML_H */
|
6306
modules/3rd/md4c/md4c.c
Normal file
6306
modules/3rd/md4c/md4c.c
Normal file
File diff suppressed because it is too large
Load Diff
397
modules/3rd/md4c/md4c.h
Normal file
397
modules/3rd/md4c/md4c.h
Normal file
@ -0,0 +1,397 @@
|
||||
/*
|
||||
* MD4C: Markdown parser for C
|
||||
* (http://github.com/mity/md4c)
|
||||
*
|
||||
* Copyright (c) 2016-2020 Martin Mitas
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef MD4C_H
|
||||
#define MD4C_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined MD4C_USE_UTF16
|
||||
/* Magic to support UTF-16. Note that in order to use it, you have to define
|
||||
* the macro MD4C_USE_UTF16 both when building MD4C as well as when
|
||||
* including this header in your code. */
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
typedef WCHAR MD_CHAR;
|
||||
#else
|
||||
#error MD4C_USE_UTF16 is only supported on Windows.
|
||||
#endif
|
||||
#else
|
||||
typedef char MD_CHAR;
|
||||
#endif
|
||||
|
||||
typedef unsigned MD_SIZE;
|
||||
typedef unsigned MD_OFFSET;
|
||||
|
||||
|
||||
/* Block represents a part of document hierarchy structure like a paragraph
|
||||
* or list item.
|
||||
*/
|
||||
typedef enum MD_BLOCKTYPE {
|
||||
/* <body>...</body> */
|
||||
MD_BLOCK_DOC = 0,
|
||||
|
||||
/* <blockquote>...</blockquote> */
|
||||
MD_BLOCK_QUOTE,
|
||||
|
||||
/* <ul>...</ul>
|
||||
* Detail: Structure MD_BLOCK_UL_DETAIL. */
|
||||
MD_BLOCK_UL,
|
||||
|
||||
/* <ol>...</ol>
|
||||
* Detail: Structure MD_BLOCK_OL_DETAIL. */
|
||||
MD_BLOCK_OL,
|
||||
|
||||
/* <li>...</li>
|
||||
* Detail: Structure MD_BLOCK_LI_DETAIL. */
|
||||
MD_BLOCK_LI,
|
||||
|
||||
/* <hr> */
|
||||
MD_BLOCK_HR,
|
||||
|
||||
/* <h1>...</h1> (for levels up to 6)
|
||||
* Detail: Structure MD_BLOCK_H_DETAIL. */
|
||||
MD_BLOCK_H,
|
||||
|
||||
/* <pre><code>...</code></pre>
|
||||
* Note the text lines within code blocks are terminated with '\n'
|
||||
* instead of explicit MD_TEXT_BR. */
|
||||
MD_BLOCK_CODE,
|
||||
|
||||
/* Raw HTML block. This itself does not correspond to any particular HTML
|
||||
* tag. The contents of it _is_ raw HTML source intended to be put
|
||||
* in verbatim form to the HTML output. */
|
||||
MD_BLOCK_HTML,
|
||||
|
||||
/* <p>...</p> */
|
||||
MD_BLOCK_P,
|
||||
|
||||
/* <table>...</table> and its contents.
|
||||
* Detail: Structure MD_BLOCK_TD_DETAIL (used with MD_BLOCK_TH and MD_BLOCK_TD)
|
||||
* Note all of these are used only if extension MD_FLAG_TABLES is enabled. */
|
||||
MD_BLOCK_TABLE,
|
||||
MD_BLOCK_THEAD,
|
||||
MD_BLOCK_TBODY,
|
||||
MD_BLOCK_TR,
|
||||
MD_BLOCK_TH,
|
||||
MD_BLOCK_TD
|
||||
} MD_BLOCKTYPE;
|
||||
|
||||
/* Span represents an in-line piece of a document which should be rendered with
|
||||
* the same font, color and other attributes. A sequence of spans forms a block
|
||||
* like paragraph or list item. */
|
||||
typedef enum MD_SPANTYPE {
|
||||
/* <em>...</em> */
|
||||
MD_SPAN_EM,
|
||||
|
||||
/* <strong>...</strong> */
|
||||
MD_SPAN_STRONG,
|
||||
|
||||
/* <a href="xxx">...</a>
|
||||
* Detail: Structure MD_SPAN_A_DETAIL. */
|
||||
MD_SPAN_A,
|
||||
|
||||
/* <img src="xxx">...</a>
|
||||
* Detail: Structure MD_SPAN_IMG_DETAIL.
|
||||
* Note: Image text can contain nested spans and even nested images.
|
||||
* If rendered into ALT attribute of HTML <IMG> tag, it's responsibility
|
||||
* of the parser to deal with it.
|
||||
*/
|
||||
MD_SPAN_IMG,
|
||||
|
||||
/* <code>...</code> */
|
||||
MD_SPAN_CODE,
|
||||
|
||||
/* <del>...</del>
|
||||
* Note: Recognized only when MD_FLAG_STRIKETHROUGH is enabled.
|
||||
*/
|
||||
MD_SPAN_DEL,
|
||||
|
||||
/* For recognizing inline ($) and display ($$) equations
|
||||
* Note: Recognized only when MD_FLAG_LATEXMATHSPANS is enabled.
|
||||
*/
|
||||
MD_SPAN_LATEXMATH,
|
||||
MD_SPAN_LATEXMATH_DISPLAY,
|
||||
|
||||
/* Wiki links
|
||||
* Note: Recognized only when MD_FLAG_WIKILINKS is enabled.
|
||||
*/
|
||||
MD_SPAN_WIKILINK,
|
||||
|
||||
/* <u>...</u>
|
||||
* Note: Recognized only when MD_FLAG_UNDERLINE is enabled. */
|
||||
MD_SPAN_U
|
||||
} MD_SPANTYPE;
|
||||
|
||||
/* Text is the actual textual contents of span. */
|
||||
typedef enum MD_TEXTTYPE {
|
||||
/* Normal text. */
|
||||
MD_TEXT_NORMAL = 0,
|
||||
|
||||
/* NULL character. CommonMark requires replacing NULL character with
|
||||
* the replacement char U+FFFD, so this allows caller to do that easily. */
|
||||
MD_TEXT_NULLCHAR,
|
||||
|
||||
/* Line breaks.
|
||||
* Note these are not sent from blocks with verbatim output (MD_BLOCK_CODE
|
||||
* or MD_BLOCK_HTML). In such cases, '\n' is part of the text itself. */
|
||||
MD_TEXT_BR, /* <br> (hard break) */
|
||||
MD_TEXT_SOFTBR, /* '\n' in source text where it is not semantically meaningful (soft break) */
|
||||
|
||||
/* Entity.
|
||||
* (a) Named entity, e.g.
|
||||
* (Note MD4C does not have a list of known entities.
|
||||
* Anything matching the regexp /&[A-Za-z][A-Za-z0-9]{1,47};/ is
|
||||
* treated as a named entity.)
|
||||
* (b) Numerical entity, e.g. Ӓ
|
||||
* (c) Hexadecimal entity, e.g. ካ
|
||||
*
|
||||
* As MD4C is mostly encoding agnostic, application gets the verbatim
|
||||
* entity text into the MD_PARSER::text_callback(). */
|
||||
MD_TEXT_ENTITY,
|
||||
|
||||
/* Text in a code block (inside MD_BLOCK_CODE) or inlined code (`code`).
|
||||
* If it is inside MD_BLOCK_CODE, it includes spaces for indentation and
|
||||
* '\n' for new lines. MD_TEXT_BR and MD_TEXT_SOFTBR are not sent for this
|
||||
* kind of text. */
|
||||
MD_TEXT_CODE,
|
||||
|
||||
/* Text is a raw HTML. If it is contents of a raw HTML block (i.e. not
|
||||
* an inline raw HTML), then MD_TEXT_BR and MD_TEXT_SOFTBR are not used.
|
||||
* The text contains verbatim '\n' for the new lines. */
|
||||
MD_TEXT_HTML,
|
||||
|
||||
/* Text is inside an equation. This is processed the same way as inlined code
|
||||
* spans (`code`). */
|
||||
MD_TEXT_LATEXMATH
|
||||
} MD_TEXTTYPE;
|
||||
|
||||
|
||||
/* Alignment enumeration. */
|
||||
typedef enum MD_ALIGN {
|
||||
MD_ALIGN_DEFAULT = 0, /* When unspecified. */
|
||||
MD_ALIGN_LEFT,
|
||||
MD_ALIGN_CENTER,
|
||||
MD_ALIGN_RIGHT
|
||||
} MD_ALIGN;
|
||||
|
||||
|
||||
/* String attribute.
|
||||
*
|
||||
* This wraps strings which are outside of a normal text flow and which are
|
||||
* propagated within various detailed structures, but which still may contain
|
||||
* string portions of different types like e.g. entities.
|
||||
*
|
||||
* So, for example, lets consider this image:
|
||||
*
|
||||
* 
|
||||
*
|
||||
* The image alt text is propagated as a normal text via the MD_PARSER::text()
|
||||
* callback. However, the image title ('foo " bar') is propagated as
|
||||
* MD_ATTRIBUTE in MD_SPAN_IMG_DETAIL::title.
|
||||
*
|
||||
* Then the attribute MD_SPAN_IMG_DETAIL::title shall provide the following:
|
||||
* -- [0]: "foo " (substr_types[0] == MD_TEXT_NORMAL; substr_offsets[0] == 0)
|
||||
* -- [1]: """ (substr_types[1] == MD_TEXT_ENTITY; substr_offsets[1] == 4)
|
||||
* -- [2]: " bar" (substr_types[2] == MD_TEXT_NORMAL; substr_offsets[2] == 10)
|
||||
* -- [3]: (n/a) (n/a ; substr_offsets[3] == 14)
|
||||
*
|
||||
* Note that these invariants are always guaranteed:
|
||||
* -- substr_offsets[0] == 0
|
||||
* -- substr_offsets[LAST+1] == size
|
||||
* -- Currently, only MD_TEXT_NORMAL, MD_TEXT_ENTITY, MD_TEXT_NULLCHAR
|
||||
* substrings can appear. This could change only of the specification
|
||||
* changes.
|
||||
*/
|
||||
typedef struct MD_ATTRIBUTE {
|
||||
const MD_CHAR* text;
|
||||
MD_SIZE size;
|
||||
const MD_TEXTTYPE* substr_types;
|
||||
const MD_OFFSET* substr_offsets;
|
||||
} MD_ATTRIBUTE;
|
||||
|
||||
|
||||
/* Detailed info for MD_BLOCK_UL. */
|
||||
typedef struct MD_BLOCK_UL_DETAIL {
|
||||
int is_tight; /* Non-zero if tight list, zero if loose. */
|
||||
MD_CHAR mark; /* Item bullet character in MarkDown source of the list, e.g. '-', '+', '*'. */
|
||||
} MD_BLOCK_UL_DETAIL;
|
||||
|
||||
/* Detailed info for MD_BLOCK_OL. */
|
||||
typedef struct MD_BLOCK_OL_DETAIL {
|
||||
unsigned start; /* Start index of the ordered list. */
|
||||
int is_tight; /* Non-zero if tight list, zero if loose. */
|
||||
MD_CHAR mark_delimiter; /* Character delimiting the item marks in MarkDown source, e.g. '.' or ')' */
|
||||
} MD_BLOCK_OL_DETAIL;
|
||||
|
||||
/* Detailed info for MD_BLOCK_LI. */
|
||||
typedef struct MD_BLOCK_LI_DETAIL {
|
||||
int is_task; /* Can be non-zero only with MD_FLAG_TASKLISTS */
|
||||
MD_CHAR task_mark; /* If is_task, then one of 'x', 'X' or ' '. Undefined otherwise. */
|
||||
MD_OFFSET task_mark_offset; /* If is_task, then offset in the input of the char between '[' and ']'. */
|
||||
} MD_BLOCK_LI_DETAIL;
|
||||
|
||||
/* Detailed info for MD_BLOCK_H. */
|
||||
typedef struct MD_BLOCK_H_DETAIL {
|
||||
unsigned level; /* Header level (1 - 6) */
|
||||
} MD_BLOCK_H_DETAIL;
|
||||
|
||||
/* Detailed info for MD_BLOCK_CODE. */
|
||||
typedef struct MD_BLOCK_CODE_DETAIL {
|
||||
MD_ATTRIBUTE info;
|
||||
MD_ATTRIBUTE lang;
|
||||
MD_CHAR fence_char; /* The character used for fenced code block; or zero for indented code block. */
|
||||
} MD_BLOCK_CODE_DETAIL;
|
||||
|
||||
/* Detailed info for MD_BLOCK_TH and MD_BLOCK_TD. */
|
||||
typedef struct MD_BLOCK_TD_DETAIL {
|
||||
MD_ALIGN align;
|
||||
} MD_BLOCK_TD_DETAIL;
|
||||
|
||||
/* Detailed info for MD_SPAN_A. */
|
||||
typedef struct MD_SPAN_A_DETAIL {
|
||||
MD_ATTRIBUTE href;
|
||||
MD_ATTRIBUTE title;
|
||||
} MD_SPAN_A_DETAIL;
|
||||
|
||||
/* Detailed info for MD_SPAN_IMG. */
|
||||
typedef struct MD_SPAN_IMG_DETAIL {
|
||||
MD_ATTRIBUTE src;
|
||||
MD_ATTRIBUTE title;
|
||||
} MD_SPAN_IMG_DETAIL;
|
||||
|
||||
/* Detailed info for MD_SPAN_WIKILINK. */
|
||||
typedef struct MD_SPAN_WIKILINK {
|
||||
MD_ATTRIBUTE target;
|
||||
} MD_SPAN_WIKILINK_DETAIL;
|
||||
|
||||
/* Flags specifying extensions/deviations from CommonMark specification.
|
||||
*
|
||||
* By default (when MD_PARSER::flags == 0), we follow CommonMark specification.
|
||||
* The following flags may allow some extensions or deviations from it.
|
||||
*/
|
||||
#define MD_FLAG_COLLAPSEWHITESPACE 0x0001 /* In MD_TEXT_NORMAL, collapse non-trivial whitespace into single ' ' */
|
||||
#define MD_FLAG_PERMISSIVEATXHEADERS 0x0002 /* Do not require space in ATX headers ( ###header ) */
|
||||
#define MD_FLAG_PERMISSIVEURLAUTOLINKS 0x0004 /* Recognize URLs as autolinks even without '<', '>' */
|
||||
#define MD_FLAG_PERMISSIVEEMAILAUTOLINKS 0x0008 /* Recognize e-mails as autolinks even without '<', '>' and 'mailto:' */
|
||||
#define MD_FLAG_NOINDENTEDCODEBLOCKS 0x0010 /* Disable indented code blocks. (Only fenced code works.) */
|
||||
#define MD_FLAG_NOHTMLBLOCKS 0x0020 /* Disable raw HTML blocks. */
|
||||
#define MD_FLAG_NOHTMLSPANS 0x0040 /* Disable raw HTML (inline). */
|
||||
#define MD_FLAG_TABLES 0x0100 /* Enable tables extension. */
|
||||
#define MD_FLAG_STRIKETHROUGH 0x0200 /* Enable strikethrough extension. */
|
||||
#define MD_FLAG_PERMISSIVEWWWAUTOLINKS 0x0400 /* Enable WWW autolinks (even without any scheme prefix, if they begin with 'www.') */
|
||||
#define MD_FLAG_TASKLISTS 0x0800 /* Enable task list extension. */
|
||||
#define MD_FLAG_LATEXMATHSPANS 0x1000 /* Enable $ and $$ containing LaTeX equations. */
|
||||
#define MD_FLAG_WIKILINKS 0x2000 /* Enable wiki links extension. */
|
||||
#define MD_FLAG_UNDERLINE 0x4000 /* Enable underline extension (and disables '_' for normal emphasis). */
|
||||
|
||||
#define MD_FLAG_PERMISSIVEAUTOLINKS (MD_FLAG_PERMISSIVEEMAILAUTOLINKS | MD_FLAG_PERMISSIVEURLAUTOLINKS | MD_FLAG_PERMISSIVEWWWAUTOLINKS)
|
||||
#define MD_FLAG_NOHTML (MD_FLAG_NOHTMLBLOCKS | MD_FLAG_NOHTMLSPANS)
|
||||
|
||||
/* Convenient sets of flags corresponding to well-known Markdown dialects.
|
||||
*
|
||||
* Note we may only support subset of features of the referred dialect.
|
||||
* The constant just enables those extensions which bring us as close as
|
||||
* possible given what features we implement.
|
||||
*
|
||||
* ABI compatibility note: Meaning of these can change in time as new
|
||||
* extensions, bringing the dialect closer to the original, are implemented.
|
||||
*/
|
||||
#define MD_DIALECT_COMMONMARK 0
|
||||
#define MD_DIALECT_GITHUB (MD_FLAG_PERMISSIVEAUTOLINKS | MD_FLAG_TABLES | MD_FLAG_STRIKETHROUGH | MD_FLAG_TASKLISTS)
|
||||
|
||||
/* Parser structure.
|
||||
*/
|
||||
typedef struct MD_PARSER {
|
||||
/* Reserved. Set to zero.
|
||||
*/
|
||||
unsigned abi_version;
|
||||
|
||||
/* Dialect options. Bitmask of MD_FLAG_xxxx values.
|
||||
*/
|
||||
unsigned flags;
|
||||
|
||||
/* Caller-provided rendering callbacks.
|
||||
*
|
||||
* For some block/span types, more detailed information is provided in a
|
||||
* type-specific structure pointed by the argument 'detail'.
|
||||
*
|
||||
* The last argument of all callbacks, 'userdata', is just propagated from
|
||||
* md_parse() and is available for any use by the application.
|
||||
*
|
||||
* Note any strings provided to the callbacks as their arguments or as
|
||||
* members of any detail structure are generally not zero-terminated.
|
||||
* Application has to take the respective size information into account.
|
||||
*
|
||||
* Any rendering callback may abort further parsing of the document by
|
||||
* returning non-zero.
|
||||
*/
|
||||
int (*enter_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
|
||||
int (*leave_block)(MD_BLOCKTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
|
||||
|
||||
int (*enter_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
|
||||
int (*leave_span)(MD_SPANTYPE /*type*/, void* /*detail*/, void* /*userdata*/);
|
||||
|
||||
int (*text)(MD_TEXTTYPE /*type*/, const MD_CHAR* /*text*/, MD_SIZE /*size*/, void* /*userdata*/);
|
||||
|
||||
/* Debug callback. Optional (may be NULL).
|
||||
*
|
||||
* If provided and something goes wrong, this function gets called.
|
||||
* This is intended for debugging and problem diagnosis for developers;
|
||||
* it is not intended to provide any errors suitable for displaying to an
|
||||
* end user.
|
||||
*/
|
||||
void (*debug_log)(const char* /*msg*/, void* /*userdata*/);
|
||||
|
||||
/* Reserved. Set to NULL.
|
||||
*/
|
||||
void (*syntax)(void);
|
||||
} MD_PARSER;
|
||||
|
||||
|
||||
/* For backward compatibility. Do not use in new code.
|
||||
*/
|
||||
typedef MD_PARSER MD_RENDERER;
|
||||
|
||||
|
||||
/* Parse the Markdown document stored in the string 'text' of size 'size'.
|
||||
* The parser provides callbacks to be called during the parsing so the
|
||||
* caller can render the document on the screen or convert the Markdown
|
||||
* to another format.
|
||||
*
|
||||
* Zero is returned on success. If a runtime error occurs (e.g. a memory
|
||||
* fails), -1 is returned. If the processing is aborted due any callback
|
||||
* returning non-zero, the return value of the callback is returned.
|
||||
*/
|
||||
int md_parse(const MD_CHAR* text, MD_SIZE size, const MD_PARSER* parser, void* userdata);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" { */
|
||||
#endif
|
||||
|
||||
#endif /* MD4C_H */
|
50
modules/3rd/zip/main.c
Normal file
50
modules/3rd/zip/main.c
Normal file
@ -0,0 +1,50 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "zip.h"
|
||||
|
||||
// callback function
|
||||
int on_extract_entry(const char *filename, void *arg) {
|
||||
static int i = 0;
|
||||
int n = *(int *)arg;
|
||||
printf("Extracted: %s (%d of %d)\n", filename, ++i, n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, const char** argv) {
|
||||
/*
|
||||
Create a new zip archive with default compression level (6)
|
||||
*/
|
||||
/*
|
||||
struct zip_t *zip = zip_open("foo.zip", ZIP_DEFAULT_COMPRESSION_LEVEL, 0);
|
||||
// we should check if zip is NULL
|
||||
{
|
||||
zip_entry_open(zip, "foo-1.txt");
|
||||
{
|
||||
char *buf = "Some data here...";
|
||||
zip_entry_write(zip, buf, strlen(buf));
|
||||
}
|
||||
zip_entry_close(zip);
|
||||
|
||||
zip_entry_open(zip, "foo-2.txt");
|
||||
{
|
||||
// merge 3 files into one entry and compress them on-the-fly.
|
||||
zip_entry_fwrite(zip, "foo-2.1.txt");
|
||||
zip_entry_fwrite(zip, "foo-2.2.txt");
|
||||
zip_entry_fwrite(zip, "foo-2.3.txt");
|
||||
}
|
||||
zip_entry_close(zip);
|
||||
}
|
||||
// always remember to close and release resources
|
||||
zip_close(zip);
|
||||
*/
|
||||
|
||||
/*
|
||||
Extract a zip archive into /tmp folder
|
||||
*/
|
||||
int arg = 5;
|
||||
zip_extract(argv[1], "/Users/mrsang/Downloads/zip-master/src/tmp", on_extract_entry, &arg);
|
||||
|
||||
return 0;
|
||||
}
|
4916
modules/3rd/zip/miniz.c
Executable file
4916
modules/3rd/zip/miniz.c
Executable file
File diff suppressed because it is too large
Load Diff
471
modules/3rd/zip/zip.c
Executable file
471
modules/3rd/zip/zip.c
Executable file
@ -0,0 +1,471 @@
|
||||
/*
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "miniz.c"
|
||||
#include "zip.h"
|
||||
|
||||
#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
|
||||
/* Win32, OS/2, DOS */
|
||||
#define HAS_DEVICE(P) ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && (P)[1] == ':')
|
||||
#define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE (P) ? 2 : 0)
|
||||
#define ISSLASH(C) ((C) == '/' || (C) == '\\')
|
||||
#endif
|
||||
|
||||
#ifndef FILESYSTEM_PREFIX_LEN
|
||||
#define FILESYSTEM_PREFIX_LEN(P) 0
|
||||
#endif
|
||||
|
||||
#ifndef ISSLASH
|
||||
#define ISSLASH(C) ((C) == '/')
|
||||
#endif
|
||||
|
||||
#define cleanup(ptr) do { if (ptr) { free((void *)ptr); ptr = NULL; } } while (0)
|
||||
#define strclone(ptr) ((ptr) ? strdup(ptr) : NULL)
|
||||
|
||||
char *basename(const char *name) {
|
||||
char const *p;
|
||||
char const *base = name += FILESYSTEM_PREFIX_LEN (name);
|
||||
int all_slashes = 1;
|
||||
|
||||
for (p = name; *p; p++) {
|
||||
if (ISSLASH(*p))
|
||||
base = p + 1;
|
||||
else
|
||||
all_slashes = 0;
|
||||
}
|
||||
|
||||
/* If NAME is all slashes, arrange to return `/'. */
|
||||
if (*base == '\0' && ISSLASH(*name) && all_slashes)
|
||||
--base;
|
||||
|
||||
return (char *)base;
|
||||
}
|
||||
|
||||
static int mkpath(const char *path) {
|
||||
const int mode = 0755;
|
||||
char const *p;
|
||||
char npath[MAX_PATH + 1] = { 0 };
|
||||
int len = 0;
|
||||
|
||||
for (p = path; *p && len < MAX_PATH; p++) {
|
||||
if (ISSLASH(*p) && len > 0) {
|
||||
if (mkdir(npath, mode) == -1)
|
||||
if (errno != EEXIST) return -1;
|
||||
}
|
||||
npath[len++] = *p;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct zip_entry_t {
|
||||
const char *name;
|
||||
mz_uint64 uncomp_size;
|
||||
mz_uint64 comp_size;
|
||||
mz_uint32 uncomp_crc32;
|
||||
mz_uint64 offset;
|
||||
mz_uint8 header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
|
||||
mz_uint64 header_offset;
|
||||
mz_uint16 method;
|
||||
mz_zip_writer_add_state state;
|
||||
tdefl_compressor comp;
|
||||
};
|
||||
|
||||
struct zip_t {
|
||||
mz_zip_archive archive;
|
||||
mz_uint level;
|
||||
struct zip_entry_t entry;
|
||||
};
|
||||
|
||||
struct zip_t *zip_open(const char *zipname, int level, int append) {
|
||||
struct zip_t *zip = NULL;
|
||||
struct MZ_FILE_STAT_STRUCT fstat;
|
||||
|
||||
if (!zipname || strlen(zipname) < 1) {
|
||||
// zip_t archive name is empty or NULL
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (level < 0) level = MZ_DEFAULT_LEVEL;
|
||||
if ((level & 0xF) > MZ_UBER_COMPRESSION) {
|
||||
// Wrong compression level
|
||||
return NULL;
|
||||
}
|
||||
|
||||
zip = (struct zip_t *)calloc((size_t)1, sizeof(struct zip_t));
|
||||
if (zip) {
|
||||
zip->level = level;
|
||||
|
||||
if (append && MZ_FILE_STAT(zipname, &fstat) == 0) {
|
||||
// Append to an existing archive.
|
||||
if (!mz_zip_reader_init_file(&(zip->archive), zipname, level | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) {
|
||||
cleanup(zip);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!mz_zip_writer_init_from_reader(&(zip->archive), zipname)) {
|
||||
mz_zip_reader_end(&(zip->archive));
|
||||
cleanup(zip);
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
// Create a new archive.
|
||||
if (!mz_zip_writer_init_file(&(zip->archive), zipname, 0)) {
|
||||
// Cannot initialize zip_archive writer
|
||||
cleanup(zip);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return zip;
|
||||
}
|
||||
|
||||
void zip_close(struct zip_t *zip) {
|
||||
if (zip) {
|
||||
// Always finalize, even if adding failed for some reason, so we have a valid central directory.
|
||||
mz_zip_writer_finalize_archive(&(zip->archive));
|
||||
mz_zip_writer_end(&(zip->archive));
|
||||
|
||||
cleanup(zip);
|
||||
}
|
||||
}
|
||||
|
||||
int zip_entry_open(struct zip_t *zip, const char *entryname) {
|
||||
size_t entrylen = 0;
|
||||
mz_zip_archive *pzip = NULL;
|
||||
mz_uint num_alignment_padding_bytes, level;
|
||||
|
||||
if (!zip || !entryname) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
entrylen = strlen(entryname);
|
||||
if (entrylen < 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
zip->entry.name = strclone(entryname);
|
||||
if (!zip->entry.name) {
|
||||
// Cannot parse zip entry name
|
||||
return -1;
|
||||
}
|
||||
|
||||
zip->entry.comp_size = 0;
|
||||
zip->entry.uncomp_size = 0;
|
||||
zip->entry.uncomp_crc32 = MZ_CRC32_INIT;
|
||||
zip->entry.offset = zip->archive.m_archive_size;
|
||||
zip->entry.header_offset = zip->archive.m_archive_size;
|
||||
memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8));
|
||||
zip->entry.method = 0;
|
||||
|
||||
pzip = &(zip->archive);
|
||||
num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pzip);
|
||||
|
||||
if (!pzip->m_pState || (pzip->m_zip_mode != MZ_ZIP_MODE_WRITING)) {
|
||||
// Wrong zip mode
|
||||
return -1;
|
||||
}
|
||||
if (zip->level & MZ_ZIP_FLAG_COMPRESSED_DATA) {
|
||||
// Wrong zip compression level
|
||||
return -1;
|
||||
}
|
||||
// no zip64 support yet
|
||||
if ((pzip->m_total_files == 0xFFFF) || ((pzip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + entrylen) > 0xFFFFFFFF)) {
|
||||
// No zip64 support yet
|
||||
return -1;
|
||||
}
|
||||
if (!mz_zip_writer_write_zeros(pzip, zip->entry.offset, num_alignment_padding_bytes + sizeof(zip->entry.header))) {
|
||||
// Cannot memset zip entry header
|
||||
return -1;
|
||||
}
|
||||
|
||||
zip->entry.header_offset += num_alignment_padding_bytes;
|
||||
if (pzip->m_file_offset_alignment) { MZ_ASSERT((zip->entry.header_offset & (pzip->m_file_offset_alignment - 1)) == 0); }
|
||||
zip->entry.offset += num_alignment_padding_bytes + sizeof(zip->entry.header);
|
||||
|
||||
if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, zip->entry.name, entrylen) != entrylen) {
|
||||
// Cannot write data to zip entry
|
||||
return -1;
|
||||
}
|
||||
|
||||
zip->entry.offset += entrylen;
|
||||
level = zip->level & 0xF;
|
||||
if (level) {
|
||||
zip->entry.state.m_pZip = pzip;
|
||||
zip->entry.state.m_cur_archive_file_ofs = zip->entry.offset;
|
||||
zip->entry.state.m_comp_size = 0;
|
||||
|
||||
if (tdefl_init(&(zip->entry.comp), mz_zip_writer_add_put_buf_callback, &(zip->entry.state), tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) {
|
||||
// Cannot initialize the zip compressor
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zip_entry_close(struct zip_t *zip) {
|
||||
mz_zip_archive *pzip = NULL;
|
||||
mz_uint level;
|
||||
tdefl_status done;
|
||||
mz_uint16 entrylen;
|
||||
time_t t;
|
||||
struct tm *tm;
|
||||
mz_uint16 dos_time, dos_date;
|
||||
|
||||
if (!zip) {
|
||||
// zip_t handler is not initialized
|
||||
return -1;
|
||||
}
|
||||
|
||||
pzip = &(zip->archive);
|
||||
level = zip->level & 0xF;
|
||||
if (level) {
|
||||
done = tdefl_compress_buffer(&(zip->entry.comp), "", 0, TDEFL_FINISH);
|
||||
if (done != TDEFL_STATUS_DONE && done != TDEFL_STATUS_OKAY) {
|
||||
// Cannot flush compressed buffer
|
||||
cleanup(zip->entry.name);
|
||||
return -1;
|
||||
}
|
||||
zip->entry.comp_size = zip->entry.state.m_comp_size;
|
||||
zip->entry.offset = zip->entry.state.m_cur_archive_file_ofs;
|
||||
zip->entry.method = MZ_DEFLATED;
|
||||
}
|
||||
|
||||
entrylen = (mz_uint16)strlen(zip->entry.name);
|
||||
t = time(NULL);
|
||||
tm = localtime(&t);
|
||||
dos_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
|
||||
dos_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
|
||||
|
||||
// no zip64 support yet
|
||||
if ((zip->entry.comp_size > 0xFFFFFFFF) || (zip->entry.offset > 0xFFFFFFFF)) {
|
||||
// No zip64 support, yet
|
||||
cleanup(zip->entry.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mz_zip_writer_create_local_dir_header(pzip, zip->entry.header, entrylen, 0, zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date)) {
|
||||
// Cannot create zip entry header
|
||||
cleanup(zip->entry.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.header_offset, zip->entry.header, sizeof(zip->entry.header)) != sizeof(zip->entry.header)) {
|
||||
// Cannot write zip entry header
|
||||
cleanup(zip->entry.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mz_zip_writer_add_to_central_dir(pzip, zip->entry.name, entrylen, NULL, 0, "", 0, zip->entry.uncomp_size, zip->entry.comp_size, zip->entry.uncomp_crc32, zip->entry.method, 0, dos_time, dos_date, zip->entry.header_offset, 0)) {
|
||||
// Cannot write to zip central dir
|
||||
cleanup(zip->entry.name);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pzip->m_total_files++;
|
||||
pzip->m_archive_size = zip->entry.offset;
|
||||
|
||||
cleanup(zip->entry.name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize) {
|
||||
mz_uint level;
|
||||
mz_zip_archive *pzip = NULL;
|
||||
tdefl_status status;
|
||||
|
||||
if (!zip) {
|
||||
// zip_t handler is not initialized
|
||||
return -1;
|
||||
}
|
||||
|
||||
pzip = &(zip->archive);
|
||||
if (buf && bufsize > 0) {
|
||||
zip->entry.uncomp_size += bufsize;
|
||||
zip->entry.uncomp_crc32 = (mz_uint32)mz_crc32(zip->entry.uncomp_crc32, (const mz_uint8 *)buf, bufsize);
|
||||
|
||||
level = zip->level & 0xF;
|
||||
if (!level) {
|
||||
if ((pzip->m_pWrite(pzip->m_pIO_opaque, zip->entry.offset, buf, bufsize) != bufsize)) {
|
||||
// Cannot write buffer
|
||||
return -1;
|
||||
}
|
||||
zip->entry.offset += bufsize;
|
||||
zip->entry.comp_size += bufsize;
|
||||
} else {
|
||||
status = tdefl_compress_buffer(&(zip->entry.comp), buf, bufsize, TDEFL_NO_FLUSH);
|
||||
if (status != TDEFL_STATUS_DONE && status != TDEFL_STATUS_OKAY) {
|
||||
// Cannot compress buffer
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zip_entry_fwrite(struct zip_t *zip, const char *filename) {
|
||||
int status = 0;
|
||||
size_t n = 0;
|
||||
FILE *stream = NULL;
|
||||
mz_uint8 buf[MZ_ZIP_MAX_IO_BUF_SIZE] = { 0 };
|
||||
|
||||
if (!zip) {
|
||||
// zip_t handler is not initialized
|
||||
return -1;
|
||||
}
|
||||
|
||||
stream = fopen(filename, "rb");
|
||||
if (!stream) {
|
||||
// Cannot open filename
|
||||
return -1;
|
||||
}
|
||||
|
||||
while((n = fread(buf, sizeof(mz_uint8), MZ_ZIP_MAX_IO_BUF_SIZE, stream)) > 0) {
|
||||
if (zip_entry_write(zip, buf, n) < 0) {
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(stream);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
int zip_create(const char *zipname, const char *filenames[], size_t len) {
|
||||
int status = 0;
|
||||
size_t i;
|
||||
mz_zip_archive zip_archive;
|
||||
|
||||
if (!zipname || strlen(zipname) < 1) {
|
||||
// zip_t archive name is empty or NULL
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create a new archive.
|
||||
if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
|
||||
// Cannot memset zip archive
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mz_zip_writer_init_file(&zip_archive, zipname, 0)) {
|
||||
// Cannot initialize zip_archive writer
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
const char *name = filenames[i];
|
||||
if (!name) {
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!mz_zip_writer_add_file(&zip_archive, basename(name), name, "", 0, ZIP_DEFAULT_COMPRESSION_LEVEL)) {
|
||||
// Cannot add file to zip_archive
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mz_zip_writer_finalize_archive(&zip_archive);
|
||||
mz_zip_writer_end(&zip_archive);
|
||||
return status;
|
||||
}
|
||||
|
||||
int zip_extract(const char *zipname, const char *dir, int (* on_extract)(const char *filename, void *arg), void *arg) {
|
||||
int status = 0;
|
||||
mz_uint i, n;
|
||||
char path[MAX_PATH + 1] = { 0 };
|
||||
mz_zip_archive zip_archive;
|
||||
mz_zip_archive_file_stat info;
|
||||
size_t dirlen = 0;
|
||||
|
||||
if (!memset(&(zip_archive), 0, sizeof(zip_archive))) {
|
||||
// Cannot memset zip archive
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!zipname || !dir) {
|
||||
// Cannot parse zip archive name
|
||||
return -1;
|
||||
}
|
||||
|
||||
dirlen = strlen(dir);
|
||||
if (dirlen + 1 > MAX_PATH) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Now try to open the archive.
|
||||
if (!mz_zip_reader_init_file(&zip_archive, zipname, 0)) {
|
||||
// Cannot initialize zip_archive reader
|
||||
status = -1;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
strcpy(path, dir);
|
||||
if (!ISSLASH(path[dirlen -1])) {
|
||||
#if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
|
||||
path[dirlen] = '\\';
|
||||
#else
|
||||
path[dirlen] = '/';
|
||||
#endif
|
||||
++dirlen;
|
||||
}
|
||||
|
||||
// Get and print information about each file in the archive.
|
||||
n = mz_zip_reader_get_num_files(&zip_archive);
|
||||
for (i = 0; i < n; ++i) {
|
||||
if (!mz_zip_reader_file_stat(&zip_archive, i, &info)) {
|
||||
// Cannot get information about zip archive;
|
||||
//printf("Cant get information about fimes");
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
strncpy(&path[dirlen], info.m_filename, MAX_PATH - dirlen);
|
||||
if (mkpath(path) < 0) {
|
||||
// Cannot make a path
|
||||
//printf("Cant create path\n");
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// verify if the path is directory first
|
||||
// issue fixed by xsang.le@gmail.com
|
||||
struct stat path_stat;
|
||||
if(stat(path, &path_stat) != 0 || !(path_stat.st_mode & S_IFDIR))
|
||||
if (!mz_zip_reader_extract_to_file(&zip_archive, i, path, 0)) {
|
||||
// Cannot extract zip archive to file
|
||||
//printf("Cant extract %s\n", path);
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (on_extract) {
|
||||
if (on_extract(path, arg) < 0) {
|
||||
status = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close the archive, freeing any resources it was using
|
||||
if (!mz_zip_reader_end(&zip_archive)) {
|
||||
// Cannot end zip reader
|
||||
status = -1;
|
||||
}
|
||||
|
||||
finally:
|
||||
return status;
|
||||
}
|
90
modules/3rd/zip/zip.h
Executable file
90
modules/3rd/zip/zip.h
Executable file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#ifndef ZIP_H
|
||||
#define ZIP_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MAX_PATH
|
||||
#define MAX_PATH 32767 /* # chars in a path name including NULL */
|
||||
#endif
|
||||
|
||||
#define ZIP_DEFAULT_COMPRESSION_LEVEL 6
|
||||
|
||||
/* This data structure is used throughout the library to represent zip archive - forward declaration. */
|
||||
struct zip_t;
|
||||
|
||||
/*
|
||||
Opens zip archive with compression level.
|
||||
If append is 0 then new archive will be created, otherwise function will try to append to the specified zip archive,
|
||||
instead of creating a new one.
|
||||
Compression levels: 0-9 are the standard zlib-style levels.
|
||||
Returns pointer to zip_t structure or NULL on error.
|
||||
*/
|
||||
struct zip_t *zip_open(const char *zipname, int level, int append);
|
||||
|
||||
/* Closes zip archive, releases resources - always finalize. */
|
||||
void zip_close(struct zip_t *zip);
|
||||
|
||||
/*
|
||||
Opens a new entry for writing in a zip archive.
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_entry_open(struct zip_t *zip, const char *entryname);
|
||||
|
||||
/*
|
||||
Closes zip entry, flushes buffer and releases resources.
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_entry_close(struct zip_t *zip);
|
||||
|
||||
/*
|
||||
Compresses an input buffer for the current zip entry.
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize);
|
||||
|
||||
/*
|
||||
Compresses a file for the current zip entry.
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_entry_fwrite(struct zip_t *zip, const char *filename);
|
||||
|
||||
/*
|
||||
Creates a new archive and puts len files into a single zip archive
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_create(const char *zipname, const char *filenames[], size_t len);
|
||||
|
||||
/*
|
||||
Extracts a zip archive file into dir.
|
||||
If on_extract_entry is not NULL, the callback will be called after successfully extracted each zip entry.
|
||||
Returning a negative value from the callback will cause abort the extract and return an error.
|
||||
|
||||
The last argument (void *arg) is optional, which you can use to pass some data to the on_extract_entry callback.
|
||||
|
||||
Returns negative number (< 0) on error, 0 on success.
|
||||
*/
|
||||
int zip_extract(const char *zipname, const char *dir, int (* on_extract_entry)(const char *filename, void *arg), void *arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user