﻿<?xml version="1.0" encoding="UTF-8"?>
<!--
*******************************************************************************
* ema-epi-html-main.xslt
* Version: 1.0
* Description:
*   This XSLT stylesheet transforms FHIR Bundle resources containing ePI 
*   (electronic Product Information) content into formatted HTML documents.
*******************************************************************************
-->
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:fhir="http://hl7.org/fhir"
                xmlns:xhtml="http://www.w3.org/1999/xhtml"
                exclude-result-prefixes="fhir xhtml">

	<!-- Output configuration - Generate HTML with indentation -->
	<xsl:output method="html" indent="yes"/>

	<!-- Set parameter to NOT display composition titles -->
	<xsl:param name="display-composition-title" select="false()"/>

	<!-- Global constants -->
	<xsl:variable name="SMPC_CODE" select="'100000155532'"/>
	<xsl:variable name="ANNEXII_CODE" select="'100000155533'"/>
	<xsl:variable name="LABELLING_CODE" select="'100000155535'"/>
	<xsl:variable name="PACKAGE_LEAFLET_CODE" select="'100000155538'"/>
	<xsl:variable name="SPECIAL_SECTION_CODE" select="'200000029796'"/>

	<!-- Named templates for calculating a checksum from a string -->
	<xsl:template name="simple-checksum">
		<xsl:param name="string"/>
		<xsl:variable name="length" select="string-length($string)"/>

		<xsl:call-template name="calculate-checksum">
			<xsl:with-param name="string" select="$string"/>
			<xsl:with-param name="length" select="$length"/>
			<xsl:with-param name="checksum" select="0"/>
			<xsl:with-param name="index" select="1"/>
		</xsl:call-template>
	</xsl:template>

	<xsl:template name="calculate-checksum">
		<xsl:param name="string"/>
		<xsl:param name="length"/>
		<xsl:param name="checksum"/>
		<xsl:param name="index"/>

		<xsl:variable name="char" select="substring($string, $index, 1)"/>
		<xsl:variable name="char-value" select="string-length(substring-before('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', $char))"/>
		<xsl:variable name="new-checksum" select="($checksum * 31 + $char-value) mod 1000000"/>

		<xsl:choose>
			<xsl:when test="$index &lt; $length">
				<xsl:call-template name="calculate-checksum">
					<xsl:with-param name="string" select="$string"/>
					<xsl:with-param name="length" select="$length"/>
					<xsl:with-param name="checksum" select="$new-checksum"/>
					<xsl:with-param name="index" select="$index + 1"/>
				</xsl:call-template>
			</xsl:when>
			<xsl:otherwise>
				<xsl:value-of select="$new-checksum"/>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- Error message templates -->
	<xsl:template name="error-message">
		<xsl:param name="message"/>
		<div class="epi__error">
			<p>
				Error: <xsl:value-of select="$message"/>
			</p>
		</div>
	</xsl:template>

	<!-- Named template for generating CSS class names -->
	<xsl:template name="get-css-class">
		<xsl:param name="classPrefix"/>
		<xsl:param name="level"/>
		<xsl:param name="sectionCode"/>

		<xsl:variable name="baseClass" select="concat($classPrefix, 'level-', $level)"/>

		<xsl:choose>
			<xsl:when test="
                $baseClass = 'epi__smpc--level-3' and 
                $sectionCode = $SPECIAL_SECTION_CODE">
				<xsl:text>epi__smpc--level-4</xsl:text>
			</xsl:when>
			<xsl:otherwise>
				<xsl:value-of select="$baseClass"/>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- Named template for determining document type class prefix -->
	<xsl:template name="get-class-prefix">
		<xsl:param name="compositionCode"/>

		<xsl:choose>
			<xsl:when test="$compositionCode = $SMPC_CODE">epi__smpc--</xsl:when>
			<xsl:when test="$compositionCode = $ANNEXII_CODE">epi__annexii--</xsl:when>
			<xsl:when test="$compositionCode = $LABELLING_CODE">epi__labelling--</xsl:when>
			<xsl:when test="$compositionCode = $PACKAGE_LEAFLET_CODE">epi__packageleaflet--</xsl:when>
			<xsl:otherwise>epi__default--</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- 
    * Composition Template
    * Processes each FHIR Composition resource and determines the document type
    * based on the coding/code value, then assigns appropriate CSS class prefixes
    -->
	<xsl:template match="fhir:Composition">
		<!-- Extract the composition code to determine document type -->
		<xsl:variable name="compositionCode" select="fhir:type/fhir:coding/fhir:code/@value"/>
		<xsl:variable name="compositionTitle" select="fhir:title/@value"/>

		<!-- Validate composition code -->
		<xsl:if test="not($compositionCode = $SMPC_CODE or 
                      $compositionCode = $ANNEXII_CODE or 
                      $compositionCode = $LABELLING_CODE or 
                      $compositionCode = $PACKAGE_LEAFLET_CODE)">
			<xsl:call-template name="error-message">
				<xsl:with-param name="message">
					Unknown composition type code: <xsl:value-of select="$compositionCode"/>
				</xsl:with-param>
			</xsl:call-template>
		</xsl:if>

		<!-- Get CSS class prefix based on composition code -->
		<xsl:variable name="classPrefix">
			<xsl:call-template name="get-class-prefix">
				<xsl:with-param name="compositionCode" select="$compositionCode"/>
			</xsl:call-template>
		</xsl:variable>

		<xsl:variable name="compositionChecksum">
			<xsl:call-template name="simple-checksum">
				<xsl:with-param name="string">
					<xsl:value-of select="$compositionTitle"/>
				</xsl:with-param>
			</xsl:call-template>
		</xsl:variable>

		<!-- Create a container for this composition -->
		<div id="comp-{$compositionCode}-{$compositionChecksum}">
			<!-- Add the Composition title if parameter is set to true -->
			<xsl:if test="$display-composition-title and $compositionTitle">
				<p class="{$classPrefix}level-0">
					<xsl:value-of select="$compositionTitle"/>
				</p>
			</xsl:if>

			<!-- Process sections within this Composition -->
			<xsl:choose>
				<xsl:when test="not(fhir:section)">
					<xsl:call-template name="error-message">
						<xsl:with-param name="message">
							No sections found in composition
						</xsl:with-param>
					</xsl:call-template>
				</xsl:when>
				<xsl:otherwise>
					<xsl:apply-templates select="fhir:section">
						<xsl:with-param name="level" select="1"/>
						<xsl:with-param name="classPrefix" select="$classPrefix"/>
						<xsl:with-param name="recursionDepth" select="1"/>
						<xsl:with-param name="compositionChecksum" select="$compositionChecksum" />
					</xsl:apply-templates>
				</xsl:otherwise>
			</xsl:choose>
		</div>
	</xsl:template>

	<!-- 
    * Section Template
    * Processes FHIR section elements recursively, applying specific formatting
    * based on section level and document type
    *
    * Parameters:
    * - level: Current nesting level (starts at 1 for top-level sections)
    * - classPrefix: CSS class prefix determined by document type
	* - recursionDepth: Track recursion depth to prevent infinite loops
    -->
	<xsl:template match="fhir:section">
		<xsl:param name="level"/>
		<xsl:param name="classPrefix"/>
		<xsl:param name="recursionDepth"/>
		<xsl:param name="compositionChecksum" />

		<!-- Safety check to prevent infinite recursion -->
		<xsl:if test="$recursionDepth &lt;= 10">
			<!-- Extract common section properties -->
			<xsl:variable name="sectionTitle" select="fhir:title/@value"/>
			<xsl:variable name="sectionCode" select="fhir:code/fhir:coding/fhir:code/@value"/>
			<xsl:variable name="hasContent" select="boolean(fhir:text/xhtml:div/node())"/>

			<!-- Determine CSS class for this section -->
			<xsl:variable name="class">
				<xsl:call-template name="get-css-class">
					<xsl:with-param name="classPrefix" select="$classPrefix"/>
					<xsl:with-param name="level" select="$level"/>
					<xsl:with-param name="sectionCode" select="$sectionCode"/>
				</xsl:call-template>
			</xsl:variable>

			<!-- Create a container for this section -->
			<div id="sec-{$sectionCode}-{$compositionChecksum}">
				<!-- Process section title if present -->
				<xsl:if test="$sectionTitle">
					<xsl:call-template name="process-section-title">
						<xsl:with-param name="title" select="$sectionTitle"/>
						<xsl:with-param name="class" select="$class"/>
					</xsl:call-template>
				</xsl:if>

				<!-- Process XHTML content if present -->
				<xsl:if test="$hasContent">
					<xsl:apply-templates select="fhir:text/xhtml:div"/>
				</xsl:if>

				<!-- Process nested sections recursively -->
				<xsl:apply-templates select="fhir:section">
					<xsl:with-param name="level" select="$level + 1"/>
					<xsl:with-param name="classPrefix" select="$classPrefix"/>
					<xsl:with-param name="recursionDepth" select="$recursionDepth + 1"/>
					<xsl:with-param name="compositionChecksum" select="$compositionChecksum" />
				</xsl:apply-templates>
			</div>
		</xsl:if>

		<!-- Show error message if maximum recursion depth is exceeded -->
		<xsl:if test="$recursionDepth > 10">
			<xsl:call-template name="error-message">
				<xsl:with-param name="message">
					Maximum section nesting depth exceeded. Check for circular references.
				</xsl:with-param>
			</xsl:call-template>
		</xsl:if>
	</xsl:template>

	<!-- 
    * Process Section Title Template
    * Handles different section title formatting based on document type and level
    -->
	<xsl:template name="process-section-title">
		<xsl:param name="title"/>
		<xsl:param name="class"/>

		<xsl:choose>
			<!-- 
            * Special formatting for numbered sections in:
            * - SMPC level 2 and 3
            * - Annex II level 2
            * - Package Leaflet level 2
            -->
			<xsl:when test="
                $class = 'epi__smpc--level-2' or
                $class = 'epi__smpc--level-3' or
                $class = 'epi__annexii--level-2' or
                $class = 'epi__packageleaflet--level-2'
            ">
				<xsl:call-template name="format-numbered-title">
					<xsl:with-param name="title" select="$title"/>
					<xsl:with-param name="class" select="$class"/>
				</xsl:call-template>
			</xsl:when>

			<!-- Special formatting for Annex II level 3 (bullet points) -->
			<xsl:when test="$class = 'epi__annexii--level-3'">
				<p class="{$class} epi-row">
					<span class="epi-row__number">&#8226;&#160;</span>
					<span class="epi-row__aligned-text">
						<xsl:value-of select="$title"/>
					</span>
				</p>
			</xsl:when>

			<!-- Special formatting for Labelling level 2 -->
			<xsl:when test="$class = 'epi__labelling--level-2'">
				<xsl:call-template name="format-labelling-title">
					<xsl:with-param name="title" select="$title"/>
					<xsl:with-param name="class" select="$class"/>
				</xsl:call-template>
			</xsl:when>

			<!-- Special formatting for Labelling level 3 -->
			<xsl:when test="$class = 'epi__labelling--level-3'">
				<div class="{concat($class, '-border')}">
					<xsl:call-template name="format-numbered-title">
						<xsl:with-param name="title" select="$title"/>
						<xsl:with-param name="class" select="$class"/>
					</xsl:call-template>
				</div>
			</xsl:when>

			<!-- Default formatting for all other section types -->
			<xsl:otherwise>
				<p class="{$class}">
					<xsl:value-of select="$title"/>
				</p>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- 
    * Format Numbered Title Template
    * Extracts number and text from section titles and formats them properly
    -->
	<xsl:template name="format-numbered-title">
		<xsl:param name="title"/>
		<xsl:param name="class"/>

		<p class="{$class} epi-row">
			<xsl:choose>
				<xsl:when test="contains($title, ' ')">
					<!-- Extract the number portion (before first space) -->
					<xsl:variable name="listindex" select="substring-before($title, ' ')"/>
					<!-- Format number with non-breaking space -->
					<span class="epi-row__number">
						<xsl:value-of select="concat($listindex, '&#160;')"/>
					</span>
					<!-- Format text content (after number and space) -->
					<span class="epi-row__aligned-text">
						<xsl:value-of select="substring-after($title, concat($listindex, ' '))"/>
					</span>
				</xsl:when>
				<xsl:otherwise>
					<!-- No space found, output the whole title -->
					<span class="aligned-text">
						<xsl:value-of select="$title"/>
					</span>
				</xsl:otherwise>
			</xsl:choose>
		</p>
	</xsl:template>

	<!-- 
    * Format Labelling Title Template
    * Handles special keywords in labelling titles
    -->
	<xsl:template name="format-labelling-title">
		<xsl:param name="title"/>
		<xsl:param name="class"/>

		<div class="{concat($class, '-border')}">
			<xsl:variable name="keyword">
				<xsl:choose>
					<xsl:when test="contains($title, ':')">:</xsl:when>
					<xsl:otherwise></xsl:otherwise>
				</xsl:choose>
			</xsl:variable>

			<xsl:choose>
				<xsl:when test="$keyword != ''">
					<p class="{$class}">
						<xsl:value-of select="normalize-space(substring-before($title, $keyword))" />
					</p>
					<p class="{$class}">&#160;</p>
					<p class="{$class}">
						<xsl:value-of select="normalize-space(substring-after($title, $keyword))" />
					</p>
				</xsl:when>
				<xsl:otherwise>
					<!-- If no special keyword, output title as single paragraph -->
					<p class="{$class}">
						<xsl:value-of select="$title"/>
					</p>
				</xsl:otherwise>
			</xsl:choose>
		</div>
	</xsl:template>

	<!-- 
    * Image Processing Template
    * Converts FHIR Binary references to inline Base64-encoded images
    * Replaces <img src="#ID"> with <img src="data:mime;base64,content">
    * Includes error handling for missing images
    -->
	<xsl:template match="xhtml:img">
		<xsl:variable name="img_src" select="@src"/>

		<xsl:choose>
			<xsl:when test="starts-with($img_src, '#')">
				<!-- Extract the image ID from the src attribute (removes the leading #) -->
				<xsl:variable name="img_id" select="substring($img_src, 2)"/>
				<!-- Lookup the Binary resource with this ID -->
				<xsl:variable name="binary_resource" select="//fhir:contained/fhir:Binary[fhir:id/@value = $img_id]"/>

				<xsl:choose>
					<xsl:when test="$binary_resource">
						<!-- Lookup the Base64-encoded data and content type -->
						<xsl:variable name="encoded_data" select="$binary_resource/fhir:data/@value"/>
						<xsl:variable name="content_type" select="$binary_resource/fhir:contentType/@value"/>

						<xsl:choose>
							<xsl:when test="$encoded_data and $content_type">
								<!-- Create new img element with data URI -->
								<xsl:element name="img">
									<xsl:attribute name="src">
										<xsl:text>data:</xsl:text>
										<xsl:value-of select="$content_type"/>
										<xsl:text>;base64,</xsl:text>
										<xsl:value-of select="$encoded_data"/>
									</xsl:attribute>
									<!-- Copy any other attributes -->
									<xsl:for-each select="@*[not(name()='src')]">
										<xsl:copy/>
									</xsl:for-each>
								</xsl:element>
							</xsl:when>
							<xsl:otherwise>
								<xsl:call-template name="error-message">
									<xsl:with-param name="message">
										Missing image data or content type for image ID: <xsl:value-of select="$img_id"/>
									</xsl:with-param>
								</xsl:call-template>
							</xsl:otherwise>
						</xsl:choose>
					</xsl:when>
					<xsl:otherwise>
						<xsl:call-template name="error-message">
							<xsl:with-param name="message">
								Referenced image not found: <xsl:value-of select="$img_id"/>
							</xsl:with-param>
						</xsl:call-template>
					</xsl:otherwise>
				</xsl:choose>
			</xsl:when>
			<xsl:otherwise>
				<!-- Pass through external image references unchanged -->
				<xsl:copy-of select="."/>
			</xsl:otherwise>
		</xsl:choose>
	</xsl:template>

	<!-- 
    * Generic XHTML Element Template
    * Preserves all other XHTML elements and their attributes
    * Enables passing through original HTML formatting intact
    -->
	<xsl:template match="xhtml:*">
		<xsl:copy>
			<xsl:copy-of select="@*"/>
			<xsl:apply-templates select="node()"/>
		</xsl:copy>
	</xsl:template>

	<!-- 
    * Root Template - Starting point of the transformation
    * Matches the document root node and creates the basic HTML structure
    * Overriding the default root template with standard HTML structure
    -->
	<xsl:template match="/">
		<!-- Set document language based on the Composition language value attribute -->
		<xsl:variable name="lang" select="//fhir:Composition/fhir:language/@value"/>
		<html lang="{$lang}">
			<head>
				<meta charset="UTF-8" />
				<title>
					<!-- Set document title based on List title value attribute or the Composition title value attribute -->
					<xsl:choose>
						<xsl:when test="//fhir:List/fhir:title/@value">
							<xsl:value-of select="//fhir:List/fhir:title/@value"/>
						</xsl:when>
						<xsl:when test="//fhir:Composition/fhir:title/@value">
							<xsl:value-of select="//fhir:Composition/fhir:title/@value"/>
						</xsl:when>
						<xsl:otherwise>Electronic Product Information</xsl:otherwise>
					</xsl:choose>
				</title>
				<!-- Link to external CSS stylesheet for styling -->
				<link rel="stylesheet" href="ema-epi-qrd.css"/>
			</head>
			<body>
				<!-- Validate that required resources exist -->
				<xsl:choose>
					<xsl:when test="not(//fhir:Bundle)">
						<xsl:call-template name="error-message">
							<xsl:with-param name="message">No FHIR Bundle found in the document</xsl:with-param>
						</xsl:call-template>
					</xsl:when>
					<xsl:when test="not(//fhir:Bundle/fhir:entry/fhir:resource/fhir:Composition)">
						<xsl:call-template name="error-message">
							<xsl:with-param name="message">No Composition resource found in the Bundle</xsl:with-param>
						</xsl:call-template>
					</xsl:when>
					<xsl:otherwise>
						<!-- Process each Composition -->
						<xsl:apply-templates select="//fhir:Bundle/fhir:entry/fhir:resource/fhir:Composition"/>
					</xsl:otherwise>
				</xsl:choose>
			</body>
		</html>
	</xsl:template>

</xsl:stylesheet>