<?xml version="1.0" encoding="UTF-8"?>
<!-- 
	Copyright (c) 2013 Stefan Krause
	
	Permission is hereby granted, free of charge, to any person obtaining
	a copy of this software and associated documentation files (the
	"Software"), to deal in the Software without restriction, including
	without limitation the rights to use, copy, modify, merge, publish,
	distribute, sublicense, and/or sell copies of the Software, and to
	permit persons to whom the Software is furnished to do so, subject to
	the following conditions:
	
	The above copyright notice and this permission notice shall be included
	in all copies or substantial portions of the Software.
	
	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
	IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
	CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
	TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
	SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl"
	xmlns:xhtml="http://www.w3.org/1999/xhtml"
	xmlns:local="http://www.expedimentum.org/example/"
	exclude-result-prefixes="xs xd xsl"
	version="2.0">
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc scope="stylesheet">
		<xd:desc>
			<xd:p><xd:b>Created on:</xd:b> Jul 13, 2013</xd:p>
			<xd:p><xd:b>Author:</xd:b> Stefan Krause</xd:p>
			<xd:p>Dieses Stylesheet teilt XML-Dokumente gruppenweise in mehrere Einzelddateien auf.</xd:p>
			<xd:p>Dieses Stylesheet und die dazugehörige Dokumentation sind unter der sogenannten Expat License (einer GPL-kompatiblen MIT License) lizensiert.
					Es darf – als Ganzes oder auszugweise –  unter Beibehaltung der Copyright-Notiz kopiert, verändert, veröffentlicht und verbreitet werden.
					Die Copyright-Notiz steht am Anfang des Quelltextes des Stylesheets.</xd:p>
		</xd:desc>
	</xd:doc>
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:preserve-space elements="*"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:output indent="yes"/>
	<!--  -->
	<xsl:output indent="no" name="format_fuer_einzeldateien"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- ==========     Stylesheet-Parameter     ========== -->
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Anzahl der Elemente pro Ausgabedatei</xd:p>
			<xd:p>Dieser Wert bestimmt die Größe der Gruppen und damit auch, welche Elemente als Gruppenanfang in Frage kommen.
				Ist bspw. <xhtml:code>$elemente-pro-datei</xhtml:code> gleich <xhtml:code>20</xhtml:code>, dann sind mögliche Gruppenanfänge 
				die Elemente mit der Position <xhtml:code>1</xhtml:code>, <xhtml:code>21</xhtml:code>, <xhtml:code>41</xhtml:code> usw. Damit wird sichergestellt,
				dass die Ausgabedateien (mit Ausnahme der letzten) immer vollständige Gruppen enthalten.
				Mit einem Wert von <xhtml:code>1</xhtml:code> wird je Element eine Datei ausgegeben.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="elemente-pro-datei" as="xs:integer">20</xsl:param>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Position des ersten zu kopierenden Elementes</xd:p>
			<xd:p>Ohne Angabe eines Wertes wird das erste Element gewählt.</xd:p>
			<xd:p>Fällt die angegebene Position nicht auf den Anfang einer Gruppe, der sich aus dem Wert für <xhtml:code>$elemente-pro-datei</xhtml:code> ergibt, wird das nächste vorhergehende Element, das ein Gruppenanfang ist, gewählt.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="erstes-element" as="xs:integer">1</xsl:param>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Position des letzten zu kopierenden Elementes</xd:p>
			<xd:p>Ohne Angabe eines Wertes wird das letzte Element der Ausgangsdatei gewählt</xd:p>
			<xd:p>Fällt die angegebene Position nicht auf das Ende einer Gruppe, der sich aus dem Wert für <xhtml:code>$elemente-pro-datei</xhtml:code> ergibt, wird das nächst folgende Element, das ein Gruppenende ist, gewählt.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="letztes-element" as="xs:integer" select="count(/*/*)"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Ordner für die Ausgabe der geteilten Dateien als URI, ohne schließenden Schrägstrich</xd:p>
			<xd:p>Ohne Angabe eines Ausgabeordners werden die Dateien in den Ordner des Ausgangsdokumentes in einen Unterordner <xhtml:code>geteilt</xhtml:code> geschrieben.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="ausgabeordner" as="xs:string" select="concat(string-join(tokenize(document-uri(/), '/')[position() lt last()], '/'), '/geteilt')"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Präfix für die Ausgabedateien</xd:p>
			<xd:p>Ohne Angabe eines Wertes wird der Name der Ausgangsdatei (ohne Dateierweiterung) mit einem nachgestellten Unterstrich verwendet.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="ausgabedatei-präfix" as="xs:string"
		select="concat(
						for $i in tokenize(document-uri(/), '/')[last()]						(: das ist der vollständige Dateiname :)
							return
								if (contains($i, '.'))
									then														(: Dateierweiterung abtrennen :)
										string-join(
													tokenize($i, '\.')[position() lt last()],	(: das is der Dateiname ohne Dateierweiterung :)
													'.'
													) 
									else $i,
						'_'
					)
				"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Dateityp-Erweiterung für die Ausgabedateien</xd:p>
			<xd:p>Ohne Angabe eines Wertes wird die Dateierweiterung der Ausgangsdatei verwendet.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="ausgabedatei-erweiterung" as="xs:string"
		select="for $i in tokenize(document-uri(/), '/')[last()]								(: das ist der vollständige Dateiname :)
					return
							if (contains($i, '.'))
								then tokenize($i, '\.')[last()]									(: das ist die Dateierweiterung :)
								else ''
					
				"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Formatierung der Zählstellen, als Picture-String (siehe <xd:a href="http://www.w3schools.com/xsl/func_formatnumber.asp">http://www.w3schools.com/xsl/func_formatnumber.asp</xd:a>)</xd:p>
			<xd:p>Wird kein Wert übergeben, werden führende Nullen entsprechend der Anzahl der Elemente im Ausgangsdokument ausgegeben.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="picture-string" as="xs:string" select="replace(string(count(/*/*)), '[0-9]', '0')" />
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Sollen nodes() vor dem ersten Element (Kommentare und/oder Processing-Instructions) in jede Ausgabe-Datei an den Anfang kopiert werden?</xd:p>
			<xd:p>Bei <xhtml:code>false</xhtml:code> werden entsprechende Knoten nur in die erste Datei kopiert.</xd:p>
			<xd:p>Hinweis: Vor dem Wurzelelement stehen oft Lizenzangaben in Kommentaren oder Processing Instructions wie bspw. das xml-model
					(siehe die W3C Working Group Note <xd:a href="http://www.w3.org/TR/xml-model/">Associating Schemas with XML documents</xd:a>).
					Es kann wünschenswert sein, diese Informationen zu wiederholen.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="wiederhole-initiale-nodes" as="xs:boolean" select="false()"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>Verhindert, dass vorhandene Dateien überschrieben werden.</xd:p>
			<xd:p>Hinweis: Um die Existenz einer Datei zu prüfen, muss die Datei geparst werden. Bei vielen und/oder großen bereits vorhandenen Ausgabedokumenten
					kann das sehr zeitaufwändig sein. In diesen Fällen empfiehlt es sich, erhaltenswerte Dokumente zu sichern und den Parameter auf <xhtml:code>false</xhtml:code> zu setzen.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:param name="schuetze-vorhandene-dateien" as="xs:boolean" select="true()"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- ==========     globale Variable     ========== -->
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc id="variable-erste-gruppe">
		<xd:desc>
			<xd:p>Es werden immer vollständige Gruppen ausgegeben. Die folgenden Variablen bestimmen, welche Gruppen das sind.</xd:p>
		</xd:desc>
	</xd:doc>
	<xsl:variable name="erste-auszugebende-gruppe" as="xs:integer" select="local:berechne-gruppennummer($erstes-element)"/>
	<!--  -->
	<xsl:variable name="letzte-auszugebende-gruppe" as="xs:integer" select="local:berechne-gruppennummer($letztes-element)"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- ==========     matching templates     ========== -->
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:template match="/">
		<bericht datum="{current-dateTime()}">
			<xsl:for-each-group select="local:nodes-auswaehlen(/)" group-adjacent="local:gruppennummer(.)">
				<xsl:call-template name="local:verarbeite-gruppe"/>
			</xsl:for-each-group>
		</bericht>
	</xsl:template>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- nur zur Sicherheit, sollte nicht gematcht werden  -->
	<xsl:template match="node()"/>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- ==========     named templates     ========== -->
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:template name="local:verarbeite-gruppe">
		<xsl:variable name="aktueller-teildateiname" as="xs:string" select="local:dateiname(current-group(), current-grouping-key())"/><!-- node()*, Gruppennummer -->
		<xsl:choose>
			<xsl:when test="$schuetze-vorhandene-dateien and doc-available($aktueller-teildateiname)">
				<xsl:sequence select="local:message('FEHLER', concat('Datei ', $aktueller-teildateiname, ' ist schon vorhanden und wurde nicht überschrieben') )"/>
			</xsl:when>
			<xsl:when test="empty(current-group()[self::*]) and (current-grouping-key() eq ($letzte-auszugebende-gruppe + 1) )">
				<!-- Das können nur processing-instructions, Kommentare und Text nach dem letzten Element sein, oder es gibt keine Kinder -->
				<xsl:choose>
					<!-- sinvoller Inhalt -->
					<xsl:when test="comment()|processing-instruction()|text()[normalize-space(.)]">
						<!-- erst das Protokoll schreiben -->
						<xsl:sequence select="local:message('info', concat('schreibe Datei ', $aktueller-teildateiname, ' (enthält keine Elemente)' ) )"/>
						<!-- dann Datei ausgeben -->
						<xsl:call-template name="local:schreibe-datei">
							<xsl:with-param name="aktueller-teildateiname" select="$aktueller-teildateiname"/>
						</xsl:call-template>
					</xsl:when>
					<xsl:otherwise>
						<!-- nur Whitespace -->
						<xsl:sequence select="local:message('info', concat('Datei ', $aktueller-teildateiname, ' übersprungen, weil nur Whitespace enthalten ist') )"/>
					</xsl:otherwise>
				</xsl:choose>
			</xsl:when>
			<xsl:when test="(current-grouping-key() lt $erste-auszugebende-gruppe) or (current-grouping-key() gt $letzte-auszugebende-gruppe)">
				<xsl:sequence select="local:message('info', concat('Datei ', $aktueller-teildateiname, ' übersprungen, weil keine auszugebende Elemente enthalten sind') )"/>
			</xsl:when>
			<xsl:otherwise>
				<!-- erst das Protokoll schreiben -->
				<xsl:sequence select="local:message('info', concat('schreibe Datei ', $aktueller-teildateiname) )"/>
				<!-- dann Datei ausgeben -->
				<xsl:call-template name="local:schreibe-datei">
					<xsl:with-param name="aktueller-teildateiname" select="$aktueller-teildateiname"/>
				</xsl:call-template>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:template name="local:schreibe-datei">
		<xsl:param name="aktueller-teildateiname" as="xs:string" required="yes"/>
		<xsl:result-document href="{$aktueller-teildateiname}" format="format_fuer_einzeldateien">
			<!-- nodes for dem Wurzelelement -->
			<xsl:if test="$wiederhole-initiale-nodes or (position() eq 1)">
				<xsl:copy-of select="/node()[not(self::*)][not(preceding-sibling::*)]"/>
			</xsl:if>
			<!-- Wurzelelement rekonstruieren -->
			<xsl:element name="{local-name(/*)}" namespace="{namespace-uri(/*)}">
				<xsl:copy-of select="/*/@*"/>
				<!-- aktuelle Gruppe schreiben -->
				<xsl:sequence select="current-group()"/>
			</xsl:element>
			<!-- nodes nach dem Wurzelelement -->
			<xsl:if test="position() eq last()">
				<xsl:copy-of select="/node()[not(self::*)][preceding-sibling::*]"/>
			</xsl:if>
		</xsl:result-document>
	</xsl:template>
	<!--  -->
	<!--  -->
	<!--  -->
	<!-- ==========     Funktionen     ========== -->
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>ermittelt die auszugebenden nodes</xd:p>
			<xd:p>Diese Funktion kann insbesondere überschrieben werden, um
				<xd:ul>
					<xd:li>nodes auf einer anderen Ebene als der zweiten auszuwählen</xd:li>
					<xd:li>die auszugebenden nodes zu filtern</xd:li>
				</xd:ul>
			</xd:p>
		</xd:desc>
		<xd:param name="document-node">das Wurzelelement</xd:param>
		<xd:return>auszugebende Elemente, Kommentare, Texte, processing instructions</xd:return>
	</xd:doc>
	<xsl:function name="local:nodes-auswaehlen" as="node()*">
		<xsl:param name="document-node" as="document-node()"/>
		<xsl:sequence select="$document-node/*/node()"/>
	</xsl:function>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>ermittelt die Gruppen-Nummer für einen gegebenen node()</xd:p>
		</xd:desc>
		<xd:param name="node">als Element, text(), comment() oder processing-instruction()</xd:param>
		<xd:return>Nummer der Gruppe, zu der der node gehört</xd:return>
	</xd:doc>
	<xsl:function name="local:gruppennummer" as="xs:integer">
		<xsl:param name="node" as="node()"/>
		<xsl:sequence select="local:berechne-gruppennummer(count($node/preceding-sibling::*) + 1)"/><!-- die Position des aktuellen Elementes ist die Anzahl der Vorgänger + 1 -->
	</xsl:function>
	<!--  -->
	<!--  -->
	<!--  -->
	<xd:doc>
		<xd:desc>
			<xd:p>berechnet die Nummer der Gruppe, zu der ein Element mit einer gegebenen Position gehört</xd:p>
		</xd:desc>
		<xd:param name="element-position">position des Elementes</xd:param>
		<xd:return>Gruppennummer</xd:return>
	</xd:doc>
	<xsl:function name="local:berechne-gruppennummer" as="xs:integer">
		<xsl:param name="element-position" as="xs:integer"/>
		<xsl:sequence select="xs:integer(floor(($element-position - 1) div $elemente-pro-datei)) + 1"/>
	</xsl:function>
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:function name="local:dateiname" as="xs:string">
		<xsl:param name="aktuelle-gruppe" as="node()*"/>
		<xsl:param name="aktuelle-gruppennummer" as="xs:integer"/>
		<xsl:variable name="offset" as="xs:integer" select="xs:integer(($aktuelle-gruppennummer - 1) * $elemente-pro-datei)"/>
		<xsl:variable name="count" as="xs:integer" select="count($aktuelle-gruppe[self::*])"/>
		<xsl:sequence select="
						concat(
							$ausgabeordner,
							if (normalize-space($ausgabeordner))
								then '/'
								else (),
							$ausgabedatei-präfix,
							format-number($offset + 1, $picture-string),
							if (xs:integer($elemente-pro-datei) gt 1)
								then concat(
											'-',
											format-number(
															$offset + (if ($count eq 0) (: in der letzten Gruppe können u.U. keine Elemente, aber Text, Kommentare und/oder Processing Instructionss sein :)
																			then 1
																			else $count
																		),
															$picture-string
														)
											)
								else (),
							if ($count eq 0)
								then '_ohne_elemente'
								else (),
							if (normalize-space($ausgabedatei-erweiterung))
								then '.'
								else (),
							$ausgabedatei-erweiterung
						)
			"/>
	</xsl:function>
	<!--  -->
	<!--  -->
	<!--  -->
	<xsl:function name="local:message" as="element()">
		<xsl:param name="level" as="xs:string"/>
		<xsl:param name="message" as="xs:string"/>
		<xsl:message select="concat('[', $level, '] ', $message)"/>
		<xsl:element name="{$level}">
			<xsl:value-of select="$message"/>
		</xsl:element>
	</xsl:function>
	<!--  -->
	<!--  -->
	<!--  -->
</xsl:stylesheet>