diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 778de229f1f57567ccf859a5c1d071199daefa59..9c5597de7c74fd46eda23fb7b247cff57c985644 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -20,6 +20,7 @@ setup:
 
     before_script:
         - apt update && apt install -y autoconf
+
     script:
         - autoreconf -vi
 
@@ -46,6 +47,7 @@ config:
 
     before_script:
         - apt update && apt install -y check
+
     script:
         - mkdir build
         - cd build
@@ -75,6 +77,7 @@ compile:
 
     before_script:
         - apt update && apt install -y make
+
     script:
         - cd build/
         - make
@@ -102,9 +105,15 @@ test:
 
     before_script:
         - apt update && apt install -y check
+        - for file in `find . -type f -not -path "./.git/*"`;
+            do git rev-list -n 1 HEAD $file
+            | xargs git show -s --format=%ai
+            | awk -v file=$file
+                '{system("touch -d \"" $1 " " $2 " " $3 "\" " file)}';
+            done
+
     script:
         - cd build/
-        - find . -type f -execdir touch '{}' +
         - make check
 
 dist:
@@ -115,6 +124,13 @@ dist:
 
     before_script:
         - apt update && apt install -y make check
+        - for file in `find . -type f -not -path "./.git/*"`;
+            do git rev-list -n 1 HEAD $file
+            | xargs git show -s --format=%ai
+            | awk -v file=$file
+                '{system("touch -d \"" $1 " " $2 " " $3 "\" " file)}';
+            done
+
     script:
         - cd build/
         - make distcheck
diff --git a/gitlab-ci/release.sh.in b/gitlab-ci/release.sh.in
index 85d6a966fc8c056d29ff54ae6c35f76961861b4e..be895dcf7cfe5827d5e66f53ea70ae1e95a8c6a8 100644
--- a/gitlab-ci/release.sh.in
+++ b/gitlab-ci/release.sh.in
@@ -14,9 +14,9 @@ DESCRIPTION="Nightly release for commit '$CI_COMMIT_SHA'
 =====================================================================
 This release was created automatically.
 
-Please do NOT use the automatically generated assets, as they don't\
-contain all the sourcecode of the project. Sadly gitlab doesn't allow\
-to turn them off.
+Please do NOT use the automatically generated assets,<br />
+as they don't contain all the sourcecode of the project.<br />
+Sadly gitlab doesn't allow to turn them off.<br />
 However the associated tarball contains a complete distribution."
 
 release-cli create --tag-name "$TAG_NAME" --description="$DESCRIPTION" \
diff --git a/sefht.geany b/sefht.geany
index e646b9ce1409c89cd36c960f20fa217ff08f9eee..2dda1faf5bf39e3abb46a7b90b3421975371295c 100644
--- a/sefht.geany
+++ b/sefht.geany
@@ -28,22 +28,22 @@ long_line_behaviour=1
 long_line_column=72
 
 [files]
-current_page=43
+current_page=9
 FILE_NAME_0=139;None;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2FREADME;0;8
 FILE_NAME_1=134;None;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2F.gitignore;0;8
 FILE_NAME_2=1737;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fconfigure.ac;0;8
 FILE_NAME_3=73;Make;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2FMakefile.am;0;8
 FILE_NAME_4=19;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Fmain.c;0;8
-FILE_NAME_5=956;Make;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2FMakefile.am;0;8
+FILE_NAME_5=1867;Make;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2FMakefile.am;0;8
 FILE_NAME_6=18;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fcms.c;0;8
 FILE_NAME_7=18;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fcms.h;0;8
-FILE_NAME_8=19;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fdata.c;0;8
-FILE_NAME_9=19;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fdata.h;0;8
+FILE_NAME_8=6482;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fdata.c;0;8
+FILE_NAME_9=2172;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fdata.h;0;8
 FILE_NAME_10=23;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ffragment.c;0;8
 FILE_NAME_11=23;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ffragment.h;0;8
 FILE_NAME_12=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ffragment_data.c;0;8
 FILE_NAME_13=29;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ffragment_class.c;0;8
-FILE_NAME_14=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fnode_fragment.c;0;8
+FILE_NAME_14=4785;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fnode_fragment.c;0;8
 FILE_NAME_15=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fnode_fragment.h;0;8
 FILE_NAME_16=6328;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ftext_fragment.c;0;8
 FILE_NAME_17=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ftext_fragment.h;0;8
@@ -58,29 +58,36 @@ FILE_NAME_25=8479;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fp
 FILE_NAME_26=1083;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ftext_segment.h;0;8
 FILE_NAME_27=900;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ftext_segment_mark.c;0;8
 FILE_NAME_28=1867;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Ftext_mark_static.c;0;8
-FILE_NAME_29=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator.c;0;8
-FILE_NAME_30=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator.h;0;8
-FILE_NAME_31=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_tag.c;0;8
-FILE_NAME_32=28;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_tag.h;0;8
-FILE_NAME_33=924;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fstatus.h;0;8
-FILE_NAME_34=18;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Flog.h;0;4
-FILE_NAME_35=20;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fmacro.h;0;8
-FILE_NAME_36=20;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fsefht.h;0;8
-FILE_NAME_37=636;Make;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2FMakefile.am;0;8
-FILE_NAME_38=218;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Fno_test.sh.in;0;8
-FILE_NAME_39=23;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_cms.c;0;8
-FILE_NAME_40=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_data.c;0;8
-FILE_NAME_41=8232;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_fragment.c;0;8
-FILE_NAME_42=33;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_node_fragment.c;0;8
-FILE_NAME_43=5714;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text_fragment.c;0;8
-FILE_NAME_44=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_attr.c;0;8
-FILE_NAME_45=4221;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text.c;0;8
-FILE_NAME_46=994;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text_mark.c;0;8
-FILE_NAME_47=29;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_validator.c;0;8
-FILE_NAME_48=536;None;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftodo.txt;0;8
-FILE_NAME_49=201;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2F.gitlab-ci.yml;0;4
-FILE_NAME_50=71;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fgitlab-ci%2Fupload.sh.in;0;8
-FILE_NAME_51=806;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fgitlab-ci%2Frelease.sh.in;0;4
+FILE_NAME_29=3229;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator.c;0;8
+FILE_NAME_30=1159;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator.h;0;8
+FILE_NAME_31=4857;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_html.h;0;8
+FILE_NAME_32=13132;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_tag.c;0;8
+FILE_NAME_33=1992;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_tag.h;0;8
+FILE_NAME_34=1150;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_tag_data.h;0;8
+FILE_NAME_35=21193;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_attr.c;0;8
+FILE_NAME_36=1834;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_attr.h;0;8
+FILE_NAME_37=1413;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fvalidator_attr_data.h;0;8
+FILE_NAME_38=924;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fstatus.h;0;8
+FILE_NAME_39=18;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Flog.h;0;4
+FILE_NAME_40=20;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fmacro.h;0;8
+FILE_NAME_41=20;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fsrc%2Flib%2Fsefht%2Fsefht.h;0;8
+FILE_NAME_42=3007;Make;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2FMakefile.am;0;8
+FILE_NAME_43=218;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Fno_test.sh.in;0;8
+FILE_NAME_44=23;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_cms.c;0;8
+FILE_NAME_45=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_data.c;0;8
+FILE_NAME_46=8232;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_fragment.c;0;8
+FILE_NAME_47=4815;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_node_fragment.c;0;8
+FILE_NAME_48=5714;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text_fragment.c;0;8
+FILE_NAME_49=24;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_attr.c;0;8
+FILE_NAME_50=4221;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text.c;0;8
+FILE_NAME_51=994;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_text_mark.c;0;8
+FILE_NAME_52=926;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_validator.c;0;8
+FILE_NAME_53=20787;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_validator_tag.c;0;8
+FILE_NAME_54=58751;C;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftests%2Ftest_validator_attr.c;0;8
+FILE_NAME_55=497;None;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Ftodo.txt;0;8
+FILE_NAME_56=2938;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2F.gitlab-ci.yml;0;4
+FILE_NAME_57=71;Sh;0;EUTF-8;1;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fgitlab-ci%2Fupload.sh.in;0;8
+FILE_NAME_58=683;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fjonathan%2FDokumente%2Fprojekte%2Fprgm%2Finternet%2Fweb%2FSeFHT%2Fgitlab-ci%2Frelease.sh.in;0;4
 
 [VTE]
 last_dir=/home/jonathan/Documents/projects/prgm/internet/web/SeFHT/tests
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index d2f37890715f80e873e979ed21f74e1d6cb2e2a5..126dfd9503ef2519c32bea603086ffbfd209e902 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -26,7 +26,11 @@ EXTRA_DIST += sefht/text_data.h
 EXTRA_DIST += sefht/text_mark_static.c
 EXTRA_DIST += sefht/text_segment.c sefht/text_segment.h
 EXTRA_DIST += sefht/text_segment_mark.c
+EXTRA_DIST += sefht/validator_html.h
 EXTRA_DIST += sefht/validator_tag.c
+EXTRA_DIST += sefht/validator_tag_data.h
+EXTRA_DIST += sefht/validator_attr.c
+EXTRA_DIST += sefht/validator_attr_data.h
 
 nobase_include_HEADERS =
 nobase_include_HEADERS += sefht/sefht.h
@@ -42,6 +46,7 @@ nobase_include_HEADERS += sefht/text_fragment.h
 nobase_include_HEADERS += sefht/text.h
 nobase_include_HEADERS += sefht/validator.h
 nobase_include_HEADERS += sefht/validator_tag.h
+nobase_include_HEADERS += sefht/validator_attr.h
 
 libsefht_la_CPPFLAGS = -DSEFHT_COMPILATION
 libsefht_la_LDFLAGS = -version_info 0:0:0
diff --git a/src/lib/sefht/data.c b/src/lib/sefht/data.c
index 413c5fd8c613117c361c9c2bde726cc8ccad941f..3f4e43280dbd86845ea423ecb84e8363e67a47ea 100644
--- a/src/lib/sefht/data.c
+++ b/src/lib/sefht/data.c
@@ -161,38 +161,12 @@ init_validator (struct SH_Data * data,
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	data->validator = SH_Validator_new (status);
-
-	if (data->validator == NULL)
+	data->validator = SH_Validator_new_html5 (status);
+	if (NULL == data->validator)
 	{
 		return FALSE;
 	}
 
-	SH_Validator_register_tag (data->validator, "html", NULL);
-	SH_Validator_register_tag (data->validator, "head", NULL);
-	SH_Validator_register_tag (data->validator, "body", NULL);
-	SH_Validator_register_tag (data->validator, "meta", NULL);
-	SH_Validator_register_tag (data->validator, "link", NULL);
-	SH_Validator_register_tag (data->validator, "title", NULL);
-	SH_Validator_register_tag (data->validator, "main", NULL);
-	SH_Validator_register_tag (data->validator, "article", NULL);
-	SH_Validator_register_tag (data->validator, "section", NULL);
-	SH_Validator_register_tag (data->validator, "header", NULL);
-	SH_Validator_register_tag (data->validator, "footer", NULL);
-	SH_Validator_register_tag (data->validator, "h1", NULL);
-	SH_Validator_register_tag (data->validator, "h2", NULL);
-	SH_Validator_register_tag (data->validator, "h3", NULL);
-	SH_Validator_register_tag (data->validator, "h4", NULL);
-	SH_Validator_register_tag (data->validator, "h5", NULL);
-	SH_Validator_register_tag (data->validator, "h6", NULL);
-	SH_Validator_register_tag (data->validator, "p", NULL);
-	SH_Validator_register_tag (data->validator, "br", NULL);
-	SH_Validator_register_tag (data->validator, "i", NULL);
-	SH_Validator_register_tag (data->validator, "b", NULL);
-	SH_Validator_register_tag (data->validator, "strong", NULL);
-	SH_Validator_register_tag (data->validator, "em", NULL);
-	SH_Validator_register_tag (data->validator, "small", NULL);
-
 	return TRUE;
 }
 
@@ -304,3 +278,30 @@ SH_Data_register_page (struct SH_Data * data, const char * name,
 
 	return data->last_page;
 }
