# OZサーバーバインディング

## 前提条件 <a href="#prerequisites" id="prerequisites"></a>

### OZサーバライセンス <a href="#oz-server-license" id="oz-server-license"></a>

下記のような権限を持つOZサーバライセンスファイル (`WEB-INF/license/ozlicense.xml`) が必要です。　

```
USE-SERVERBIND="TRUE"
```

OZサーバは、OZサーババインディングを使用するために以下のような4つのファイル&#x3092;*`WEB-INF/lib/`*&#x914D;下に配置する必要があります。&#x20;

<div align="left"><img src="https://gblobscdn.gitbook.com/assets%2F-M0LRvAG2w89F7pN5LX0%2F-M0LS-5iLuHl7xvC2A8d%2F-M0LYQKe5CEdYNI2362W%2Ftech-server-binding.png?alt=media&#x26;token=7e42d878-dfd4-4cfe-b133-ef55daf53dce" alt=""></div>

### 設定 <a href="#settings" id="settings"></a>

設定ファイル `WEB-INF/conf/spmgr.properties` を開き、下記のように必要な権限を設定します。

```bash
allow_exportbind_service=true # uncomment 1 and set to true
exportbind_sessionkey= # uncomment
```

## OZRエクスポート <a href="#exporting-ozr-with-inputjson" id="exporting-ozr-with-inputjson"></a>

1. クライアント側(html)で入力値をサーバに伝送します。
2. サーバ側のjspプログラムはOZサーバに要請します。 program requests the OZ server to bind the input data with OZR and export it to OZD.

{% hint style="info" %}
OZD (OZデータファイル)は、OZRとその入力値を含んでいます。
{% endhint %}

### export-inputjson.html <a href="#export-inputjson-html" id="export-inputjson-html"></a>

1. OZRを開き、ユーザーから入力値を取得します。
2. 下記のような関数でOZビューアから入力値を取得します。

   `OZViewer.GetInformation("INPUT_JSON_ALL");`
3. server-binding-inputjson.jspに入力値をパラメータで伝送します。

### export-inputjson.jsp <a href="#export-inputjson-jsp" id="export-inputjson-jsp"></a>

1. パラメータで入力値を受け取ります。
2. 下記のようなビューアオプションを使用します。

   OZRフォームを開く ( connection.reportname )

   OZRにinputjsonを事前バインディングする ( connection.inputjson )

   OZDでエクスポートする ( export.format )
3. OZサーバにバインディングとエクスポートを要請します。

   `request.setAttribute("OZViewerExportParam", param);`

   `getRequestDispatcher("/server");`

   `dispatcher.include(request, response);`
4. OZサーバから結果を取得します。

   `request.getAttribute("OZViewerExportResult");`

{% tabs %}
{% tab title="export-inputjson.html" %}

```markup
<!DOCTYPE html>
<html>
<html style="height:100%">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" type="text/css"/>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="/oz/HTML5viewer/ui.dynatree.css" type="text/css"/>
<script type="text/javascript" src="/oz/HTML5viewer/jquery.dynatree.js" charset="utf-8"></script>
<script type="text/javascript" src="/oz/HTML5viewer/OZJSViewer.js" charset="utf-8"></script>
</head>

<script>
function sendForm() {
	var input_data = OZViewer.GetInformation("INPUT_JSON_ALL");
	document.form.input.value = input_data;
}
</script>

<form name="form" method="POST" action="export-inputjson.jsp">
  <input type="hidden" name="input" value="">
  <input type="submit" value="Export to OZD on Server" onclick="sendForm()">
</form> 

<body style="width:98%;height:98%">
<div id="OZViewer" style="width:98%;height:98%"></div>
<script type="text/javascript" >
	var serverUrl = "https://" + location.host;
	function SetOZParamters_OZViewer(){
		var oz = document.getElementById("OZViewer");
		oz.sendToActionScript("information.debug", "true");
		oz.sendToActionScript("connection.servlet",serverUrl + "/oz/server");
		oz.sendToActionScript("connection.reportname","guide/server-binding/export.ozr");
		oz.sendToActionScript("global.language", "en_US");
		return true;
	}
	start_ozjs("OZViewer", serverUrl + "/oz/HTML5viewer/");
</script>
</body>
</html>
```

