diff --git a/Makefile b/Makefile
index a59f3685f398f70de19d5952226518966a064485..4a01cc8be358651ffd6418cbb35be1a323c154ef 100644
--- a/Makefile
+++ b/Makefile
@@ -49,7 +49,9 @@ check: bjcp-2015-styleguide-orig.xml bjcp-2015-styleguide-de.xml
 	@xmllint --noout --schema xsd/bjcp-styleguide-2015.xsd bjcp-2015-styleguide-de.xml
 
 install: bjcp-2015-styleguide-de-edit.html
+	ssh z "if [ ! -d /var/www/bjcp-styleguide ] ; then sudo mkdir /var/www/bjcp-styleguide ; sudo chown frank.www-data /var/www/bjcp-styleguide ; sudo chmod u+rwx,g+rwxs /var/www/bjcp-styleguide ; fi ; touch /var/www/bjcp-styleguide/logfile ; mkdir /var/www/bjcp-styleguide/snippets"
 	scp bjcp-2015-styleguide-de-edit.html web/bjcp-styleguide.css web/edit.css web/edit.js web/pell.css web/pell.js web/save.cgi z:/var/www/bjcp-styleguide/
+	ssh z "cd /var/www/bjcp-styleguide ; git clone https://github.com/frsteinb/bjcp-2015-styleguide.git ; cd bjcp-2015-styleguide ; make"
 
 clean:
 	@rm -rf orig
diff --git a/web/save.cgi b/web/save.cgi
index 516bee0d60750b4f22aad44500beef8c1397b317..9df3971a22e1f9dfbac6e15db9b3aa4e0dd9806b 100755
--- a/web/save.cgi
+++ b/web/save.cgi
@@ -1,8 +1,29 @@
 #!/usr/bin/python3
 
 import os
+import os.path
+import re
 import sys
 import urllib.parse
+import codecs
+import time
+import datetime
+
+DIR = "/var/www/bjcp-styleguide"
+LOGFILE = "%s/logfile" % DIR
+SNIPPETDIR = "/var/www/bjcp-styleguide/snippets"
+REPODIR = "/var/www/bjcp-styleguide/bjcp-2015-styleguide"
+LANG = "de"
+
+
+
+def log(msg):
+    time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+    f = open(LOGFILE, "a+")
+    f.write("%s %s\n" % (time, msg))
+    f.close()
+
+
 
 print("Content-Type: text/plain")
 print()
@@ -12,18 +33,32 @@ if "QUERY_STRING" in os.environ:
 else:
     form = {}
     
+level = None
+
 if "id" in form:
     id = form["id"][0]
+    id = re.sub(r'[^a-zA-Z0-9\-]+', '', id)
+    if re.match(r'^[0-9]+$', id):
+        level = 1
+    elif re.match(r'^[0-9]+[A-Z]-.*$', id):
+        level = 3
+        id1 = re.sub(r'^([0-9]+)[A-Z]-.*$', r'\1', id)
+        id2 = re.sub(r'^([0-9]+[A-Z])-.*$', r'\1', id)
+    else:
+        level = 2
+        id1 = re.sub(r'^([0-9]+).*$', r'\1', id)
 else:
     id = "unknown"
     
 if "elem" in form:
     elem = form["elem"][0]
+    elem = re.sub('[^a-z\-]+', '', elem)
 else:
     elem = "unknown"
     
 if "user" in form:
     user = form["user"][0]
+    elem = re.sub('[^a-zA-Z0-9_+@\-]+', '', elem)
 else:
     user = "unknown"
 
@@ -34,15 +69,58 @@ else:
 
 
 data = sys.stdin.buffer.read()
+len = len(data)
+data = data.decode("utf-8")
 
-msg = "received %d bytes for id %s elem %s from %s by %s" % (len(data), id, elem, addr, user)
+time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+filename = "snippet-%s-%d.xml" % (time.replace(" ", "-").replace(":", "-"), os.getpid())
+snippetfilename = "%s/%s" % (SNIPPETDIR, filename)
+msg = "received %d bytes for id %s elem %s from %s by %s to %s" % (len, id, elem, addr, user, filename)
 
-f = open ("/var/log/bjcp-styleguide/log", "w+")
-f.write("%s\n" % msg)
-f.close()
+log(msg)
 
-f = open ("/var/log/bjcp-styleguide/data", "wb")
-f.write(data)
+f = codecs.open(snippetfilename, "w", "utf-8")
+f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
+f.write('<styleguide xmlns="http://heimbrauconvention.de/bjcp-styleguide/2015">\n')
+f.write('<!-- snippet supplied by %s from %s at %s -->\n' % (user, addr, time))
+if level == 1:
+    f.write('  <category id="%s">\n' % id)
+    f.write('    <%s>%s</%s>\n' % (elem, data, elem))
+    f.write('  </category>\n')
+elif level == 2:
+    f.write('  <category id="%s">\n' % id1)
+    f.write('    <subcategory id="%s">\n' % id)
+    f.write('      <%s>%s</%s>\n' % (elem, data, elem))
+    f.write('    </subcategory>\n')
+    f.write('  </category>\n')
+elif level == 3:
+    f.write('  <category id="%s">\n' % id1)
+    f.write('    <subcategory id="%s">\n' % id2)
+    f.write('      <subcategory id="%s">\n' % id)
+    f.write('        <%s>%s</%s>\n' % (elem, data, elem))
+    f.write('      </subcategory>\n')
+    f.write('    </subcategory>\n')
+    f.write('  </category>\n')
+else:
+    f.write('  <!-- ID %s could not be parsed -->\n')
+ 
+f.write('</styleguide>\n')
 f.close()
 