+
+
+bool
+SH_Data_check_tag (struct SH_Data * data, const char * tag)
+{
+	return SH_Validator_check_tag (data->validator, tag);
+}
+
+bool
+SH_Data_check_attr (struct SH_Data * data,
+                    /*@null@*/ const char * tag, const char * attr)
+{
+	return SH_Validator_check_attr (data->validator, tag, attr);
+}
+
+bool
+SH_Data_is_self_closing_tag (const SH_Data * data,
+                             const char * tag,
+                             /*@null@*/ /*@out@*/
+                             struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	return SH_Validator_is_self_closing_tag (data->validator, tag,
+	                                         status);
+}
diff --git a/src/lib/sefht/data.h b/src/lib/sefht/data.h
index 86c74c2403503d305eb7118c1892e228df3e618f..1c73c589436e0a8cfebd7cbb6eabfcad06afbbc8 100644
--- a/src/lib/sefht/data.h
+++ b/src/lib/sefht/data.h
@@ -65,4 +65,23 @@ SH_Data_register_page (SH_Data * data, const char * name,
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/;
 
+
+bool
+SH_Data_check_tag (SH_Data * data, const char * tag)
+	/*@*/;
+
+bool
+SH_Data_check_attr (SH_Data * data,
+                    /*@null@*/ const char * tag, const char * attr)
+	/*@*/;
+
+bool
+SH_Data_is_self_closing_tag (const SH_Data * data,
+                             const char * tag,
+                             /*@null@*/ /*@out@*/
+                             struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
 #endif /* SEFHT_DATA_H */
diff --git a/src/lib/sefht/node_fragment.c b/src/lib/sefht/node_fragment.c
index f87f179cb8aaed05ff40ae2bbd96ee78e93b838f..0f98972d10a2fcb92f07db5c4fd8697fcf884f25 100644
--- a/src/lib/sefht/node_fragment.c
+++ b/src/lib/sefht/node_fragment.c
@@ -152,6 +152,14 @@ SH_NodeFragment_new (const char * tag,
 {
 	struct SH_NodeFragment * fragment;
 
+	if (!SH_Data_check_tag (data, tag))
+	{
+		set_status_ (status, E_VALUE, 2, strlen (tag),
+		             "Tag %s isn't valid.\n", tag);
+
+		return NULL;
+	}
+
 	fragment = malloc (sizeof (struct SH_NodeFragment));
 	if (fragment == NULL)
 	{
@@ -203,6 +211,14 @@ SH_NodeFragment_raw_new (/*@only@*/ char * tag,
 {
 	struct SH_NodeFragment * fragment;
 
+	if (!SH_Data_check_tag (data, tag))
+	{
+		set_status_ (status, E_VALUE, 2, strlen (tag),
+		             "Tag %s isn't valid.\n", tag);
+
+		return NULL;
+	}
+
 	fragment = malloc (sizeof (struct SH_NodeFragment));
 	if (fragment == NULL)
 	{
diff --git a/src/lib/sefht/validator.c b/src/lib/sefht/validator.c
index 5b97aeaa562e4d62fd55c0d426601d6686fdf719..741e591e8cd358ffd0bed1ce3654dc541b60dccd 100644
--- a/src/lib/sefht/validator.c
+++ b/src/lib/sefht/validator.c
@@ -30,22 +30,18 @@
 #include "validator.h"
 
 
-/* "validator_tag.c" is included twice,
- * because TAG_DATA must be defined,
- * before SH_VAlidator can be defined,
- * but SH_Validator must be defined,
- * before the functions in "validator_tag.c"
- * can use the definition, which themselves
- * are needed before the functions in this
- * file can be defined. */
-#include "validator_tag.c"
+#include "validator_tag_data.h"
+#include "validator_attr_data.h"
+
 struct SH_Validator
 {
 	TAG_DATA
+	ATTR_DATA
 };
 
-#define VALIDATOR_IS_DEFINED
+
 #include "validator_tag.c"
+#include "validator_attr.c"
 
 
 /*@null@*/
@@ -57,29 +53,52 @@ SH_Validator_new (/*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@modifies status@*/
 {
 	struct SH_Validator * validator;
+
 	validator = malloc (sizeof (struct SH_Validator));
+	if (NULL == validator)
+	{
+		set_status (status, E_ALLOC, 3, "malloc failed");
+		return NULL;
+	}
+
+	init_tags (validator);
+	init_attrs (validator);
+
+	set_success (status);
+	return validator;
+}
 
-	if (validator == NULL)
+/*@null@*/
+/*@only@*/
+struct SH_Validator *
+SH_Validator_new_html5 (/*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	struct SH_Validator * validator;
+
+	validator = malloc (sizeof (struct SH_Validator));
+	if (NULL == validator)
 	{
-		set_status (status, E_ALLOC, 4,
-		            "Memory allocation for "
-		            "SH_Validator failed.\n");
+		set_status (status, E_ALLOC, 4, "malloc failed");
+		return NULL;
+	}
 
+	if (!init_tags_spec_html5 (validator, status))
+	{
+		free (validator);
 		return NULL;
 	}
 
-	if (!init_tags (validator, status))
+	if (!init_attrs_spec_html5 (validator, status))
 	{
-/* dangerous call to silence splint, should never be executed. */
-#ifdef S_SPLINT_S
-		free (validator->tags);
-#endif
+		free_tags (validator);
 		free (validator);
 		return NULL;
 	}
 
 	set_success (status);
-
 	return validator;
 }
 
@@ -93,14 +112,11 @@ SH_Validator_copy (const struct SH_Validator * validator,
 	/*@modifies status@*/
 {
 	struct SH_Validator * copy;
-	copy = malloc (sizeof (struct SH_Validator));
 
-	if (copy == NULL)
+	copy = malloc (sizeof (struct SH_Validator));
+	if (NULL == copy)
 	{
-		set_status (status, E_ALLOC, 4,
-		            "Memory allocation for "
-		            "SH_Validator failed.\n");
-
+		set_status (status, E_ALLOC, 3, "malloc failed");
 		return NULL;
 	}
 
@@ -114,8 +130,18 @@ SH_Validator_copy (const struct SH_Validator * validator,
 		return NULL;
 	}
 
-	set_success (status);
+	if (!copy_attrs (copy, validator, status))
+	{
+		free_tags (copy);
+/* dangerous call to silence splint, should never be executed. */
+#ifdef S_SPLINT_S
+		free (copy->attrs);
+#endif
+		free (copy);
+		return NULL;
+	}
 
+	set_success (status);
 	return copy;
 }
 
@@ -125,7 +151,7 @@ SH_Validator_free (/*@only@*/ struct SH_Validator * validator)
 	/*@releases validator@*/
 {
 	free_tags (validator);
+	free_attrs (validator);
 	free (validator);
-
 	return;
 }
diff --git a/src/lib/sefht/validator.h b/src/lib/sefht/validator.h
index 6f5272656f03c29a5f6fa5885684c6bb6f833557..6e5fa175b3462416b7d7b9535bf743df22515415 100644
--- a/src/lib/sefht/validator.h
+++ b/src/lib/sefht/validator.h
@@ -36,6 +36,7 @@ typedef struct SH_Validator SH_Validator;
 
 #define __VALIDATOR_H_INSIDE__
 #include "validator_tag.h"
+#include "validator_attr.h"
 #undef __VALIDATOR_H_INSIDE__
 
 
@@ -47,6 +48,14 @@ SH_Validator_new (/*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/;
 
+/*@null@*/
+/*@only@*/
+SH_Validator *
+SH_Validator_new_html5 (/*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
 /*@null@*/
 /*@only@*/
 SH_Validator *
diff --git a/src/lib/sefht/validator_attr.c b/src/lib/sefht/validator_attr.c
new file mode 100644
index 0000000000000000000000000000000000000000..7a09483ceeb5469c349cce839fe6d6dc7e60aa58
--- /dev/null
+++ b/src/lib/sefht/validator_attr.c
@@ -0,0 +1,1109 @@
+/*
+ * validator_attr.c
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "macro.h"
+#include "log.h"
+#include "status.h"
+
+#include "validator_attr.h"
+
+
+static inline
+bool
+init_attrs (/*@special@*/ struct SH_Validator * validator)
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/;
+
+static inline
+bool
+init_attrs_spec_html5 (/*@special@*/ struct SH_Validator * validator,
+                       /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+init_attr_from_spec (/*@special@*/ struct SH_Validator * validator,
+                     /*@out@*/ struct attr_info * attr_data,
+                     const struct HTML_ATTR_DEFINITION spec,
+                     /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@modifies attr_data->name@*/
+	/*@modifies attr_data->tags@*/
+	/*@modifies attr_data->tag_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+init_attrs_from_spec (/*@special@*/ struct SH_Validator * validator,
+                      const struct HTML_ATTR_DEFINITION * spec,
+                      const size_t size,
+                      /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+copy_attrs (/*@special@*/ struct SH_Validator * copy,
+            const struct SH_Validator * validator,
+            /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines copy->attrs,
+	           copy->attr_n@*/
+	/*@modifies copy->attrs@*/
+	/*@modifies copy->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+void
+free_attr (struct attr_info attr)
+	/*@modifies attr.name@*/
+	/*@releases attr.name@*/;
+
+static inline
+void
+free_attrs (/*@special@*/ struct SH_Validator * validator)
+	/*@modifies validator->attrs@*/
+	/*@releases validator->attrs@*/;
+
+static inline
+bool
+find_attr (const struct SH_Validator * validator,
+           const char * attr,
+           /*@out@*/ size_t * index)
+	/*@modifies index@*/;
+
+static inline
+bool
+find_attr_tag (const struct attr_info * attr,
+               /*@dependent@*/ const char * tag,
+               /*@out@*/ size_t * index)
+	/*@modifies index@*/;
+
+static inline
+bool
+add_attr (struct SH_Validator * validator,
+          const char * attr,
+          /*@null@*/ /*@dependent@*/ const char * tag,
+          size_t index,
+          /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+remove_attr (struct SH_Validator * validator,
+             const size_t index,
+             /*@null@*/ /*@out@*/ struct SH_Status  * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+
+static inline
+bool
+attr_add_tag (struct attr_info * attr,
+              const size_t index,
+              /*@null@*/ /*@dependent@*/ const char * tag,
+              /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+attr_remove_tag (struct attr_info * attr,
+                 const size_t index,
+                 /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+remove_tag_for_all_attrs (struct SH_Validator * validator,
+                          /*@dependent@*/ const char * tag,
+                          /*@null@*/ /*@out@*/
+                          struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+static inline
+bool
+attr_global_remove_tag (struct attr_info * attr,
+                        const struct tag_info * tags,
+                        const size_t tag_n,
+                        const char * tag,
+                        /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr->tags@*/
+	/*@modifies attr->tag_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+
+static inline
+bool
+init_attrs (/*@special@*/ struct SH_Validator * validator)
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+{
+	validator->attrs = malloc (0);
+	validator->attr_n = 0;
+	return TRUE;
+}
+
+static inline
+bool
+init_attrs_spec_html5 (/*@special@*/ struct SH_Validator * validator,
+                       /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	size_t size;
+	size = sizeof (HTML5_ATTRS) / sizeof (HTML5_ATTRS[0]);
+	return init_attrs_from_spec (validator, HTML5_ATTRS, size, status);
+}
+
+static inline
+bool
+init_attr_from_spec (/*@special@*/ struct SH_Validator * validator,
+                     /*@out@*/ struct attr_info * attr_data,
+                     const struct HTML_ATTR_DEFINITION spec,
+                     /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@modifies validator->tags@*/
+	/*@modifies validator->tag_n@*/
+	/*@modifies attr_data->name@*/
+	/*@modifies attr_data->tags@*/
+	/*@modifies attr_data->tag_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	attr_data->name = strdup (spec.attr);
+	if (NULL == attr_data->name)
+	{
+		set_status (status, E_ALLOC, 3, "strdup failed");
+		return FALSE;
+	}
+
+	if (0 == spec.tag_n)
+	{
+		attr_data->tags = NULL;
+		attr_data->tag_n = 0;
+		return TRUE;
+	}
+
+	if (spec.tag_n >= (SIZE_MAX / sizeof (struct attr_tag_info)))
+	{
+		set_status (status, E_DOMAIN, 2,
+		            "maximum number of tags per attr reached");
+		free (attr_data->name);
+		return FALSE;
+	}
+
+	attr_data->tags = malloc (spec.tag_n
+	                          * sizeof (struct attr_tag_info));
+	if (NULL == attr_data->tags)
+	{
+		set_status (status, E_ALLOC, 3, "malloc failed");
+		free (attr_data->name);
+		return FALSE;
+	}
+
+	attr_data->tag_n = 0;
+
+	for (size_t index = 0; index < spec.tag_n; index++)
+	{
+		size_t position;
+		const char * tag;
+
+		tag = spec.tags[index].tag;
+		if (!find_tag (validator, tag, &position))
+		{
+			set_status (status, E_STATE, 2, "no such tag");
+			free (attr_data->name);
+			free (attr_data->tags);
+			return FALSE;
+		}
+		tag = validator->tags[position].name;
+
+		#define tags attr_data->tags
+		#define tag_n attr_data->tag_n
+		/* ignore duplicate tags */
+		if (!find_attr_tag (attr_data, tag, &position))
+		{
+			for (size_t i = tag_n; i > position; i--)
+			{
+				tags[i] = tags[i-1];
+			}
+			tags[position].name = tag;
+			tag_n++;
+		}
+		#undef tag_n
+		#undef tags
+	}
+
+	return TRUE;
+}
+
+static inline
+bool
+init_attrs_from_spec (/*@special@*/ struct SH_Validator * validator,
+                      const struct HTML_ATTR_DEFINITION * spec,
+                      const size_t size,
+                      /*@null@*/ /*@out@*/ struct SH_Status * status)
+        /*@uses validator->tags,
+                validator->tag_n@*/
+	/*@defines validator->attrs,
+	           validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	/* overflow detection */
+	if (size >= (SIZE_MAX / sizeof (struct attr_info)))
+	{
+		set_status (status, E_DOMAIN, 2,
+		            "maximum number of attrs reached");
+		return FALSE;
+	}
+
+	validator->attrs = malloc (size * sizeof (struct attr_info));
+	if (NULL == validator->attrs)
+	{
+		set_status (status, E_ALLOC, 3, "malloc failed");
+		return FALSE;
+	}
+
+
+	/* needs to be initialized, as find_attr uses it */
+	validator->attr_n = 0;
+
+	/* insertion sort */
+	#define attrs validator->attrs
+	#define attr_n validator->attr_n
+	for (size_t index = 0; index < size; index++)
+	{
+		size_t position;
+
+		/* ignore duplicate attrs */
+		if (!find_attr (validator, spec[index].attr, &position))
+		{
+			struct attr_info attr_data;
+
+			if (!init_attr_from_spec (validator, &attr_data,
+			                          spec[index], status))
+			{
+				free_attrs (validator);
+				return FALSE;
+			}
+
+			for (size_t i = attr_n; i > position; i--)
+			{
+				attrs[i] = attrs[i-1];
+			}
+
+			attrs[position] = attr_data;
+			attr_n++;
+		}
+		else if ((0 == spec[index].tag_n)
+		&& (0 != attrs[position].tag_n))
+		{
+			free (attrs[position].tags);
+			attrs[position].tags = NULL;
+			attrs[position].tag_n = 0;
+		}
+	}
+	#undef attr_n
+	#undef attrs
+
+	return TRUE;
+}
+
+static inline
+bool
+copy_attr (/*@special@*/ struct attr_info * copy,
+           const struct attr_info * attr,
+          /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines copy->name,
+	           copy->tag_n,
+	           copy->tags@*/
+	/*@modifies copy->name@*/
+	/*@modifies copy->tag_n@*/
+	/*@modifies copy->tags@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	size_t index;
+	char * name;
+	struct attr_tag_info * tags;
+
+	name = strdup (attr->name);
+	if (NULL == name)
+	{
+		set_status (status, E_ALLOC, 3, "strdup failed");
+		return FALSE;
+	}
+
+	if (0 == attr->tag_n)
+	{
+		tags = NULL;
+	}
+	else
+	{
+		tags = malloc (attr->tag_n
+		               * sizeof (struct attr_tag_info));
+		if (NULL == tags)
+		{
+			set_status (status, E_ALLOC, 3, "malloc failed");
+			free (name);
+			return FALSE;
+		}
+	}
+
+	copy->name = name;
+	copy->tag_n = attr->tag_n;
+	copy->tags = tags;
+	for (index = 0; index < attr->tag_n; index++)
+	{
+		copy->tags[index] = attr->tags[index];
+	}
+
+	return TRUE;
+}
+
+static inline
+bool
+copy_attrs (/*@special@*/ struct SH_Validator * copy,
+            const struct SH_Validator * validator,
+            /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines copy->attrs,
+	           copy->attr_n@*/
+	/*@modifies copy->attrs@*/
+	/*@modifies copy->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	size_t index;
+
+	/* The size calculation is save,
+	 * because validator is already allocated. */
+	copy->attrs = malloc (validator->attr_n
+	                      * sizeof (struct attr_info));
+
+	if (NULL == copy->attrs)
+	{
+		set_status (status, E_ALLOC, 5, "malloc failed");
+		return FALSE;
+	}
+
+	/* copy data */
+	for (index = 0; index < validator->attr_n; index++)
+	{
+		if (!copy_attr (&copy->attrs[index],
+		                &validator->attrs[index], status))
+		{
+			copy->attr_n = index;
+			free_attrs (copy);
+			return FALSE;
+		}
+	}
+
+	copy->attr_n = validator->attr_n;
+
+	return TRUE;
+}
+
+static inline
+void
+free_attr (struct attr_info attr)
+	/*@modifies attr.name@*/
+	/*@releases attr.name@*/
+{
+	free (attr.name);
+	if (0 != attr.tag_n) free (attr.tags);
+	return;
+}
+
+static inline
+void
+free_attrs (/*@special@*/ struct SH_Validator * validator)
+	/*@modifies validator->attrs@*/
+	/*@releases validator->attrs@*/
+{
+	size_t index;
+
+	for (index = 0; index < validator->attr_n; index++)
+	{
+		free_attr (validator->attrs[index]);
+	}
+
+	free (validator->attrs);
+	return;
+}
+
+static inline
+bool
+find_attr (const struct SH_Validator * validator,
+           const char * attr,
+           /*@out@*/ size_t * index)
+	/*@modifies index@*/
+{
+	size_t start;
+	size_t end;
+	size_t pivot;
+
+	start = 0;
+	end = validator->attr_n;
+	pivot = (end - start) / 2;
+
+	while (start != end)
+	{
+		int cmp = strcmp (attr, validator->attrs[pivot].name);
+
+		if (0 > cmp)
+		{
+			end = pivot;
+			pivot = (pivot - start) / 2 + start;
+		}
+		else if (0 == cmp)
+		{
+			*index = pivot;
+			return TRUE;
+		}
+		else
+		{
+			start = pivot + 1;
+			pivot = (end - pivot) / 2 + pivot;
+		}
+	}
+
+	*index = start;
+	return FALSE;
+}
+
+static inline
+bool
+find_attr_tag (const struct attr_info * attr,
+               /*@dependent@*/ const char * tag,
+               /*@out@*/ size_t * index)
+	/*@modifies index@*/
+{
+	size_t start;
+	size_t end;
+	size_t pivot;
+
+	start = 0;
+	end = attr->tag_n;
+	pivot = (end - start) / 2;
+
+	while (start != end)
+	{
+		if (tag < attr->tags[pivot].name)
+		{
+			end = pivot;
+			pivot = (pivot - start) / 2 + start;
+		}
+		else if (tag == attr->tags[pivot].name)
+		{
+			*index = pivot;
+			return TRUE;
+		}
+		else
+		{
+			start = pivot + 1;
+			pivot = (end - pivot) / 2 + pivot;
+		}
+	}
+
+	*index = start;
+	return FALSE;
+}
+
+static inline
+bool
+add_attr (struct SH_Validator * validator,
+          const char * attr,
+          /*@null@*/ /*@dependent@*/ const char * tag,
+          size_t index,
+          /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	struct attr_info attr_data;
+	struct attr_info * new_attrs;
+
+	/* abort on overflow:
+	 * - no unused Attr or
+	 * - no unused index */
+	if ((validator->attr_n == SIZE_MAX)
+	|| ((validator->attr_n + 1)
+	            > (SIZE_MAX / sizeof (struct attr_info))))
+	{
+		set_status (status, E_DOMAIN, 2,
+		            "maximum number of attributes reached");
+		return FALSE;
+	}
+
+	attr_data.name = strdup (attr);
+	if (NULL == attr_data.name)
+	{
+		set_status (status, E_ALLOC, 3, "strdup failed");
+		return FALSE;
+	}
+
+	if (NULL == tag)
+	{
+		attr_data.tags = NULL;
+		attr_data.tag_n = 0;
+	}
+	else
+	{
+		attr_data.tags = malloc (sizeof (struct attr_tag_info));
+		if (NULL == attr_data.tags)
+		{
+			set_status (status, E_ALLOC, 3, "malloc failed");
+			free (attr_data.name);
+			return FALSE;
+		}
+
+		attr_data.tags[0].name = tag;
+		attr_data.tag_n = 1;
+	}
+
+	/* allocate new space */
+	/* The addition and the multiplication is save,
+	 * because we have tested for this
+	 * in the first condition. */
+	new_attrs = realloc (validator->attrs,
+			     sizeof (struct attr_info)
+			     * (validator->attr_n + 1));
+
+	if (new_attrs == NULL)
+	{
+		set_status (status, E_ALLOC, 6, "realloc failed");
+
+/* bad code to silence splint, should never be executed. */
+#ifdef S_SPLINT_S
+		validator->attrs = (void *) 0x12345;
+#endif
+		free_attr (attr_data);
+		return FALSE;
+	}
+
+	validator->attrs = new_attrs;
+
+	for (size_t i = validator->attr_n; i > index; i--)
+	{
+		validator->attrs[i] = validator->attrs[i-1];
+	}
+
+	/* commit changes */
+	validator->attrs[index] = attr_data;
+	validator->attr_n++;
+
+	set_success (status);
+	return TRUE;
+}
+
+static inline
+bool
+remove_attr (struct SH_Validator * validator,
+             const size_t index,
+             /*@null@*/ /*@out@*/ struct SH_Status  * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	struct attr_info attr_data;
+	struct attr_info * new_attrs;
+
+	/* preserve data as realloc may fail */
+	attr_data = validator->attrs[index];
+
+	for (size_t i = index; i < validator->attr_n-1; i++)
+	{
+		validator->attrs[i] = validator->attrs[i+1];
+	}
+
+	new_attrs = realloc (validator->attrs,
+	                     sizeof (struct attr_info)
+		             * (validator->attr_n - 1));
+
+	if ((1 != validator->attr_n) && (NULL == new_attrs))
+	{
+		set_status (status, E_ALLOC, 3, "realloc failed");
+
+		for (size_t i = validator->attr_n-1; i > index; i--)
+		{
+			validator->attrs[i] = validator->attrs[i-1];
+		}
+		validator->attrs[index] = attr_data;
+
+		return FALSE;
+	}
+
+	free_attr (attr_data);
+
+	validator->attrs = new_attrs;
+	validator->attr_n--;
+
+	set_success (status);
+	return TRUE;
+}
+
+static inline
+bool
+attr_add_tag (struct attr_info * attr,
+              const size_t index,
+              /*@null@*/ /*@dependent@*/ const char * tag,
+              /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	struct attr_tag_info * new_tags;
+
+	if (0 == attr->tag_n)
+	{
+		set_success (status);
+		return TRUE;
+	}
+
+	if (NULL == tag)
+	{
+		free (attr->tags);
+		attr->tags = NULL;
+		attr->tag_n = 0;
+
+		set_success (status);
+		return TRUE;
+	}
+
+	if ((attr->tag_n == SIZE_MAX)
+	|| ((attr->tag_n + 1)
+	            > (SIZE_MAX / sizeof (struct attr_tag_info))))
+	{
+		set_status (status, E_DOMAIN, 2,
+		            "maximum number of tags per attr reached");
+		return FALSE;
+	}
+
+	new_tags = realloc (attr->tags, sizeof (struct attr_tag_info)
+	                                * (attr->tag_n + 1));
+	if (NULL == new_tags)
+	{
+		set_status (status, E_ALLOC, 4, "realloc failed");
+		return FALSE;
+	}
+
+	for (size_t i = attr->tag_n; i > index; i--)
+	{
+		new_tags[i] = new_tags[i-1];
+	}
+	new_tags[index].name = tag;
+
+	attr->tags = new_tags;
+	attr->tag_n++;
+
+	return TRUE;
+}
+
+static inline
+bool
+attr_remove_tag (struct attr_info * attr,
+                 const size_t index,
+                 /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	struct attr_tag_info tag_data;
+	struct attr_tag_info * new_tags;
+
+	/* preserve data as realloc may fail */
+	tag_data = attr->tags[index];
+
+	for (size_t i = index; i < attr->tag_n-1; i++)
+	{
+		attr->tags[i] = attr->tags[i+1];
+	}
+
+	new_tags = realloc (attr->tags, sizeof (struct attr_tag_info)
+		                        * (attr->tag_n - 1));
+	if (NULL == new_tags)
+	{
+		set_status (status, E_ALLOC, 4, "realloc failed");
+
+		for (size_t i = attr->tag_n; i > index; i--)
+		{
+			attr->tags[i] = attr->tags[i-1];
+		}
+		attr->tags[index] = tag_data;
+
+		return FALSE;
+	}
+
+	attr->tags = new_tags;
+	attr->tag_n--;
+
+	set_success (status);
+	return TRUE;
+}
+
+static inline
+bool
+remove_tag_for_all_attrs (struct SH_Validator * validator,
+                          /*@dependent@*/ const char * tag,
+                          /*@null@*/ /*@out@*/
+                          struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	/* note, that index is used for two different arrays */
+	size_t index;
+
+	for (index = 0; index < validator->attr_n; index++)
+	{
+		struct attr_info * attr;
+		attr = &validator->attrs[index];
+
+		if (1 == attr->tag_n)
+		{
+			if (tag == attr->tags[0].name)
+			{
+				if (!remove_attr (validator, index,
+				                  status))
+				{
+					return FALSE;
+				}
+				/* rescan current index,
+				 * as the current attr just vanished */
+				index--;
+			}
+		}
+		else
+		{
+			size_t index;
+			if (find_attr_tag (attr, tag, &index))
+			{
+				if (!attr_remove_tag (attr, index,
+				                      status))
+				{
+					return FALSE;
+				}
+			}
+		}
+	}
+
+	set_success (status);
+	return TRUE;
+}
+
+static inline
+bool
+attr_global_remove_tag (struct attr_info * attr,
+                        const struct tag_info * tags,
+                        const size_t tag_n,
+                        const char * tag,
+                        /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies attr->tags@*/
+	/*@modifies attr->tag_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	attr->tags = malloc ((tag_n-1) * sizeof (struct attr_tag_info));
+	if (NULL == attr->tags)
+	{
+		set_status (status, E_ALLOC, 3, "malloc failed");
+		return FALSE;
+	}
+
+	for (size_t index = 0; index < tag_n; index++)
+	{
+		size_t position;
+
+		/* remove this tag */
+		if (tag == tags[index].name) continue;
+
+		find_attr_tag (attr, tags[index].name, &position);
+
+		for (size_t i = attr->tag_n; i > position; i--)
+		{
+			attr->tags[i] = attr->tags[i-1];
+		}
+		attr->tags[position].name = tags[index].name;
+		attr->tag_n++;
+	}
+
+	set_success (status);
+	return TRUE;
+}
+
+bool
+SH_Validator_check_attr (const struct SH_Validator * validator,
+                         /*@null@*/ const char * tag,
+                         const char * attr)
+	/*@*/
+{
+	/* note, that index is used for three different arrays */
+	size_t index;
+
+	if (!find_attr (validator, attr, &index)) return FALSE;
+
+	{
+		struct attr_info * attr;
+		attr = &validator->attrs[index];
+
+		if (NULL == tag) return TRUE;
+
+		if (!find_tag (validator, tag, &index)) return FALSE;
+		tag = validator->tags[index].name;
+
+		/* global attr */
+		if (0 == attr->tag_n) return TRUE;
+
+		if (!find_attr_tag (attr, tag, &index)) return FALSE;
+	}
+
+	return TRUE;
+}
+
+bool
+SH_Validator_register_attr (struct SH_Validator * validator,
+                            /*@null@*/ const char * tag,
+                            const char * attr,
+                            /*@null@*/ /*@out@*/
+                            struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	/* note, that index is used for three different arrays */
+	size_t index;
+
+	if (NULL != tag)
+	{
+		if (!find_tag (validator, tag, &index))
+		{
+			set_status (status, E_STATE, 2, "no such tag");
+			return FALSE;
+		}
+
+		tag = validator->tags[index].name;
+	}
+
+	/* attr already registered */
+	if (find_attr (validator, attr, &index))
+	{
+		struct attr_info * attr;
+		attr = &validator->attrs[index];
+
+		if (NULL == tag)
+		{
+			return attr_add_tag (attr, 0, NULL, status);
+		}
+
+		if ((!find_attr_tag (attr, tag, &index))
+		&& (!attr_add_tag (attr, index, tag, status)))
+		{
+			return FALSE;
+		}
+
+		set_success (status);
+		return TRUE;
+	}
+
+	return add_attr (validator, attr, tag, index, status);
+}
+
+bool
+SH_Validator_deregister_attr (struct SH_Validator * validator,
+                              /*@null@*/ const char * tag,
+                              /*@null@*/ const char * attr,
+                              /*@null@*/ /*@out@*/
+                              struct SH_Status * status)
+	/*@modifies validator->attr_n@*/
+	/*@modifies validator->attrs@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	/* note, that index is used for three different arrays */
+	size_t index;
+
+	if ((NULL == tag) && (NULL == attr))
+	{
+		free_attrs (validator);
+		init_attrs (validator);
+		set_success (status);
+		return TRUE;
+	}
+
+	if (NULL == attr)
+	{
+		if (!find_tag (validator, tag, &index))
+		{
+			/* TODO: define whether this is an error */
+			set_status (status, E_VALUE, 3, "no such tag");
+			return FALSE;
+		}
+
+		tag = validator->tags[index].name;
+		return remove_tag_for_all_attrs (validator, tag, status);
+	}
+
+	if (!find_attr (validator, attr, &index))
+	{
+		/* TODO: define whether this is an error */
+		set_status (status, E_VALUE, 3, "no such attr");
+		return FALSE;
+	}
+
+	{
+		struct attr_info * attr;
+		attr = &validator->attrs[index];
+
+		if (NULL == tag)
+		{
+			return remove_attr (validator, index, status);
+		}
+
+		if (1 == attr->tag_n)
+		{
+			/* TODO: define whether this is an error */
+			if (0 != strcmp (tag, attr->tags[0].name))
+			{
+				set_status (status, E_VALUE, 2,
+				            "no such tag");
+				return FALSE;
+			}
+
+			return remove_attr (validator, index, status);
+		}
+
+		if ((0 == attr->tag_n) && (1 == validator->tag_n))
+		{
+			/* TODO: define whether this is an error */
+			if (0 != strcmp (tag, validator->tags[0].name))
+			{
+				set_status (status, E_VALUE, 2,
+				            "no such tag");
+				return FALSE;
+			}
+
+			return remove_attr (validator, index, status);
+		}
+
+		if (!find_tag (validator, tag, &index))
+		{
+			/* TODO: define whether this is an error */
+			set_status (status, E_VALUE, 3, "no such tag");
+			return FALSE;
+		}
+
+		tag = validator->tags[index].name;
+
+		if (0 == attr->tag_n)
+		{
+			return attr_global_remove_tag (attr,
+			                               validator->tags,
+			                               validator->tag_n,
+			                               tag, status);
+		}
+
+		if (!find_attr_tag (attr, tag, &index))
+		{
+			/* TODO: define whether this is an error */
+			set_status (status, E_VALUE, 3, "no such tag");
+			return FALSE;
+		}
+
+		return attr_remove_tag (attr, index, status);
+	}
+}
diff --git a/src/lib/sefht/validator_attr.h b/src/lib/sefht/validator_attr.h
new file mode 100644
index 0000000000000000000000000000000000000000..c8745e806fa910e7061d4a73bcfb3c19800462ee
--- /dev/null
+++ b/src/lib/sefht/validator_attr.h
@@ -0,0 +1,66 @@
+/*
+ * validator_attr.h
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#ifndef SEFHT_VALIDATOR_ATTR_H
+#define SEFHT_VALIDATOR_ATTR_H
+
+#if !defined (SEFHT_VALIDATOR_H)
+#error "Please include only <sefht/validator.h>."
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "status.h"
+
+
+bool
+SH_Validator_register_attr (SH_Validator * validator,
+                            const char * tag,
+                            const char * attr,
+                            /*@null@*/ /*@out@*/
+                            struct SH_Status * status)
+	/*@modifies validator@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+bool
+SH_Validator_deregister_attr (SH_Validator * validator,
+                              /*@null@*/ const char * tag,
+                              /*@null@*/ const char * attr,
+                              /*@null@*/ /*@out@*/
+                              struct SH_Status * status)
+	/*@modifies validator@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
+bool
+SH_Validator_check_attr (const SH_Validator * validator,
+                         /*@null@*/ const char * tag,
+                         const char * attr)
+	/*@*/;
+
+#endif /* SEFHT_VALIDATOR_ATTR_H */
diff --git a/src/lib/sefht/validator_attr_data.h b/src/lib/sefht/validator_attr_data.h
new file mode 100644
index 0000000000000000000000000000000000000000..b877f117a0cbc00e89d5bdc5f620b41ec1f82cf3
--- /dev/null
+++ b/src/lib/sefht/validator_attr_data.h
@@ -0,0 +1,48 @@
+/*
+ * validator_attr_data.h
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#ifndef SEFHT_VALIDATOR_ATTR_DATA_H
+#define SEFHT_VALIDATOR_ATTR_DATA_H
+
+#if !defined (SEFHT_SEFHT_H_INSIDE) && !defined (SEFHT_COMPILATION)
+#error "Only <sefht/sefht.h> can be included directly."
+#endif
+
+struct attr_tag_info
+{
+	/*@relnull@*/ /*@dependent@*/ const char * name;
+};
+
+struct attr_info
+{
+	/*@only@*/ char * name;
+	size_t tag_n;
+	struct attr_tag_info * tags;
+};
+
+#define ATTR_DATA                                                      \
+	/*@only@*/ struct attr_info * attrs;                           \
+	size_t attr_n;                                                 \
+
+#endif /* SEFHT_VALIDATOR_ATTR_DATA_H */
diff --git a/src/lib/sefht/validator_html.h b/src/lib/sefht/validator_html.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e419a2cba7c3bbb27df9f78efbae309892a87e3
--- /dev/null
+++ b/src/lib/sefht/validator_html.h
@@ -0,0 +1,167 @@
+/*
+ * validator_html.h
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#ifndef SEFHT_VALIDATOR_HTML_H
+#define SEFHT_VALIDATOR_HTML_H
+
+#if !defined (SEFHT_COMPILATION)
+#error "Only <sefht/sefht.h> can be included directly."
+#endif
+
+#include "validator.h"
+
+
+struct HTML_TAG_DEFINITION
+{
+	/*@observer@*/ const char * tag;
+	enum SH_VALIDATOR_TAG_TYPE type;
+};
+
+struct HTML_ATTR_DEFINITION
+{
+	/*@observer@*/ const char * attr;
+	/*@observer@*/ const struct HTML_TAG_DEFINITION * tags;
+	size_t tag_n;
+};
+
+
+static const struct HTML_TAG_DEFINITION HTML5_TAGS[] = {
+	{"a",		SH_TAG_TYPE_NORMAL},
+	{"abbr",	SH_TAG_TYPE_NORMAL},
+	{"address",	SH_TAG_TYPE_NORMAL},
+	{"area",	SH_TAG_TYPE_VOID},
+	{"article",	SH_TAG_TYPE_NORMAL},
+	{"aside",	SH_TAG_TYPE_NORMAL},
+	{"audio",	SH_TAG_TYPE_NORMAL},
+	{"b",		SH_TAG_TYPE_NORMAL},
+	{"base",	SH_TAG_TYPE_VOID},
+	{"bdi",		SH_TAG_TYPE_NORMAL},
+	{"bdo",		SH_TAG_TYPE_NORMAL},
+	{"blockquote",	SH_TAG_TYPE_NORMAL},
+	{"body",	SH_TAG_TYPE_NORMAL},
+	{"br",		SH_TAG_TYPE_VOID},
+	{"button",	SH_TAG_TYPE_NORMAL},
+	{"canvas",	SH_TAG_TYPE_NORMAL},
+	{"caption",	SH_TAG_TYPE_NORMAL},
+	{"cite",	SH_TAG_TYPE_NORMAL},
+	{"code",	SH_TAG_TYPE_NORMAL},
+	{"col",		SH_TAG_TYPE_VOID},
+	{"colgroup",	SH_TAG_TYPE_NORMAL},
+	{"data",	SH_TAG_TYPE_NORMAL},
+	{"datalist",	SH_TAG_TYPE_NORMAL},
+	{"dd",		SH_TAG_TYPE_NORMAL},
+	{"del",		SH_TAG_TYPE_NORMAL},
+	{"details",	SH_TAG_TYPE_NORMAL},
+	{"dfn",		SH_TAG_TYPE_NORMAL},
+	{"dialog",	SH_TAG_TYPE_NORMAL},
+	{"div",		SH_TAG_TYPE_NORMAL},
+	{"dl",		SH_TAG_TYPE_NORMAL},
+	{"dt",		SH_TAG_TYPE_NORMAL},
+	{"em",		SH_TAG_TYPE_NORMAL},
+	{"embed",	SH_TAG_TYPE_VOID},
+	{"fieldset",	SH_TAG_TYPE_NORMAL},
+	{"figcaption",	SH_TAG_TYPE_NORMAL},
+	{"figure",	SH_TAG_TYPE_NORMAL},
+	{"footer",	SH_TAG_TYPE_NORMAL},
+	{"form",	SH_TAG_TYPE_NORMAL},
+	{"h1",		SH_TAG_TYPE_NORMAL},
+	{"h2",		SH_TAG_TYPE_NORMAL},
+	{"h3",		SH_TAG_TYPE_NORMAL},
+	{"h4",		SH_TAG_TYPE_NORMAL},
+	{"h5",		SH_TAG_TYPE_NORMAL},
+	{"h6",		SH_TAG_TYPE_NORMAL},
+	{"head",	SH_TAG_TYPE_NORMAL},
+	{"header",	SH_TAG_TYPE_NORMAL},
+	{"hgroup",	SH_TAG_TYPE_NORMAL},
+	{"hr",		SH_TAG_TYPE_VOID},
+	{"html",	SH_TAG_TYPE_NORMAL},
+	{"i",		SH_TAG_TYPE_NORMAL},
+	{"iframe",	SH_TAG_TYPE_NORMAL},
+	{"img",		SH_TAG_TYPE_VOID},
+	{"input",	SH_TAG_TYPE_VOID},
+	{"ins",		SH_TAG_TYPE_NORMAL},
+	{"kbd",		SH_TAG_TYPE_NORMAL},
+	{"label",	SH_TAG_TYPE_NORMAL},
+	{"legend",	SH_TAG_TYPE_NORMAL},
+	{"li",		SH_TAG_TYPE_NORMAL},
+	{"link",	SH_TAG_TYPE_VOID},
+	{"main",	SH_TAG_TYPE_NORMAL},
+	{"map",		SH_TAG_TYPE_NORMAL},
+	{"mark",	SH_TAG_TYPE_NORMAL},
+	{"menu",	SH_TAG_TYPE_NORMAL},
+	{"meta",	SH_TAG_TYPE_VOID},
+	{"meter",	SH_TAG_TYPE_NORMAL},
+	{"nav",		SH_TAG_TYPE_NORMAL},
+	{"noscript",	SH_TAG_TYPE_NORMAL},
+	{"object",	SH_TAG_TYPE_NORMAL},
+	{"ol",		SH_TAG_TYPE_NORMAL},
+	{"optgroup",	SH_TAG_TYPE_NORMAL},
+	{"option",	SH_TAG_TYPE_NORMAL},
+	{"output",	SH_TAG_TYPE_NORMAL},
+	{"p",		SH_TAG_TYPE_NORMAL},
+	{"picture",	SH_TAG_TYPE_NORMAL},
+	{"pre",		SH_TAG_TYPE_NORMAL},
+	{"progress",	SH_TAG_TYPE_NORMAL},
+	{"q",		SH_TAG_TYPE_NORMAL},
+	{"rp",		SH_TAG_TYPE_NORMAL},
+	{"rt",		SH_TAG_TYPE_NORMAL},
+	{"ruby",	SH_TAG_TYPE_NORMAL},
+	{"s",		SH_TAG_TYPE_NORMAL},
+	{"samp",	SH_TAG_TYPE_NORMAL},
+	{"script",	SH_TAG_TYPE_RAW_TEXT},
+	{"search",	SH_TAG_TYPE_NORMAL},
+	{"section",	SH_TAG_TYPE_NORMAL},
+	{"select",	SH_TAG_TYPE_NORMAL},
+	{"slot",	SH_TAG_TYPE_NORMAL},
+	{"small",	SH_TAG_TYPE_NORMAL},
+	{"source",	SH_TAG_TYPE_VOID},
+	{"span",	SH_TAG_TYPE_NORMAL},
+	{"strong",	SH_TAG_TYPE_NORMAL},
+	{"style",	SH_TAG_TYPE_RAW_TEXT},
+	{"sub",		SH_TAG_TYPE_NORMAL},
+	{"summary",	SH_TAG_TYPE_NORMAL},
+	{"sup",		SH_TAG_TYPE_NORMAL},
+	{"table",	SH_TAG_TYPE_NORMAL},
+	{"tbody",	SH_TAG_TYPE_NORMAL},
+	{"td",		SH_TAG_TYPE_NORMAL},
+	{"template",	SH_TAG_TYPE_TEMPLATE},
+	{"textarea",	SH_TAG_TYPE_ESC_RAW_TEXT},
+	{"tfoot",	SH_TAG_TYPE_NORMAL},
+	{"th",		SH_TAG_TYPE_NORMAL},
+	{"thead",	SH_TAG_TYPE_NORMAL},
+	{"time",	SH_TAG_TYPE_NORMAL},
+	{"title",	SH_TAG_TYPE_ESC_RAW_TEXT},
+	{"tr",		SH_TAG_TYPE_NORMAL},
+	{"track",	SH_TAG_TYPE_VOID},
+	{"u",		SH_TAG_TYPE_NORMAL},
+	{"ul",		SH_TAG_TYPE_NORMAL},
+	{"var",		SH_TAG_TYPE_NORMAL},
+	{"video",	SH_TAG_TYPE_NORMAL},
+	{"wbr",		SH_TAG_TYPE_VOID},
+};
+
+static const struct HTML_ATTR_DEFINITION HTML5_ATTRS[] = {
+};
+
+#endif /* SEFHT_VALIDATOR_HTML_H */
diff --git a/src/lib/sefht/validator_tag.c b/src/lib/sefht/validator_tag.c
index 076eec0a0f49647f48d5e92e4b3c4f89630d9f69..a0f51c2662d3bfa826f006e4d00d6bb15d47f12a 100644
--- a/src/lib/sefht/validator_tag.c
+++ b/src/lib/sefht/validator_tag.c
@@ -33,41 +33,51 @@
 
 #include "validator_tag.h"
 
+#include "validator_html.h"
 
-#ifndef VALIDATOR_IS_DEFINED
 
-#define NEXT_TAG(tag) (tag + 1)
+static inline
+bool
+tag_type_to_internal (/*@out@*/ enum TAG_TYPE * internal,
+                      enum SH_VALIDATOR_TAG_TYPE external)
+	/*@modifies internal@*/;
 
-struct tag_info
-{
-	union
-	{
-		struct
-		{
-			Tag id;
-			/*@only@*/ char * name;
-		} data;
-		size_t next;
-	};
-};
+/*@unused@*/
+static inline
+enum SH_VALIDATOR_TAG_TYPE
+tag_type_to_external (enum TAG_TYPE type)
+	/*@*/;
 
-#define TAG_DATA                                                       \
-	/*@only@*/ struct tag_info * tags;                             \
-	size_t tag_n;                                                  \
-	Tag last_tag;                                                  \
+static inline
+void
+init_tags (/*@special@*/ struct SH_Validator * validator)
+	/*@defines validator->tags,
+	           validator->tag_n@*/
+	/*@modifies validator->tags@*/
+	/*@modifies validator->tag_n@*/;
 
-#else /* VALIDATOR_IS_DEFINED */
+static inline
+bool
+init_tags_from_spec (/*@special@*/ struct SH_Validator * validator,
+                     const struct HTML_TAG_DEFINITION * tags,
+                     const size_t size,
+                     /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines validator->tags,
+	           validator->tag_n@*/
+	/*@modifies validator->tags@*/
+	/*@modifies validator->tag_n@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
 
 static inline
 bool
-init_tags (/*@special@*/ struct SH_Validator * validator,
-           /*@null@*/ /*@out@*/ struct SH_Status * status)
+init_tags_spec_html5 (/*@special@*/ struct SH_Validator * validator,
+                      /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@defines validator->tags,
-	        validator->tag_n,
-	        validator->last_tag@*/
+	           validator->tag_n@*/
 	/*@modifies validator->tags@*/
 	/*@modifies validator->tag_n@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/;
@@ -78,15 +88,19 @@ copy_tags (/*@special@*/ struct SH_Validator * copy,
            const struct SH_Validator * validator,
            /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@defines copy->tags,
-	           copy->tag_n,
-	           copy->last_tag@*/
+	           copy->tag_n@*/
 	/*@modifies copy->tags@*/
 	/*@modifies copy->tag_n@*/
-	/*@modifies copy->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/;
 
+static inline
+void
+free_tag (struct tag_info tag)
+	/*@modifies tag.name@*/
+	/*@releases tag.name@*/;
+
 static inline
 void
 free_tags (/*@special@*/ struct SH_Validator * validator)
@@ -94,570 +108,559 @@ free_tags (/*@special@*/ struct SH_Validator * validator)
 	/*@releases validator->tags@*/;
 
 static inline
-size_t
-get_tag_number (const struct SH_Validator * validator)
-	/*@*/;
+bool
+find_tag (const struct SH_Validator * validator,
+          const char * tag,
+          /*@out@*/ size_t * index)
+	/*@modifies index@*/;
 
 static inline
-Tag
+bool
 add_tag (struct SH_Validator * validator,
          const char * tag,
+         enum TAG_TYPE type,
+         size_t index,
          /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@modifies validator->tags@*/
 	/*@modifies validator->tag_n@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/;
 
-/*@unused@*/
 static inline
 bool
-is_tag_id (const struct SH_Validator * validator, Tag id)
-	/*@*/;
+remove_tag (struct SH_Validator * validator,
+            const size_t index,
+            /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@modifies validator->tag_n@*/
+	/*@modifies validator->tags@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
 
 static inline
 bool
-is_tag_name (const struct SH_Validator * validator, const char * name)
-	/*@*/;
+tag_type_to_internal (/*@out@*/ enum TAG_TYPE * internal,
+                      enum SH_VALIDATOR_TAG_TYPE external)
+	/*@modifies internal@*/
+{
+	switch (external)
+	{
+		case SH_TAG_TYPE_VOID:
+			*internal = TAG_T_VOID;
+			return TRUE;
+
+		case SH_TAG_TYPE_TEMPLATE:
+			*internal = TAG_T_TEMPLATE;
+			return TRUE;
+
+		case SH_TAG_TYPE_RAW_TEXT:
+			*internal = TAG_T_RAW_TEXT;
+			return TRUE;
+
+		case SH_TAG_TYPE_ESC_RAW_TEXT:
+			*internal = TAG_T_ESC_RAW_TEXT;
+			return TRUE;
+
+		case SH_TAG_TYPE_FOREIGN:
+			*internal = TAG_T_FOREIGN;
+			return TRUE;
+
+		case SH_TAG_TYPE_NORMAL:
+			*internal = TAG_T_NORMAL;
+			return TRUE;
+
+		default:
+			return FALSE;
+	}
+}
 
 /*@unused@*/
 static inline
-/*@null@*/
-/*@only@*/
-char *
-get_tag_name_by_id (const struct SH_Validator * validator, Tag id,
-                    /*@null@*/ /*@out@*/ struct SH_Status * status)
-	/*@globals fileSystem@*/
-	/*@modifies fileSystem@*/
-	/*@modifies status@*/;
+enum SH_VALIDATOR_TAG_TYPE
+tag_type_to_external (enum TAG_TYPE type)
+	/*@*/
+{
+	switch (type)
+	{
+		case TAG_T_VOID: return SH_TAG_TYPE_VOID;
+		case TAG_T_TEMPLATE: return SH_TAG_TYPE_TEMPLATE;
+		case TAG_T_RAW_TEXT: return SH_TAG_TYPE_RAW_TEXT;
+		case TAG_T_ESC_RAW_TEXT: return SH_TAG_TYPE_ESC_RAW_TEXT;
+		case TAG_T_FOREIGN: return SH_TAG_TYPE_FOREIGN;
+		case TAG_T_NORMAL: return SH_TAG_TYPE_NORMAL;
+	}
+}
 
 static inline
-Tag
-get_tag_id_by_name (const struct SH_Validator * validator,
-                    const char * name)
-	/*@*/;
+void
+init_tags (/*@special@*/ struct SH_Validator * validator)
+	/*@defines validator->tags,
+	           validator->tag_n@*/
+	/*@modifies validator->tags@*/
+	/*@modifies validator->tag_n@*/
+{
+	validator->tags = malloc (0);
+	validator->tag_n = 0;
+	return;
+}
 
 static inline
 bool
-remove_tag (struct SH_Validator * validator, Tag id,
-            /*@null@*/ /*@out@*/ struct SH_Status * status)
-	/*@modifies validator->tag_n@*/
+init_tags_spec_html5 (/*@special@*/ struct SH_Validator * validator,
+                      /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines validator->tags,
+	           validator->tag_n@*/
 	/*@modifies validator->tags@*/
-	/*@modifies validator->last_tag@*/
+	/*@modifies validator->tag_n@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
-	/*@modifies status@*/;
-
-
-#define EXECUTE_ON_ALL_TAGS_IF(ITER, CONDITION, BLOCK)                 \
-do                                                                     \
-{                                                                      \
-	bool is_free;                                                  \
-	size_t index;                                                  \
-	size_t free_index;                                             \
-                                                                       \
-	for (index = (size_t) 1; index <= ITER##_n; index++)           \
-	{                                                              \
-		/* if tag is not in the list of free blocks */         \
-		is_free = FALSE;                                       \
-		for (free_index = ITER##s[0].next;                     \
-		     free_index != 0;                                  \
-		     free_index = ITER##s[free_index].next)            \
-		{                                                      \
-			if (index == free_index)                       \
-			{                                              \
-				is_free = TRUE;                        \
-                                                                       \
-				/*@innerbreak@*/                       \
-				break;                                 \
-			}                                              \
-		}                                                      \
-                                                                       \
-		if (!is_free && CONDITION) BLOCK                       \
-	}                                                              \
-}                                                                      \
-while (FALSE)
-
-#define EXECUTE_ON_ALL_TAGS(BASE, BLOCK)                               \
-        EXECUTE_ON_ALL_TAGS_IF (BASE, TRUE, BLOCK)
-
+	/*@modifies status@*/
+{
+	size_t size;
+	size = sizeof (HTML5_TAGS) / sizeof (HTML5_TAGS[0]);
+	return init_tags_from_spec (validator, HTML5_TAGS, size, status);
+}
 
 static inline
 bool
-init_tags (/*@special@*/ struct SH_Validator * validator,
-           /*@null@*/ /*@out@*/ struct SH_Status * status)
+init_tags_from_spec (/*@special@*/ struct SH_Validator * validator,
+                     const struct HTML_TAG_DEFINITION * spec,
+                     const size_t size,
+                     /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@defines validator->tags,
-	           validator->tag_n,
-	           validator->last_tag@*/
+	           validator->tag_n@*/
 	/*@modifies validator->tags@*/
 	/*@modifies validator->tag_n@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	validator->tags = malloc (sizeof (struct tag_info));
-	if (validator->tags == NULL)
+	size_t index;
+
+	/* overflow detection */
+	if ((size >= (SIZE_MAX / sizeof (struct tag_info))))
 	{
-		set_status (status, E_ALLOC, 3, "malloc failed");
+		set_status (status, E_DOMAIN, 2,
+		            "maximum number of tags reached");
+		return FALSE;
+	}
 
-		validator->tag_n = 0;
-		validator->last_tag = TAG_ERR;
+	validator->tags = malloc (sizeof (struct tag_info) * size);
+	if (NULL == validator->tags)
+	{
+		set_status (status, E_ALLOC, 3, "malloc failed");
 		return FALSE;
 	}
 
-	validator->tags[0].next = 0;
 	validator->tag_n = 0;
-	validator->last_tag = TAG_ERR;
 
+	/* insertion sort */
+	#define tags validator->tags
+	#define tag_n validator->tag_n
+	for (index = 0; index < size; index++)
+	{
+		size_t position;
+
+		/* ignore duplicate tags */
+		if (!find_tag (validator, spec[index].tag, &position))
+		{
+			struct tag_info tag_data;
+
+			if (!tag_type_to_internal (&tag_data.type,
+			                           spec[index].type))
+			{
+				set_status (status, E_VALUE, 3,
+				            "invalid tag type");
+				free_tags (validator);
+				return FALSE;
+			}
+
+			tag_data.name = strdup (spec[index].tag);
+			if (NULL == tag_data.name)
+			{
+				set_status (status, E_ALLOC, 3,
+				            "strdup failed");
+				free_tags (validator);
+				return FALSE;
+			}
+
+			for (size_t i = tag_n; i > position; i--)
+			{
+				tags[i] = tags[i-1];
+			}
+
+			tags[position] = tag_data;
+			tag_n++;
+		}
+	}
+	#undef tag_n
+	#undef tags
+
+	set_success (status);
 	return TRUE;
 }
 
+static inline
+bool
+copy_tag (/*@special@*/ struct tag_info * copy,
+          const struct tag_info * tag,
+          /*@null@*/ /*@out@*/ struct SH_Status * status)
+	/*@defines copy->name@*/
+	/*@modifies copy->name@*/
+	/*@defines copy->type@*/
+	/*@modifies copy->type@*/
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	char * name;
+
+	name = strdup (tag->name);
+	if (NULL == name)
+	{
+		set_status (status, E_ALLOC, 3, "strdup failed");
+		return FALSE;
+	}
+
+	copy->name = name;
+	copy->type = tag->type;
+	return TRUE;
+
+}
 static inline
 bool
 copy_tags (/*@special@*/ struct SH_Validator * copy,
            const struct SH_Validator * validator,
            /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@defines copy->tags,
-	           copy->tag_n,
-	           copy->last_tag@*/
+	           copy->tag_n@*/
 	/*@modifies copy->tags@*/
 	/*@modifies copy->tag_n@*/
-	/*@modifies copy->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	bool is_free;
 	size_t index;
-	size_t free_index;
-	size_t copy_index;
-	size_t tag_n;
-
-	tag_n = get_tag_number (validator);
 
 	/* The size calculation is save,
 	 * because validator is already allocated. */
-	copy->tags = malloc ((tag_n + 1) * sizeof (struct tag_info));
+	copy->tags = malloc (validator->tag_n * sizeof (struct tag_info));
 
 	if (copy->tags == NULL)
 	{
 		set_status (status, E_ALLOC, 5, "malloc failed");
-
-		copy->tag_n = 0;
-		copy->last_tag = TAG_ERR;
 		return FALSE;
 	}
 
-	/* copy allocation info */
-	copy->tags[0].next = 0;
-	copy->tag_n = tag_n;
-	copy->last_tag = validator->last_tag;
-
-
 	/* copy data */
-	copy_index = 0;
-	for (index = (size_t) 1; index <= validator->tag_n; index++)
+	for (index = 0; index < validator->tag_n; index++)
 	{
-		/* if tag is not in the list of free blocks */
-		is_free = FALSE;
-		for (free_index = validator->tags[0].next;
-		     free_index != 0;
-		     free_index = validator->tags[free_index].next)
-		{
-			if (index == free_index)
-			{
-				is_free = TRUE;
-
-				/*@innerbreak@*/
-				break;
-			}
-		}
-
-		if (!is_free)
+		if (!copy_tag (&copy->tags[index],
+		               &validator->tags[index], status))
 		{
-			copy->tags[copy_index].data.id =
-			              validator->tags[index].data.id;
-
-			copy->tags[copy_index].data.name = strdup (
-			              validator->tags[index].data.name);
-
-			if (copy->tags[copy_index].data.name == NULL)
-			{
-				size_t index;
-
-				set_status (status, E_ALLOC, 5,
-				            "strdup failed");
-
-				for (index = 0; index < copy_index;
-				     index++)
-				{
-					free (copy->tags[index]
-					                    .data.name);
-				}
-
-				free (copy->tags);
-
-				return FALSE;
-			}
-
-			copy_index++;
+			copy->tag_n = index;
+			free_tags (copy);
+			return FALSE;
 		}
 	}
 
+	copy->tag_n = validator->tag_n;
+
 	return TRUE;
 }
 
+static inline
+void
+free_tag (struct tag_info tag)
+	/*@modifies tag.name@*/
+	/*@releases tag.name@*/
+{
+	free (tag.name);
+	return;
+}
+
 static inline
 void
 free_tags (/*@special@*/ struct SH_Validator * validator)
 	/*@modifies validator->tags@*/
 	/*@releases validator->tags@*/
 {
-	EXECUTE_ON_ALL_TAGS (
-	    validator->tag,
+	size_t index;
+
+	for (index = 0; index < validator->tag_n; index++)
 	{
-		free (validator->tags[index].data.name);
-	});
+		free_tag (validator->tags[index]);
+	}
 
 	free (validator->tags);
 	return;
 }
 
 static inline
-size_t
-get_tag_number (const struct SH_Validator * validator)
-	/*@*/
+bool
+find_tag (const struct SH_Validator * validator,
+          const char * tag,
+          /*@out@*/ size_t * index)
+	/*@modifies index@*/
 {
-	size_t tag_n;
+	size_t start;
+	size_t end;
+	size_t pivot;
 
-	tag_n = 0;
+	start = 0;
+	end = validator->tag_n;
+	pivot = (end - start) / 2;
 
-	EXECUTE_ON_ALL_TAGS (
-	    validator->tag,
+	while (start != end)
 	{
-		/* This addition is always save,
-		 * because tag_n is always smaller than
-		 * validator->tag_n and it is also of size_t. */
-		tag_n++;
-	});
+		int cmp = strcmp (tag, validator->tags[pivot].name);
+
+		if (0 > cmp)
+		{
+			end = pivot;
+			pivot = (pivot - start) / 2 + start;
+		}
+		else if (0 == cmp)
+		{
+			*index = pivot;
+			return TRUE;
+		}
+		else
+		{
+			start = pivot + 1;
+			pivot = (end - pivot) / 2 + pivot;
+		}
+	}
 
-	return tag_n;
+	*index = start;
+	return FALSE;
 }
 
 static inline
-Tag
+bool
 add_tag (struct SH_Validator * validator,
          const char * tag,
+         enum TAG_TYPE type,
+         size_t index,
          /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@modifies validator->tags@*/
 	/*@modifies validator->tag_n@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	Tag tag_id;
-	size_t index;
-	bool is_new;
+	struct tag_info tag_data;
+	struct tag_info * new_tags;
 
 	/* abort on overflow:
-	 * - no unused Tag or
 	 * - no unused index */
-	if ((validator->last_tag == TAG_MAX)
-	|| ((validator->tags[0].next == 0)
-	    && ((validator->tag_n >= SIZE_MAX - 1)
-	        || ((validator->tag_n + 2)
-	            > (SIZE_MAX / sizeof (struct tag_info))))))
+	if ((validator->tag_n == SIZE_MAX)
+	|| ((validator->tag_n + 1)
+	            > (SIZE_MAX / sizeof (struct tag_info))))
 	{
 		set_status (status, E_DOMAIN, 2,
 		            "maximum number of tags reached");
-		return TAG_ERR;
+		return FALSE;
 	}
 
-	if (validator->tags[0].next == 0)
-	/* allocate new space */
+	tag_data.name = strdup (tag);
+	if (NULL == tag_data.name)
 	{
-		struct tag_info * new_tags;
+		set_status (status, E_ALLOC, 3, "strdup failed");
+		return FALSE;
+	}
 
-		/* The addition and the multiplication is save,
-		 * because we have tested for this
-		 * in the first condition. */
-		new_tags = realloc (validator->tags,
-		                    sizeof (struct tag_info)
-		                    * (validator->tag_n + 2));
+	tag_data.type = type;
 
-		if (new_tags == NULL)
-		{
-			set_status (status, E_ALLOC, 6,
-			            "realloc failed");
+	/* allocate new space */
+	/* The addition and the multiplication is save,
+	 * because we have tested for this
+	 * in the first condition. */
+	new_tags = realloc (validator->tags,
+			    sizeof (struct tag_info)
+			    * (validator->tag_n + 1));
+
+	if (NULL == new_tags)
+	{
+		set_status (status, E_ALLOC, 6, "realloc failed");
 
 /* bad code to silence splint, should never be executed. */
 #ifdef S_SPLINT_S
-			validator->tags = (void *) 0x12345;
+		validator->tags = (void *) 0x12345;
 #endif
-			return TAG_ERR;
-		}
-
-		validator->tags = new_tags;
-		index = validator->tag_n + 1;
-		is_new = TRUE;
-	}
-	/* reuse old space */
-	else
-	{
-		index = validator->tags[0].next;
-		validator->tags[0].next = validator->tags[index].next;
-		is_new = FALSE;
+		return FALSE;
 	}
 
-	tag_id = NEXT_TAG (validator->last_tag);
-	validator->tags[index].data.id = tag_id;
-	validator->tags[index].data.name = strdup (tag);
+	validator->tags = new_tags;
 
-	if (validator->tags[index].data.name == NULL)
+	for (size_t i = validator->tag_n; i > index; i--)
 	{
-		set_status (status, E_ALLOC, 4, "strdup failed");
-
-		/* restore free space list */
-		if (!is_new)
-		{
-			validator->tags[index].next =                  \
-			                        validator->tags[0].next;
-			validator->tags[0].next = index;
-		}
-
-		return TAG_ERR;
+		validator->tags[i] = validator->tags[i-1];
 	}
 
 	/* commit changes */
-	if (is_new)
-	{
-		validator->tag_n++;
-	}
-
-	validator->last_tag = tag_id;
+	validator->tags[index] = tag_data;
+	validator->tag_n++;
 
 	set_success (status);
-
-	return tag_id;
-}
-
-/*@unused@*/
-static inline
-bool
-is_tag_id (const struct SH_Validator * validator, Tag id)
-	/*@*/
-{
-	EXECUTE_ON_ALL_TAGS_IF (
-	    validator->tag,
-	    (validator->tags[index].data.id == id),
-	{
-		return TRUE;
-	});
-
-	return FALSE;
+	return TRUE;
 }
 
 static inline
 bool
-is_tag_name (const struct SH_Validator * validator, const char * name)
-	/*@*/
-{
-	EXECUTE_ON_ALL_TAGS_IF (
-	    validator->tag,
-	    (strcmp (validator->tags[index].data.name, name) == 0),
-	{
-		return TRUE;
-	});
-
-	return FALSE;
-}
-
-/*@unused@*/
-static inline
-/*@null@*/
-/*@only@*/
-char *
-get_tag_name_by_id (const struct SH_Validator * validator, Tag id,
-                    /*@null@*/ /*@out@*/ struct SH_Status * status)
+remove_tag_for_all_attrs (struct SH_Validator * validator,
+                          /*@dependent@*/ const char * tag,
+                          /*@null@*/ /*@out@*/
+                          struct SH_Status * status)
+	/*@modifies validator->attrs@*/
+	/*@modifies validator->attr_n@*/
+	/*@modifies validator->last_attr@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
-	/*@modifies status@*/
-{
-	char * name;
-
-	EXECUTE_ON_ALL_TAGS_IF (
-	    validator->tag,
-	    (validator->tags[index].data.id == id),
-	{
-		name = strdup (validator->tags[index].data.name);
-		if (name == NULL)
-		{
-			set_status (status, E_ALLOC, 3,
-			            "strdup failed");
-			return NULL;
-		}
-
-		return name;
-	});
-
-	return NULL;
-}
-
-static inline
-Tag
-get_tag_id_by_name (const struct SH_Validator * validator,
-                    const char * name)
-	/*@*/
-{
-	EXECUTE_ON_ALL_TAGS_IF (
-	    validator->tag,
-	    (strcmp (validator->tags[index].data.name, name) == 0),
-	{
-		return validator->tags[index].data.id;
-	});
-
-	return TAG_ERR;
-}
+	/*@modifies status@*/;
 
 static inline
 bool
-remove_tag (struct SH_Validator * validator, Tag id,
+remove_tag (struct SH_Validator * validator,
+            const size_t index,
             /*@null@*/ /*@out@*/ struct SH_Status * status)
 	/*@modifies validator->tag_n@*/
 	/*@modifies validator->tags@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	#define tags validator->tags
-	#define tag_n validator->tag_n
-	EXECUTE_ON_ALL_TAGS_IF (
-	    tag,
-	    (tags[index].data.id == id),
-	{
-		free (tags[index].data.name);
+	struct tag_info tag;
+	struct tag_info * new_tags;
 
-		if (index == tag_n)
-		{
-			struct tag_info * new_tags;
+	/* preserve data, as realloc may fail */
+	tag = validator->tags[index];
 
-			do
-			{
-				/* find next last free blocks
-				 * in the list of free blocks */
-				is_free = FALSE;
-				for (free_index = tags[0].next;
-				     free_index != 0;
-				     free_index = tags[free_index].next)
-				{
-
-				if (tags[free_index].next == (index - 1))
-				{
-					is_free = TRUE;
-					index--;
-
-					tags[free_index].next =
-					tags[tags[free_index].next].next;
-
-					/*@innerbreak@*/
-					break;
-				}
-				}
-			}
-			while (is_free);
-
-			if (index
-			> (SIZE_MAX / sizeof (struct tag_info)))
-			{
-				set_status (status, E_DOMAIN, 2,
-				      "overflow while calling realloc");
-			}
+	/* clear all references of this tag */
+	if (!remove_tag_for_all_attrs (validator, tag.name, status))
+	{
+		return FALSE;
+	}
 
-			new_tags = realloc (tags,
-			                    sizeof (struct tag_info)
-			                    * index);
+	for (size_t i = index; i < validator->tag_n-1; i++)
+	{
+		validator->tags[i] = validator->tags[i+1];
+	}
 
-			if (new_tags == NULL)
-			{
-				set_status (status, E_ALLOC, 4,
-				            "realloc failed");
+	new_tags = realloc (validator->tags, sizeof (struct tag_info)
+	                                     * (validator->tag_n - 1));
+	if ((1 != validator->tag_n) && (NULL == new_tags))
+	{
+		set_status (status, E_ALLOC, 3, "realloc failed");
 
+		for (size_t i = validator->tag_n-1; i > index; i++)
+		{
+			validator->tags[i] = validator->tags[i-1];
+		}
+		validator->tags[index] = tag;
 /* bad code to silence splint, should never be executed. */
 #ifdef S_SPLINT_S
-				tags = (void *) 0x12345;
+		new_tags = (void *) 0x12345;
 #endif
-				return FALSE;
-			}
 
-			tags = new_tags;
-		}
-		else
-		{
-			tags[index].next = tags[0].next;
-			tags[0].next = index;
-		}
+		return FALSE;
+	}
 
-		set_success (status);
-		return TRUE;
-	});
-	#undef tags
-	#undef tag_n
+	free_tag (tag);
 
-	set_status (status, E_VALUE, 68, "unknown Tag id");
-	return FALSE;
+	validator->tags = new_tags;
+	validator->tag_n--;
+
+	set_success (status);
+	return TRUE;
 }
 
 
 bool
-SH_Validator_check_tag (struct SH_Validator * validator,
+SH_Validator_check_tag (const struct SH_Validator * validator,
                         const char * tag)
 	/*@*/
 {
-	return is_tag_name (validator, tag);
+	size_t index;
+	return find_tag (validator, tag, &index);
+}
+
+bool
+SH_Validator_is_self_closing_tag (const struct SH_Validator * validator,
+                                  const char * tag,
+                                  /*@null@*/ /*@out@*/
+                                  struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/
+{
+	size_t index;
+
+	if (!find_tag (validator, tag, &index))
+	{
+		set_status (status, E_VALUE, 2, "no such tag");
+		return FALSE;
+	}
+
+	set_success (status);
+	return (TAG_T_VOID == validator->tags[index].type);
 }
 
-Tag
+bool
 /*@alt void@*/
 SH_Validator_register_tag (struct SH_Validator * validator,
                            const char * tag,
+                           enum SH_VALIDATOR_TAG_TYPE type,
                            /*@null@*/ /*@out@*/
                            struct SH_Status * status)
 	/*@modifies validator->tags@*/
 	/*@modifies validator->tag_n@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	Tag tag_id;
+	size_t index;
+	enum TAG_TYPE type_internal;
+
+	if (!tag_type_to_internal (&type_internal, type))
+	{
+		set_status (status, E_VALUE, 2, "invalid tag type");
+		return FALSE;
+	}
 
 	/* tag already registered */
-	tag_id = get_tag_id_by_name (validator, tag);
-	if (tag_id != TAG_ERR)
+	if (find_tag (validator, tag, &index))
 	{
-		return tag_id;
+		/* update type */
+		validator->tags[index].type = type_internal;
+		set_success (status);
+		return TRUE;
 	}
 
-	return add_tag (validator, tag, status);
+	return add_tag (validator, tag, type_internal, index, status);
 }
 
 bool
 SH_Validator_deregister_tag (struct SH_Validator * validator,
-                             Tag id,
+                             const char * tag,
                              /*@null@*/ /*@out@*/
                              struct SH_Status * status)
 	/*@modifies validator->tag_n@*/
 	/*@modifies validator->tags@*/
-	/*@modifies validator->last_tag@*/
 	/*@globals fileSystem@*/
 	/*@modifies fileSystem@*/
 	/*@modifies status@*/
 {
-	return remove_tag (validator, id, status);
-}
+	size_t index;
 
-#endif /* VALIDATOR_IS_DEFINED */
+	if (!find_tag (validator, tag, &index))
+	{
+		/* TODO: define whether this is an error */
+		set_status (status, E_VALUE, 3, "no such tag");
+		return FALSE;
+	}
+
+	return remove_tag (validator, index, status);
+}
diff --git a/src/lib/sefht/validator_tag.h b/src/lib/sefht/validator_tag.h
index a941d0b16b51556adc7858ce26e55971c3456d10..95d438528904ae97e711f9960436ce98080f8b16 100644
--- a/src/lib/sefht/validator_tag.h
+++ b/src/lib/sefht/validator_tag.h
@@ -29,22 +29,30 @@
 #error "Please include only <sefht/validator.h>."
 #endif
 
+#include <limits.h>
 #include <stdbool.h>
 #include <stdint.h>
 
 #include "status.h"
 
 
-typedef unsigned int Tag;
-#define TAG_ERR (Tag) 0
-#define TAG_MIN (Tag) 1
-#define TAG_MAX (Tag) SIZE_MAX
+enum SH_VALIDATOR_TAG_TYPE
+{
+	SH_TAG_TYPE_UNDEFINED,
+	SH_TAG_TYPE_VOID,
+	SH_TAG_TYPE_TEMPLATE,
+	SH_TAG_TYPE_RAW_TEXT,
+	SH_TAG_TYPE_ESC_RAW_TEXT,
+	SH_TAG_TYPE_FOREIGN,
+	SH_TAG_TYPE_NORMAL,
+};
 
 
-Tag
+bool
 /*@alt void@*/
 SH_Validator_register_tag (SH_Validator * validator,
                            const char * tag,
+                           enum SH_VALIDATOR_TAG_TYPE type,
                            /*@null@*/ /*@out@*/
                            struct SH_Status * status)
 	/*@modifies validator@*/
@@ -54,7 +62,7 @@ SH_Validator_register_tag (SH_Validator * validator,
 
 bool
 SH_Validator_deregister_tag (SH_Validator * validator,
-                             Tag id,
+                             const char * tag,
                              /*@null@*/ /*@out@*/
                              struct SH_Status * status)
 	/*@modifies validator@*/
@@ -63,8 +71,17 @@ SH_Validator_deregister_tag (SH_Validator * validator,
 	/*@modifies status@*/;
 
 bool
-SH_Validator_check_tag (struct SH_Validator * validator,
+SH_Validator_check_tag (const SH_Validator * validator,
                         const char * tag)
 	/*@*/;
 
+bool
+SH_Validator_is_self_closing_tag (const SH_Validator * validator,
+                                  const char * tag,
+                                  /*@null@*/ /*@out@*/
+                                  struct SH_Status * status)
+	/*@globals fileSystem@*/
+	/*@modifies fileSystem@*/
+	/*@modifies status@*/;
+
 #endif /* SEFHT_VALIDATOR_TAG_H */
diff --git a/src/lib/sefht/validator_tag_data.h b/src/lib/sefht/validator_tag_data.h
new file mode 100644
index 0000000000000000000000000000000000000000..2809cd0f687d7c588ce4c005f9615ac0191831eb
--- /dev/null
+++ b/src/lib/sefht/validator_tag_data.h
@@ -0,0 +1,52 @@
+/*
+ * validator_tag_data.h
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#ifndef SEFHT_VALIDATOR_TAG_DATA_H
+#define SEFHT_VALIDATOR_TAG_DATA_H
+
+#if !defined (SEFHT_SEFHT_H_INSIDE) && !defined (SEFHT_COMPILATION)
+#error "Only <sefht/sefht.h> can be included directly."
+#endif
+
+enum TAG_TYPE
+{
+	TAG_T_VOID,
+	TAG_T_TEMPLATE,
+	TAG_T_RAW_TEXT,
+	TAG_T_ESC_RAW_TEXT,
+	TAG_T_FOREIGN,
+	TAG_T_NORMAL,
+};
+
+struct tag_info
+{
+	/*@only@*/ char * name;
+	enum TAG_TYPE type;
+};
+
+#define TAG_DATA                                                       \
+	/*@only@*/ struct tag_info * tags;                             \
+	size_t tag_n;                                                  \
+
+#endif /* SEFHT_VALIDATOR_TAG_DATA_H */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 158cbc51fabcd040cfcbc2f14dbeb72f2b1f1216..54d468b92eaed042e95a05866a6f934ed3fb91f8 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -19,6 +19,9 @@ check_PROGRAMS += sefht_node_fragment_test
 check_PROGRAMS += sefht_text_fragment_test
 check_PROGRAMS += sefht_text_test
 check_PROGRAMS += sefht_text_mark_test
+check_PROGRAMS += sefht_validator_test
+check_PROGRAMS += sefht_validator_tag_test
+check_PROGRAMS += sefht_validator_attr_test
 
 XFAIL_TESTS =
 XFAIL_TESTS += sefht_fragment_test
@@ -84,3 +87,16 @@ sefht_text_test_LDADD += $(LDADD)
 sefht_text_mark_test_SOURCES = test_text_mark.c
 sefht_text_mark_test_LDADD =
 sefht_text_mark_test_LDADD += $(LDADD)
+
+sefht_validator_test_SOURCES = test_validator.c
+sefht_validator_test_LDADD =
+sefht_validator_test_LDADD += $(OBJECT_PREFIX)validator.o
+sefht_validator_test_LDADD += $(LDADD)
+
+sefht_validator_tag_test_SOURCES = test_validator_tag.c
+sefht_validator_tag_test_LDADD =
+sefht_validator_tag_test_LDADD += $(LDADD)
+
+sefht_validator_attr_test_SOURCES = test_validator_attr.c
+sefht_validator_attr_test_LDADD =
+sefht_validator_attr_test_LDADD += $(LDADD)
diff --git a/tests/test_node_fragment.c b/tests/test_node_fragment.c
index 5d7f22f55d85944337ea87b8d6743069f72afb31..70673f2f59b00f704a79c8692930b29d402945f4 100644
--- a/tests/test_node_fragment.c
+++ b/tests/test_node_fragment.c
@@ -31,18 +31,37 @@
 
 #include "node_fragment.c"
 
+/* TODO: keep in sync with data.c
+ * TODO: remove */
+struct SH_Data
+{
+	SH_Validator * validator;
+};
 
 START_TEST(test_node_fragment_no_status)
 {
 	struct SH_NodeFragment * fragment;
 	SH_Data * data;
 	const char * tag = "tag";
+	const char * no_tag = "no_tag";
+	bool result;
 
+	/* setup */
 	data = SH_Data_new (NULL);
+	ck_assert_ptr_ne (NULL, data);
+
+	result = SH_Validator_register_tag (data->validator, tag,
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
 
+	/* test - invalid tag */
 	fragment = (struct SH_NodeFragment *)
-	           SH_NodeFragment_new (tag, data, NULL);
+	           SH_NodeFragment_new (no_tag, data, NULL);
+	ck_assert_ptr_eq (NULL, fragment);
 
+	/* test - valid tag */
+	fragment = (struct SH_NodeFragment *)
+	           SH_NodeFragment_new (tag, data, NULL);
 	ck_assert_ptr_ne (NULL, fragment);
 
 	ck_assert_ptr_eq (NULL, fragment->base.parent);
@@ -54,8 +73,8 @@ START_TEST(test_node_fragment_no_status)
 	ck_assert_int_eq (0, fragment->child_n);
 	ck_assert_int_eq (0, fragment->child_s);
 
+	/* cleanup */
 	SH_NodeFragment_free (fragment);
-
 	SH_Data_free (data);
 }
 END_TEST
@@ -66,13 +85,28 @@ START_TEST(test_node_fragment_with_status)
 	struct SH_NodeFragment * fragment;
 	SH_Data * data;
 	const char * tag = "tag";
+	const char * no_tag = "no_tag";
+	bool result;
 
+	/* setup */
 	data = SH_Data_new (NULL);
+	ck_assert_ptr_ne (NULL, data);
+
+	result = SH_Validator_register_tag (data->validator, tag,
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
 
+	/* test - invalid tag */
 	_status_preinit (status);
 	fragment = (struct SH_NodeFragment *)
-	           SH_NodeFragment_new (tag, data, &status);
+	           SH_NodeFragment_new (no_tag, data, &status);
+	ck_assert_ptr_eq (NULL, fragment);
+	ck_assert_int_eq (E_VALUE, status.status);
 
+	/* test - valid tag */
+	_status_preinit (status);
+	fragment = (struct SH_NodeFragment *)
+	           SH_NodeFragment_new (tag, data, &status);
 	ck_assert_ptr_ne (NULL, fragment);
 	ck_assert (succeed (&status));
 
@@ -85,8 +119,8 @@ START_TEST(test_node_fragment_with_status)
 	ck_assert_int_eq (0, fragment->child_n);
 	ck_assert_int_eq (0, fragment->child_s);
 
+	/* cleanup */
 	SH_NodeFragment_free (fragment);
-
 	SH_Data_free (data);
 }
 END_TEST
@@ -96,12 +130,25 @@ START_TEST(test_node_fragment_raw_no_status)
 	struct SH_NodeFragment * fragment;
 	SH_Data * data;
 	char * tag = strdup ("tag");
+	char * no_tag = strdup ("no_tag");
+	bool result;
 
+	/* setup */
 	data = SH_Data_new (NULL);
+	ck_assert_ptr_ne (NULL, data);
 
+	result = SH_Validator_register_tag (data->validator, tag,
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - invalid tag */
 	fragment = (struct SH_NodeFragment *)
-	           SH_NodeFragment_raw_new (tag, data, NULL);
+	           SH_NodeFragment_new (no_tag, data, NULL);
+	ck_assert_ptr_eq (NULL, fragment);
 
+	/* test - valid tag */
+	fragment = (struct SH_NodeFragment *)
+	           SH_NodeFragment_raw_new (tag, data, NULL);
 	ck_assert_ptr_ne (NULL, fragment);
 
 	ck_assert_ptr_eq (NULL, fragment->base.parent);
@@ -113,9 +160,10 @@ START_TEST(test_node_fragment_raw_no_status)
 	ck_assert_int_eq (0, fragment->child_n);
 	ck_assert_int_eq (0, fragment->child_s);
 
+	/* cleanup */
 	SH_NodeFragment_free (fragment);
-
 	SH_Data_free (data);
+	free (no_tag);
 }
 END_TEST
 
@@ -125,13 +173,28 @@ START_TEST(test_node_fragment_raw_with_status)
 	struct SH_NodeFragment * fragment;
 	SH_Data * data;
 	char * tag = strdup ("tag");
+	char * no_tag = strdup ("no_tag");
+	bool result;
 
+	/* setup */
 	data = SH_Data_new (NULL);
+	ck_assert_ptr_ne (NULL, data);
+
+	result = SH_Validator_register_tag (data->validator, tag,
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
 
+	/* test - invalid tag */
 	_status_preinit (status);
 	fragment = (struct SH_NodeFragment *)
-	           SH_NodeFragment_raw_new (tag, data, &status);
+	           SH_NodeFragment_raw_new (no_tag, data, &status);
+	ck_assert_ptr_eq (NULL, fragment);
+	ck_assert_int_eq (E_VALUE, status.status);
 
+	/* test - valid tag */
+	_status_preinit (status);
+	fragment = (struct SH_NodeFragment *)
+	           SH_NodeFragment_raw_new (tag, data, &status);
 	ck_assert_ptr_ne (NULL, fragment);
 	ck_assert (succeed (&status));
 
@@ -144,9 +207,10 @@ START_TEST(test_node_fragment_raw_with_status)
 	ck_assert_int_eq (0, fragment->child_n);
 	ck_assert_int_eq (0, fragment->child_s);
 
+	/* cleanup */
 	SH_NodeFragment_free (fragment);
-
 	SH_Data_free (data);
+	free (no_tag);
 }
 END_TEST
 
diff --git a/tests/test_validator.c b/tests/test_validator.c
index e1a9e318fd6e1f6885e316789efe81e535bc741f..024e9064ad3223b131677642fd337efe77382125 100644
--- a/tests/test_validator.c
+++ b/tests/test_validator.c
@@ -28,216 +28,104 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-/* test static method */
-#define static
-
-/* lower SIZE_MAX as we try to reach it */
-#include <limits.h>
-#define SIZE_MAX 10 * sizeof (struct tag_info)
 
 #include "macro.h"
 #include "status.h"
 
-/* C file is needed, because we wan't to override SIZE_MAX and static */
-#include "validator.c"
+#include "validator.h"
 
 
-START_TEST(test_validator)
+START_TEST(test_validator_no_status)
 {
-	struct SH_Status status;
 	struct SH_Validator * validator;
 
 	validator = SH_Validator_new (NULL);
-	ck_assert_int_ne ((long int) validator, (long int) NULL);
+	ck_assert_ptr_ne (NULL, validator);
 
 	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
 
 	_status_preinit (status);
 	validator = SH_Validator_new (&status);
-	ck_assert_int_ne ((long int) validator, (long int) NULL);
+	ck_assert_ptr_ne (NULL, validator);
 	ck_assert_int_eq (status.status, SUCCESS);
 
 	SH_Validator_free (validator);
 }
 END_TEST
 
-START_TEST(test_validator_tag)
+START_TEST(test_validator_copy_no_status)
 {
-	struct SH_Status status;
 	struct SH_Validator * validator;
-	const char * tag1 = "html";
-	const char * tag2 = "head";
-	const char * tag3 = "body";
-	const char * tag4 = "link";
-	const char * tag5 = "main";
-	const char * tag6 = "article";
-	char * tagN;
-	Tag tag;
-	Tag tag_return;
-	bool check;
+	struct SH_Validator * copy;
 
+	/* setup */
 	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
 
-	/* success without error */
-	tag = SH_Validator_register_tag (validator, tag1, NULL);
-	ck_assert_int_eq (tag, 1);
-
-	ck_assert_int_eq (validator->tag_n, 1);
-	ck_assert_int_eq (validator->last_tag, tag);
-
-	ck_assert_int_eq (validator->tags[1].data.id, tag);
-	ck_assert_str_eq (validator->tags[1].data.name, tag1);
-
-	tag_return = get_tag_id_by_name (validator, tag1);
-	ck_assert_int_eq (tag_return, tag);
-
-	/* retry without error */
-	tag_return = SH_Validator_register_tag (validator, tag1, NULL);
-	ck_assert_int_eq (tag_return, tag);
-
-	ck_assert_int_eq (validator->tag_n, 1);
-	ck_assert_int_eq (validator->last_tag, tag);
-
-	ck_assert_int_eq (validator->tags[1].data.id, tag);
-	ck_assert_str_eq (validator->tags[1].data.name, tag1);
-
-	/* fail without error */
-	/* make method fail by filling with garbage until
-	 * upper boundary is reached */
-
-	/* ensure enough space  inside string*/
-	/* log10 +1 = number length */
-	/* +3 "tag" */
-	/* +1 NULL */
-	/* = +5 */
-	tagN = calloc (((int) floor (log10 ((double) SIZE_MAX))) + 5,
-		       sizeof (char));
-
-	/* fill with garbage */
-	sprintf (tagN, "tag%lu", validator->tag_n);
-	while (SH_Validator_register_tag (validator, tagN, NULL) != TAG_ERR)
-	{
-		printf ("tag%lu\n", validator->tag_n);
-		sprintf (tagN, "tag%lu", validator->tag_n);
-	}
-
-	free (tagN);
-
-	tag = SH_Validator_register_tag (validator, tag2, NULL);
-	ck_assert_int_eq (tag, TAG_ERR);
-
-	ck_assert_int_eq (validator->tag_n, 9);
-
-	tag_return = get_tag_id_by_name (validator, tag2);
-	ck_assert_int_eq (tag_return, TAG_ERR);
-
-	/* fail2 without error */
-	validator->tag_n = 1;
-	validator->last_tag = TAG_MAX;
-
-	tag = SH_Validator_register_tag (validator, tag3, NULL);
-	ck_assert_int_eq (tag, TAG_ERR);
-
-	ck_assert_int_eq (validator->tag_n, 1);
-	ck_assert_int_eq (validator->last_tag, TAG_MAX);
-
-	tag_return = get_tag_id_by_name (validator, tag3);
-	ck_assert_int_eq (tag_return, TAG_ERR);
-
-	/* also free garbage created for overflow test */
-	validator->tag_n = 9;
-
-	/* check tag */
-	check = SH_Validator_check_tag (validator, tag1);
-	ck_assert_int_eq (check, TRUE);
-
-	check = SH_Validator_check_tag (validator, tag2);
-	ck_assert_int_eq (check, FALSE);
-
-	check = SH_Validator_check_tag (validator, tag3);
-	ck_assert_int_eq (check, FALSE);
+	/* test */
+	copy = SH_Validator_copy (validator, NULL);
+	ck_assert_ptr_ne (NULL, copy);
 
+	/* cleanup */
+	SH_Validator_free (copy);
 	SH_Validator_free (validator);
+}
+END_TEST
 
+START_TEST(test_validator_copy_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	struct SH_Validator * copy;
 
+	/* setup */
 	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
 
-	/* success with error */
+	/* test */
 	_status_preinit (status);
-	tag = SH_Validator_register_tag (validator, tag4, &status);
-	ck_assert_int_eq (tag, 1);
-	ck_assert_int_eq (status.status, SUCCESS);
-
-	ck_assert_int_eq (validator->tag_n, 1);
-	ck_assert_int_eq (validator->last_tag, tag);
-
-	ck_assert_int_eq (validator->tags[1].data.id, tag);
-	ck_assert_str_eq (validator->tags[1].data.name, tag4);
-
-	tag_return = get_tag_id_by_name (validator, tag4);
-	ck_assert_int_eq (tag_return, tag);
+	copy = SH_Validator_copy (validator, &status);
+	ck_assert_ptr_ne (NULL, copy);
+	ck_assert_int_eq (SUCCESS, status.status);
 
-	/* fail with error */
-	/* make method fail by filling with garbage until
-	 * upper boundary is reached */
-
-	/* ensure enough space  inside string*/
-	/* log10 +1 = number length */
-	/* +3 "tag" */
-	/* +1 NULL */
-	/* = +5 */
-	tagN = calloc (((int) floor (log10 ((double) SIZE_MAX))) + 5,
-		       sizeof (char));
-
-	/* fill with garbage */
-		sprintf (tagN, "tag%lu", validator->tag_n);
-	while (SH_Validator_register_tag (validator, tagN, NULL) != TAG_ERR)
-	{
-		printf ("tag%lu\n", validator->tag_n);
-		sprintf (tagN, "tag%lu", validator->tag_n);
-	}
-
-	free (tagN);
+	/* cleanup */
+	SH_Validator_free (copy);
+	SH_Validator_free (validator);
+}
+END_TEST
 
-	_status_preinit (status);
-	tag = SH_Validator_register_tag (validator, tag5, &status);
-	ck_assert_int_eq (tag, TAG_ERR);
-	ck_assert_int_eq (status.status, E_DOMAIN);
+START_TEST(test_validator_spec_no_status)
+{
+	struct SH_Validator * validator;
 
-	ck_assert_int_eq (validator->tag_n, 9);
+	/* test */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
 
-	tag_return = get_tag_id_by_name (validator, tag5);
-	ck_assert_int_eq (tag_return, TAG_ERR);
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
 
-	/* fail2 with error */
-	validator->tag_n = 1;
-	validator->last_tag = TAG_MAX;
+START_TEST(test_validator_spec_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
 
+	/* test */
 	_status_preinit (status);
-	tag = SH_Validator_register_tag (validator, tag6, &status);
-	ck_assert_int_eq (tag, TAG_ERR);
-	ck_assert_int_eq (status.status, E_DOMAIN);
-
-	ck_assert_int_eq (validator->tag_n, 1);
-	ck_assert_int_eq (validator->last_tag, TAG_MAX);
-
-	tag_return = get_tag_id_by_name (validator, tag6);
-	ck_assert_int_eq (tag_return, TAG_ERR);
-
-
-	/* check tag */
-	check = SH_Validator_check_tag (validator, tag4);
-	ck_assert_int_eq (check, TRUE);
-
-	check = SH_Validator_check_tag (validator, tag5);
-	ck_assert_int_eq (check, FALSE);
-
-	check = SH_Validator_check_tag (validator, tag6);
-	ck_assert_int_eq (check, FALSE);
-
-	/* also free garbage created for overflow test */
-	validator->tag_n = 9;
+	validator = SH_Validator_new_html5 (&status);
+	ck_assert_ptr_ne (NULL, validator);
+	ck_assert_int_eq (SUCCESS, status.status);
 
+	/* cleanup */
 	SH_Validator_free (validator);
 }
 END_TEST
@@ -252,8 +140,12 @@ Suite * validator_suite (void)
 	/* Core test case */
 	tc_core = tcase_create ("Core");
 
-	tcase_add_test (tc_core, test_validator);
-	tcase_add_test (tc_core, test_validator_tag);
+	tcase_add_test (tc_core, test_validator_no_status);
+	tcase_add_test (tc_core, test_validator_with_status);
+	tcase_add_test (tc_core, test_validator_copy_no_status);
+	tcase_add_test (tc_core, test_validator_copy_with_status);
+	tcase_add_test (tc_core, test_validator_spec_no_status);
+	tcase_add_test (tc_core, test_validator_spec_with_status);
 	suite_add_tcase (s, tc_core);
 
 	return s;
diff --git a/tests/test_validator_attr.c b/tests/test_validator_attr.c
new file mode 100644
index 0000000000000000000000000000000000000000..f18979118698b508f780d887be5e2e77dfeaf208
--- /dev/null
+++ b/tests/test_validator_attr.c
@@ -0,0 +1,1737 @@
+/*
+ * test_validator_attr.c
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#include <check.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+/* lower SIZE_MAX as we try to reach it */
+#include <limits.h>
+#undef SIZE_MAX
+#define SIZE_MAX 10 * sizeof (struct attr_info)
+
+#include "macro.h"
+#include "status.h"
+
+
+/* override HTML spec */
+#include "validator_html.h"
+
+const struct HTML_TAG_DEFINITION HTML_TAGS[] = {
+	{"html", SH_TAG_TYPE_NORMAL},
+	{"aside", SH_TAG_TYPE_NORMAL},
+	{"a", SH_TAG_TYPE_NORMAL},
+	{"p", SH_TAG_TYPE_NORMAL},
+};
+
+const struct HTML_ATTR_DEFINITION HTML_ATTRS[] = {
+	{"lang", &HTML_TAGS[0], 2},
+	{"href", &HTML_TAGS[2], 1},
+	{"class", &HTML_TAGS[3], 1},
+	{"id", NULL, 0},
+	{"class", NULL, 0},
+};
+
+#define HTML5_TAGS HTML_TAGS
+#define HTML5_ATTRS HTML_ATTRS
+
+
+/* C file is needed, because we want to override SIZE_MAX */
+#include "validator.c"
+
+
+START_TEST(test_validator_attr_no_status)
+{
+	struct SH_Validator * validator;
+
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	ck_assert_int_eq (0, validator->attr_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+
+	_status_preinit (status);
+	validator = SH_Validator_new (&status);
+	ck_assert_ptr_ne (NULL, validator);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	ck_assert_int_eq (0, validator->attr_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_copy_no_status)
+{
+	struct SH_Validator * validator;
+	struct SH_Validator * copy;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	result = SH_Validator_register_attr (validator, NULL, "id",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, "html", "lang",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, "p", "lang",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test */
+	copy = SH_Validator_copy (validator, NULL);
+	ck_assert_ptr_ne (NULL, copy);
+
+	ck_assert_ptr_ne (NULL, copy->attrs);
+	ck_assert_ptr_ne (validator->attrs, copy->attrs);
+	ck_assert_int_eq (validator->attr_n, copy->attr_n);
+
+	#define TEST_INT(I) ck_assert_int_eq (validator->I, copy->I);
+	#define TEST_PTR(P) ck_assert_ptr_eq (validator->P, copy->P);
+	#define TEST_STR(S) ck_assert_ptr_ne (validator->S, copy->S);  \
+	                    ck_assert_str_eq (validator->S, copy->S);
+
+	for (size_t index = 0; index < copy->attr_n; index++)
+	{
+		TEST_STR(attrs[index].name)
+
+		TEST_INT(attrs[index].tag_n)
+		for (size_t j = 0; j < copy->attrs[index].tag_n; j++)
+		{
+			TEST_PTR(attrs[index].tags[j].name)
+		}
+
+		if (0 == validator->attrs[index].tag_n)
+		{
+			TEST_PTR(attrs[index].tags);
+		}
+	}
+	#undef TEST_INT
+	#undef TEST_PTR
+	#undef TEST_STR
+
+	SH_Validator_free (copy);
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_copy_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	struct SH_Validator * copy;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	result = SH_Validator_register_attr (validator, NULL, "id",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, "html", "lang",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, "p", "lang",
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test */
+	_status_preinit (status);
+	copy = SH_Validator_copy (validator, &status);
+	ck_assert_ptr_ne (NULL, copy);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	ck_assert_ptr_ne (NULL, copy->attrs);
+	ck_assert_ptr_ne (validator->attrs, copy->attrs);
+	ck_assert_int_eq (validator->attr_n, copy->attr_n);
+
+	#define TEST_INT(I) ck_assert_int_eq (validator->I, copy->I);
+	#define TEST_PTR(P) ck_assert_ptr_eq (validator->P, copy->P);
+	#define TEST_STR(S) ck_assert_ptr_ne (validator->S, copy->S);  \
+	                    ck_assert_str_eq (validator->S, copy->S);
+
+	for (size_t index = 0; index < copy->attr_n; index++)
+	{
+		TEST_STR(attrs[index].name)
+
+		TEST_INT(attrs[index].tag_n)
+		for (size_t j = 0; j < copy->attrs[index].tag_n; j++)
+		{
+			TEST_PTR(attrs[index].tags[j].name)
+		}
+
+		if (0 == validator->attrs[index].tag_n)
+		{
+			TEST_PTR(attrs[index].tags);
+		}
+	}
+	#undef TEST_INT
+	#undef TEST_PTR
+	#undef TEST_STR
+
+	SH_Validator_free (copy);
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_spec_no_status)
+{
+	struct SH_Validator * validator;
+
+	/* test */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	ck_assert_ptr_ne (NULL, validator->attrs);
+	ck_assert_int_eq (4, validator->attr_n);
+
+	#define attrs validator->attrs
+
+	/* lang, 2 tags */
+	ck_assert_str_eq ("lang", HTML_ATTRS[0].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[0].attr, attrs[3].name);
+	ck_assert_str_eq (HTML_ATTRS[0].attr, attrs[3].name);
+	ck_assert_int_eq (HTML_ATTRS[0].tag_n, attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, attrs[3].tags);
+
+
+	#define TEST_STR(S1, S2) ck_assert_ptr_ne (S1, S2);            \
+	                         ck_assert_str_eq (S1, S2);
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (attrs[3].tags[0].name[0]
+	  > attrs[3].tags[1].name[0])
+	{
+		TEST_STR (HTML_TAGS[0].tag, attrs[3].tags[0].name);
+		TEST_STR (HTML_TAGS[1].tag, attrs[3].tags[1].name);
+	}
+	else
+	{
+		TEST_STR (HTML_TAGS[1].tag, attrs[3].tags[0].name);
+		TEST_STR (HTML_TAGS[0].tag, attrs[3].tags[1].name);
+	}
+	#undef TEST_STR
+
+	/* href, 1 tag */
+	ck_assert_str_eq ("href", HTML_ATTRS[1].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[1].attr, attrs[1].name);
+	ck_assert_str_eq (HTML_ATTRS[1].attr, attrs[1].name);
+	ck_assert_int_eq (HTML_ATTRS[1].tag_n, attrs[1].tag_n);
+	ck_assert_ptr_ne (NULL, attrs[1].tags);
+
+	ck_assert_ptr_ne (HTML_TAGS[2].tag, attrs[1].tags[0].name);
+	ck_assert_str_eq (HTML_TAGS[2].tag, attrs[1].tags[0].name);
+
+	/* class, global, overwrite */
+	ck_assert_str_eq ("class", HTML_ATTRS[2].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[2].attr, attrs[0].name);
+	ck_assert_str_eq (HTML_ATTRS[2].attr, attrs[0].name);
+	ck_assert_int_eq (0, attrs[0].tag_n);
+	ck_assert_ptr_eq (NULL, attrs[0].tags);
+
+	/* id, global */
+	ck_assert_str_eq ("id", HTML_ATTRS[3].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[3].attr, attrs[2].name);
+	ck_assert_str_eq (HTML_ATTRS[3].attr, attrs[2].name);
+	ck_assert_int_eq (0, attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, attrs[2].tags);
+
+	#undef attrs
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_spec_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+
+	/* test */
+	_status_preinit (status);
+	validator = SH_Validator_new_html5 (&status);
+	ck_assert_ptr_ne (NULL, validator);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_ptr_ne (NULL, validator->attrs);
+	ck_assert_int_eq (4, validator->attr_n);
+
+	#define attrs validator->attrs
+
+	/* lang, 2 tags */
+	ck_assert_str_eq ("lang", HTML_ATTRS[0].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[0].attr, attrs[3].name);
+	ck_assert_str_eq (HTML_ATTRS[0].attr, attrs[3].name);
+	ck_assert_int_eq (HTML_ATTRS[0].tag_n, attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, attrs[3].tags);
+
+
+	#define TEST_STR(S1, S2) ck_assert_ptr_ne (S1, S2);            \
+	                         ck_assert_str_eq (S1, S2);
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (attrs[3].tags[0].name[0]
+	  > attrs[3].tags[1].name[0])
+	{
+		TEST_STR (HTML_TAGS[0].tag, attrs[3].tags[0].name);
+		TEST_STR (HTML_TAGS[1].tag, attrs[3].tags[1].name);
+	}
+	else
+	{
+		TEST_STR (HTML_TAGS[1].tag, attrs[3].tags[0].name);
+		TEST_STR (HTML_TAGS[0].tag, attrs[3].tags[1].name);
+	}
+	#undef TEST_STR
+
+	/* href, 1 tag */
+	ck_assert_str_eq ("href", HTML_ATTRS[1].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[1].attr, attrs[1].name);
+	ck_assert_str_eq (HTML_ATTRS[1].attr, attrs[1].name);
+	ck_assert_int_eq (HTML_ATTRS[1].tag_n, attrs[1].tag_n);
+	ck_assert_ptr_ne (NULL, attrs[1].tags);
+
+	ck_assert_ptr_ne (HTML_TAGS[2].tag, attrs[1].tags[0].name);
+	ck_assert_str_eq (HTML_TAGS[2].tag, attrs[1].tags[0].name);
+
+	/* class, global, overwrite */
+	ck_assert_str_eq ("class", HTML_ATTRS[2].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[2].attr, attrs[0].name);
+	ck_assert_str_eq (HTML_ATTRS[2].attr, attrs[0].name);
+	ck_assert_int_eq (0, attrs[0].tag_n);
+	ck_assert_ptr_eq (NULL, attrs[0].tags);
+
+	/* id, global */
+	ck_assert_str_eq ("id", HTML_ATTRS[3].attr);
+	ck_assert_ptr_ne (HTML_ATTRS[3].attr, attrs[2].name);
+	ck_assert_str_eq (HTML_ATTRS[3].attr, attrs[2].name);
+	ck_assert_int_eq (0, attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, attrs[2].tags);
+
+	#undef attrs
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_register_no_status)
+{
+	struct SH_Validator * validator;
+	const char * tag1 = "html";
+	const char * tag2 = "form";
+	const char * tag3 = "body";
+	const char * tag4 = "nav";
+	const char * tag5 = "aside";
+	const char * notag = "foobarbaz"; // mustn't be registered
+	const char * attr1 = "id";
+	const char * attr2 = "class";
+	const char * attr3 = "name";
+	const char * attr4 = "src";
+	const char * attr5 = "content";
+	const char * attr6 = "lang";
+	char * attrN;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag2, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag3, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag4, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag5, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	/* test - register */
+	result = SH_Validator_register_attr (validator, tag1, attr1,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_ptr_ne (tag1, validator->attrs[0].tags[0].name);
+	ck_assert_str_eq (tag1, validator->attrs[0].tags[0].name);
+
+	/* test - duplicate registration */
+	result = SH_Validator_register_attr (validator, tag1, attr1,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_ptr_ne (tag1, validator->attrs[0].tags[0].name);
+	ck_assert_str_eq (tag1, validator->attrs[0].tags[0].name);
+
+	/* test - register #2 */
+	result = SH_Validator_register_attr (validator, tag2, attr1,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	/* test - register unknown tag */
+	result = SH_Validator_register_attr (validator, notag, attr1,
+	                                     NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	#define TEST_STR(S1, S2) ck_assert_ptr_ne (S1, S2);            \
+	                         ck_assert_str_eq (S1, S2);
+
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		TEST_STR(tag1, validator->attrs[0].tags[0].name)
+		TEST_STR(tag2, validator->attrs[0].tags[1].name)
+	}
+	else
+	{
+		TEST_STR(tag2, validator->attrs[0].tags[0].name)
+		TEST_STR(tag1, validator->attrs[0].tags[1].name)
+	}
+
+	#undef TEST_STR
+
+	/* test - order (attr) */
+	result = SH_Validator_register_attr (validator, tag1, attr3,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, tag1, attr4,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, tag1, attr5,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr3, validator->attrs[2].name);
+	ck_assert_str_eq (attr4, validator->attrs[3].name);
+
+	/* test - order (tag) */
+	result = SH_Validator_register_attr (validator, tag2, attr4,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, tag3, attr4,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, tag4, attr4,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_attr (validator, tag5, attr4,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	#define attr validator->attrs[3]
+	ck_assert_int_eq (5, attr.tag_n);
+	for (size_t i = 0; i < attr.tag_n-1; i++)
+	{
+		ck_assert_int_lt ((size_t)attr.tags[i].name,
+		                  (size_t)attr.tags[i+1].name);
+	}
+	#undef attr
+
+	/* test - change to global attr */
+	result = SH_Validator_register_attr (validator, NULL, attr1,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr3, validator->attrs[2].name);
+	ck_assert_str_eq (attr4, validator->attrs[3].name);
+
+	ck_assert_int_eq (0, validator->attrs[1].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[1].tags);
+
+	/* test - new global attr */
+	result = SH_Validator_register_attr (validator, NULL, attr6,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (5, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr6, validator->attrs[2].name);
+	ck_assert_str_eq (attr3, validator->attrs[3].name);
+	ck_assert_str_eq (attr4, validator->attrs[4].name);
+
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+
+	/* test - existing global attr */
+	result = SH_Validator_register_attr (validator, tag1, attr6,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (5, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr6, validator->attrs[2].name);
+	ck_assert_str_eq (attr3, validator->attrs[3].name);
+	ck_assert_str_eq (attr4, validator->attrs[4].name);
+
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+
+	/* test - overflow detection */
+	/* make method fail by filling with garbage until
+	 * upper boundary is reached */
+
+	/* ensure enough space  inside string*/
+	/* log10 +1 = number length */
+	/* +4 "attr" */
+	/* +1 '\0' */
+	/* = +5 */
+	attrN = malloc (((int) floor (log10 ((double) SIZE_MAX))) + 6);
+	ck_assert_ptr_ne (NULL, attrN);
+
+	/* fill with garbage */
+	do
+	{
+		sprintf (attrN, "attr%zu", validator->attr_n);
+	}
+	while (SH_Validator_register_attr (validator, tag1, attrN, NULL));
+
+	free (attrN);
+
+	/* test overflow */
+	result = SH_Validator_register_attr (validator, tag1, attr2,
+	                                     NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	ck_assert_int_eq (10, validator->attr_n);
+
+	/* cleanup */
+	/* also free garbage created for overflow test */
+	validator->attr_n = 10;
+	SH_Validator_free (validator);
+}
+
+START_TEST(test_validator_attr_register_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	const char * tag1 = "html";
+	const char * tag2 = "form";
+	const char * tag3 = "body";
+	const char * tag4 = "nav";
+	const char * tag5 = "aside";
+	const char * notag = "foobarbaz"; // mustn't be registered
+	const char * attr1 = "id";
+	const char * attr2 = "class";
+	const char * attr3 = "name";
+	const char * attr4 = "src";
+	const char * attr5 = "content";
+	const char * attr6 = "lang";
+	char * attrN;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag2, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag3, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag4, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag5, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	/* test - register */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr1,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_ptr_ne (tag1, validator->attrs[0].tags[0].name);
+	ck_assert_str_eq (tag1, validator->attrs[0].tags[0].name);
+
+	/* test - duplicate registration */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr1,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_ptr_ne (tag1, validator->attrs[0].tags[0].name);
+	ck_assert_str_eq (tag1, validator->attrs[0].tags[0].name);
+
+	/* test - register #2 */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag2, attr1,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	/* test - register unknown tag */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, notag, attr1,
+	                                     &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_STATE, status.status);
+
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_ptr_ne (attr1, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[0].name);
+
+	#define TEST_STR(S1, S2) ck_assert_ptr_ne (S1, S2);            \
+	                         ck_assert_str_eq (S1, S2);
+
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		TEST_STR(tag1, validator->attrs[0].tags[0].name)
+		TEST_STR(tag2, validator->attrs[0].tags[1].name)
+	}
+	else
+	{
+		TEST_STR(tag2, validator->attrs[0].tags[0].name)
+		TEST_STR(tag1, validator->attrs[0].tags[1].name)
+	}
+
+	#undef TEST_STR
+
+	/* test - order (attr) */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr3,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr4,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr5,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr3, validator->attrs[2].name);
+	ck_assert_str_eq (attr4, validator->attrs[3].name);
+
+	/* test - order (tag) */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag2, attr4,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag3, attr4,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag4, attr4,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag5, attr4,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (5, validator->attrs[3].tag_n);
+
+	#define attr validator->attrs[3]
+	ck_assert_int_eq (5, attr.tag_n);
+	for (size_t i = 0; i < attr.tag_n-1; i++)
+	{
+		ck_assert_int_lt ((size_t)attr.tags[i].name,
+		                  (size_t)attr.tags[i+1].name);
+	}
+	#undef attr
+
+	/* test - change to global attr */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, NULL, attr1,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr3, validator->attrs[2].name);
+	ck_assert_str_eq (attr4, validator->attrs[3].name);
+
+	ck_assert_int_eq (0, validator->attrs[1].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[1].tags);
+
+	/* test - new global attr */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, NULL, attr6,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (5, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr6, validator->attrs[2].name);
+	ck_assert_str_eq (attr3, validator->attrs[3].name);
+	ck_assert_str_eq (attr4, validator->attrs[4].name);
+
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+
+	/* test - existing global attr */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr6,
+	                                     &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (5, validator->attr_n);
+	ck_assert_str_eq (attr5, validator->attrs[0].name);
+	ck_assert_str_eq (attr1, validator->attrs[1].name);
+	ck_assert_str_eq (attr6, validator->attrs[2].name);
+	ck_assert_str_eq (attr3, validator->attrs[3].name);
+	ck_assert_str_eq (attr4, validator->attrs[4].name);
+
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+
+	/* test - overflow detection */
+	/* make method fail by filling with garbage until
+	 * upper boundary is reached */
+
+	/* ensure enough space  inside string*/
+	/* log10 +1 = number length */
+	/* +4 "attr" */
+	/* +1 '\0' */
+	/* = +5 */
+	attrN = malloc (((int) floor (log10 ((double) SIZE_MAX))) + 6);
+	ck_assert_ptr_ne (NULL, attrN);
+
+	/* fill with garbage */
+	do
+	{
+		sprintf (attrN, "attr%zu", validator->attr_n);
+	}
+	while (SH_Validator_register_attr (validator, tag1, attrN, NULL));
+
+	free (attrN);
+
+	/* test overflow */
+	_status_preinit (status);
+	result = SH_Validator_register_attr (validator, tag1, attr2,
+	                                     &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_DOMAIN, status.status);
+
+	ck_assert_int_eq (10, validator->attr_n);
+
+	/* cleanup */
+	/* also free garbage created for overflow test */
+	validator->attr_n = 10;
+	SH_Validator_free (validator);
+}
+
+START_TEST(test_validator_attr_deregister_no_status)
+{
+	struct SH_Validator * validator;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, "html", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, "body", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	#define REGISTER SH_Validator_register_attr
+	result = REGISTER (validator, "html", "attr", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "name", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "attr", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "name", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "v", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "w", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "x", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "y", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "z", NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef REGISTER
+
+	/* test - consistency */
+	ck_assert_int_eq (9, validator->attr_n);
+	ck_assert_str_eq ("attr", validator->attrs[0].name);
+	ck_assert_str_eq ("class", validator->attrs[1].name);
+	ck_assert_str_eq ("id", validator->attrs[2].name);
+	ck_assert_str_eq ("name", validator->attrs[3].name);
+	ck_assert_str_eq ("v", validator->attrs[4].name);
+	ck_assert_str_eq ("w", validator->attrs[5].name);
+	ck_assert_str_eq ("x", validator->attrs[6].name);
+	ck_assert_str_eq ("y", validator->attrs[7].name);
+	ck_assert_str_eq ("z", validator->attrs[8].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (2, validator->attrs[2].tag_n);
+	ck_assert_int_eq (2, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_int_eq (0, validator->attrs[7].tag_n);
+	ck_assert_int_eq (0, validator->attrs[8].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[7].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[8].tags);
+
+	#define STR_EQ ck_assert_str_eq
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		STR_EQ ("html", validator->attrs[0].tags[0].name);
+		STR_EQ ("html", validator->attrs[1].tags[0].name);
+		STR_EQ ("html", validator->attrs[2].tags[0].name);
+		STR_EQ ("html", validator->attrs[3].tags[0].name);
+		STR_EQ ("body", validator->attrs[0].tags[1].name);
+		STR_EQ ("body", validator->attrs[1].tags[1].name);
+		STR_EQ ("body", validator->attrs[2].tags[1].name);
+		STR_EQ ("body", validator->attrs[3].tags[1].name);
+	}
+	else
+	{
+		STR_EQ ("html", validator->attrs[0].tags[1].name);
+		STR_EQ ("html", validator->attrs[1].tags[1].name);
+		STR_EQ ("html", validator->attrs[2].tags[1].name);
+		STR_EQ ("html", validator->attrs[3].tags[1].name);
+		STR_EQ ("body", validator->attrs[0].tags[0].name);
+		STR_EQ ("body", validator->attrs[1].tags[0].name);
+		STR_EQ ("body", validator->attrs[2].tags[0].name);
+		STR_EQ ("body", validator->attrs[3].tags[0].name);
+	}
+	#undef STR_EQ
+
+
+	/* test - existent attr */
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - non existent tag */
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - existent attr, auto remove */
+	result = SH_Validator_deregister_attr (validator, "body", "attr",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (8, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("name", validator->attrs[2].name);
+	ck_assert_str_eq ("v", validator->attrs[3].name);
+	ck_assert_str_eq ("w", validator->attrs[4].name);
+	ck_assert_str_eq ("x", validator->attrs[5].name);
+	ck_assert_str_eq ("y", validator->attrs[6].name);
+	ck_assert_str_eq ("z", validator->attrs[7].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (2, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_int_eq (0, validator->attrs[7].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[7].tags);
+
+
+	/* test - non existent attr */
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - existent attr, total remove */
+	result = SH_Validator_deregister_attr (validator, NULL, "name",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - non existent attr, total remove */
+	result = SH_Validator_deregister_attr (validator, NULL, "attr",
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (7, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("w", validator->attrs[3].name);
+	ck_assert_str_eq ("x", validator->attrs[4].name);
+	ck_assert_str_eq ("y", validator->attrs[5].name);
+	ck_assert_str_eq ("z", validator->attrs[6].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+
+
+	#define STR_EQ ck_assert_str_eq
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		STR_EQ ("html", validator->attrs[0].tags[0].name);
+		STR_EQ ("html", validator->attrs[1].tags[0].name);
+		STR_EQ ("body", validator->attrs[0].tags[1].name);
+		STR_EQ ("body", validator->attrs[1].tags[1].name);
+	}
+	else
+	{
+		STR_EQ ("html", validator->attrs[0].tags[1].name);
+		STR_EQ ("html", validator->attrs[1].tags[1].name);
+		STR_EQ ("body", validator->attrs[0].tags[0].name);
+		STR_EQ ("body", validator->attrs[1].tags[0].name);
+	}
+	#undef STR_EQ
+
+
+	/* test - existent tag, total remove */
+	result = SH_Validator_deregister_attr (validator, "html", NULL,
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - non existent tag, total remove */
+	result = SH_Validator_deregister_attr (validator, "nav", NULL,
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (7, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("w", validator->attrs[3].name);
+	ck_assert_str_eq ("x", validator->attrs[4].name);
+	ck_assert_str_eq ("y", validator->attrs[5].name);
+	ck_assert_str_eq ("z", validator->attrs[6].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+
+	/* test - global attr, to local */
+	result = SH_Validator_deregister_attr (validator, "html", "v",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - global attr, non existent tag */
+	result = SH_Validator_deregister_attr (validator, "nav", "w",
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - global attr, total remove */
+	result = SH_Validator_deregister_attr (validator, NULL, "w",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (6, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("x", validator->attrs[3].name);
+	ck_assert_str_eq ("y", validator->attrs[4].name);
+	ck_assert_str_eq ("z", validator->attrs[5].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (1, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[2].tags[0].name);
+
+	/* test - global attr, auto remove */
+	result = SH_Validator_deregister_tag (validator, "html", NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_deregister_attr (validator, "body", "x",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - global attr, non existent tag */
+	result = SH_Validator_deregister_attr (validator, "nav", "y",
+	                                       NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - global attr, total remove */
+	result = SH_Validator_deregister_attr (validator, NULL, "y",
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("z", validator->attrs[3].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (1, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[2].tags[0].name);
+
+	/* test - last tag, auto remove local attrs */
+	result = SH_Validator_deregister_tag (validator, "body", NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_str_eq ("z", validator->attrs[0].name);
+	ck_assert_int_eq (0, validator->attrs[0].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[0].tags);
+
+	/* undo auto remove */
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, "body", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	#define REGISTER SH_Validator_register_attr
+	result = REGISTER (validator, "body", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "v", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "z", NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef REGISTER
+
+	/* test - consistency */
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("z", validator->attrs[3].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+
+	/* test - total clear */
+	result = SH_Validator_deregister_attr (validator, NULL, NULL,
+	                                       NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (0, validator->attr_n);
+
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_deregister_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, "html", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, "body", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	#define REGISTER SH_Validator_register_attr
+	result = REGISTER (validator, "html", "attr", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "name", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "html", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "attr", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "name", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "v", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "w", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "x", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "y", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "z", NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef REGISTER
+
+	/* test - consistency */
+	ck_assert_int_eq (9, validator->attr_n);
+	ck_assert_str_eq ("attr", validator->attrs[0].name);
+	ck_assert_str_eq ("class", validator->attrs[1].name);
+	ck_assert_str_eq ("id", validator->attrs[2].name);
+	ck_assert_str_eq ("name", validator->attrs[3].name);
+	ck_assert_str_eq ("v", validator->attrs[4].name);
+	ck_assert_str_eq ("w", validator->attrs[5].name);
+	ck_assert_str_eq ("x", validator->attrs[6].name);
+	ck_assert_str_eq ("y", validator->attrs[7].name);
+	ck_assert_str_eq ("z", validator->attrs[8].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (2, validator->attrs[2].tag_n);
+	ck_assert_int_eq (2, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_int_eq (0, validator->attrs[7].tag_n);
+	ck_assert_int_eq (0, validator->attrs[8].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[7].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[8].tags);
+
+	#define STR_EQ ck_assert_str_eq
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		STR_EQ ("html", validator->attrs[0].tags[0].name);
+		STR_EQ ("html", validator->attrs[1].tags[0].name);
+		STR_EQ ("html", validator->attrs[2].tags[0].name);
+		STR_EQ ("html", validator->attrs[3].tags[0].name);
+		STR_EQ ("body", validator->attrs[0].tags[1].name);
+		STR_EQ ("body", validator->attrs[1].tags[1].name);
+		STR_EQ ("body", validator->attrs[2].tags[1].name);
+		STR_EQ ("body", validator->attrs[3].tags[1].name);
+	}
+	else
+	{
+		STR_EQ ("html", validator->attrs[0].tags[1].name);
+		STR_EQ ("html", validator->attrs[1].tags[1].name);
+		STR_EQ ("html", validator->attrs[2].tags[1].name);
+		STR_EQ ("html", validator->attrs[3].tags[1].name);
+		STR_EQ ("body", validator->attrs[0].tags[0].name);
+		STR_EQ ("body", validator->attrs[1].tags[0].name);
+		STR_EQ ("body", validator->attrs[2].tags[0].name);
+		STR_EQ ("body", validator->attrs[3].tags[0].name);
+	}
+	#undef STR_EQ
+
+
+	/* test - existent attr */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - non existent tag */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - existent attr, auto remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "body", "attr",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (8, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("name", validator->attrs[2].name);
+	ck_assert_str_eq ("v", validator->attrs[3].name);
+	ck_assert_str_eq ("w", validator->attrs[4].name);
+	ck_assert_str_eq ("x", validator->attrs[5].name);
+	ck_assert_str_eq ("y", validator->attrs[6].name);
+	ck_assert_str_eq ("z", validator->attrs[7].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (2, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_int_eq (0, validator->attrs[7].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[7].tags);
+
+
+	/* test - non existent attr */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "html", "attr",
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - existent attr, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, NULL, "name",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - non existent attr, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, NULL, "attr",
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (7, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("w", validator->attrs[3].name);
+	ck_assert_str_eq ("x", validator->attrs[4].name);
+	ck_assert_str_eq ("y", validator->attrs[5].name);
+	ck_assert_str_eq ("z", validator->attrs[6].name);
+	ck_assert_int_eq (2, validator->attrs[0].tag_n);
+	ck_assert_int_eq (2, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_int_eq (0, validator->attrs[6].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+
+
+	#define STR_EQ ck_assert_str_eq
+	/* The storage order depends on the relative position of memory,
+	 * allocated by different malloc calls. This can change and
+	 * thus must be determined. */
+	if (validator->attrs[0].tags[0].name[0]
+	  > validator->attrs[0].tags[1].name[0])
+	{
+		STR_EQ ("html", validator->attrs[0].tags[0].name);
+		STR_EQ ("html", validator->attrs[1].tags[0].name);
+		STR_EQ ("body", validator->attrs[0].tags[1].name);
+		STR_EQ ("body", validator->attrs[1].tags[1].name);
+	}
+	else
+	{
+		STR_EQ ("html", validator->attrs[0].tags[1].name);
+		STR_EQ ("html", validator->attrs[1].tags[1].name);
+		STR_EQ ("body", validator->attrs[0].tags[0].name);
+		STR_EQ ("body", validator->attrs[1].tags[0].name);
+	}
+	#undef STR_EQ
+
+
+	/* test - existent tag, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "html", NULL,
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - non existent tag, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "nav", NULL,
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (7, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("w", validator->attrs[3].name);
+	ck_assert_str_eq ("x", validator->attrs[4].name);
+	ck_assert_str_eq ("y", validator->attrs[5].name);
+	ck_assert_str_eq ("z", validator->attrs[6].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[6].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+
+	/* test - global attr, to local */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "html", "v",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - global attr, non existent tag */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "nav", "w",
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - global attr, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, NULL, "w",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (6, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("x", validator->attrs[3].name);
+	ck_assert_str_eq ("y", validator->attrs[4].name);
+	ck_assert_str_eq ("z", validator->attrs[5].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (1, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_int_eq (0, validator->attrs[4].tag_n);
+	ck_assert_int_eq (0, validator->attrs[5].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[4].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[5].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[2].tags[0].name);
+
+	/* test - global attr, auto remove */
+	result = SH_Validator_deregister_tag (validator, "html", NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "body", "x",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - global attr, non existent tag */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, "nav", "y",
+	                                       &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - global attr, total remove */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, NULL, "y",
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("z", validator->attrs[3].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (1, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[2].tags[0].name);
+
+	/* test - last tag, auto remove local attrs */
+	result = SH_Validator_deregister_tag (validator, "body", NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (1, validator->attr_n);
+	ck_assert_str_eq ("z", validator->attrs[0].name);
+	ck_assert_int_eq (0, validator->attrs[0].tag_n);
+	ck_assert_ptr_eq (NULL, validator->attrs[0].tags);
+
+	/* undo auto remove */
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, "body", V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	#define REGISTER SH_Validator_register_attr
+	result = REGISTER (validator, "body", "id", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, "body", "class", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "v", NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, "z", NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef REGISTER
+
+	/* test - consistency */
+	ck_assert_int_eq (4, validator->attr_n);
+	ck_assert_str_eq ("class", validator->attrs[0].name);
+	ck_assert_str_eq ("id", validator->attrs[1].name);
+	ck_assert_str_eq ("v", validator->attrs[2].name);
+	ck_assert_str_eq ("z", validator->attrs[3].name);
+	ck_assert_int_eq (1, validator->attrs[0].tag_n);
+	ck_assert_int_eq (1, validator->attrs[1].tag_n);
+	ck_assert_int_eq (0, validator->attrs[2].tag_n);
+	ck_assert_int_eq (0, validator->attrs[3].tag_n);
+	ck_assert_ptr_ne (NULL, validator->attrs[0].tags);
+	ck_assert_ptr_ne (NULL, validator->attrs[1].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[2].tags);
+	ck_assert_ptr_eq (NULL, validator->attrs[3].tags);
+	ck_assert_str_eq ("body", validator->attrs[0].tags[0].name);
+	ck_assert_str_eq ("body", validator->attrs[1].tags[0].name);
+
+	/* test - total clear */
+	_status_preinit (status);
+	result = SH_Validator_deregister_attr (validator, NULL, NULL,
+	                                       &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (0, validator->attr_n);
+
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_attr_check)
+{
+	struct SH_Validator * validator;
+	const char * tag_1 = "html";
+	const char * tag_2 = "head";
+	const char * notag = "body";
+	const char * attr_1  = "type";
+	const char * attr_2  = "class";
+	const char * attr_g  = "id";
+	const char * noattr  = "name";
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag_1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag_2, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	#define REGISTER SH_Validator_register_attr
+	result = REGISTER (validator, tag_1, attr_1, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, tag_2, attr_2, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = REGISTER (validator, NULL, attr_g, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef REGISTER
+
+	/* test */
+	result = SH_Validator_check_attr (validator, tag_1, attr_1);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_attr (validator, tag_1, attr_2);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, tag_2, noattr);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, notag, attr_1);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, notag, noattr);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, NULL, attr_1);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_attr (validator, NULL, noattr);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, tag_1, attr_g);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_attr (validator, notag, attr_g);
+	ck_assert_int_eq (FALSE, result);
+
+	result = SH_Validator_check_attr (validator, NULL, attr_g);
+	ck_assert_int_eq (TRUE, result);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+Suite * test_suite (void)
+{
+	Suite *s;
+	TCase *tc_core;
+
+	s = suite_create ("Testsuite SeFHT Validator Attr");
+
+	/* Core test case */
+	tc_core = tcase_create ("Core");
+
+	tcase_add_test (tc_core, test_validator_attr_no_status);
+	tcase_add_test (tc_core, test_validator_attr_with_status);
+	tcase_add_test (tc_core, test_validator_attr_copy_no_status);
+	tcase_add_test (tc_core, test_validator_attr_copy_with_status);
+	tcase_add_test (tc_core, test_validator_attr_spec_no_status);
+	tcase_add_test (tc_core, test_validator_attr_spec_with_status);
+	tcase_add_test (tc_core, test_validator_attr_register_no_status);
+	tcase_add_test (tc_core, test_validator_attr_register_with_status);
+	tcase_add_test (tc_core, test_validator_attr_deregister_no_status);
+	tcase_add_test (tc_core, test_validator_attr_deregister_with_status);
+	tcase_add_test (tc_core, test_validator_attr_check);
+	suite_add_tcase (s, tc_core);
+
+	return s;
+}
+
+int main (void)
+{
+	int number_failed;
+	Suite *s;
+	SRunner *sr;
+
+	s = test_suite ();
+	sr = srunner_create (s);
+
+	srunner_run_all (sr, CK_NORMAL);
+	number_failed = srunner_ntests_failed (sr);
+	srunner_free (sr);
+
+	return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/tests/test_validator_tag.c b/tests/test_validator_tag.c
new file mode 100644
index 0000000000000000000000000000000000000000..61d9c5f0660ece201767b2e70095fa32f313da65
--- /dev/null
+++ b/tests/test_validator_tag.c
@@ -0,0 +1,732 @@
+/*
+ * test_validator_tag.c
+ *
+ * Copyright 2023 Jonathan Schöbel <jonathan@xn--schbel-yxa.info>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+
+#include <check.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+
+/* lower SIZE_MAX as we try to reach it */
+#include <limits.h>
+#undef SIZE_MAX
+#define SIZE_MAX 10 * sizeof (struct tag_info)
+
+#include "macro.h"
+#include "status.h"
+
+/* override HTML spec */
+#include "validator_html.h"
+const struct HTML_TAG_DEFINITION HTML_TAGS[] = {
+	{"html", SH_TAG_TYPE_NORMAL},
+	{"aside", SH_TAG_TYPE_ESC_RAW_TEXT},
+	{"html", SH_TAG_TYPE_VOID},
+	{"body", SH_TAG_TYPE_TEMPLATE}
+};
+#define HTML5_TAGS HTML_TAGS
+
+/* C file is needed, because we want to override SIZE_MAX */
+#include "validator.c"
+
+
+START_TEST(test_validator_tag_no_status)
+{
+	struct SH_Validator * validator;
+
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	ck_assert_int_eq (0, validator->tag_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+
+	_status_preinit (status);
+	validator = SH_Validator_new (&status);
+	ck_assert_ptr_ne (NULL, validator);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	ck_assert_int_eq (0, validator->tag_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_copy_no_status)
+{
+	struct SH_Validator * validator;
+	struct SH_Validator * copy;
+
+	/* setup */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	/* test */
+	copy = SH_Validator_copy (validator, NULL);
+	ck_assert_ptr_ne (NULL, copy);
+
+	ck_assert_ptr_ne (NULL, copy->tags);
+	ck_assert_ptr_ne (validator->tags, copy->tags);
+	ck_assert_int_eq (validator->tag_n, copy->tag_n);
+
+	#define TEST_STR(S) ck_assert_ptr_ne (validator->S, copy->S);  \
+	                    ck_assert_str_eq (validator->S, copy->S);
+	#define TEST_INT(I) ck_assert_int_eq (validator->I, copy->I);
+
+	for (size_t index = 0; index < copy->tag_n; index++)
+	{
+		TEST_STR (tags[index].name);
+		TEST_INT (tags[index].type);
+	}
+
+	#undef TEST_STR
+
+	SH_Validator_free (copy);
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_copy_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	struct SH_Validator * copy;
+
+	/* setup */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	/* test */
+	_status_preinit (status);
+	copy = SH_Validator_copy (validator, &status);
+	ck_assert_ptr_ne (NULL, copy);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	ck_assert_ptr_ne (NULL, copy->tags);
+	ck_assert_ptr_ne (validator->tags, copy->tags);
+	ck_assert_int_eq (validator->tag_n, copy->tag_n);
+
+	#define TEST_STR(S) ck_assert_ptr_ne (validator->S, copy->S);  \
+	                    ck_assert_str_eq (validator->S, copy->S);
+	#define TEST_INT(I) ck_assert_int_eq (validator->I, copy->I);
+
+	for (size_t index = 0; index < copy->tag_n; index++)
+	{
+		TEST_STR (tags[index].name);
+		TEST_INT (tags[index].type);
+	}
+
+	#undef TEST_STR
+
+	SH_Validator_free (copy);
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_spec_no_status)
+{
+	struct SH_Validator * validator;
+
+	/* test */
+	validator = SH_Validator_new_html5 (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	ck_assert_ptr_ne (NULL, validator->tags);
+	ck_assert_int_eq (3, validator->tag_n);
+
+	ck_assert_ptr_ne (HTML_TAGS[1].tag, validator->tags[0].name);
+	ck_assert_str_eq (HTML_TAGS[1].tag, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_ESC_RAW_TEXT, validator->tags[0].type);
+
+	ck_assert_ptr_ne (HTML_TAGS[3].tag, validator->tags[1].name);
+	ck_assert_str_eq (HTML_TAGS[3].tag, validator->tags[1].name);
+	ck_assert_int_eq (TAG_T_TEMPLATE, validator->tags[1].type);
+
+	ck_assert_ptr_ne (HTML_TAGS[0].tag, validator->tags[2].name);
+	ck_assert_str_eq (HTML_TAGS[0].tag, validator->tags[2].name);
+	ck_assert_int_eq (TAG_T_NORMAL, validator->tags[2].type);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_spec_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+
+	/* test */
+	_status_preinit (status);
+	validator = SH_Validator_new_html5 (&status);
+	ck_assert_ptr_ne (NULL, validator);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_ptr_ne (NULL, validator->tags);
+	ck_assert_int_eq (3, validator->tag_n);
+
+	ck_assert_ptr_ne (HTML_TAGS[1].tag, validator->tags[0].name);
+	ck_assert_str_eq (HTML_TAGS[1].tag, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_ESC_RAW_TEXT, validator->tags[0].type);
+
+	ck_assert_ptr_ne (HTML_TAGS[3].tag, validator->tags[1].name);
+	ck_assert_str_eq (HTML_TAGS[3].tag, validator->tags[1].name);
+	ck_assert_int_eq (TAG_T_TEMPLATE, validator->tags[1].type);
+
+	ck_assert_ptr_ne (HTML_TAGS[0].tag, validator->tags[2].name);
+	ck_assert_str_eq (HTML_TAGS[0].tag, validator->tags[2].name);
+	ck_assert_int_eq (TAG_T_NORMAL, validator->tags[2].type);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_register_no_status)
+{
+	struct SH_Validator * validator;
+	const char * tag1 = "html";
+	const char * tag2 = "head";
+	const char * tag3 = "article";
+	const char * tag4 = "p";
+	const char * tag5 = "img";
+	char * tagN;
+	bool result;
+
+	#define V SH_TAG_TYPE_VOID
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	/* test - register */
+	result = SH_Validator_register_tag (validator, tag1,
+	                                    SH_TAG_TYPE_VOID, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_ptr_ne (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_VOID, validator->tags[0].type);
+
+	/* test - invalid type */
+	result = SH_Validator_register_tag (validator, tag2, -1, NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - duplicate registration */
+	result = SH_Validator_register_tag (validator, tag1,
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_ptr_ne (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_NORMAL, validator->tags[0].type);
+
+	/* test - duplicate invalid type */
+	result = SH_Validator_register_tag (validator, tag1, 20, NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - order */
+	result = SH_Validator_register_tag (validator, tag3, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_tag (validator, tag4, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_tag (validator, tag5, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	ck_assert_int_eq (4, validator->tag_n);
+	ck_assert_str_eq (tag3, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[1].name);
+	ck_assert_str_eq (tag5, validator->tags[2].name);
+	ck_assert_str_eq (tag4, validator->tags[3].name);
+
+	/* test - overflow detection */
+	/* make method fail by filling with garbage until
+	 * upper boundary is reached */
+
+	/* ensure enough space  inside string*/
+	/* log10 +1 = number length */
+	/* +3 "tag" */
+	/* +1 '\0' */
+	/* = +5 */
+	tagN = malloc (((int) floor (log10 ((double) SIZE_MAX))) + 5);
+	ck_assert_ptr_ne (NULL, tagN);
+
+	/* fill with garbage */
+	do
+	{
+		sprintf (tagN, "tag%zu", validator->tag_n);
+	}
+	while (SH_Validator_register_tag (validator, tagN, V, NULL));
+
+	free (tagN);
+
+	/* test overflow */
+	result = SH_Validator_register_tag (validator, tag2, V, NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	ck_assert_int_eq (10, validator->tag_n);
+
+	#undef V
+
+	/* cleanup */
+	/* also free garbage created for overflow test */
+	validator->tag_n = 10;
+	SH_Validator_free (validator);
+}
+
+START_TEST(test_validator_tag_register_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	const char * tag1 = "html";
+	const char * tag2 = "head";
+	const char * tag3 = "article";
+	const char * tag4 = "p";
+	const char * tag5 = "img";
+	char * tagN;
+	bool result;
+
+	#define V SH_TAG_TYPE_VOID
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	/* test - register */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag1,
+	                                    SH_TAG_TYPE_VOID, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_ptr_ne (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_VOID, validator->tags[0].type);
+
+	/* test - invalid type */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag2, -1, &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - duplicate registration */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag1,
+	                                    SH_TAG_TYPE_NORMAL, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_ptr_ne (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_int_eq (TAG_T_NORMAL, validator->tags[0].type);
+
+	/* test - duplicate invalid type */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag1, 20, &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - order */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag3, V, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag4, V, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag5, V, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (status.status, SUCCESS);
+
+	ck_assert_int_eq (4, validator->tag_n);
+	ck_assert_str_eq (tag3, validator->tags[0].name);
+	ck_assert_str_eq (tag1, validator->tags[1].name);
+	ck_assert_str_eq (tag5, validator->tags[2].name);
+	ck_assert_str_eq (tag4, validator->tags[3].name);
+
+	/* test - overflow detection */
+	/* make method fail by filling with garbage until
+	 * upper boundary is reached */
+
+	/* ensure enough space  inside string*/
+	/* log10 +1 = number length */
+	/* +3 "tag" */
+	/* +1 '\0' */
+	/* = +5 */
+	tagN = malloc (((int) floor (log10 ((double) SIZE_MAX))) + 5);
+	ck_assert_ptr_ne (NULL, tagN);
+
+	/* fill with garbage */
+	do
+	{
+		sprintf (tagN, "tag%zu", validator->tag_n);
+	}
+	while (SH_Validator_register_tag (validator, tagN, V, NULL));
+
+	free (tagN);
+
+	/* test overflow */
+	_status_preinit (status);
+	result = SH_Validator_register_tag (validator, tag2, V, &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_DOMAIN, status.status);
+
+	ck_assert_int_eq (validator->tag_n, 10);
+
+	#undef V
+
+	/* cleanup */
+	/* also free garbage created for overflow test */
+	validator->tag_n = 10;
+	SH_Validator_free (validator);
+}
+
+START_TEST(test_validator_tag_deregister_no_status)
+{
+	struct SH_Validator * validator;
+	const char * tag1 = "tag1";
+	const char * tag2 = "tag2";
+	const char * tag3 = "tag3";
+	const char * attr = "attr";
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag2, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag3, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	result = SH_Validator_register_attr (validator, tag1, attr,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (3, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag2, validator->tags[1].name);
+	ck_assert_str_eq (tag3, validator->tags[2].name);
+
+	/* test - non existent */
+	result = SH_Validator_deregister_tag (validator, "notag", NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (3, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag2, validator->tags[1].name);
+	ck_assert_str_eq (tag3, validator->tags[2].name);
+
+	/* test - existent #1 */
+	result = SH_Validator_deregister_tag (validator, tag2, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (2, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag3, validator->tags[1].name);
+
+	/* test - existent #2, automatic attr deregister */
+	result = SH_Validator_deregister_tag (validator, tag1, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_attr (validator, NULL, attr);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_str_eq (tag3, validator->tags[0].name);
+
+	/* test - existent #3 */
+	result = SH_Validator_deregister_tag (validator, tag3, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (0, validator->tag_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_deregister_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	const char * tag1 = "tag1";
+	const char * tag2 = "tag2";
+	const char * tag3 = "tag3";
+	const char * attr = "attr";
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag2, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	result = SH_Validator_register_tag (validator, tag3, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	result = SH_Validator_register_attr (validator, tag1, attr,
+	                                     NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (3, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag2, validator->tags[1].name);
+	ck_assert_str_eq (tag3, validator->tags[2].name);
+
+	/* test - non existent */
+	_status_preinit (status);
+	result = SH_Validator_deregister_tag (validator, "notag",
+	                                      &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (3, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag2, validator->tags[1].name);
+	ck_assert_str_eq (tag3, validator->tags[2].name);
+
+	/* test - existent #1 */
+	_status_preinit (status);
+	result = SH_Validator_deregister_tag (validator, tag2, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (2, validator->tag_n);
+	ck_assert_str_eq (tag1, validator->tags[0].name);
+	ck_assert_str_eq (tag3, validator->tags[1].name);
+
+	/* test - existent #2, automatic attr deregister */
+	_status_preinit (status);
+	result = SH_Validator_deregister_tag (validator, tag1, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	result = SH_Validator_check_attr (validator, NULL, attr);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - consistency */
+	ck_assert_int_eq (1, validator->tag_n);
+	ck_assert_str_eq (tag3, validator->tags[0].name);
+
+	/* test - existent #3 */
+	_status_preinit (status);
+	result = SH_Validator_deregister_tag (validator, tag3, &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - consistency */
+	ck_assert_int_eq (0, validator->tag_n);
+
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_check)
+{
+	struct SH_Validator * validator;
+	const char * tag1  = "html";
+	const char * tag2  = "html";
+	const char * tag3  = "head";
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	#define V SH_TAG_TYPE_VOID
+	result = SH_Validator_register_tag (validator, tag1, V, NULL);
+	ck_assert_int_eq (TRUE, result);
+	#undef V
+
+	/* test */
+	result = SH_Validator_check_tag (validator, tag1);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_tag (validator, tag2);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_check_tag (validator, tag3);
+	ck_assert_int_eq (FALSE, result);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+END_TEST
+
+START_TEST(test_validator_tag_self_closing_no_status)
+{
+	struct SH_Validator * validator;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	result = SH_Validator_register_tag (validator, "html",
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_tag (validator, "link",
+	                                    SH_TAG_TYPE_VOID, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - self closing */
+	result = SH_Validator_is_self_closing_tag (validator, "link",
+	                                           NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - not self closing */
+	result = SH_Validator_is_self_closing_tag (validator, "html",
+	                                           NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* test - invalid tag */
+	result = SH_Validator_is_self_closing_tag (validator, "body",
+	                                           NULL);
+	ck_assert_int_eq (FALSE, result);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+
+START_TEST(test_validator_tag_self_closing_with_status)
+{
+	struct SH_Status status;
+	struct SH_Validator * validator;
+	bool result;
+
+	/* setup */
+	validator = SH_Validator_new (NULL);
+	ck_assert_ptr_ne (NULL, validator);
+
+	result = SH_Validator_register_tag (validator, "html",
+	                                    SH_TAG_TYPE_NORMAL, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	result = SH_Validator_register_tag (validator, "link",
+	                                    SH_TAG_TYPE_VOID, NULL);
+	ck_assert_int_eq (TRUE, result);
+
+	/* test - self closing */
+	_status_preinit (status);
+	result = SH_Validator_is_self_closing_tag (validator, "link",
+	                                           &status);
+	ck_assert_int_eq (TRUE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - not self closing */
+	_status_preinit (status);
+	result = SH_Validator_is_self_closing_tag (validator, "html",
+	                                           &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (SUCCESS, status.status);
+
+	/* test - invalid tag */
+	_status_preinit (status);
+	result = SH_Validator_is_self_closing_tag (validator, "body",
+	                                           &status);
+	ck_assert_int_eq (FALSE, result);
+	ck_assert_int_eq (E_VALUE, status.status);
+
+	/* cleanup */
+	SH_Validator_free (validator);
+}
+
+Suite * test_suite (void)
+{
+	Suite *s;
+	TCase *tc_core;
+
+	s = suite_create ("Testsuite SeFHT Validator Tag");
+
+	/* Core test case */
+	tc_core = tcase_create ("Core");
+
+	tcase_add_test (tc_core, test_validator_tag_no_status);
+	tcase_add_test (tc_core, test_validator_tag_with_status);
+	tcase_add_test (tc_core, test_validator_tag_copy_no_status);
+	tcase_add_test (tc_core, test_validator_tag_copy_with_status);
+	tcase_add_test (tc_core, test_validator_tag_spec_no_status);
+	tcase_add_test (tc_core, test_validator_tag_spec_with_status);
+	tcase_add_test (tc_core, test_validator_tag_register_no_status);
+	tcase_add_test (tc_core, test_validator_tag_register_with_status);
+	tcase_add_test (tc_core, test_validator_tag_deregister_no_status);
+	tcase_add_test (tc_core, test_validator_tag_deregister_with_status);
+	tcase_add_test (tc_core, test_validator_tag_check);
+	tcase_add_test (tc_core, test_validator_tag_self_closing_no_status);
+	tcase_add_test (tc_core, test_validator_tag_self_closing_with_status);
+	suite_add_tcase (s, tc_core);
+
+	return s;
+}
+
+int main (void)
+{
+	int number_failed;
+	Suite *s;
+	SRunner *sr;
+
+	s = test_suite ();
+	sr = srunner_create (s);
+
+	srunner_run_all (sr, CK_NORMAL);
+	number_failed = srunner_ntests_failed (sr);
+	srunner_free (sr);
+
+	return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
diff --git a/todo.txt b/todo.txt
index 0de5b07d491a360e2d6eeda4d7085fd3463d95a4..c2bf52062f518850fd659fd4ef11c8b030ef5684 100644
--- a/todo.txt
+++ b/todo.txt
@@ -2,14 +2,9 @@ create Logger
 
 create Docs
 
-dynamic Validator initialization
-
 remove -Wno-nonnull from AM_CFLAGS
 fix warnings for tests
 
-rewrite validator test
-restructure validator
-
 Fragment:
 - create html on single Text object
 - add customized styling in html generation
@@ -20,3 +15,8 @@ Fragment:
 - support of newlines (72/79/80)
 - support of escaping html symbols (but only which are special in context)
 - support for mapping special chars -> html entities
+
+Validator:
+- check for global attributes
+- fix cursed behaviour, when removing fails (remove_tag_for_all_attrs)
+- initialize from file