{% endtab %}

{% tab title="export-inputjson.jsp" %}

```javascript
<%@ page contentType="charset=UTF-8" autoFlush="true"%>
<%@ page import="java.util.*,java.io.*"%><%!

	public String getParameter(HttpServletRequest req, String key) {
		String value = req.getParameter(key);
		return value;
	}	
	   
	public boolean writefile(byte[] b, String path)
	{
		BufferedOutputStream fout = null;
		boolean result = false;
		try{
			fout = new BufferedOutputStream(new FileOutputStream(path));
			fout.write(b);
			fout.flush();
			fout.close();
			fout = null;
			result = true;
		}catch(Exception e){
			result = false; 
		}finally{
			if(fout!=null) try{fout.close();}catch(Exception e){}
		}
		return result;
	}
%><%
try {
		String input = getParameter(request,"input");
		String jsondata = input.replaceAll("\"","\\\"");
		out.println("inputjson=<br>" + input);

		String sourceFile = "guide\\server-binding\\export.ozr";
		String targetType = "attachment"; // : to save the target file as a file. 
						 // "inline": to open the target file with browser
		String targetFormat = "ozd"; 
		String targetFile = "export." + targetFormat;
		String targetFolder = "C:\\Program Files\\Apache Software Foundation\\Tomcat 8.5\\webapps\\oz\\guide\\server-binding\\";
		File tf = new File(targetFolder);
		if (!tf.exists()) { new File(targetFolder).mkdir(); }
      	String targetPath = targetFolder + targetFile;	

		// delete if pdf exists
		File pdffile = new File(targetFile);
        if(pdffile.delete()){}else{} 

		// OZ parameters 	
		Hashtable param = new Hashtable();	
		param.put("information.debug", "true");  // enable viewer console
		param.put("export.mode", "silent");
		param.put("export.confirmsave", "flase"); // no confirm = silent
		param.put("export.saveonefile", "true"); 
		param.put("export.format", targetFormat);
		param.put("export.path", targetFolder);
		param.put("export.filename", targetFile);	
		param.put("connection.inputjson", jsondata); // fill values into components
		param.put("connection.reportname", sourceFile);
		param.put("pdf.fontembedding", "true"); // embed the font used in the OZR into the target pdf
		param.put("ozd.allowreplaceformparam", "true"); // allow to pass form parameter

		// request OZ server to bind and export
		request.setAttribute("OZViewerExportParam", param);
		RequestDispatcher dispatcher = pageContext.getServletContext().getRequestDispatcher("/server");		
		dispatcher.include(request, response);

		// get result from OZ server 
		boolean isSaved = false;
		Object o = request.getAttribute("OZViewerExportResult");
		if (o == null) { // Server Error
			Throwable t = (Throwable) request.getAttribute("OZViewerExportError");
			if(t != null) {
				throw t;
			} else {
				throw new Exception("No result from OZ Server.");
			}
		} else { // save as file in target format
			Hashtable t = (Hashtable) o;
			byte[] b = (byte[]) t.get(targetPath);
			if (b != null) {				
				isSaved = writefile(b, targetPath);
			} else {
				throw new Exception("targetPath is required: " + targetPath);
			}			
			response.setContentType("text/html");
			if (isSaved) {   
				out.println("<br><br>Successfully exported. <a href=\"https://demo.ozeform.io/oz/guide/server-binding/export.ozd\">Download OZD</a>");
			} 
		}
	} catch (Throwable e) {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		e.printStackTrace(pw);
		response.sendError(500, sw.getBuffer().toString());
	} finally {
	}
%>
```

{% endtab %}
{% endtabs %}

### pdfのフォント埋め込み <a href="#embedding-fonts-in-pdf" id="embedding-fonts-in-pdf"></a>

pdfエクスポートする際、OZRで使用するフォントをPDFファイルの埋め込むためには、サーバにフォントをインストールした後、下記のようなパラメータオプションを使用します。

