summaryrefslogtreecommitdiffhomepage
path: root/libs/web
diff options
context:
space:
mode:
authorJo-Philipp Wich <jow@openwrt.org>2012-11-25 19:17:55 +0000
committerJo-Philipp Wich <jow@openwrt.org>2012-11-25 19:17:55 +0000
commit0e50aa690af6cd9f37fa97b4a521fe523cce3c39 (patch)
treec0ab4edc0dd221dfa3e6fb4eeba049ecc05326fe /libs/web
parentc647ff9f0e1af211a762dc9a773c1b5c4aacd168 (diff)
libs/web: rewrite template engine, merge lmo library
- template parser: merge lmo library - template parser: rewrite to operate on memory mapped files - template parser: implement proper line number reporting on syntax errors - template parser: process translate tags directly and bypass Lua - template lmo: introduce load_catalog(), change_catalog() and close_catalog() - template lmo: rewrite index processing to operate directly on the memory mapped file - template lmo: implement binary search keys, reducing the lookup complexity to O(log n) - po2lmo: write sorted indixes when generating *.lmo archives - i18n: use the template parser for translations - i18n: stub load(), loadc() and clear() - i18n: map setlanguage() to load_catalog()
Diffstat (limited to 'libs/web')
-rw-r--r--libs/web/Makefile22
-rw-r--r--libs/web/luasrc/i18n.lua44
-rw-r--r--libs/web/luasrc/template.lua5
-rw-r--r--libs/web/src/po2lmo.c242
-rw-r--r--libs/web/src/template_lmo.c325
-rw-r--r--libs/web/src/template_lmo.h91
-rw-r--r--libs/web/src/template_lualib.c92
-rw-r--r--libs/web/src/template_lualib.h1
-rw-r--r--libs/web/src/template_parser.c603
-rw-r--r--libs/web/src/template_parser.h70
-rw-r--r--libs/web/src/template_utils.c121
-rw-r--r--libs/web/src/template_utils.h17
-rw-r--r--libs/web/standalone.mk56
13 files changed, 1223 insertions, 466 deletions
diff --git a/libs/web/Makefile b/libs/web/Makefile
index d9f9700c1..1d28a3a4c 100644
--- a/libs/web/Makefile
+++ b/libs/web/Makefile
@@ -1,19 +1,28 @@
+ifneq (,$(wildcard ../../build/config.mk))
include ../../build/config.mk
include ../../build/module.mk
include ../../build/gccconfig.mk
+else
+include standalone.mk
+endif
TPL_LDFLAGS =
TPL_CFLAGS =
TPL_SO = parser.so
+TPL_PO2LMO = po2lmo
+TPL_PO2LMO_OBJ = src/po2lmo.o
+TPL_LMO_OBJ = src/template_lmo.o
TPL_COMMON_OBJ = src/template_parser.o src/template_utils.o
TPL_LUALIB_OBJ = src/template_lualib.o
%.o: %.c
$(COMPILE) $(TPL_CFLAGS) $(LUA_CFLAGS) $(FPIC) -c -o $@ $<
-compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ)
+compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
$(LINK) $(SHLIB_FLAGS) $(TPL_LDFLAGS) -o src/$(TPL_SO) \
- $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ)
+ $(TPL_COMMON_OBJ) $(TPL_LMO_OBJ) $(TPL_LUALIB_OBJ)
+ $(LINK) -o src/$(TPL_PO2LMO) \
+ $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
mkdir -p dist$(LUCI_LIBRARYDIR)/template
cp src/$(TPL_SO) dist$(LUCI_LIBRARYDIR)/template/$(TPL_SO)
@@ -24,3 +33,12 @@ clean: build-clean
build-clean:
rm -f src/*.o src/$(TPL_SO)
+
+host-compile: build-clean host-clean $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
+ $(LINK) -o src/$(TPL_PO2LMO) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
+
+host-install: host-compile
+ cp src/$(TPL_PO2LMO) ../../build/$(TPL_PO2LMO)
+
+host-clean:
+ rm -f ../../build/$(TPL_PO2LMO)
diff --git a/libs/web/luasrc/i18n.lua b/libs/web/luasrc/i18n.lua
index 816d90310..ff917c6f3 100644
--- a/libs/web/luasrc/i18n.lua
+++ b/libs/web/luasrc/i18n.lua
@@ -27,7 +27,8 @@ limitations under the License.
--- LuCI translation library.
module("luci.i18n", package.seeall)
require("luci.util")
-require("lmo")
+
+local tparser = require "luci.template.parser"
table = {}
i18ndir = luci.util.libpath() .. "/i18n/"
@@ -37,7 +38,6 @@ default = "en"
--- Clear the translation table.
function clear()
- table = {}
end
--- Load a translation and copy its data into the translation table.
@@ -46,33 +46,6 @@ end
-- @param force Force reload even if already loaded (optional)
-- @return Success status
function load(file, lang, force)
- lang = lang and lang:gsub("_", "-") or ""
- if force or not loaded[lang] or not loaded[lang][file] then
- local f = lmo.open(i18ndir .. file .. "." .. lang .. ".lmo")
- if f then
- if not table[lang] then
- table[lang] = { f }
- setmetatable(table[lang], {
- __index = function(tbl, key)
- for i = 1, #tbl do
- local s = rawget(tbl, i):lookup(key)
- if s then return s end
- end
- end
- })
- else
- table[lang][#table[lang]+1] = f
- end
-
- loaded[lang] = loaded[lang] or {}
- loaded[lang][file] = true
- return true
- else
- return false
- end
- else
- return true
- end
end
--- Load a translation file using the default translation language.
@@ -80,9 +53,6 @@ end
-- @param file Language file
-- @param force Force reload even if already loaded (optional)
function loadc(file, force)
- load(file, default, force)
- if context.parent then load(file, context.parent, force) end
- return load(file, context.lang, force)
end
--- Set the context default translation language.
@@ -90,16 +60,18 @@ end
function setlanguage(lang)
context.lang = lang:gsub("_", "-")
context.parent = (context.lang:match("^([a-z][a-z])_"))
+ if not tparser.load_catalog(context.lang, i18ndir) then
+ if context.parent then
+ tparser.load_catalog(context.parent, i18ndir)
+ end
+ end
end
--- Return the translated value for a specific translation key.
-- @param key Default translation text
-- @return Translated string
function translate(key)
- return (table[context.lang] and table[context.lang][key])
- or (table[context.parent] and table[context.parent][key])
- or (table[default] and table[default][key])
- or key
+ return tparser.translate(key) or key
end
--- Return the translated value for a specific translation key and use it as sprintf pattern.
diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua
index 962c2ea88..72127d1df 100644
--- a/libs/web/luasrc/template.lua
+++ b/libs/web/luasrc/template.lua
@@ -79,9 +79,8 @@ function Template.__init__(self, name)
-- If we have no valid template throw error, otherwise cache the template
if not self.template then
error("Failed to load template '" .. name .. "'.\n" ..
- "Error while parsing template '" .. sourcefile .. "'.\n" ..
- "A syntax error occured near '" ..
- (err or "(nil)"):gsub("\t", "\\t"):gsub("\n", "\\n") .. "'.")
+ "Error while parsing template '" .. sourcefile .. "':\n" ..
+ (err or "Unknown syntax error"))
else
self.cache[name] = self.template
end
diff --git a/libs/web/src/po2lmo.c b/libs/web/src/po2lmo.c
new file mode 100644
index 000000000..fb607a46f
--- /dev/null
+++ b/libs/web/src/po2lmo.c
@@ -0,0 +1,242 @@
+/*
+ * lmo - Lua Machine Objects - PO to LMO conversion tool
+ *
+ * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "template_lmo.h"
+
+static void die(const char *msg)
+{
+ fprintf(stderr, "Error: %s\n", msg);
+ exit(1);
+}
+
+static void usage(const char *name)
+{
+ fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
+ exit(1);
+}
+
+static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+ if( fwrite(ptr, size, nmemb, stream) == 0 )
+ die("Failed to write stdout");
+}
+
+static int extract_string(const char *src, char *dest, int len)
+{
+ int pos = 0;
+ int esc = 0;
+ int off = -1;
+
+ for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
+ {
+ if( (off == -1) && (src[pos] == '"') )
+ {
+ off = pos + 1;
+ }
+ else if( off >= 0 )
+ {
+ if( esc == 1 )
+ {
+ switch (src[pos])
+ {
+ case '"':
+ case '\\':
+ off++;
+ break;
+ }
+ dest[pos-off] = src[pos];
+ esc = 0;
+ }
+ else if( src[pos] == '\\' )
+ {
+ dest[pos-off] = src[pos];
+ esc = 1;
+ }
+ else if( src[pos] != '"' )
+ {
+ dest[pos-off] = src[pos];
+ }
+ else
+ {
+ dest[pos-off] = '\0';
+ break;
+ }
+ }
+ }
+
+ return (off > -1) ? strlen(dest) : -1;
+}
+
+static int cmp_index(const void *a, const void *b)
+{
+ uint32_t x = ntohl(((const lmo_entry_t *)a)->key_id);
+ uint32_t y = ntohl(((const lmo_entry_t *)b)->key_id);
+
+ if (x < y)
+ return -1;
+ else if (x > y)
+ return 1;
+
+ return 0;
+}
+
+static void print_index(void *array, int n, FILE *out)
+{
+ lmo_entry_t *e;
+
+ qsort(array, n, sizeof(*e), cmp_index);
+
+ for (e = array; n > 0; n--, e++)
+ {
+ print(&e->key_id, sizeof(uint32_t), 1, out);
+ print(&e->val_id, sizeof(uint32_t), 1, out);
+ print(&e->offset, sizeof(uint32_t), 1, out);
+ print(&e->length, sizeof(uint32_t), 1, out);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ char line[4096];
+ char key[4096];
+ char val[4096];
+ char tmp[4096];
+ int state = 0;
+ int offset = 0;
+ int length = 0;
+ int n_entries = 0;
+ void *array = NULL;
+ lmo_entry_t *entry = NULL;
+ uint32_t key_id, val_id;
+
+ FILE *in;
+ FILE *out;
+
+ if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
+ usage(argv[0]);
+
+ memset(line, 0, sizeof(key));
+ memset(key, 0, sizeof(val));
+ memset(val, 0, sizeof(val));
+
+ while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
+ {
+ if( state == 0 && strstr(line, "msgid \"") == line )
+ {
+ switch(extract_string(line, key, sizeof(key)))
+ {
+ case -1:
+ die("Syntax error in msgid");
+ case 0:
+ state = 1;
+ break;
+ default:
+ state = 2;
+ }
+ }
+ else if( state == 1 || state == 2 )
+ {
+ if( strstr(line, "msgstr \"") == line || state == 2 )
+ {
+ switch(extract_string(line, val, sizeof(val)))
+ {
+ case -1:
+ state = 4;
+ break;
+ default:
+ state = 3;
+ }
+ }
+ else
+ {
+ switch(extract_string(line, tmp, sizeof(tmp)))
+ {
+ case -1:
+ state = 2;
+ break;
+ default:
+ strcat(key, tmp);
+ }
+ }
+ }
+ else if( state == 3 )
+ {
+ switch(extract_string(line, tmp, sizeof(tmp)))
+ {
+ case -1:
+ state = 4;
+ break;
+ default:
+ strcat(val, tmp);
+ }
+ }
+
+ if( state == 4 )
+ {
+ if( strlen(key) > 0 && strlen(val) > 0 )
+ {
+ key_id = sfh_hash(key, strlen(key));
+ val_id = sfh_hash(val, strlen(val));
+
+ if( key_id != val_id )
+ {
+ n_entries++;
+ array = realloc(array, n_entries * sizeof(lmo_entry_t));
+ entry = (lmo_entry_t *)array + n_entries - 1;
+
+ if (!array)
+ die("Out of memory");
+
+ entry->key_id = htonl(key_id);
+ entry->val_id = htonl(val_id);
+ entry->offset = htonl(offset);
+ entry->length = htonl(strlen(val));
+
+ length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
+
+ print(val, length, 1, out);
+ offset += length;
+ }
+ }
+
+ state = 0;
+ memset(key, 0, sizeof(key));
+ memset(val, 0, sizeof(val));
+ }
+
+ memset(line, 0, sizeof(line));
+ }
+
+ print_index(array, n_entries, out);
+
+ if( offset > 0 )
+ {
+ offset = htonl(offset);
+ print(&offset, sizeof(uint32_t), 1, out);
+ fsync(fileno(out));
+ fclose(out);
+ }
+ else
+ {
+ fclose(out);
+ unlink(argv[2]);
+ }
+
+ fclose(in);
+ return(0);
+}
diff --git a/libs/web/src/template_lmo.c b/libs/web/src/template_lmo.c
new file mode 100644
index 000000000..7fcd2cda1
--- /dev/null
+++ b/libs/web/src/template_lmo.c
@@ -0,0 +1,325 @@
+/*
+ * lmo - Lua Machine Objects - Base functions
+ *
+ * Copyright (C) 2009-2010 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "template_lmo.h"
+
+/*
+ * Hash function from http://www.azillionmonkeys.com/qed/hash.html
+ * Copyright (C) 2004-2008 by Paul Hsieh
+ */
+
+uint32_t sfh_hash(const char *data, int len)
+{
+ uint32_t hash = len, tmp;
+ int rem;
+
+ if (len <= 0 || data == NULL) return 0;
+
+ rem = len & 3;
+ len >>= 2;
+
+ /* Main loop */
+ for (;len > 0; len--) {
+ hash += sfh_get16(data);
+ tmp = (sfh_get16(data+2) << 11) ^ hash;
+ hash = (hash << 16) ^ tmp;
+ data += 2*sizeof(uint16_t);
+ hash += hash >> 11;
+ }
+
+ /* Handle end cases */
+ switch (rem) {
+ case 3: hash += sfh_get16(data);
+ hash ^= hash << 16;
+ hash ^= data[sizeof(uint16_t)] << 18;
+ hash += hash >> 11;
+ break;
+ case 2: hash += sfh_get16(data);
+ hash ^= hash << 11;
+ hash += hash >> 17;
+ break;
+ case 1: hash += *data;
+ hash ^= hash << 10;
+ hash += hash >> 1;
+ }
+
+ /* Force "avalanching" of final 127 bits */
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+
+ return hash;
+}
+
+uint32_t lmo_canon_hash(const char *str, int len)
+{
+ char res[4096];
+ char *ptr, prev;
+ int off;
+
+ if (!str || len >= sizeof(res))
+ return 0;
+
+ for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
+ {
+ if (isspace(*str))
+ {
+ if (!isspace(prev))
+ *ptr++ = ' ';
+ }
+ else
+ {
+ *ptr++ = *str;
+ }
+ }
+
+ if ((ptr > res) && isspace(*(ptr-1)))
+ ptr--;
+
+ return sfh_hash(res, ptr - res);
+}
+
+lmo_archive_t * lmo_open(const char *file)
+{
+ int in = -1;
+ uint32_t idx_offset = 0;
+ struct stat s;
+
+ lmo_archive_t *ar = NULL;
+
+ if (stat(file, &s) == -1)
+ goto err;
+
+ if ((in = open(file, O_RDONLY)) == -1)
+ goto err;
+
+ if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
+ {
+ memset(ar, 0, sizeof(*ar));
+
+ ar->fd = in;
+ ar->size = s.st_size;
+
+ fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
+
+ if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
+ goto err;
+
+ idx_offset = *((const uint32_t *)
+ (ar->mmap + ar->size - sizeof(uint32_t)));
+
+ if (idx_offset >= ar->size)
+ goto err;
+
+ ar->index = (lmo_entry_t *)(ar->mmap + idx_offset);
+ ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
+ ar->end = ar->mmap + ar->size;
+
+ return ar;
+ }
+
+err:
+ if (in > -1)
+ close(in);
+
+ if (ar != NULL)
+ {
+ if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+ munmap(ar->mmap, ar->size);
+
+ free(ar);
+ }
+
+ return NULL;
+}
+
+void lmo_close(lmo_archive_t *ar)
+{
+ if (ar != NULL)
+ {
+ if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+ munmap(ar->mmap, ar->size);
+
+ close(ar->fd);
+ free(ar);
+
+ ar = NULL;
+ }
+}
+
+
+lmo_catalog_t *_lmo_catalogs = NULL;
+lmo_catalog_t *_lmo_active_catalog = NULL;
+
+int lmo_load_catalog(const char *lang, const char *dir)
+{
+ DIR *dh = NULL;
+ char pattern[16];
+ char path[PATH_MAX];
+ struct dirent *de = NULL;
+
+ lmo_archive_t *ar = NULL;
+ lmo_catalog_t *cat = NULL;
+
+ if (!lmo_change_catalog(lang))
+ return 0;
+
+ if (!dir || !(dh = opendir(dir)))
+ goto err;
+
+ if (!(cat = malloc(sizeof(*cat))))
+ goto err;
+
+ memset(cat, 0, sizeof(*cat));
+
+ snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
+ snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
+
+ while ((de = readdir(dh)) != NULL)
+ {
+ if (!fnmatch(pattern, de->d_name, 0))
+ {
+ snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+ ar = lmo_open(path);
+
+ if (ar)
+ {
+ ar->next = cat->archives;
+ cat->archives = ar;
+ }
+ }
+ }
+
+ closedir(dh);
+
+ cat->next = _lmo_catalogs;
+ _lmo_catalogs = cat;
+
+ if (!_lmo_active_catalog)
+ _lmo_active_catalog = cat;
+
+ return 0;
+
+err:
+ if (dh) closedir(dh);
+ if (cat) free(cat);
+
+ return -1;
+}
+
+int lmo_change_catalog(const char *lang)
+{
+ lmo_catalog_t *cat;
+
+ for (cat = _lmo_catalogs; cat; cat = cat->next)
+ {
+ if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+ {
+ _lmo_active_catalog = cat;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
+{
+ unsigned int m, l, r;
+
+ l = 0;
+ r = ar->length - 1;
+
+ while (1)
+ {
+ m = l + ((r - l) / 2);
+
+ if (r < l)
+ break;
+
+ if (ar->index[m].key_id == hash)
+ return &ar->index[m];
+
+ if (ar->index[m].key_id > hash)
+ {
+ if (!m)
+ break;
+
+ r = m - 1;
+ }
+ else
+ {
+ l = m + 1;
+ }
+ }
+
+ return NULL;
+}
+
+int lmo_translate(const char *key, int keylen, char **out, int *outlen)
+{
+ uint32_t hash;
+ lmo_entry_t *e;
+ lmo_archive_t *ar;
+
+ if (!key || !_lmo_active_catalog)
+ return -2;
+
+ hash = htonl(lmo_canon_hash(key, keylen));
+
+ for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
+ {
+ if ((e = lmo_find_entry(ar, hash)) != NULL)
+ {
+ *out = ar->mmap + e->offset;
+ *outlen = e->length;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+void lmo_close_catalog(const char *lang)
+{
+ lmo_archive_t *ar, *next;
+ lmo_catalog_t *cat, *prev;
+
+ for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
+ {
+ if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+ {
+ if (prev)
+ prev->next = cat->next;
+ else
+ _lmo_catalogs = cat->next;
+
+ for (ar = cat->archives; ar; ar = next)
+ {
+ next = ar->next;
+ lmo_close(ar);
+ }
+
+ free(cat);
+ break;
+ }
+ }
+}
diff --git a/libs/web/src/template_lmo.h b/libs/web/src/template_lmo.h
new file mode 100644
index 000000000..a40d7587a
--- /dev/null
+++ b/libs/web/src/template_lmo.h
@@ -0,0 +1,91 @@
+/*
+ * lmo - Lua Machine Objects - General header
+ *
+ * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _TEMPLATE_LMO_H_
+#define _TEMPLATE_LMO_H_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#if (defined(__GNUC__) && defined(__i386__))
+#define sfh_get16(d) (*((const uint16_t *) (d)))
+#else
+#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+ +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+
+
+struct lmo_entry {
+ uint32_t key_id;
+ uint32_t val_id;
+ uint32_t offset;
+ uint32_t length;
+} __attribute__((packed));
+
+typedef struct lmo_entry lmo_entry_t;
+
+
+struct lmo_archive {
+ int fd;
+ int length;
+ uint32_t size;
+ lmo_entry_t *index;
+ char *mmap;
+ char *end;
+ struct lmo_archive *next;
+};
+
+typedef struct lmo_archive lmo_archive_t;
+
+
+struct lmo_catalog {
+ char lang[6];
+ struct lmo_archive *archives;
+ struct lmo_catalog *next;
+};
+
+typedef struct lmo_catalog lmo_catalog_t;
+
+
+uint32_t sfh_hash(const char *data, int len);
+uint32_t lmo_canon_hash(const char *data, int len);
+
+lmo_archive_t * lmo_open(const char *file);
+void lmo_close(lmo_archive_t *ar);
+
+
+extern lmo_catalog_t *_lmo_catalogs;
+extern lmo_catalog_t *_lmo_active_catalog;
+
+int lmo_load_catalog(const char *lang, const char *dir);
+int lmo_change_catalog(const char *lang);
+int lmo_translate(const char *key, int keylen, char **out, int *outlen);
+void lmo_close_catalog(const char *lang);
+
+#endif
diff --git a/libs/web/src/template_lualib.c b/libs/web/src/template_lualib.c
index d3a5f89bb..f40ef2d6a 100644
--- a/libs/web/src/template_lualib.c
+++ b/libs/web/src/template_lualib.c
@@ -21,37 +21,27 @@
int template_L_parse(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
- struct template_parser parser;
- int lua_status;
+ struct template_parser *parser = template_open(file);
+ int lua_status, rv;
- if( (parser.fd = open(file, O_RDONLY)) > 0 )
+ if (!parser)
{
- parser.flags = 0;
- parser.bufsize = 0;
- parser.state = T_STATE_TEXT_NEXT;
-
- lua_status = lua_load(L, template_reader, &parser, file);
+ lua_pushnil(L);
+ lua_pushinteger(L, errno);
+ lua_pushstring(L, strerror(errno));
+ return 3;
+ }
- (void) close(parser.fd);
+ lua_status = lua_load(L, template_reader, parser, file);
+ if (lua_status == 0)
+ rv = 1;
+ else
+ rv = template_error(L, parser);
- if( lua_status == 0 )
- {
- return 1;
- }
- else
- {
- lua_pushnil(L);
- lua_pushinteger(L, lua_status);
- lua_pushlstring(L, parser.out, parser.outsize);
- return 3;
- }
- }
+ template_close(parser);
- lua_pushnil(L);
- lua_pushinteger(L, 255);
- lua_pushstring(L, "No such file or directory");
- return 3;
+ return rv;
}
int template_L_sanitize_utf8(lua_State *L)
@@ -88,12 +78,64 @@ int template_L_sanitize_pcdata(lua_State *L)
return 0;
}
+static int template_L_load_catalog(lua_State *L) {
+ const char *lang = luaL_optstring(L, 1, "en");
+ const char *dir = luaL_optstring(L, 2, NULL);
+ lua_pushboolean(L, !lmo_load_catalog(lang, dir));
+ return 1;
+}
+
+static int template_L_close_catalog(lua_State *L) {
+ const char *lang = luaL_optstring(L, 1, "en");
+ lmo_close_catalog(lang);
+ return 0;
+}
+
+static int template_L_change_catalog(lua_State *L) {
+ const char *lang = luaL_optstring(L, 1, "en");
+ lua_pushboolean(L, !lmo_change_catalog(lang));
+ return 1;
+}
+
+static int template_L_translate(lua_State *L) {
+ size_t len;
+ char *tr;
+ int trlen;
+ const char *key = luaL_checklstring(L, 1, &len);
+
+ switch (lmo_translate(key, len, &tr, &trlen))
+ {
+ case 0:
+ lua_pushlstring(L, tr, trlen);
+ return 1;
+
+ case -1:
+ return 0;
+ }
+
+ lua_pushnil(L);
+ lua_pushstring(L, "no catalog loaded");
+ return 2;
+}
+
+static int template_L_hash(lua_State *L) {
+ size_t len;
+ const char *key = luaL_checklstring(L, 1, &len);
+ lua_pushinteger(L, sfh_hash(key, len));
+ return 1;
+}
+
/* module table */
static const luaL_reg R[] = {
{ "parse", template_L_parse },
{ "sanitize_utf8", template_L_sanitize_utf8 },
{ "sanitize_pcdata", template_L_sanitize_pcdata },
+ { "load_catalog", template_L_load_catalog },
+ { "close_catalog", template_L_close_catalog },
+ { "change_catalog", template_L_change_catalog },
+ { "translate", template_L_translate },
+ { "hash", template_L_hash },
{ NULL, NULL }
};
diff --git a/libs/web/src/template_lualib.h b/libs/web/src/template_lualib.h
index d628b9dce..1b659be12 100644
--- a/libs/web/src/template_lualib.h
+++ b/libs/web/src/template_lualib.h
@@ -21,6 +21,7 @@
#include "template_parser.h"
#include "template_utils.h"
+#include "template_lmo.h"
#define TEMPLATE_LUALIB_META "template.parser"
diff --git a/libs/web/src/template_parser.c b/libs/web/src/template_parser.c
index a0a400bdf..69f0f1c48 100644
--- a/libs/web/src/template_parser.c
+++ b/libs/web/src/template_parser.c
@@ -1,7 +1,7 @@
/*
* LuCI Template - Parser implementation
*
- * Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,17 +17,21 @@
*/
#include "template_parser.h"
+#include "template_utils.h"
+#include "template_lmo.h"
/* leading and trailing code for different types */
-const char * gen_code[7][2] = {
+const char *gen_code[9][2] = {
+ { NULL, NULL },
{ "write(\"", "\")" },
{ NULL, NULL },
{ "write(tostring(", " or \"\"))" },
{ "include(\"", "\")" },
- { "write(pcdata(translate(\"", "\")))" },
- { "write(translate(\"", "\"))" },
- { NULL, " " }
+ { "write(\"", "\")" },
+ { "write(\"", "\")" },
+ { NULL, " " },
+ { NULL, NULL },
};
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
@@ -59,407 +63,326 @@ static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
return NULL;
}
-/*
- * Inspect current read buffer and find the number of "vague" characters at the end
- * which could indicate an opening token. Returns the number of "vague" chars.
- * The last continuous sequence of whitespace, optionally followed by a "<" is
- * treated as "vague" because whitespace may be discarded if the upcoming opening
- * token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char
- * can't be differentiated from an opening token ("<%"), so it's kept to be processed
- * in the next cycle.
- */
-static int stokscan(struct template_parser *data, int off, int no_whitespace)
+struct template_parser * template_open(const char *file)
{
- int i;
- int skip = 0;
- int tokoff = data->bufsize - 1;
+ struct stat s;
+ static struct template_parser *parser;
- for( i = tokoff; i >= off; i-- )
- {
- if( data->buf[i] == T_TOK_START[0] )
- {
- skip = tokoff - i + 1;
- tokoff = i - 1;
- break;
- }
- }
+ if (!(parser = malloc(sizeof(*parser))))
+ goto err;
+
+ memset(parser, 0, sizeof(*parser));
+ parser->fd = -1;
+ parser->file = file;
+
+ if (stat(file, &s))
+ goto err;
+
+ if ((parser->fd = open(file, O_RDONLY)) < 0)
+ goto err;
- if( !no_whitespace )
+ parser->size = s.st_size;
+ parser->mmap = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
+ parser->fd, 0);
+
+ if (parser->mmap != MAP_FAILED)
{
- for( i = tokoff; i >= off; i-- )
- {
- if( isspace(data->buf[i]) )
- skip++;
- else
- break;
- }
+ parser->off = parser->mmap;
+ parser->cur_chunk.type = T_TYPE_INIT;
+ parser->cur_chunk.s = parser->mmap;
+ parser->cur_chunk.e = parser->mmap;
+
+ return parser;
}
- return skip;
+err:
+ template_close(parser);
+ return NULL;
}
-/*
- * Similar to stokscan() but looking for closing token indicators.
- * Matches "-", optionally followed by a "%" char.
- */
-static int etokscan(struct template_parser *data)
+void template_close(struct template_parser *parser)
{
- int skip = 0;
+ if (!parser)
+ return;
+
+ if (parser->gc != NULL)
+ free(parser->gc);
- if( (data->bufsize > 0) && (data->buf[data->bufsize-1] == T_TOK_END[0]) )
- skip++;
+ if ((parser->mmap != NULL) && (parser->mmap != MAP_FAILED))
+ munmap(parser->mmap, parser->size);
- if( (data->bufsize > skip) && (data->buf[data->bufsize-skip-1] == T_TOK_SKIPWS[0]) )
- skip++;
+ if (parser->fd >= 0)
+ close(parser->fd);
- return skip;
+ free(parser);
}
-/*
- * Generate Lua expressions from the given raw code, write it into the
- * output buffer and set the lua_Reader specific size pointer.
- * Takes parser-state, lua_Reader's size pointer and generator flags
- * as parameter. The given flags indicate whether leading or trailing
- * code should be added. Returns a pointer to the output buffer.
- */
-static const char * generate_expression(struct template_parser *data, size_t *sz, int what)
+void template_text(struct template_parser *parser, const char *e)
{
- char tmp[T_OUTBUFSZ];
- int i;
- int size = 0;
- int start = 0;
- int whitespace = 0;
-
- memset(tmp, 0, T_OUTBUFSZ);
-
- /* Inject leading expression code (if any) */
- if( (what & T_GEN_START) && (gen_code[data->type][0] != NULL) )
- {
- memcpy(tmp, gen_code[data->type][0], strlen(gen_code[data->type][0]));
- size += strlen(gen_code[data->type][0]);
- }
+ const char *s = parser->off;
- /* Parse source buffer */
- for( i = 0; i < data->outsize; i++ )
+ if (s < (parser->mmap + parser->size))
{
- /* Skip leading whitespace for non-raw and non-expr chunks */
- if( !start && isspace(data->out[i]) && (data->type == T_TYPE_I18N ||
- data->type == T_TYPE_I18N_RAW || data->type == T_TYPE_INCLUDE) )
- continue;
- else if( !start )
- start = 1;
-
- /* Found whitespace after i18n key */
- if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW )
+ if (parser->strip_after)
{
- /* Is initial whitespace, insert space */
- if( !whitespace && isspace(data->out[i]) )
- {
- tmp[size++] = ' ';
- whitespace = 1;
- }
-
- /* Suppress subsequent whitespace, escape special chars */
- else if( !isspace(data->out[i]) )
- {
- if( data->out[i] == '\\' || data->out[i] == '"' )
- tmp[size++] = '\\';
-
- tmp[size++] = data->out[i];
- whitespace = 0;
- }
+ while ((s <= e) && isspace(*s))
+ s++;
}
- /* Escape quotes, backslashes and newlines for plain and include expressions */
- else if( (data->type == T_TYPE_TEXT || data->type == T_TYPE_INCLUDE) &&
- (data->out[i] == '\\' || data->out[i] == '"' || data->out[i] == '\n' || data->out[i] == '\t') )
- {
- tmp[size++] = '\\';
+ parser->cur_chunk.type = T_TYPE_TEXT;
+ }
+ else
+ {
+ parser->cur_chunk.type = T_TYPE_EOF;
+ }
- switch(data->out[i])
- {
- case '\n':
- tmp[size++] = 'n';
- break;
+ parser->cur_chunk.line = parser->line;
+ parser->cur_chunk.s = s;
+ parser->cur_chunk.e = e;
+}
- case '\t':
- tmp[size++] = 't';
- break;
+void template_code(struct template_parser *parser, const char *e)
+{
+ const char *s = parser->off;
- default:
- tmp[size++] = data->out[i];
- }
- }
+ parser->strip_before = 0;
+ parser->strip_after = 0;
- /* Normal char */
- else
- {
- tmp[size++] = data->out[i];
- }
+ if (*s == '-')
+ {
+ parser->strip_before = 1;
+ for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
}
- /* Inject trailing expression code (if any) */
- if( (what & T_GEN_END) && (gen_code[data->type][1] != NULL) )
+ if (*(e-1) == '-')
{
- /* Strip trailing space for i18n expressions */
- if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW )
- if( (size > 0) && (tmp[size-1] == ' ') )
- size--;
-
- memcpy(&tmp[size], gen_code[data->type][1], strlen(gen_code[data->type][1]));
- size += strlen(gen_code[data->type][1]);
+ parser->strip_after = 1;
+ for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
}
- *sz = data->outsize = size;
- memset(data->out, 0, T_OUTBUFSZ);
- memcpy(data->out, tmp, size);
+ switch (*s)
+ {
+ /* comment */
+ case '#':
+ s++;
+ parser->cur_chunk.type = T_TYPE_COMMENT;
+ break;
- //printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out);
+ /* include */
+ case '+':
+ s++;
+ parser->cur_chunk.type = T_TYPE_INCLUDE;
+ break;
- return data->out;
-}
+ /* translate */
+ case ':':
+ s++;
+ parser->cur_chunk.type = T_TYPE_I18N;
+ break;
-/*
- * Move the number of bytes specified in data->bufsize from the
- * given source pointer to the beginning of the read buffer.
- */
-static void bufmove(struct template_parser *data, const char *src)
-{
- if( data->bufsize > 0 )
- memmove(data->buf, src, data->bufsize);
- else if( data->bufsize < 0 )
- data->bufsize = 0;
+ /* translate raw */
+ case '_':
+ s++;
+ parser->cur_chunk.type = T_TYPE_I18N_RAW;
+ break;
+
+ /* expr */
+ case '=':
+ s++;
+ parser->cur_chunk.type = T_TYPE_EXPR;
+ break;
- data->buf[data->bufsize] = 0;
+ /* code */
+ default:
+ parser->cur_chunk.type = T_TYPE_CODE;
+ break;
+ }
+
+ parser->cur_chunk.line = parser->line;
+ parser->cur_chunk.s = s;
+ parser->cur_chunk.e = e;
}
-/*
- * Move the given amount of bytes from the given source pointer
- * to the output buffer and set data->outputsize.
- */
-static void bufout(struct template_parser *data, const char *src, int len)
+static const char *
+template_format_chunk(struct template_parser *parser, size_t *sz)
{
- if( len >= 0 )
- {
- memset(data->out, 0, T_OUTBUFSZ);
- memcpy(data->out, src, len);
- data->outsize = len;
- }
- else
+ const char *s, *p;
+ const char *head, *tail;
+ struct template_chunk *c = &parser->prv_chunk;
+ struct template_buffer *buf;
+
+ *sz = 0;
+ s = parser->gc = NULL;
+
+ if (parser->strip_before && c->type == T_TYPE_TEXT)
{
- data->outsize = 0;
+ while ((c->e > c->s) && isspace(*(c->e - 1)))
+ c->e--;
}
-}
-/*
- * lua_Reader compatible function that parses template code on demand from
- * the given file handle.
- */
-const char *template_reader(lua_State *L, void *ud, size_t *sz)
-{
- struct template_parser *data = ud;
- char *match = NULL;
- int off = 0;
- int ignore = 0;
- int genflags = 0;
- int readlen = 0;
- int vague = 0;
-
- while( !(data->flags & T_FLAG_EOF) || (data->bufsize > 0) )
+ /* empty chunk */
+ if (c->s == c->e)
{
- /* Fill buffer */
- if( !(data->flags & T_FLAG_EOF) && (data->bufsize < T_READBUFSZ) )
+ if (c->type == T_TYPE_EOF)
+ {
+ *sz = 0;
+ s = NULL;
+ }
+ else
{
- if( (readlen = read(data->fd, &data->buf[data->bufsize], T_READBUFSZ - data->bufsize)) > 0 )
- data->bufsize += readlen;
- else if( readlen == 0 )
- data->flags |= T_FLAG_EOF;
- else
- return NULL;
+ *sz = 1;
+ s = " ";
}
+ }
+
+ /* format chunk */
+ else if ((buf = buf_init(c->e - c->s)) != NULL)
+ {
+ if ((head = gen_code[c->type][0]) != NULL)
+ buf_append(buf, head, strlen(head));
- /* Evaluate state */
- switch(data->state)
+ switch (c->type)
{
- /* Plain text chunk (before "<%") */
- case T_STATE_TEXT_INIT:
- case T_STATE_TEXT_NEXT:
- off = 0; ignore = 0; *sz = 0;
- data->type = T_TYPE_TEXT;
-
- /* Skip leading whitespace if requested */
- if( data->flags & T_FLAG_SKIPWS )
- {
- data->flags &= ~T_FLAG_SKIPWS;
- while( (off < data->bufsize) && isspace(data->buf[off]) )
- off++;
- }
+ case T_TYPE_TEXT:
+ escape_luastr(buf, c->s, c->e - c->s, 0);
+ break;
- /* Found "<%" */
- if( (match = strfind(&data->buf[off], data->bufsize - off - 1, T_TOK_START, strlen(T_TOK_START))) != NULL )
- {
- readlen = (int)(match - &data->buf[off]);
- data->bufsize -= (readlen + strlen(T_TOK_START) + off);
- match += strlen(T_TOK_START);
-
- /* Check for leading '-' */
- if( match[0] == T_TOK_SKIPWS[0] )
- {
- data->bufsize--;
- match++;
-
- while( (readlen > 1) && isspace(data->buf[off+readlen-1]) )
- {
- readlen--;
- }
- }
-
- bufout(data, &data->buf[off], readlen);
- bufmove(data, match);
- data->state = T_STATE_CODE_INIT;
- }
+ case T_TYPE_EXPR:
+ buf_append(buf, c->s, c->e - c->s);
+ for (p = c->s; p < c->e; p++)
+ parser->line += (*p == '\n');
+ break;
- /* Maybe plain chunk */
- else
- {
- /* Preserve trailing "<" or white space, maybe a start token */
- vague = stokscan(data, off, 0);
-
- /* We can process some bytes ... */
- if( vague < data->bufsize )
- {
- readlen = data->bufsize - vague - off;
- }
-
- /* No bytes to process, so try to remove at least whitespace ... */
- else
- {
- /* ... but try to preserve trailing "<" ... */
- vague = stokscan(data, off, 1);
-
- if( vague < data->bufsize )
- {
- readlen = data->bufsize - vague - off;
- }
-
- /* ... no chance, push out buffer */
- else
- {
- readlen = vague - off;
- vague = 0;
- }
- }
-
- bufout(data, &data->buf[off], readlen);
-
- data->state = T_STATE_TEXT_NEXT;
- data->bufsize = vague;
- bufmove(data, &data->buf[off+readlen]);
- }
+ case T_TYPE_INCLUDE:
+ escape_luastr(buf, c->s, c->e - c->s, 0);
+ break;
- if( ignore || data->outsize == 0 )
- continue;
- else
- return generate_expression(data, sz, T_GEN_START | T_GEN_END);
+ case T_TYPE_I18N:
+ translate_luastr(buf, c->s, c->e - c->s, 1);
+ break;
+ case T_TYPE_I18N_RAW:
+ translate_luastr(buf, c->s, c->e - c->s, 0);
break;
- /* Ignored chunk (inside "<%# ... %>") */
- case T_STATE_SKIP:
- ignore = 1;
+ case T_TYPE_CODE:
+ buf_append(buf, c->s, c->e - c->s);
+ for (p = c->s; p < c->e; p++)
+ parser->line += (*p == '\n');
+ break;
+ }
- /* Initial code chunk ("<% ...") */
- case T_STATE_CODE_INIT:
- off = 0;
+ if ((tail = gen_code[c->type][1]) != NULL)
+ buf_append(buf, tail, strlen(tail));
- /* Check for leading '-' */
- if( data->buf[off] == T_TOK_SKIPWS[0] )
- off++;
+ *sz = buf_length(buf);
+ s = parser->gc = buf_destroy(buf);
- /* Determine code type */
- switch(data->buf[off])
- {
- case '#':
- ignore = 1;
- off++;
- data->type = T_TYPE_COMMENT;
- break;
-
- case '=':
- off++;
- data->type = T_TYPE_EXPR;
- break;
-
- case '+':
- off++;
- data->type = T_TYPE_INCLUDE;
- break;
-
- case ':':
- off++;
- data->type = T_TYPE_I18N;
- break;
-
- case '_':
- off++;
- data->type = T_TYPE_I18N_RAW;
- break;
-
- default:
- data->type = T_TYPE_CODE;
- break;
- }
+ if (!*sz)
+ {
+ *sz = 1;
+ s = " ";
+ }
+ }
- /* Subsequent code chunk ("..." or "... %>") */
- case T_STATE_CODE_NEXT:
- /* Found "%>" */
- if( (match = strfind(&data->buf[off], data->bufsize - off, T_TOK_END, strlen(T_TOK_END))) != NULL )
- {
- genflags = ( data->state == T_STATE_CODE_INIT )
- ? (T_GEN_START | T_GEN_END) : T_GEN_END;
+ return s;
+}
- readlen = (int)(match - &data->buf[off]);
+const char *template_reader(lua_State *L, void *ud, size_t *sz)
+{
+ struct template_parser *parser = ud;
+ int rem = parser->size - (parser->off - parser->mmap);
+ char *tag;
- /* Check for trailing '-' */
- if( (match > data->buf) && (*(match-1) == T_TOK_SKIPWS[0]) )
- {
- readlen--;
- data->flags |= T_FLAG_SKIPWS;
- }
+ parser->prv_chunk = parser->cur_chunk;
- bufout(data, &data->buf[off], readlen);
+ /* free previous string */
+ if (parser->gc)
+ {
+ free(parser->gc);
+ parser->gc = NULL;
+ }
- data->state = T_STATE_TEXT_INIT;
- data->bufsize -= ((int)(match - &data->buf[off]) + strlen(T_TOK_END) + off);
- bufmove(data, &match[strlen(T_TOK_END)]);
- }
+ /* before tag */
+ if (!parser->in_expr)
+ {
+ if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
+ {
+ template_text(parser, tag);
+ parser->off = tag + 2;
+ parser->in_expr = 1;
+ }
+ else
+ {
+ template_text(parser, parser->mmap + parser->size);
+ parser->off = parser->mmap + parser->size;
+ }
+ }
- /* Code chunk */
- else
- {
- genflags = ( data->state == T_STATE_CODE_INIT ) ? T_GEN_START : 0;
+ /* inside tag */
+ else
+ {
+ if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
+ {
+ template_code(parser, tag);
+ parser->off = tag + 2;
+ parser->in_expr = 0;
+ }
+ else
+ {
+ /* unexpected EOF */
+ template_code(parser, parser->mmap + parser->size);
- /* Preserve trailing "%" and "-", maybe an end token */
- vague = etokscan(data);
- readlen = data->bufsize - off - vague;
- bufout(data, &data->buf[off], readlen);
+ *sz = 1;
+ return "\033";
+ }
+ }
- data->state = T_STATE_CODE_NEXT;
- data->bufsize = vague;
- bufmove(data, &data->buf[readlen+off]);
- }
+ return template_format_chunk(parser, sz);
+}
- if( ignore || (data->outsize == 0 && !genflags) )
- continue;
- else
- return generate_expression(data, sz, genflags);
+int template_error(lua_State *L, struct template_parser *parser)
+{
+ const char *err = luaL_checkstring(L, -1);
+ const char *off = parser->prv_chunk.s;
+ const char *ptr;
+ char msg[1024];
+ int line = 0;
+ int chunkline = 0;
+ fprintf(stderr, "E[%s]\n", err);
+
+ if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
+ {
+ chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
+
+ while (*ptr)
+ {
+ if (*ptr++ == ' ')
+ {
+ err = ptr;
break;
+ }
}
}
- *sz = 0;
- return NULL;
-}
+ if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
+ {
+ off = parser->mmap + parser->size;
+ err = "'%>' expected before end of file";
+ chunkline = 0;
+ }
+
+ for (ptr = parser->mmap; ptr < off; ptr++)
+ if (*ptr == '\n')
+ line++;
+ snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
+ parser->file, line + chunkline, err ? err : "(unknown error)");
+ lua_pushnil(L);
+ lua_pushinteger(L, line + chunkline);
+ lua_pushstring(L, msg);
+
+ return 3;
+}
diff --git a/libs/web/src/template_parser.h b/libs/web/src/template_parser.h
index 24933f0c9..d1c606272 100644
--- a/libs/web/src/template_parser.h
+++ b/libs/web/src/template_parser.h
@@ -21,61 +21,59 @@
#include <stdlib.h>
#include <stdio.h>
+#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
#include <string.h>
#include <ctype.h>
+#include <errno.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
-#define T_READBUFSZ 1024
-#define T_OUTBUFSZ T_READBUFSZ * 3
-
-/* parser states */
-#define T_STATE_TEXT_INIT 0
-#define T_STATE_TEXT_NEXT 1
-#define T_STATE_CODE_INIT 2
-#define T_STATE_CODE_NEXT 3
-#define T_STATE_SKIP 4
-
-/* parser flags */
-#define T_FLAG_EOF 0x01
-#define T_FLAG_SKIPWS 0x02
-
-/* tokens used in matching and expression generation */
-#define T_TOK_START "<%"
-#define T_TOK_END "%>"
-#define T_TOK_SKIPWS "-"
+/* code types */
+#define T_TYPE_INIT 0
+#define T_TYPE_TEXT 1
+#define T_TYPE_COMMENT 2
+#define T_TYPE_EXPR 3
+#define T_TYPE_INCLUDE 4
+#define T_TYPE_I18N 5
+#define T_TYPE_I18N_RAW 6
+#define T_TYPE_CODE 7
+#define T_TYPE_EOF 8
-/* generator flags */
-#define T_GEN_START 0x01
-#define T_GEN_END 0x02
-/* code types */
-#define T_TYPE_TEXT 0
-#define T_TYPE_COMMENT 1
-#define T_TYPE_EXPR 2
-#define T_TYPE_INCLUDE 3
-#define T_TYPE_I18N 4
-#define T_TYPE_I18N_RAW 5
-#define T_TYPE_CODE 6
+struct template_chunk {
+ const char *s;
+ const char *e;
+ int type;
+ int line;
+};
/* parser state */
struct template_parser {
int fd;
- int bufsize;
- int outsize;
- int state;
- int flags;
- int type;
- char buf[T_READBUFSZ];
- char out[T_OUTBUFSZ];
+ uint32_t size;
+ char *mmap;
+ char *off;
+ char *gc;
+ int line;
+ int in_expr;
+ int strip_before;
+ int strip_after;
+ struct template_chunk prv_chunk;
+ struct template_chunk cur_chunk;
+ const char *file;
};
+struct template_parser * template_open(const char *file);
+void template_close(struct template_parser *parser);
const char *template_reader(lua_State *L, void *ud, size_t *sz);
+int template_error(lua_State *L, struct template_parser *parser);
#endif
diff --git a/libs/web/src/template_utils.c b/libs/web/src/template_utils.c
index 36f08aa22..6ed20d355 100644
--- a/libs/web/src/template_utils.c
+++ b/libs/web/src/template_utils.c
@@ -17,19 +17,23 @@
*/
#include "template_utils.h"
+#include "template_lmo.h"
/* initialize a buffer object */
-static struct template_buffer * buf_init(void)
+struct template_buffer * buf_init(int size)
{
struct template_buffer *buf;
+ if (size <= 0)
+ size = 1024;
+
buf = (struct template_buffer *)malloc(sizeof(struct template_buffer));
if (buf != NULL)
{
buf->fill = 0;
- buf->size = 1024;
- buf->data = (unsigned char *)malloc(buf->size);
+ buf->size = size;
+ buf->data = malloc(buf->size);
if (buf->data != NULL)
{
@@ -46,17 +50,21 @@ static struct template_buffer * buf_init(void)
}
/* grow buffer */
-static int buf_grow(struct template_buffer *buf)
+int buf_grow(struct template_buffer *buf, int size)
{
unsigned int off = (buf->dptr - buf->data);
- unsigned char *data =
- (unsigned char *)realloc(buf->data, buf->size + 1024);
+ char *data;
+
+ if (size <= 0)
+ size = 1024;
+
+ data = realloc(buf->data, buf->size + size);
if (data != NULL)
{
buf->data = data;
buf->dptr = data + off;
- buf->size += 1024;
+ buf->size += size;
return buf->size;
}
@@ -65,9 +73,9 @@ static int buf_grow(struct template_buffer *buf)
}
/* put one char into buffer object */
-static int buf_putchar(struct template_buffer *buf, unsigned char c)
+int buf_putchar(struct template_buffer *buf, char c)
{
- if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf) )
+ if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) )
return 0;
*(buf->dptr++) = c;
@@ -78,11 +86,11 @@ static int buf_putchar(struct template_buffer *buf, unsigned char c)
}
/* append data to buffer */
-static int buf_append(struct template_buffer *buf, unsigned char *s, int len)
+int buf_append(struct template_buffer *buf, const char *s, int len)
{
- while ((buf->fill + len + 1) >= buf->size)
+ if ((buf->fill + len + 1) >= buf->size)
{
- if (!buf_grow(buf))
+ if (!buf_grow(buf, len + 1))
return 0;
}
@@ -95,13 +103,19 @@ static int buf_append(struct template_buffer *buf, unsigned char *s, int len)
return len;
}
+/* read buffer length */
+int buf_length(struct template_buffer *buf)
+{
+ return buf->fill;
+}
+
/* destroy buffer object and return pointer to data */
-static char * buf_destroy(struct template_buffer *buf)
+char * buf_destroy(struct template_buffer *buf)
{
- unsigned char *data = buf->data;
+ char *data = buf->data;
free(buf);
- return (char *)data;
+ return data;
}
@@ -229,7 +243,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf)
!mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n))
{
/* copy sequence */
- if (!buf_append(buf, ptr, n))
+ if (!buf_append(buf, (char *)ptr, n))
return 0;
}
@@ -266,7 +280,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf)
/* sanitize given string and replace all invalid UTF-8 sequences with "?" */
char * sanitize_utf8(const char *s, unsigned int l)
{
- struct template_buffer *buf = buf_init();
+ struct template_buffer *buf = buf_init(l);
unsigned char *ptr = (unsigned char *)s;
unsigned int v, o;
@@ -278,7 +292,7 @@ char * sanitize_utf8(const char *s, unsigned int l)
/* ascii char */
if ((*ptr >= 0x01) && (*ptr <= 0x7F))
{
- if (!buf_putchar(buf, *ptr++))
+ if (!buf_putchar(buf, (char)*ptr++))
break;
}
@@ -300,7 +314,7 @@ char * sanitize_utf8(const char *s, unsigned int l)
* Escape XML control chars */
char * sanitize_pcdata(const char *s, unsigned int l)
{
- struct template_buffer *buf = buf_init();
+ struct template_buffer *buf = buf_init(l);
unsigned char *ptr = (unsigned char *)s;
unsigned int o, v;
char esq[8];
@@ -329,7 +343,7 @@ char * sanitize_pcdata(const char *s, unsigned int l)
{
esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
- if (!buf_append(buf, (unsigned char *)esq, esl))
+ if (!buf_append(buf, esq, esl))
break;
ptr++;
@@ -338,7 +352,7 @@ char * sanitize_pcdata(const char *s, unsigned int l)
/* ascii char */
else if (*ptr <= 0x7F)
{
- buf_putchar(buf, *ptr++);
+ buf_putchar(buf, (char)*ptr++);
}
/* multi byte sequence */
@@ -353,3 +367,68 @@ char * sanitize_pcdata(const char *s, unsigned int l)
return buf_destroy(buf);
}
+
+void escape_luastr(struct template_buffer *out, const char *s, unsigned int l,
+ int escape_xml)
+{
+ int esl;
+ char esq[8];
+ char *ptr;
+
+ for (ptr = (char *)s; ptr < (s + l); ptr++)
+ {
+ switch (*ptr)
+ {
+ case '\\':
+ buf_append(out, "\\\\", 2);
+ break;
+
+ case '"':
+ if (escape_xml)
+ buf_append(out, "&#34;", 5);
+ else
+ buf_append(out, "\\\"", 2);
+ break;
+
+ case '\n':
+ buf_append(out, "\\n", 2);
+ break;
+
+ case '\'':
+ case '&':
+ case '<':
+ case '>':
+ if (escape_xml)
+ {
+ esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
+ buf_append(out, esq, esl);
+ break;
+ }
+
+ default:
+ buf_putchar(out, *ptr);
+ }
+ }
+}
+
+void translate_luastr(struct template_buffer *out, const char *s, unsigned int l,
+ int escape_xml)
+{
+ char *tr;
+ int trlen;
+
+ switch (lmo_translate(s, l, &tr, &trlen))
+ {
+ case 0:
+ escape_luastr(out, tr, trlen, escape_xml);
+ break;
+
+ case -1:
+ escape_luastr(out, s, l, escape_xml);
+ break;
+
+ default:
+ /* no catalog loaded */
+ break;
+ }
+}
diff --git a/libs/web/src/template_utils.h b/libs/web/src/template_utils.h
index 1f7d438c6..371b6a37c 100644
--- a/libs/web/src/template_utils.h
+++ b/libs/web/src/template_utils.h
@@ -1,7 +1,7 @@
/*
* LuCI Template - Utility header
*
- * Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2010-2012 Jo-Philipp Wich <xm@subsignal.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,17 +22,28 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <dlfcn.h>
/* buffer object */
struct template_buffer {
- unsigned char *data;
- unsigned char *dptr;
+ char *data;
+ char *dptr;
unsigned int size;
unsigned int fill;
};
+struct template_buffer * buf_init(int size);
+int buf_grow(struct template_buffer *buf, int size);
+int buf_putchar(struct template_buffer *buf, char c);
+int buf_append(struct template_buffer *buf, const char *s, int len);
+int buf_length(struct template_buffer *buf);
+char * buf_destroy(struct template_buffer *buf);
+
char * sanitize_utf8(const char *s, unsigned int l);
char * sanitize_pcdata(const char *s, unsigned int l);
+void escape_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
+void translate_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
+
#endif
diff --git a/libs/web/standalone.mk b/libs/web/standalone.mk
new file mode 100644
index 000000000..66a0e5a2e
--- /dev/null
+++ b/libs/web/standalone.mk
@@ -0,0 +1,56 @@
+LUAC = luac
+LUAC_OPTIONS = -s
+LUA_TARGET ?= source
+
+LUA_MODULEDIR = /usr/local/share/lua/5.1
+LUA_LIBRARYDIR = /usr/local/lib/lua/5.1
+
+OS ?= $(shell uname)
+
+LUA_SHLIBS = $(shell pkg-config --silence-errors --libs lua5.1 || pkg-config --silence-errors --libs lua-5.1 || pkg-config --silence-errors --libs lua)
+LUA_LIBS = $(if $(LUA_SHLIBS),$(LUA_SHLIBS),$(firstword $(wildcard /usr/lib/liblua.a /usr/local/lib/liblua.a /opt/local/lib/liblua.a)))
+LUA_CFLAGS = $(shell pkg-config --silence-errors --cflags lua5.1 || pkg-config --silence-errors --cflags lua-5.1 || pkg-config --silence-errors --cflags lua)
+
+CC = gcc
+AR = ar
+RANLIB = ranlib
+CFLAGS = -O2
+FPIC = -fPIC
+EXTRA_CFLAGS = --std=gnu99
+WFLAGS = -Wall -Werror -pedantic
+CPPFLAGS =
+COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(WFLAGS)
+ifeq ($(OS),Darwin)
+ SHLIB_FLAGS = -bundle -undefined dynamic_lookup
+else
+ SHLIB_FLAGS = -shared
+endif
+LINK = $(CC) $(LDFLAGS)
+
+.PHONY: all build compile luacompile luasource clean luaclean
+
+all: build
+
+build: luabuild gccbuild
+
+luabuild: lua$(LUA_TARGET)
+
+gccbuild: compile
+compile:
+
+clean: luaclean
+
+luasource:
+ mkdir -p dist$(LUA_MODULEDIR)
+ cp -pR root/* dist 2>/dev/null || true
+ cp -pR lua/* dist$(LUA_MODULEDIR) 2>/dev/null || true
+ for i in $$(find dist -name .svn); do rm -rf $$i || true; done
+
+luastrip: luasource
+ for i in $$(find dist -type f -name '*.lua'); do perl -e 'undef $$/; open( F, "< $$ARGV[0]" ) || die $$!; $$src = <F>; close F; $$src =~ s/--\[\[.*?\]\](--)?//gs; $$src =~ s/^\s*--.*?\n//gm; open( F, "> $$ARGV[0]" ) || die $$!; print F $$src; close F' $$i; done
+
+luacompile: luasource
+ for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done
+
+luaclean:
+ rm -rf dist