50#define PRETTY_OUT_PADDING 256
52#define PRETTY_INDENT_STEP 2
54#define PRETTY_TAG_NAME_MAX 31
58 if (n == 0)
return TRUE;
69 for (
int it = 0; it < depth; it++) {
78 if (!o->
buf)
return NULL;
93 while (*p && isspace((
unsigned char)*p)) p++;
94 if (*p !=
'{' && *p !=
'[')
return NULL;
95 cJSON* json = cJSON_Parse(text);
96 if (!json)
return NULL;
97 char* printed = cJSON_Print(json);
99 if (!printed)
return NULL;
101 if (!out) free(printed);
104 n_log(
LOG_ERR,
"n_pretty_json: built without cJSON support (HAVE_CJSON)");
111 if (!*at_line_start)
return TRUE;
118 if (*at_line_start)
return TRUE;
125 const char* p = text;
127 int at_line_start = 1;
131 if (!text[0])
return NULL;
132 memset(&o, 0,
sizeof(o));
136 if (isspace((
unsigned char)c)) {
145 if (p[n] ==
'\\' && p[n + 1]) {
159 if (c ==
'{' || c ==
'[') {
160 char close = (c ==
'{') ?
'}' :
']';
161 const char* q = p + 1;
162 while (*q && isspace((
unsigned char)*q)) q++;
175 if (c ==
'}' || c ==
']') {
176 if (depth > 0) depth--;
197 while (p[n] && !strchr(
"\"{}[],:", p[n]) && !isspace((
unsigned char)p[n])) n++;
213 "area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
214 "link",
"meta",
"param",
"source",
"track",
"wbr", NULL};
221 for (
size_t it = 0; list[it]; it++) {
222 if (strcmp(name, list[it]) == 0)
return TRUE;
228static void _tag_name(
const char* p,
char* name,
size_t name_size) {
232 while (*p && out + 1 < name_size &&
233 (isalnum((
unsigned char)*p) || *p ==
':' || *p ==
'-' || *p ==
'_')) {
234 name[out++] = (char)tolower((
unsigned char)*p);
247 if (*p == quote) quote = 0;
248 }
else if (*p ==
'"' || *p ==
'\'') {
250 }
else if (*p ==
'>') {
259static const char*
_stristr(
const char* haystack,
const char* needle) {
260 size_t nlen = strlen(needle);
261 if (nlen == 0)
return haystack;
262 for (
const char* p = haystack; *p; p++) {
263 if (strncasecmp(p, needle, nlen) == 0)
return p;
271 if (!
_out_mem(o, s, n))
return FALSE;
280 while (a < b && isspace((
unsigned char)s[a])) a++;
281 while (b > a && isspace((
unsigned char)s[b - 1])) b--;
282 if (b <= a)
return TRUE;
288 const char* p = text;
289 while (*p && isspace((
unsigned char)*p)) p++;
290 if (*p !=
'<')
return NULL;
299 const char* e = strchr(p,
'<');
300 size_t n = e ? (size_t)(e - p) : strlen(p);
307 if (p[1] ==
'!' && p[2] ==
'-' && p[3] ==
'-') {
308 const char* e = strstr(p + 4,
"-->");
309 size_t n = e ? (size_t)(e + 3 - p) : strlen(p);
314 if (strncasecmp(p,
"<![CDATA[", 9) == 0) {
315 const char* e = strstr(p + 9,
"]]>");
316 size_t n = e ? (size_t)(e + 3 - p) : strlen(p);
321 if (p[1] ==
'?' || p[1] ==
'!') {
324 size_t n = e ? (size_t)(e + 1 - p) : strlen(p);
332 size_t n = e ? (size_t)(e + 1 - p) : strlen(p);
333 int is_close = (p[1] ==
'/');
334 int self_close = (e && n >= 2 && p[n - 2] ==
'/');
337 if (is_close && depth > 0) depth--;
340 if (!ok || is_close || self_close || !e)
continue;
346 snprintf(closer,
sizeof(closer),
"</%s", name);
347 const char* end =
_stristr(p, closer);
348 size_t rn = end ? (size_t)(end - p) : strlen(p);
364 if (c == 0)
return TRUE;
365 return strchr(
"(,=:[!&|?;{}<>+-*%~^", c) != NULL;
371 static const char* kw[] = {
"await",
"case",
"delete",
"do",
"else",
"in",
372 "instanceof",
"new",
"of",
"return",
"throw",
373 "typeof",
"void",
"yield", NULL};
374 for (
size_t it = 0; kw[it]; it++) {
375 if (strlen(kw[it]) == n && strncmp(s, kw[it], n) == 0)
return TRUE;
402 if (!
_out_ch(&js->
o,
' '))
return FALSE;
428 if (s[it] ==
'\\' && s[it + 1]) {
432 if (s[it] == quote)
return it + 1;
433 if (s[it] ==
'\n')
break;
444 if (s[it] ==
'\\' && s[it + 1]) {
448 if (s[it] ==
'`')
return it + 1;
449 if (s[it] ==
'$' && s[it + 1] ==
'{') {
452 while (s[it] && braces > 0) {
453 if (s[it] ==
'{') braces++;
454 if (s[it] ==
'}') braces--;
470 if (s[it] ==
'\\' && s[it + 1]) {
474 if (s[it] ==
'\n')
return it;
476 if (s[it] ==
']') in_class = 0;
477 }
else if (s[it] ==
'[') {
479 }
else if (s[it] ==
'/') {
481 while (isalpha((
unsigned char)s[it])) it++;
491 if (!text[0])
return NULL;
493 JS_STATE js = {{NULL, 0, 0}, 0, 1, 0, 0, 0};
495 const char* p = text;
500 if (isspace((
unsigned char)c)) {
505 if (c ==
'/' && p[1] ==
'/') {
506 const char* e = strchr(p,
'\n');
507 size_t n = e ? (size_t)(e - p) : strlen(p);
512 if (c ==
'/' && p[1] ==
'*') {
513 const char* e = strstr(p + 2,
"*/");
514 size_t n = e ? (size_t)(e + 2 - p) : strlen(p);
520 if (c ==
'"' || c ==
'\'') {
545 if (isalpha((
unsigned char)c) || c ==
'_' || c ==
'$') {
548 while (p[n] && (isalnum((
unsigned char)p[n]) || p[n] ==
'_' || p[n] ==
'$')) n++;
555 if (c ==
'{' && paren == 0) {
563 if (c ==
'}' && paren == 0) {
571 while (*p && isspace((
unsigned char)*p) && *p !=
'\n') p++;
572 if (*p ==
';' || *p ==
',') {
580 if (c ==
';' && paren == 0) {
587 if (c ==
'(' || c ==
'[') paren++;
588 if ((c ==
')' || c ==
']') && paren > 0) paren--;
594 if (!ok || !js.
o.
buf) {
#define __n_assert(__ptr, __ret)
macro to assert things
#define Free(__ptr)
Free Handler to get errors.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_ERR
error conditions
N_STR * n_pretty_xml(const char *text)
Re-indent an XML or HTML document (one tag or text run per line).
N_STR * n_pretty_js(const char *text)
Conservatively re-indent JavaScript (newlines at top-level braces and semicolons, two-space indent by...
N_STR * n_pretty_json(const char *text)
Reformat a JSON document with cJSON (parse + print).
N_STR * n_pretty_json_lenient(const char *text)
Re-indent a JSON document by punctuation alone, without parsing it.
size_t NSTRBYTE
N_STR base unit.
N_STR * char_to_nstr_nocopy(char *src)
Convert a char into a N_STR, direct use of linked source pointer.
int write_and_fit_ex(char **dest, NSTRBYTE *size, NSTRBYTE *written, const char *src, NSTRBYTE src_size, NSTRBYTE additional_padding)
Append src_size bytes from src to *dest, updating size/written, growing the buffer if needed.
A box including a string and his lenght.
Common headers and low-level functions & define.
static int _emit_trimmed(PRETTY_OUT *o, int depth, const char *s, size_t n)
emit s trimmed of leading/trailing whitespace as one indented line; empty-after-trim runs are skipped
static size_t _js_string_len(const char *s)
length of the string literal starting at s (quote to closing quote, escapes honored,...
static size_t _js_regex_len(const char *s)
length of the regex literal starting at s (slash to closing slash, escapes and character classes hono...
static size_t _js_template_len(const char *s)
length of the template literal starting at s (backtick to backtick, escapes honored,...
static int _js_token(JS_STATE *js, const char *s, size_t n)
emit a raw token of n bytes with leading indent/space handling
#define PRETTY_INDENT_STEP
spaces per indentation level
int indent
current indentation depth
int kw_regex
1 when the last token was a keyword that can precede a regex literal
static int _js_newline(JS_STATE *js)
terminate the current line unless already at a line start
static const char * PRETTY_RAWTEXT_ELEMENTS[]
HTML elements whose content is raw text (no nested markup).
int at_line_start
1 when nothing has been emitted on the current line yet
static const char * _find_tag_end(const char *p)
find the '>' closing a tag starting at p, honoring quoted attribute values; returns NULL when the tag...
int pending_space
1 when collapsed whitespace separates the previous and next token
NSTRBYTE written
meaningful bytes, excluding the terminating '\0'
static N_STR * _out_finish(PRETTY_OUT *o)
hand the buffer over to a new N_STR (no copy); the PRETTY_OUT is emptied
static int _jsonl_newline(PRETTY_OUT *o, int *at_line_start)
end the current lenient JSON line unless it is still empty, TRUE on success
static int _emit_line(PRETTY_OUT *o, int depth, const char *s, size_t n)
emit one indented line holding n raw bytes of s
static int _out_mem(PRETTY_OUT *o, const char *s, size_t n)
append n bytes of s to the output, TRUE on success
char last_sig
last significant code character emitted (0 = none yet)
static int _js_regex_possible(char c)
whether a '/' may start a regex literal after last significant char c (start of input,...
PRETTY_OUT o
output buffer
static const char * PRETTY_VOID_ELEMENTS[]
HTML elements that never have content or a closing tag.
static const char * _stristr(const char *haystack, const char *needle)
case-insensitive search for needle in haystack
static int _js_regex_keyword(const char *s, size_t n)
whether the n bytes at s are a keyword after which an expression (and therefore a regex literal) can ...
#define PRETTY_TAG_NAME_MAX
longest element name kept for void/raw-text lookups
static int _js_lead(JS_STATE *js)
emit the indent or the collapsed space before a token, as appropriate
static int _out_indent(PRETTY_OUT *o, int depth)
append depth * PRETTY_INDENT_STEP spaces, TRUE on success
static int _out_ch(PRETTY_OUT *o, char c)
append a single character to the output, TRUE on success
static int _jsonl_lead(PRETTY_OUT *o, int depth, int *at_line_start)
emit the pending indent of a lenient JSON line, TRUE on success
char * buf
buffer, reallocated on growth
static int _name_in_list(const char *name, const char **list)
whether lowercase element name is in a NULL-terminated list
NSTRBYTE size
total allocation in bytes
#define PRETTY_OUT_PADDING
growth headroom so consecutive short appends share one realloc
static void _tag_name(const char *p, char *name, size_t name_size)
extract the lowercased element name from a tag starting at p ("<name" or "</name")
JS emitter state threaded through the helpers.
growing output buffer fed by write_and_fit_ex
Lexical pretty-printers for JSON, XML/HTML and JavaScript text.