```
param.put("pdf.fontembedding", "true"); // embedd all fonts
param.put("pdf.fontembedding_subset", "true"); // embedd only selected fonts
```

## メモリーストリームとしてエクスポート <a href="#exporting-ozr-to-memory-stream" id="exporting-ozr-to-memory-stream"></a>

1. クライアント (html)は、OZRフォームとそのデータをOZDタイプでメモリーストリームエクスポートを行なった後、サーバに伝送します。.
2. サーバ側のjspプログラムはOZDメモリーストリームを受け取り、OZDファイルで保存します。.

### export-memorystream.html <a href="#export-memorystream-html" id="export-memorystream-html"></a>

1. OZRフォームを開き、ユーザーから入力値を取得します。
2. 下記のようなスクリプトを利用し、現在のビューアからデータと共にOZRをOZDメモリーストリームとしてエクスポートします。

   `ScriptEx("save_memorystream", "export.format=ozd;");`

   `OZCommand_ozviewer(cmd, msg){};`

   `OZExportMemoryStreamCallBack_ozviewer(outputdata){}`
3. 当該ファイルストリームがBASE64でインコーディングされ、出力データ`outputdata` としてリターンされます。
4. サーバにファイルストリームを伝送します。

### export-memorystream.jsp <a href="#export-memorystream-jsp" id="export-memorystream-jsp"></a>

1. ファイルストリームを取得します。
2. ファイルストリームをデコードします。
3. OZDファイルとして保存します。

{% tabs %}
{% tab title="export-stream.html" %}

```markup
<!DOCTYPE html>
<html style="height:100%">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" type="text/css"/>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="/oz/HTML5viewer/ui.dynatree.css" type="text/css"/>
<script type="text/javascript" src="/oz/HTML5viewer/jquery.dynatree.js" charset="utf-8"></script>
<script type="text/javascript" src="/oz/HTML5viewer/OZJSViewer.js" charset="utf-8"></script>
</head>
<body topmargin="0" leftmargin="0" style="width:98%;height:98%;webkit-text-size-adjust:none;">

<div> 
	<form>
		<input type="button" value="Export to OZD stream" onclick="Save_MemoryStream()">
		<input type="hidden" id="strOZD"	name="strOZD">
	</form>
	
</div>

<div id="ozviewer" style="width:98;height:90%"></div>

<script type="text/javascript" >

	function Save_MemoryStream() {
		ozviewer.ScriptEx("save_memorystream", "export.format=ozd; ozd.saveall=true; ozd.allowreplaceformparam=true; export.path=C:\\TEMP;export.mode=silent;export.filename=temp;export.confirmsave=false",";");	
		// export.path=C:\\TEMP -> "C:\\TEM" is just a dummy value, no meaning, but the option is required.
	}
	function OZCommand_ozviewer(cmd, msg) {
		// Do not remove this function. This is required for "save_memorystream" option to work correctly.
	}
	function OZExportMemoryStreamCallBack_ozviewer(outputdata) {			   
		if(outputdata == "{}") {
		  alert("Export failed.");
		}else {
			var obj = eval('(' + outputdata + ')');
			var value = null;
			for(var key in obj) value = obj[key];

			$('#strOZD').val(value);
			var param = $('form').serialize();	
 			var url = "./export-stream.jsp";

			$.ajax({
				type : "POST",
				url : url,
				data : param,
				async : false,
				success : sucessCallback,
				error : function(request, status, error) {
					if (request.status != '0') {
						alert("code : " + request.status + "\r\nmessage : "	+ request.reponseText + "\r\nerror : " + error);
					}
				}
			});
		}	   
    }
	var sucessCallback = function(data){
		document.write("Export complete. <a href=\"https://demo.ozeform.io/oz/guide/server-binding/export.ozd\">Download OZD</a>");
	};

    function SetOZParamters_ozviewer()	{
		var serverUrl = "https://" + location.host;
        var oz = document.getElementById("ozviewer");
		oz.sendToActionScript("information.debug", "true");
		oz.sendToActionScript("connection.servlet", serverUrl + "/oz/server");
		oz.sendToActionScript("connection.reportname","guide/server-binding/export.ozr");
		oz.sendToActionScript("global.language", "en_US");
        return true;
    }
    start_ozjs("ozviewer", "/oz/HTML5viewer/");

</script>
</body>
</html>
```

