summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile35
-rw-r--r--deps.mk8
-rw-r--r--examples/Makefile29
-rw-r--r--examples/error.cpp20
-rw-r--r--examples/error.html1
-rw-r--r--examples/filter.cpp28
-rw-r--r--examples/message.html2
-rw-r--r--license9
-rw-r--r--src/attribute.cpp119
-rw-r--r--src/attribute.hpp47
-rw-r--r--src/attribute.obin0 -> 4456 bytes
-rw-r--r--src/exporter.cpp10
-rw-r--r--src/exporter.hpp12
-rw-r--r--src/exporter.obin0 -> 2488 bytes
-rw-r--r--src/file.cpp17
-rw-r--r--src/file.hpp20
-rw-r--r--src/file.obin0 -> 2992 bytes
-rw-r--r--src/str.cpp32
-rw-r--r--src/str.hpp21
-rw-r--r--src/str.obin0 -> 3328 bytes
-rw-r--r--src/tag.cpp120
-rw-r--r--src/tag.hpp44
-rw-r--r--src/tag.obin0 -> 6832 bytes
-rw-r--r--src/utils.cpp36
-rw-r--r--src/utils.hpp12
-rw-r--r--src/utils.obin0 -> 1856 bytes
-rw-r--r--src/xift.cpp358
-rw-r--r--src/xift.hpp87
28 files changed, 1067 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3cbfb95
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+# Debug mode:
+# DEBUG = yes
+
+CC = g++
+LIB = libxift.a
+
+CPPFLAGS = -Wall -ansi -pedantic
+ifeq '$(DEBUG)' 'yes'
+CPPFLAGS += -g -O0
+else
+CPPFLAGS += -O3
+endif
+
+SRC_DIR = src
+SRC = $(wildcard $(SRC_DIR)/*.cpp)
+OBJ = $(SRC:.cpp=.o)
+
+
+default: $(LIB)
+
+ifneq "clean" "$(MAKECMDGOALS)"
+-include deps.mk
+endif
+
+deps.mk: $(SRC)
+ $(CC) -MM $^ > $@
+
+%.o: %.cpp %.hpp
+ $(CC) $(CPPFLAGS) -c $< -o $@
+
+$(LIB): $(OBJ)
+ ar rcs $@ $^
+
+clean:
+ rm -f $(OBJ) $(LIB) deps.mk
diff --git a/deps.mk b/deps.mk
new file mode 100644
index 0000000..327e510
--- /dev/null
+++ b/deps.mk
@@ -0,0 +1,8 @@
+attribute.o: src/attribute.cpp src/attribute.hpp src/utils.hpp
+exporter.o: src/exporter.cpp src/exporter.hpp
+file.o: src/file.cpp src/file.hpp src/exporter.hpp
+str.o: src/str.cpp src/str.hpp src/exporter.hpp src/utils.hpp
+tag.o: src/tag.cpp src/tag.hpp src/attribute.hpp src/utils.hpp
+utils.o: src/utils.cpp src/utils.hpp
+xift.o: src/xift.cpp src/xift.hpp src/exporter.hpp src/tag.hpp \
+ src/attribute.hpp src/utils.hpp
diff --git a/examples/Makefile b/examples/Makefile
new file mode 100644
index 0000000..3f40167
--- /dev/null
+++ b/examples/Makefile
@@ -0,0 +1,29 @@
+# Uncomment following to enable debug mode:
+# DEBUG = yes
+
+CC = g++
+EXAMPLES = filter error
+
+CPPFLAGS = -Wall -ansi -pedantic
+ifeq '$(DEBUG)' 'yes'
+CPPFLAGS += -g -O0
+else
+CPPFLAGS += -O3
+endif
+
+INCLUDE = -I../src
+LFLAGS = -L.. -lxenophage
+
+XENOPHAGE = ../libxenophage.a
+
+
+default: $(EXAMPLES)
+
+$(XENOPHAGE):
+ cd ..; $(MAKE)
+
+%: %.cpp $(XENOPHAGE)
+ $(CC) $(CPPFLAGS) $(INCLUDE) $< $(LFLAGS) -o $@
+
+clean:
+ rm -f $(EXAMPLES)
diff --git a/examples/error.cpp b/examples/error.cpp
new file mode 100644
index 0000000..54bb17d
--- /dev/null
+++ b/examples/error.cpp
@@ -0,0 +1,20 @@
+#include <file.hpp>
+#include <xift.hpp>
+
+
+int main()
+{
+ XiftFile exporter(stdout);
+ Xift xift(exporter);
+
+ xift.permitted.Tag("p").Attribute("class");
+
+ xift.PutFile("error.html");
+
+ if (!xift.End()) {
+ printf("Error on %d line, %d column:\n%s",
+ xift.ErrorLine(), xift.ErrorColumn(), xift.Error());
+ }
+
+ return 0;
+}
diff --git a/examples/error.html b/examples/error.html
new file mode 100644
index 0000000..b687c25
--- /dev/null
+++ b/examples/error.html
@@ -0,0 +1 @@
+<p class=ужас> text text text </p>
diff --git a/examples/filter.cpp b/examples/filter.cpp
new file mode 100644
index 0000000..6e786c9
--- /dev/null
+++ b/examples/filter.cpp
@@ -0,0 +1,28 @@
+#include <file.hpp>
+#include <xift.hpp>
+
+
+int main()
+{
+ XiftFile exporter(stdout);
+ Xift xift(exporter);
+
+ xift.Tag("a").Attribute("href");
+ XiftTags::Tag &p = xift.Tag("p");
+ p.Attribute("class");
+ p.Attribute("hidden");
+ p.Attribute("id");
+ xift.Tag("script");
+
+ xift.Remove("script");
+ p.Remove("id");
+
+ xift.PutFile("message.html");
+
+ if (!xift.End()) {
+ printf("Error on %d line, %d column:\n%s",
+ xift.ErrorLine(), xift.ErrorColumn(), xift.Error());
+ }
+
+ return 0;
+}
diff --git a/examples/message.html b/examples/message.html
new file mode 100644
index 0000000..7646366
--- /dev/null
+++ b/examples/message.html
@@ -0,0 +1,2 @@
+<script>SOME EVIL MAGIC!</script>
+<a href='#'>some link</a> <p class="some" hidden>parag</p>
diff --git a/license b/license
new file mode 100644
index 0000000..8f83b9c
--- /dev/null
+++ b/license
@@ -0,0 +1,9 @@
+Copyright 2019 Aleksey Veresov
+
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from
+the use of this software.
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
diff --git a/src/attribute.cpp b/src/attribute.cpp
new file mode 100644
index 0000000..4458713
--- /dev/null
+++ b/src/attribute.cpp
@@ -0,0 +1,119 @@
+#include "attribute.hpp"
+
+#include "utils.hpp"
+#include <stdlib.h>
+#include <string.h>
+
+
+XiftAttributes::XiftAttributes(): stack(0)
+{}
+
+XiftAttributes::~XiftAttributes()
+{
+ while (stack) {
+ Stack *old = stack;
+ stack = stack->next;
+ delete old;
+ }
+}
+
+
+XiftAttributes::Attr &XiftAttributes::Attribute(const char *name)
+{
+ Stack *current = stack;
+ while (current) {
+ if (!strcmp(current->item.name, name)) {
+ return current->item;
+ }
+ current = current->next;
+ }
+ Attr &res = New();
+ res.nlen = strlen(name);
+ res.name = xift_str_create_copy(name, name + res.nlen);
+ res.nsize = res.nlen + 1;
+ return res;
+}
+
+void XiftAttributes::Remove(const char *name)
+{
+ Stack **current = &stack;
+ while (*current) {
+ if (!strcmp((*current)->item.name, name)) {
+ Stack *old = *current;
+ *current = (*current)->next;
+ delete old;
+ return;
+ }
+ current = &(*current)->next;
+ }
+}
+
+
+XiftAttributes::Attr::Attr():
+ name(0), nlen(0), nsize(0), value(0), vlen(0), vsize(0)
+{}
+
+XiftAttributes::Attr::~Attr()
+{
+ if (name) free(name);
+ if (value) free(value);
+}
+
+
+bool XiftAttributes::Attr::MatchesForm(const XiftAttributes::Attr &form) const
+{
+ return !strcmp(name, form.name);
+}
+
+
+XiftAttributes::Attr *XiftAttributes::Top()
+{
+ if (stack) {
+ return &stack->item;
+ } else {
+ return 0;
+ }
+}
+
+void XiftAttributes::Pop()
+{
+ if (stack) {
+ Stack *old = stack;
+ stack = stack->next;
+ delete old;
+ }
+}
+
+XiftAttributes::Attr &XiftAttributes::New()
+{
+ Stack *old = stack;
+ stack = new Stack;
+ stack->next = old;
+ return stack->item;
+}
+
+bool XiftAttributes::ContainsMatchedForm(
+ const XiftAttributes::Attr &attribute
+) const
+{
+ Stack *current = stack;
+ while (current) {
+ if (attribute.MatchesForm(current->item)) {
+ return true;
+ }
+ current = current->next;
+ }
+ return false;
+}
+
+bool XiftAttributes::MatchesForm(const XiftAttributes &form) const
+{
+ Stack *current = stack;
+ while (current) {
+ if (!form.ContainsMatchedForm(current->item)) {
+ return false;
+ }
+ current = current->next;
+ }
+ return true;
+}
diff --git a/src/attribute.hpp b/src/attribute.hpp
new file mode 100644
index 0000000..d54d5a3
--- /dev/null
+++ b/src/attribute.hpp
@@ -0,0 +1,47 @@
+#ifndef XIFT_INCLUDED_ATTRIBUTE
+#define XIFT_INCLUDED_ATTRIBUTE
+
+
+class XiftAttributes {
+public:
+ class Attr {
+ friend class XiftAttributes;
+
+ Attr();
+ ~Attr();
+
+ bool MatchesForm(const Attr &form) const;
+
+ char *name;
+ int nlen;
+ int nsize;
+ char *value;
+ int vlen;
+ int vsize;
+
+ char value_quota;
+ };
+
+ XiftAttributes();
+ ~XiftAttributes();
+
+ Attr &Attribute(const char *name);
+ void Remove(const char *name);
+
+protected:
+ Attr *Top();
+ void Pop();
+ Attr &New();
+
+ bool ContainsMatchedForm(const Attr &attribute) const;
+ bool MatchesForm(const XiftAttributes &form) const;
+
+private:
+ struct Stack {
+ Stack *next;
+ Attr item;
+ } *stack;
+};
+
+
+#endif
diff --git a/src/attribute.o b/src/attribute.o
new file mode 100644
index 0000000..88d99c2
--- /dev/null
+++ b/src/attribute.o
Binary files differ
diff --git a/src/exporter.cpp b/src/exporter.cpp
new file mode 100644
index 0000000..bd414a7
--- /dev/null
+++ b/src/exporter.cpp
@@ -0,0 +1,10 @@
+#include "exporter.hpp"
+
+
+void XiftExporter::Put(const char *str)
+{
+ while (*str) {
+ Put(*str);
+ ++str;
+ }
+}
diff --git a/src/exporter.hpp b/src/exporter.hpp
new file mode 100644
index 0000000..b28ad6c
--- /dev/null
+++ b/src/exporter.hpp
@@ -0,0 +1,12 @@
+#ifndef XIFT_INCLUDED_EXPORTER
+#define XIFT_INCLUDED_EXPORTER
+
+
+class XiftExporter {
+public:
+ virtual void Put(char c) = 0;
+ virtual void Put(const char *str);
+};
+
+
+#endif
diff --git a/src/exporter.o b/src/exporter.o
new file mode 100644
index 0000000..18565b9
--- /dev/null
+++ b/src/exporter.o
Binary files differ
diff --git a/src/file.cpp b/src/file.cpp
new file mode 100644
index 0000000..ba6b78e
--- /dev/null
+++ b/src/file.cpp
@@ -0,0 +1,17 @@
+#include "file.hpp"
+
+#include <string.h>
+
+
+XiftFile::XiftFile(FILE *file): file(file)
+{}
+
+void XiftFile::Put(char c)
+{
+ fputc(c, file);
+}
+
+void XiftFile::Put(const char *str)
+{
+ fwrite(str, 1, strlen(str), file);
+}
diff --git a/src/file.hpp b/src/file.hpp
new file mode 100644
index 0000000..0e189d6
--- /dev/null
+++ b/src/file.hpp
@@ -0,0 +1,20 @@
+#ifndef XIFT_INCLUDED_FILE
+#define XIFT_INCLUDED_FILE
+
+#include "exporter.hpp"
+#include <stdio.h>
+
+
+class XiftFile: public XiftExporter {
+public:
+ XiftFile(FILE *file);
+
+ void Put(char c);
+ void Put(const char *str);
+
+private:
+ FILE *file;
+};
+
+
+#endif
diff --git a/src/file.o b/src/file.o
new file mode 100644
index 0000000..857e72e
--- /dev/null
+++ b/src/file.o
Binary files differ
diff --git a/src/str.cpp b/src/str.cpp
new file mode 100644
index 0000000..a0792c8
--- /dev/null
+++ b/src/str.cpp
@@ -0,0 +1,32 @@
+#include "str.hpp"
+
+#include "utils.hpp"
+#include <stdlib.h>
+#include <string.h>
+
+
+XiftString::XiftString(char *&str): str(str)
+{
+ str = (char *)malloc(1);
+ *str = 0;
+ len = 0;
+ size = 1;
+}
+
+void XiftString::Put(char c)
+{
+ xift_str_add(str, len, size, c);
+}
+
+void XiftString::Put(const char *addon)
+{
+ int alen = strlen(addon);
+ if (len + alen >= size) {
+ size = len + alen + 1;
+ str = (char *)realloc(str, size);
+ }
+ if (str) {
+ memcpy(str + len, addon, alen + 1);
+ len += alen;
+ }
+}
diff --git a/src/str.hpp b/src/str.hpp
new file mode 100644
index 0000000..fbedb28
--- /dev/null
+++ b/src/str.hpp
@@ -0,0 +1,21 @@
+#ifndef XIFT_INCLUDED_STRING
+#define XIFT_INCLUDED_STRING
+
+#include "exporter.hpp"
+
+
+class XiftString: public XiftExporter {
+public:
+ XiftString(char *&str);
+
+ void Put(char c);
+ void Put(const char *addon);
+
+private:
+ char *&str;
+ int len;
+ int size;
+};
+
+
+#endif
diff --git a/src/str.o b/src/str.o
new file mode 100644
index 0000000..0ebee34
--- /dev/null
+++ b/src/str.o
Binary files differ
diff --git a/src/tag.cpp b/src/tag.cpp
new file mode 100644
index 0000000..2f4bbcb
--- /dev/null
+++ b/src/tag.cpp
@@ -0,0 +1,120 @@
+#include "tag.hpp"
+
+#include "utils.hpp"
+#include <stdlib.h>
+#include <string.h>
+
+
+XiftTags::Item::Item(): name(0), len(0), size(0)
+{}
+
+XiftTags::Item::~Item()
+{
+ if (name) free(name);
+}
+
+
+bool XiftTags::Item::MatchesForm(const XiftTags::Item &form) const
+{
+ return !strcmp(name, form.name) && XiftAttributes::MatchesForm(form);
+}
+
+
+XiftTags::XiftTags(): stack(0)
+{}
+
+XiftTags::~XiftTags()
+{
+ while (stack) {
+ Stack *old = stack;
+ stack = stack->next;
+ delete old->item;
+ delete old;
+ }
+}
+
+
+XiftTags::Item &XiftTags::Tag(const char *name)
+{
+ Stack *current = stack;
+ while (current) {
+ if (!strcmp(current->item->name, name)) {
+ return *current->item;
+ }
+ current = current->next;
+ }
+ Item &res = New();
+ res.len = strlen(name);
+ res.name = xift_str_create_copy(name, name + res.len);
+ res.size = res.len + 1;
+ return res;
+}
+
+void XiftTags::Remove(const char *name)
+{
+ Stack **current = &stack;
+ while (*current) {
+ if (!strcmp((*current)->item->name, name)) {
+ Stack *old = *current;
+ *current = (*current)->next;
+ delete old->item;
+ delete old;
+ return;
+ }
+ current = &(*current)->next;
+ }
+}
+
+
+XiftTags::Item *XiftTags::Top()
+{
+ if (stack) {
+ return stack->item;
+ } else {
+ return 0;
+ }
+}
+
+void XiftTags::Pop()
+{
+ if (stack) {
+ Stack *old = stack;
+ stack = stack->next;
+ delete old->item;
+ delete old;
+ }
+}
+
+XiftTags::Item *XiftTags::PopToBeDeleted()
+{
+ XiftTags::Item *res = 0;
+ if (stack) {
+ Stack *old = stack;
+ res = stack->item;
+ stack = stack->next;
+ delete old;
+ }
+ return res;
+}
+
+XiftTags::Item &XiftTags::New()
+{
+ Stack *old = stack;
+ stack = new Stack;
+ stack->item = new XiftTags::Item;
+ stack->next = old;
+ return *stack->item;
+}
+
+
+bool XiftTags::ContainsMatchedForm(const XiftTags::Item & tag) const
+{
+ Stack *current = stack;
+ while (current) {
+ if (tag.MatchesForm(*current->item)) {
+ return true;
+ }
+ current = current->next;
+ }
+ return false;
+}
diff --git a/src/tag.hpp b/src/tag.hpp
new file mode 100644
index 0000000..469c4fd
--- /dev/null
+++ b/src/tag.hpp
@@ -0,0 +1,44 @@
+#ifndef XIFT_INCLUDED_TAG
+#define XIFT_INCLUDED_TAG
+
+#include "attribute.hpp"
+
+
+class XiftTags {
+public:
+ class Item: public XiftAttributes {
+ friend class XiftTags;
+
+ Item();
+ ~Item();
+
+ bool MatchesForm(const Item & form) const;
+
+ char *name;
+ int len;
+ int size;
+ };
+
+ XiftTags();
+ ~XiftTags();
+
+ Item &Tag(const char *name);
+ void Remove(const char *name);
+
+protected:
+ Item *Top();
+ void Pop();
+ Item *PopToBeDeleted();
+ Item &New();
+
+ bool ContainsMatchedForm(const Item &tag) const;
+
+private:
+ struct Stack {
+ Stack *next;
+ Item *item;
+ } *stack;
+};
+
+
+#endif
diff --git a/src/tag.o b/src/tag.o
new file mode 100644
index 0000000..00a8cac
--- /dev/null
+++ b/src/tag.o
Binary files differ
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644
index 0000000..2137aa8
--- /dev/null
+++ b/src/utils.cpp
@@ -0,0 +1,36 @@
+#include "utils.hpp"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+char *xift_str_create_copy(const char *begin, const char *end)
+{
+ char *res;
+ res = (char *)malloc(end - begin + 1);
+ if (res) {
+ memcpy(res, begin, end - begin);
+ res[end - begin] = 0;
+ }
+ return res;
+}
+
+
+bool xift_str_add(char *&dest, int &len, int &size, char c)
+{
+ if (!dest) {
+ dest = (char *)malloc(2);
+ size = 2;
+ len = 0;
+ }
+ if (len + 1 == size) {
+ size *= 2;
+ dest = (char *)realloc(dest, size);
+ }
+ if (dest) {
+ dest[len] = c;
+ ++len;
+ dest[len] = 0;
+ }
+ return dest != 0;
+}
diff --git a/src/utils.hpp b/src/utils.hpp
new file mode 100644
index 0000000..2db1436
--- /dev/null
+++ b/src/utils.hpp
@@ -0,0 +1,12 @@
+#ifndef XIFT_INCLUDED_UTILS
+#define XIFT_INCLUDED_UTILS
+
+
+/* Results of both create functions are malloced, so need to be freed. */
+char *xift_str_create_copy(const char *begin, const char *end);
+
+/* False only in case of error. */
+bool xift_str_add(char *&dest, int &len, int &size, char c);
+
+
+#endif
diff --git a/src/utils.o b/src/utils.o
new file mode 100644
index 0000000..7531793
--- /dev/null
+++ b/src/utils.o
Binary files differ
diff --git a/src/xift.cpp b/src/xift.cpp
new file mode 100644
index 0000000..8b703cb
--- /dev/null
+++ b/src/xift.cpp
@@ -0,0 +1,358 @@
+#include "xift.hpp"
+
+#include "utils.hpp"
+#include <ctype.h>
+
+
+const char *XiftErrorMessage(XiftError error)
+{
+ switch (error) {
+ case xift_ok: return "";
+ case xift_wrong_char: return "Wrong character in tag.";
+ case xift_attr_sep: return "Attribute separator missed.";
+ case xift_something_after_close: return "Met something after tag close.";
+ case xift_close_selfclose: return "Tag is closing and self-closing.";
+ case xift_unwanted_close: return "Closing not opened tag.";
+ }
+}
+
+
+Xift::Xift(XiftExporter &exporter):
+ error_pos(0), error_line(1), error_column(0), exporter(exporter),
+ state(reading_text), is_closing(false), is_self_closing(false)
+{}
+
+
+void Xift::Put(char c)
+{
+ if (state != state_error) {
+ ++error_pos;
+ if (c == '\n') {
+ ++error_line;
+ error_column = 0;
+ }
+ ++error_column;
+ }
+ switch (state) {
+ case state_error: break;
+ case reading_text: ReadText(c); break;
+ case opening_tag: OpenTag(c); break;
+ case reading_name: ReadName(c); break;
+ case waiting_attr: WaitAttr(c); break;
+ case reading_attr_name: ReadAttrName(c); break;
+ case waiting_attr_sep: WaitAttrSep(c); break;
+ case waiting_attr_value: WaitAttrValue(c); break;
+ case reading_attr_value: ReadAttrValue(c); break;
+ case closing_tag: CloseTag(c); break;
+ }
+}
+
+void Xift::Put(const char *str)
+{
+ while (*str) {
+ Put(*str);
+ ++str;
+ }
+}
+
+void Xift::Put(FILE *file)
+{
+ if (file) {
+ for (int c = fgetc(file); c != EOF; c = fgetc(file)) {
+ Put(c);
+ }
+ }
+}
+
+void Xift::PutFile(const char *path)
+{
+ FILE *file = fopen(path, "r");
+ Put(file);
+ fclose(file);
+}
+
+
+bool Xift::End()
+{
+ if (state != state_error) {
+ while (opened.Top()) {
+ CloseTop();
+ }
+ }
+ return !IsError();
+}
+
+
+bool Xift::IsError() { return state == state_error;}
+int Xift::ErrorPosition() { return error_pos; }
+int Xift::ErrorLine() { return error_line; }
+int Xift::ErrorColumn() { return error_column; }
+XiftError Xift::GetError() { return error; }
+
+
+void Xift::ReadText(char c)
+{
+ switch (c) {
+ case '<': state = opening_tag; break;
+ case '>': exporter.Put("&gt;"); break;
+ default: exporter.Put(c); break;
+ }
+}
+
+void Xift::OpenTag(char c)
+{
+ Item &t = opened.New();
+ if (c == '/') {
+ is_closing = true;
+ state = reading_name;
+ } else if (AllowedInTokenStart(c)) {
+ xift_str_add(t.name, t.len, t.size, c);
+ state = reading_name;
+ } else {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+}
+
+void Xift::ReadName(char c)
+{
+ if (AllowedInToken(c)) {
+ Item &t = *opened.Top();
+ xift_str_add(t.name, t.len, t.size, c);
+ } else if (IsWhitespace(c)) {
+ state = waiting_attr;
+ } else if (c == '>') {
+ CompleteCurrent();
+ state = reading_text;
+ } else {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+}
+
+void Xift::WaitAttr(char c)
+{
+ if (AllowedInTokenStart(c)) {
+ Item::Attr &a = opened.Top()->New();
+ xift_str_add(a.name, a.nlen, a.nsize, c);
+ state = reading_attr_name;
+ } else if (c == '/') {
+ is_self_closing = true;
+ state = closing_tag;
+ } else if (c == '>') {
+ CompleteCurrent();
+ state = reading_text;
+ } else if (!IsWhitespace(c)) {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+}
+
+void Xift::ReadAttrName(char c)
+{
+ if (AllowedInToken(c)) {
+ Item::Attr &a = *opened.Top()->Top();
+ xift_str_add(a.name, a.nlen, a.nsize, c);
+ } else if (c == '=') {
+ state = waiting_attr_value;
+ } else if (c == '>') {
+ Item::Attr &a = *opened.Top()->Top();
+ a.value = xift_str_create_copy(a.name, a.name + a.len);
+ a.vlen = a.nlen;
+ a.vsize = a.vlen + 1;
+ a.value_quota = '\'';
+ CompleteCurrent();
+ state = reading_text;
+ } else if (IsWhitespace(c)) {
+ state = waiting_attr_sep;
+ } else {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+}
+
+void Xift::WaitAttrSep(char c)
+{
+ if (c == '=') {
+ state = waiting_attr_value;
+ } else if (c == '>') {
+ Item::Attr &a = *opened.Top()->Top();
+ a.value = xift_str_create_copy(a.name, a.name + a.len);
+ a.vlen = a.nlen;
+ a.vsize = a.vlen + 1;
+ a.value_quota = '\'';
+ CompleteCurrent();
+ state = reading_text;
+ } else if (AllowedInTokenStart(c)) {
+ Item::Attr &a = *opened.Top()->Top();
+ a.value = xift_str_create_copy(a.name, a.name + a.len);
+ a.vlen = a.nlen;
+ a.vsize = a.vlen + 1;
+ a.value_quota = '\'';
+ Item::Attr &n = opened.Top()->New();
+ xift_str_add(n.name, n.nlen, n.nsize, c);
+ state = reading_attr_name;
+ } else if (!IsWhitespace(c)) {
+ state = state_error;
+ error = xift_attr_sep;
+ }
+}
+
+void Xift::WaitAttrValue(char c)
+{
+ if (c == '"' || c == '\'') {
+ opened.Top()->Top()->value_quota = c;
+ value_quota = c;
+ state = reading_attr_value;
+ } else if (AllowedInTokenStart(c)) {
+ Item::Attr &a = *opened.Top()->Top();
+ a.value_quota = '\'';
+ xift_str_add(a.value, a.vlen, a.vsize, c);
+ value_quota = 0;
+ state = reading_attr_value;
+ } else {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+}
+
+void Xift::ReadAttrValue(char c)
+{
+ if (value_quota) {
+ if (c == value_quota) {
+ state = waiting_attr;
+ } else {
+ Item::Attr &a = *opened.Top()->Top();
+ xift_str_add(a.value, a.vlen, a.vsize, c);
+ }
+ } else {
+ if (c == '>') {
+ CompleteCurrent();
+ state = reading_text;
+ }
+ if (AllowedInToken(c)) {
+ Item::Attr &a = *opened.Top()->Top();
+ xift_str_add(a.value, a.vlen, a.vsize, c);
+ } else if (IsWhitespace(c)) {
+ state = waiting_attr;
+ } else {
+ state = state_error;
+ error = xift_wrong_char;
+ }
+ }
+}
+
+void Xift::CloseTag(char c)
+{
+ if (c == '>') {
+ CompleteCurrent();
+ state = reading_text;
+ } else if (!IsWhitespace(c)) {
+ state = state_error;
+ error = something_after_close;
+ }
+}
+
+
+void Xift::CompleteCurrent()
+{
+ if (ContainsMatchedForm(*opened.Top())) {
+ if (is_closing && is_self_closing) {
+ state = state_error;
+ error = xift_close_selfclose;
+ } else if (is_closing) {
+ CloseCurrent();
+ } else if (is_self_closing) {
+ SelfcloseCurrent();
+ } else {
+ PutCurrent();
+ }
+ } else {
+ opened.Pop();
+ }
+ is_self_closing = false;
+ is_closing = false;
+}
+
+void Xift::CloseCurrent()
+{
+ XiftTag *closing = opened.PopToBeDeleted();
+ if (closing) {
+ while (opened.Top() && !opened.Top()->MatchesForm(*closing)) {
+ CloseTop();
+ }
+ delete closing;
+ if (opened.Top()) {
+ CloseTop();
+ } else {
+ state = state_error;
+ error = xift_unwanted_close;
+ }
+ }
+}
+
+void Xift::CloseTop()
+{
+ CloseName(opened.Top()->name);
+ opened.Pop();
+}
+
+void Xift::CloseName(const ScriptVariable &name)
+{
+ exporter.Put("</");
+ exporter.Put(name);
+ exporter.Put('>');
+}
+
+void Xift::SelfcloseCurrent()
+{
+ Item &t = *opened.Top();
+ exporter.Put('<');
+ exporter.Put(t.name);
+ Item::Attr *a;
+ while (a = t.Top()) {
+ exporter.Put(' ');
+ exporter.Put(a->name);
+ exporter.Put('=');
+ exporter.Put(a->value_quota);
+ exporter.Put(a->value);
+ exporter.Put(a->value_quota);
+ t.Pop();
+ }
+ exporter.Put("/>");
+ opened.Pop();
+}
+
+void Xift::PutCurrent()
+{
+ Item &t = *opened.Top();
+ exporter.Put('<');
+ exporter.Put(t.name);
+ Item::Attr *a;
+ while (a = t.Top()) {
+ exporter.Put(' ');
+ exporter.Put(a->name);
+ exporter.Put('=');
+ exporter.Put(a->value_quota);
+ exporter.Put(a->value);
+ exporter.Put(a->value_quota);
+ t.Pop();
+ }
+ exporter.Put('>');
+}
+
+
+bool Xift::AllowedInTokenStart(char c)
+{
+ return c == ':' || c == '_' || isalpha(c);
+}
+
+bool Xift::AllowedInToken(char c)
+{
+ return c == '-' || c == '.' || isdigit(c) || AllowedInTokenStart(c);
+}
+
+bool Xift::IsWhitespace(char c)
+{
+ return c == ' ' || c == '\t' || c == '\n';
+}
diff --git a/src/xift.hpp b/src/xift.hpp
new file mode 100644
index 0000000..a37f1c2
--- /dev/null
+++ b/src/xift.hpp
@@ -0,0 +1,87 @@
+#ifndef XIFT_INCLUDED_XIFT
+#define XIFT_INCLUDED_XIFT
+
+#include "exporter.hpp"
+#include "tag.hpp"
+#include <stdio.h>
+
+
+enum XiftError {
+ xift_ok = 0,
+ xift_wrong_char,
+ xift_attr_sep,
+ xift_something_after_close,
+ xift_close_selfclose,
+ xift_unwanted_close
+};
+
+const char *XiftErrorMessage(XiftError error);
+
+
+class Xift: public XiftTags {
+public:
+ Xift(XiftExporter &exporter);
+
+ void Put(char c);
+ void Put(const char *str);
+ void Put(FILE *file);
+ void PutFile(const char *path);
+
+ bool End();
+
+ bool IsError();
+ int ErrorPosition();
+ int ErrorLine();
+ int ErrorColumn();
+ XiftError GetError();
+
+private:
+ int error_pos;
+ int error_line;
+ int error_column;
+ XiftError error;
+
+ XiftExporter &exporter;
+ XiftTags opened;
+
+ enum State {
+ state_error = 0,
+ reading_text,
+ opening_tag,
+ reading_name,
+ waiting_attr,
+ reading_attr_name,
+ waiting_attr_sep,
+ waiting_attr_value,
+ reading_attr_value,
+ closing_tag
+ } state;
+ char value_quota;
+
+ bool is_closing;
+ bool is_self_closing;
+
+ void ReadText(char c);
+ void OpenTag(char c);
+ void ReadName(char c);
+ void WaitAttr(char c);
+ void ReadAttrName(char c);
+ void WaitAttrSep(char c);
+ void WaitAttrValue(char c);
+ void ReadAttrValue(char c);
+ void CloseTag(char c);
+
+ void CompleteCurrent();
+ void CloseCurrent();
+ void CloseTop();
+ void CloseName(const char *name);
+ void SelfcloseCurrent();
+ void PutCurrent();
+
+ static bool AllowedInTokenStart(char c);
+ static bool AllowedInToken(char c);
+ static bool IsWhitespace(char c);
+};
+
+
+#endif