+origfilename = "%s/orig/%s.xml" % (REPODIR, id)
+translatedfilename = "%s/%s/%s.xml" % (REPODIR, LANG, id)
+
+if os.path.isfile(translatedfilename):
+    inputfilename = translatedfilename
+if os.path.isfile(origfilename):
+    inputfilename = origfilename
+else:
+    log("neither orig file %s nor translated file %s for id %s exists" % (origfilename, translatedfilename, id))
+    inputfilename = None
+
+if inputfilename:
+    cmd = "xsltproc --stringparam snippet %s %s/xsl/bjcp-2015-styleguide-merge.xsl %s > %s" % (snippetfilename, REPODIR, inputfilename, translatedfilename)
+    log(cmd)
+    os.system(cmd)
+
 # TODO: parse, create response, show error response on client, write xml on server, git commit, 
diff --git a/xsl/bjcp-2015-styleguide-merge.xsl b/xsl/bjcp-2015-styleguide-merge.xsl
new file mode 100644
index 0000000000000000000000000000000000000000..3ef5320d9cc7ff14337c7d4a25b276680f19bb78
--- /dev/null
+++ b/xsl/bjcp-2015-styleguide-merge.xsl
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" 
+    xmlns="http://heimbrauconvention.de/bjcp-styleguide/2015"
+    xmlns:bjcp="http://heimbrauconvention.de/bjcp-styleguide/2015"
+    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+    xmlns:exsl="http://exslt.org/common"
+    xmlns:dyn="http://exslt.org/dynamic"
+    extension-element-prefixes="exsl dyn">
+
+
+
+  <xsl:param name="snippet"></xsl:param>
+
+
+
+  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
+
+
+
+  <xsl:variable name="snippetid">
+    <xsl:choose>
+      <xsl:when test="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category/bjcp:subcategory/bjcp:subcategory">
+	<xsl:value-of select="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category/bjcp:subcategory/bjcp:subcategory[@id]/@id"/>
+      </xsl:when>
+      <xsl:when test="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category/bjcp:subcategory">
+	<xsl:value-of select="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category/bjcp:subcategory[@id]/@id"/>
+      </xsl:when>
+      <xsl:when test="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category">
+	<xsl:value-of select="document(concat('../',$snippet))/bjcp:styleguide/bjcp:category[@id]/@id"/>
+      </xsl:when>
+      <xsl:otherwise>
+	<xsl:text>unknown</xsl:text>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:variable>
+
+
+  <xsl:variable name="snippetnode" select="document(concat('../',$snippet))/bjcp:styleguide//bjcp:*[@id = $snippetid]"/>
+
+
+  <!-- suppress these tags in translations -->
+  <xsl:template match="bjcp:tags|bjcp:specs">
+  </xsl:template>
+
+
+
+  <xsl:template match="bjcp:*">
+    <xsl:element name="{local-name(.)}">
+      <xsl:apply-templates select="@*"/>
+      <xsl:apply-templates/>
+    </xsl:element>
+  </xsl:template>
+
+
+
+  <xsl:template match="bjcp:name|bjcp:description|bjcp:overall-impression|bjcp:aroma|bjcp:appearance|bjcp:flavor|bjcp:mouthfeel|bjcp:comments|bjcp:history|bjcp:characteristic-ingredients|bjcp:style-comparison|bjcp:entry-instructions|bjcp:commercial-examples">
+    <xsl:variable name="name">
+      <xsl:value-of select="local-name(.)"/>
+    </xsl:variable>
+    <xsl:element name="{$name}">
+      <xsl:apply-templates select="@*"/>
+      <xsl:choose>
+	<xsl:when test="(../@id = $snippetid) and ($snippetnode/bjcp:*[local-name(.)=$name])">
+	  <xsl:apply-templates select="$snippetnode/bjcp:*[local-name(.)=$name]/bjcp:* | $snippetnode/bjcp:*[local-name(.)=$name]/text()"/>
+	</xsl:when>
+	<xsl:otherwise>
+	  <xsl:apply-templates/>
+	</xsl:otherwise>
+      </xsl:choose>
+    </xsl:element>
+  </xsl:template>
+
+
+
+  <xsl:template match="@*">
+    <xsl:copy>
+      <xsl:apply-templates select="@*"/>
+      <xsl:apply-templates/>
+    </xsl:copy>
+  </xsl:template>
+
+
+
+  <xsl:template match="text()">
+    <xsl:copy>
+      <xsl:apply-templates/>
+    </xsl:copy>
+  </xsl:template>
+
+
+
+</xsl:stylesheet>