{% endtab %}

{% tab title="export-stream.jsp" %}

```javascript
<%@ page language="java" import="java.io.*,sun.misc.*,org.xml.sax.InputSource,org.w3c.dom.*, javax.xml.parsers.*, java.util.*, java.text.*" contentType="text/html; charset=UTF-8" autoFlush="true"%><%!
	public String encoder(String key) {
		return new BASE64Encoder().encode(key.getBytes());
	}
	public byte[] decoder(String cipher) throws IOException {
		return new BASE64Decoder().decodeBuffer(cipher);
	} 
%><%
try {
	String _PATH = "C:/Program Files/Apache Software Foundation/Tomcat 8.5/webapps/oz/guide/server-binding/"; 
	String _FILE	= "export";
	String _FILE_PATH = _PATH +_FILE + ".ozd";
	
	request.setCharacterEncoding("utf-8");
	String base64Str =  request.getParameter("strOZD");
	//System.out.println(base64Str);
	byte[] decodedBytes = new BASE64Decoder().decodeBuffer(base64Str.substring(base64Str.indexOf(",")+1));
	try {	
		FileOutputStream fout = new FileOutputStream(_FILE_PATH);
	    fout.write(decodedBytes, 0, decodedBytes.length);
	    fout.close();
        System.out.println("OZD saved.");
		out.println(_FILE_PATH);
	} catch (IOException e) {
		e.printStackTrace();
	} 
}
catch(Exception e) {
	out.print(e.getMessage());
} finally {
}
%>
```

{% endtab %}
{% endtabs %}

## サーバ側へのpdfエクスポート(OZD) <a href="#exporting-ozd-to-pdf-at-server-side" id="exporting-ozd-to-pdf-at-server-side"></a>

サーバ側のjspプログラムはconnection.reportnameの代わりにconnection.openfileのオプションを利用し、OZDファイルを開きます。

```bash
String sourceFile = "file://C:\\Program Files\\Apache Software Foundation\\Tomcat 8.5\\webapps\\oz\\guide\\server-binding\\export.ozd";
param.put("connection.openfile", sourceFile); // open ozd
```

{% hint style="warning" %}
ozd.allowreplaceformparamオプションの値をtrueに設定したOZDのみ、OZフォームパラメータを使用することが可能です。上記の例示にも下記のようなコードを含んでいます。

`param.put("ozd.allowreplaceformparam", "true"); ozviewer.ScriptEx(ozd.allowreplaceformparam=true;)`
{% endhint %}

inputjsonを渡して入力項目の入力値を事前入力することも可能ですが、このサンプルではコメントアウト処理されています。

{% tabs %}
{% tab title="export-ozd2pdf.jsp" %}

