diff --git a/.gitignore b/.gitignore index 8616e19..b585751 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ test/testMatch test/testParser test/testRender test/testServer +test/testText test/isRoutingVar test/getVarType diff --git a/include/psyc.h b/include/psyc.h index 93358f1..8ca562b 100644 --- a/include/psyc.h +++ b/include/psyc.h @@ -149,7 +149,7 @@ typedef struct psycListFlag flag; } psycList; -/* intermediate struct for a PSYC packet */ +/** intermediate struct for a PSYC packet */ typedef struct { psycHeader routing; ///< Routing header. @@ -234,34 +234,4 @@ int psyc_inherits(char *sho, size_t slen, int psyc_matches(char *sho, size_t slen, char *lon, size_t llen); -/** - * Callback for psyc_text() that produces a value for a match. - * - * The application looks up a match such as _fruit from [_fruit] and - * if found writes its current value from its variable store into the - * outgoing buffer.. "Apple" for example. The template returns the - * number of bytes written. 0 is a legal return value. Should the - * callback return -1, psyc_text leaves the original template text as is. - */ -typedef int (*psyctextCB)(char *match, size_t mlen, - char **buffer, size_t *blen); - -/** - * Fills out text templates by asking a callback for content. - * - * Copies the contents of the template into the buffer while looking - * for braceOpen and braceClose strings and calling the callback for - * each enclosed string between these braces. Should the callback - * return -1, the original template text is copied as is. - * - * By default PSYC's "[" and "]" are used but you can provide any other - * brace strings such as "${" and "}" or "". - * - * See also http://about.psyc.eu/psyctext - */ -int psyc_text(char *template, size_t tlen, - char **buffer, size_t *blen, - psyctextCB lookupValue, - char *braceOpen, char *braceClose); - #endif // PSYC_H diff --git a/include/psyc/text.h b/include/psyc/text.h new file mode 100644 index 0000000..7774684 --- /dev/null +++ b/include/psyc/text.h @@ -0,0 +1,71 @@ +/** + * The return value definitions for the PSYC text parsing function. + * @see psyc_text() + */ + +typedef enum +{ + PSYC_TEXT_NO_SUBST = -1, + PSYC_TEXT_COMPLETE = 0, + PSYC_TEXT_INCOMPLETE = 1, +} psycTextRC; + +typedef enum +{ + PSYC_TEXT_VALUE_NOT_FOUND = -1, + PSYC_TEXT_VALUE_FOUND = 0, +} psycTextValueRC; + +/** + * Struct for keeping PSYC text parser state. + */ +typedef struct +{ + size_t cursor; ///< current position in the template + size_t written; ///< number of bytes written to buffer + psycString template; ///< template to parse + psycString buffer; ///< buffer for writing to + psycString open; + psycString close; +} psycTextState; + +/** + * Callback for psyc_text() that produces a value for a match. + * + * The application looks up a match such as _fruit from [_fruit] and + * if found writes its current value from its variable store into the + * outgoing buffer.. "Apple" for example. The template returns the + * number of bytes written. 0 is a legal return value. Should the + * callback return -1, psyc_text leaves the original template text as is. + */ +typedef psycTextValueRC (*psycTextCB)(char *name, size_t len, psycString *value); + +inline void psyc_initTextState (psycTextState *state, + char *template, size_t tlen, + char *buffer, size_t blen); + +inline void psyc_initTextState2 (psycTextState* state, + char *template, size_t tlen, + char *buffer, size_t blen, + char *open, size_t openlen, + char *close, size_t closelen); + +inline void psyc_setTextBuffer (psycTextState *state, psycString buffer); + +inline void psyc_setTextBuffer2 (psycTextState *state, char *buffer, size_t length); + +/** + * Fills out text templates by asking a callback for content. + * + * Copies the contents of the template into the buffer while looking + * for braceOpen and braceClose strings and calling the callback for + * each enclosed string between these braces. Should the callback + * return -1, the original template text is copied as is. + * + * By default PSYC's "[" and "]" are used but you can provide any other + * brace strings such as "${" and "}" or "". + * + * See also http://about.psyc.eu/psyctext + */ + +psycTextRC psyc_text(psycTextState *state, psycTextCB getValue); diff --git a/src/Makefile b/src/Makefile index 26c07fd..4ffaed1 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3,8 +3,8 @@ DEBUG = 2 CFLAGS = -I../include -Wall ${OPT} DIET = diet -S = packet.c misc.c parser.c match.c render.c memmem.c itoa.c variable.c -O = packet.o misc.o parser.o match.o render.o memmem.o itoa.o variable.o +S = packet.c misc.c parser.c match.c render.c memmem.c itoa.c variable.c text.c +O = packet.o misc.o parser.o match.o render.o memmem.o itoa.o variable.o text.o all: CC := ${WRAPPER} ${CC} all: lib diff --git a/src/text.c b/src/text.c index 6bc8c1d..2d00058 100644 --- a/src/text.c +++ b/src/text.c @@ -1,9 +1,116 @@ -/* psyc_text() */ +#include +#include -int psyc_text(char *template, size_t tlen, - char **buffer, size_t *blen, - psyctextCB lookupValue, - char *braceOpen, char *braceClose) +inline void psyc_initTextState (psycTextState *state, + char *template, size_t tlen, + char *buffer, size_t blen) { - + state->cursor = state->written = 0; + state->template = psyc_newString(template, tlen); + state->buffer = psyc_newString(buffer, blen); + state->open = psyc_newString("[", 1); + state->close = psyc_newString("]", 1); +} + +inline void psyc_initTextState2 (psycTextState* state, + char *template, size_t tlen, + char *buffer, size_t blen, + char *open, size_t openlen, + char *close, size_t closelen) +{ + state->template = psyc_newString(template, tlen); + state->buffer = psyc_newString(buffer, blen); + state->open = psyc_newString(open, openlen); + state->close = psyc_newString(close, closelen); +} + +inline void psyc_setTextBuffer (psycTextState* state, psycString buffer) +{ + state->buffer = buffer; + state->written = 0; +} + +inline void psyc_setTextBuffer2 (psycTextState* state, char *buffer, size_t length) +{ + psyc_setTextBuffer(state, psyc_newString(buffer, length)); +} + +psycTextRC psyc_text (psycTextState *state, psycTextCB getValue) +{ + char *start = state->template.ptr, *end; // start & end of variable name + char *prev = state->template.ptr + state->cursor; + psycString value; + int ret; + size_t len; + uint8_t no_subst = (state->cursor == 0); // whether we can return NO_SUBST + + while (state->cursor < state->template.length) + { + start = memmem(state->template.ptr + state->cursor, + state->template.length - state->cursor, + state->open.ptr, state->open.length); + if (!start) + break; + + state->cursor = (start - state->template.ptr) + state->open.length; + if (state->cursor >= state->template.length) + break; // [ at the end + + end = memmem(state->template.ptr + state->cursor, + state->template.length - state->cursor, + state->close.ptr, state->close.length); + state->cursor = (end - state->template.ptr) + state->close.length; + + if (!end) + break; // ] not found + if (start + state->open.length == end) + { + state->cursor += state->close.length; + continue; // [] is invalid, name can't be empty + } + + ret = getValue(start + state->open.length, end - start - state->open.length, &value); + + if (ret < 0) + continue; // value not found, no substitution + + // first copy the part in the template from the previous subst. to the current one + // if there's enough buffer space for that + len = start - prev; + if (state->written + len > state->buffer.length) + { + state->cursor = prev - state->template.ptr; + return PSYC_TEXT_INCOMPLETE; + } + + memcpy((void *)(state->buffer.ptr + state->written), prev, len); + state->written += len; + + // now substitute the value if there's enough buffer space + if (state->written + value.length > state->buffer.length) + { + state->cursor = start - state->template.ptr; + return PSYC_TEXT_INCOMPLETE; + } + + memcpy((void *)(state->buffer.ptr + state->written), value.ptr, value.length); + state->written += value.length; + + // mark the start of the next chunk of text in the template + prev = state->template.ptr + state->cursor; + no_subst = 0; + } + + if (no_subst) + return PSYC_TEXT_NO_SUBST; + + // copy the rest of the template after the last var + len = state->template.length - (prev - state->template.ptr); + if (state->written + len > state->buffer.length) + return PSYC_TEXT_INCOMPLETE; + + memcpy((void *)(state->buffer.ptr + state->written), prev, len); + state->written += len; + + return PSYC_TEXT_COMPLETE; } diff --git a/test/Makefile b/test/Makefile index c7d8813..7b7dbb5 100644 --- a/test/Makefile +++ b/test/Makefile @@ -3,7 +3,7 @@ DEBUG = 2 CFLAGS = -I../include -Wall ${OPT} LDFLAGS = -L../lib LOADLIBES = -lpsyc -lm -TARGETS = testServer testParser testMatch testRender isRoutingVar getVarType +TARGETS = testServer testParser testMatch testRender testText isRoutingVar getVarType WRAPPER = DIET = diet PORT = 4440 diff --git a/test/testText.c b/test/testText.c new file mode 100644 index 0000000..26ffcd6 --- /dev/null +++ b/test/testText.c @@ -0,0 +1,90 @@ +#include +#include + +#define BUFSIZE 512 + +uint8_t verbose; + +psycTextValueRC getValueFooBar (char *name, size_t len, psycString *value) +{ + value->ptr = "Foo Bar"; + value->length = 7; + return PSYC_TEXT_VALUE_FOUND; +} + +psycTextValueRC getValueEmpty (char *name, size_t len, psycString *value) +{ + value->ptr = ""; + value->length = 0; + return PSYC_TEXT_VALUE_FOUND; +} + +psycTextValueRC getValueNotFound (char *name, size_t len, psycString *value) +{ + return PSYC_TEXT_VALUE_NOT_FOUND; +} + +int testText (char *template, size_t tmplen, char *buffer, size_t buflen, psycString *result, psycTextCB getValue) +{ + psycTextState state; + size_t length = 0; + psycTextRC ret; + + psyc_initTextState(&state, template, tmplen, buffer, buflen); + do + { + ret = psyc_text(&state, getValue); + length += state.written; + switch (ret) + { + case PSYC_TEXT_INCOMPLETE: + if (verbose) + printf("# %.*s...\n", (int)length, buffer); + psyc_setTextBuffer2(&state, buffer + length, BUFSIZE - length); + break; + case PSYC_TEXT_COMPLETE: + if (verbose) + printf("%.*s\n", (int)length, buffer); + result->length = length; + result->ptr = buffer; + return ret; + case PSYC_TEXT_NO_SUBST: + if (verbose) + printf("%.*s\n", (int)tmplen, template); + return ret; + } + } + while (ret == PSYC_TEXT_INCOMPLETE); + + return -2; // shouldn't be reached +} + +int main(int argc, char **argv) +{ + verbose = argc > 1; + char buffer[BUFSIZE]; + psycString result; + + char *str = "Hello [_foo] & [_bar]!"; + size_t len = strlen(str); + int i; + + testText(str, len, buffer, BUFSIZE, &result, &getValueFooBar); + if (memcmp(result.ptr, PSYC_C2ARG("Hello Foo Bar & Foo Bar!"))) + return 1; + + testText(str, len, buffer, BUFSIZE, &result, &getValueEmpty); + if (memcmp(result.ptr, PSYC_C2ARG("Hello & !"))) + return 2; + + if (testText(str, len, buffer, BUFSIZE, &result, &getValueNotFound) != PSYC_TEXT_NO_SUBST) + return 3; + + for (i = 1; i < 22; i++) + { + testText(str, len, buffer, i, &result, &getValueFooBar); + if (memcmp(result.ptr, PSYC_C2ARG("Hello Foo Bar & Foo Bar!"))) + return 10 + i; + } + return 0; +}