```javascript

<%@ page contentType="charset=UTF-8" autoFlush="true"%>
<%@ page import="java.util.*,java.io.*"%><%!
	   
	public boolean writefile(byte[] b, String path)
	{
		BufferedOutputStream fout = null;
		boolean result = false;
		try{
			fout = new BufferedOutputStream(new FileOutputStream(path));
			fout.write(b);
			fout.flush();
			fout.close();
			fout = null;
			result = true;
		}catch(Exception e){
			result = false; 
		}finally{
			if(fout!=null) try{fout.close();}catch(Exception e){}
		}
		return result;
	}
%><%
try {
    // add inputjson
		// String input = "{\"no\":\"1200\",\"name\":\"John Kim\",\"email\":\"john.kim@forcs.com\",\"title\":\"Consultant\"}";
		// String jsondata = input.replaceAll("\"","\\\"");
		// out.println(jsondata);

		// source and target	
		String sourceFile = "file://C:\\Program Files\\Apache Software Foundation\\Tomcat 8.5\\webapps\\oz\\guide\\server-binding\\export.ozd";
		//String sourceFile = "http://localhost/oz/guide/server-binding/export.ozd";
		
		String targetType = "attachment"; // : to save the target file as a file. 
		String targetFormat = "pdf"; // target document format
		String targetFile = "export.pdf";
		String targetFolder = "C:\\Program Files\\Apache Software Foundation\\Tomcat 8.5\\webapps\\oz\\guide\\server-binding\\";

		// target path
		File tf = new File(targetFolder);
		if (!tf.exists()) { new File(targetFolder).mkdir(); }
      	String targetPath = targetFolder + targetFile;	

		// prepare OZ parameters 
		java.util.Date date=new java.util.Date();  	
		Hashtable param = new Hashtable(); 
		param.put("connection.openfile", sourceFile); // open ozd
		param.put("connection.pcount", "1");
		param.put("connection.args1", "date="+date);
		param.put("export.format", targetFormat);
		param.put("export.path", targetFolder);
		param.put("export.filename", targetFile);	
		param.put("pdf.fontembedding", "true");
		param.put("html.charset", "unicode");
		param.put("tiff.savemultipage", "true");
		param.put("export.saveonefile", "true");
		param.put("viewer.useractioncommand", "true");	
	  // memo options	
		param.put("pdf.savecomment", "true");
		param.put("memo.exportoption","nothing");
		
		// replace values of components using inputjson
		//param.put("connection.inputjson", jsondata); 

	  // pass inputjson to replace component values
		request.setAttribute("OZViewerExportParam", param);
		String OZserver = "/server"; // OZ Server path
		RequestDispatcher dispatcher = pageContext.getServletContext().getRequestDispatcher(OZserver);		
		dispatcher.include(request, response);

		// get result from OZ server 
		boolean isSaved = false;
		Object o = request.getAttribute("OZViewerExportResult");
		if (o == null) {
			// Server Error
			Throwable t = (Throwable) request.getAttribute("OZViewerExportError");
			if(t != null) {
				throw t;
			} else {
				throw new Exception("No result from OZ Server.");
			}
		} else {
			// save as file in target format
			Hashtable t = (Hashtable) o;

			byte[] b = (byte[]) t.get(targetPath);
			if (b != null) {				
				isSaved = writefile(b, targetPath);
			} else {
				throw new Exception("targetPath is required: " + targetPath);
			}
			
			response.setContentType("text/html");

			if (isSaved)
			{   
				out.println("<br><br>Successfully exported. <a href=\"https://demo.ozeform.io/oz/guide/server-binding/export.pdf\">open PDF</a>");
			} 
		}

	} catch (Throwable e) {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw);
		e.printStackTrace(pw);
		response.sendError(500, sw.getBuffer().toString());
	} finally {
	}
%>
```

{% endtab %}
{% endtabs %}

## Consolidation with Empty Viewer <a href="#consolidation-with-empty-viewer" id="consolidation-with-empty-viewer"></a>

今回は空ビューアを開き、OZRとOZDを開くことが可能なボタンを提供します。ユーザーがSubmitボタンを利用して入力値を提出すると、OZRまたはOZDがOZDファイルとしてサーバに伝送されます。OZDファイルをエクスポートする際、以前の例題のように生成されたOZDがフォームパラメータを許可するようにする必要があります。ユーザーはOZDファイルを開いてアップデートし、OZDとして再びエクスポートをすることが可能できます。PDFエクスポートボタンは、サーバでexport-ozd2pdf.jspを実行し、PDFを生成します。

{% tabs %}
{% tab title="export-form2ozd.html" %}

```markup
<!DOCTYPE html>
<html style="height:100%">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css" type="text/css"/>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
<link rel="stylesheet" href="/oz/HTML5viewer/ui.dynatree.css" type="text/css"/>
<script type="text/javascript" src="/oz/HTML5viewer/jquery.dynatree.js" charset="utf-8"></script>
<script type="text/javascript" src="/oz/HTML5viewer/OZJSViewer.js" charset="utf-8"></script>

<script type="text/javascript" src="/oz/HTML5viewer/pdf_js/web/compatibility.js"></script>  
<script type="text/javascript" src="/oz/HTML5viewer/pdf_js/build/pdf.js"></script>
</head>
<body topmargin="0" leftmargin="0" style="width:98%;height:98%;webkit-text-size-adjust:none;">

Form File: <input type="text" id="file" size="100" value="">

<div> 
	<form>
		<input type="hidden" id="strOZD"	name="strOZD">
		<input type="button" value="open OZR" onClick="openOZR()">
		<input type="button" value="SUBMIT" onclick="Save_MemoryStream()">		
		<input type="button" value="open OZD" onClick="openOZD()">	
		<a href="https://demo.ozeform.io/oz/guide/server-binding/export-ozd2pdf.jsp">Export to PDF</a>
	</form>
</div>

<div id="ozviewer" style="width:98;height:90%"></div>

<script type="text/javascript" >

	function Save_MemoryStream() {
		ozviewer.ScriptEx("save_memorystream", "export.format=ozd; ozd.saveall=true; ozd.allowreplaceformparam=true; export.path=C:\\TEMP;export.mode=silent;export.filename=temp;export.confirmsave=false",";");	
		// export.path=C:\\TEMP -> "C:\\TEM" is just a dummy value, no meaning, but the option is required.
	}
	function OZCommand_ozviewer(cmd, msg) {
		// Do not remove this function. This is required for "save_memorystream" option to work correctly.
	}
	function OZExportMemoryStreamCallBack_ozviewer(outputdata) {			   
		if(outputdata == "{}") {
		  alert("Export failed.");
		}else {
			var obj = eval('(' + outputdata + ')');
			var value = null;
			for(var key in obj) value = obj[key];

			$('#strOZD').val(value);
			var param = $('form').serialize();	
 			var url = "./export-stream.jsp";

			$.ajax({
				type : "POST",
				url : url,
				data : param,
				async : false,
				success : sucessCallback,
				error : function(request, status, error) {
					if (request.status != '0') {
						alert("code : " + request.status + "\r\nmessage : "	+ request.reponseText + "\r\nerror : " + error);
					}
				}
			});
		}	   
    }
	var sucessCallback = function(data){
		alert("Export complete.");
	};
	
	function openOZR(){	
		var form = "connection.reportname=guide/server-binding/export.ozr; ";
		document.getElementById("file").value = form;
		var param = form + "connection.servlet=/oz/server; viewer.pagedisplay=singlepagecontinuous; comment.all=true; comment.selectedpen=highlightpen;";
		ozviewer.Script("closeall");
		ozviewer.CreateReportEx(param, ";");
    }

	function openOZD(){
	var date = new Date();
		var form = "connection.openfile=https://demo.ozeform.io/oz/guide/server-binding/export.ozd; ";
		document.getElementById("file").value = form;
		var formparam = "connection.pcount=1; connection.args1=date=" + date.toISOString() + ";";
		var param = form + formparam + "connection.servlet=/oz/server; viewer.pagedisplay=singlepagecontinuous; comment.all=true; comment.selectedpen=highlightpen;";
		alert(param);
		ozviewer.Script("closeall");
		ozviewer.CreateReportEx(param, ";");
    }

    function SetOZParamters_ozviewer()	{	
        var oz = document.getElementById("ozviewer");
		oz.sendToActionScript("viewer.emptyframe", "true");
        return true;
    }
    start_ozjs("ozviewer", "/oz/HTML5viewer/");

</script>
</body>
</html>
```

{% endtab %}
{% endtabs %}

​[Run sample](http://oz.ozeform.io/oz/guide/server-binding/server-binding.html)

{% file src="<https://1097513732-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LwWWN-Gla4aA9ONdCK6%2F-MJa85CzwEN-2eRdmuXo%2F-MJa8Xe2dmJKOINCZ724%2Fguide-server-binding.zip?alt=media&token=367fe890-a116-440d-b544-5ba1ed2d2dae>" %}
guiide-server-binding.zip
{% endfile %}
