From 06021f67883d6ad5eb1e65725043b7b88ed97498 Mon Sep 17 00:00:00 2001
From: Anthony Gauchy <anthony.gauchy@belledonne-communications.com>
Date: Wed, 3 Feb 2021 18:08:44 +0100
Subject: [PATCH] CSharp wrapper documentation improvment

 - Update python wrapper generation to genrate LinphoneWrapper.cs with a
   full documentation.
 - Update cmake to generate a html doc site with docfx for C# wrapper
 - minor fixes in lib documentation
---
 coreapi/help/doc/doxygen/doxygen.dox.in    |   2 +-
 include/linphone/types.h                   |   4 +-
 tools/metadoc.py                           | 103 ++++++++++++++++++---
 wrappers/cpp/doxygen.dox.in                |   2 +-
 wrappers/csharp/CMakeLists.txt             |  62 +++++++++++++
 wrappers/csharp/docfx_project/api/index.md |   1 +
 wrappers/csharp/docfx_project/docfx.json   |  44 +++++++++
 wrappers/csharp/docfx_project/index.md     |  25 +++++
 wrappers/csharp/docfx_project/toc.yml      |   4 +
 wrappers/csharp/genwrapper.py              |  18 +++-
 wrappers/csharp/version.txt.in             |   2 +
 wrappers/csharp/wrapper_impl.mustache      |  85 ++++++++++++++---
 12 files changed, 321 insertions(+), 31 deletions(-)
 create mode 100644 wrappers/csharp/docfx_project/api/index.md
 create mode 100644 wrappers/csharp/docfx_project/docfx.json
 create mode 100644 wrappers/csharp/docfx_project/index.md
 create mode 100644 wrappers/csharp/docfx_project/toc.yml
 create mode 100644 wrappers/csharp/version.txt.in

diff --git a/coreapi/help/doc/doxygen/doxygen.dox.in b/coreapi/help/doc/doxygen/doxygen.dox.in
index 2b7f916ebb..40d97b622c 100644
--- a/coreapi/help/doc/doxygen/doxygen.dox.in
+++ b/coreapi/help/doc/doxygen/doxygen.dox.in
@@ -15,7 +15,7 @@
  * - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++)
  * - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift)
  * - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java)
- * - C# (coming soon)
+ * - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs)
  * - Python (coming soon)
  *
  * Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it!
diff --git a/include/linphone/types.h b/include/linphone/types.h
index d5625b79b4..7c415da595 100644
--- a/include/linphone/types.h
+++ b/include/linphone/types.h
@@ -205,8 +205,8 @@ typedef enum _LinphoneAccountCreatorStatus {
 	LinphoneAccountCreatorStatusPhoneNumberInvalid, /**< Error cannot send SMS */
 	LinphoneAccountCreatorStatusWrongActivationCode, /**< Error key doesn't match */
 	LinphoneAccountCreatorStatusPhoneNumberOverused, /**< Error too many SMS sent */
-	LinphoneAccountCreatorStatusAlgoNotSupported, /** < Error algo isn't MD5 or SHA-256 */
-	LinphoneAccountCreatorStatusUnexpectedError, /** < Generic error */
+	LinphoneAccountCreatorStatusAlgoNotSupported, /**< Error algo isn't MD5 or SHA-256 */
+	LinphoneAccountCreatorStatusUnexpectedError, /**< Generic error */
 } LinphoneAccountCreatorStatus;
 
 // -----------------------------------------------------------------------------
diff --git a/tools/metadoc.py b/tools/metadoc.py
index d0d5b10961..81fe46668c 100644
--- a/tools/metadoc.py
+++ b/tools/metadoc.py
@@ -22,6 +22,7 @@ import abstractapi
 import logging
 import metaname
 import re
+import sys
 
 
 class ParsingError(RuntimeError):
@@ -509,19 +510,29 @@ class Translator:
 		lines = []
 		while len(line) > width:
 			cutIndex = line.rfind(' ', 0, width)
-			if cutIndex != -1:
+			if cutIndex >= 0:
 				if self.langCode == 'Java':
 					# Do not break a line in the middle of a { }
 					while (not line[0:cutIndex].count('{') == line[0:cutIndex].count('}')) and (not line[cutIndex:].count('{') == line[cutIndex:].count('}')):
 						cutIndex += 1
-				lines.append(line[0:cutIndex])
-				line = line[cutIndex+1:]
+				if self.langCode == 'CSharp':
+					# Do not break a line in the middle of a xml tag
+					while not line[0:cutIndex].count('<') == line[0:cutIndex].count('>'):
+						cutIndex += 1
+				if line[cutIndex] == ' ':
+					# Don't keep a whitespace at the start of the next line if you cut on one
+					lines.append(line[0:cutIndex])
+					line = line[cutIndex+1:]
+				else:
+					lines.append(line[0:cutIndex])
+					line = line[cutIndex:]
 			else:
 				# Don't break http links
-				cutIndex = len(line) if ('http://' or 'https://') in line else width
+				cutIndex = len(line) if 'http://' or 'https://' in line else width
 				lines.append(line[0:cutIndex])
 				line = line[cutIndex:]
-		lines.append(line)
+		if line:
+			lines.append(line)
 		
 		if indent:
 			lines = [line if line is lines[0] else '\t' + line for line in lines]
@@ -784,15 +795,85 @@ class SphinxTranslator(Translator):
 
 
 class SandCastleTranslator(Translator):
-	def _tag_as_brief(self, lines):
-		if len(lines) > 0:
-			lines.insert(0, '<summary>')
-			lines.append('</summary>')
+	def __init__(self, langCode):
+		super().__init__(langCode)
+		self.isEndTagPlaced = False
+
+	def translate_text(self, textpart):
+		text = super().translate_text(textpart)
+		xmlSpecialCharDict = {'<': '&lt;',
+			'>': '&gt;',
+			"'": '&apos;',
+			'"': '&quot;',
+			'&': '&amp;'}
+		if sys.version_info[0] >= 3:
+			xmlTranslationTable = str.maketrans(xmlSpecialCharDict)
+		else:
+			import string
+			xmlTranslationTable = string.maketrans(xmlSpecialCharDict)
+		return text.translate(xmlTranslationTable)
+
+	def translate_description(self, description, tagAsBrief=False):
+		self.isEndTagPlaced = False
+		translatedDoc = super().translate_description(description, tagAsBrief)
+		if not tagAsBrief:
+			if not self.isEndTagPlaced:
+				translatedDoc['lines'].append({'line': '</para>'})
+				translatedDoc['lines'].append({'line': '</summary>'})
+				self.isEndTagPlaced = True		
+		return translatedDoc
 
 	def translate_function_reference(self, ref):
 		refStr = Translator.translate_reference(self, ref, absName=True)
-		return '<see cref="{0}()" />'.format(refStr)
+		subnResult = re.subn('(\.Get\(\))', '.Instance' , refStr)
+		if subnResult[1] > 0:
+			return '<see cref="{0}">{0}</see>'.format(subnResult[0])
+		subnResult = re.subn('(\.Get|\.Set)', '.' , subnResult[0])
+		if subnResult[1] > 0:
+			return '<see cref="{0}">{0}</see>'.format(subnResult[0])
+		return '<see cref="{0}()">{0}()</see>'.format(subnResult[0])
+		# In every cases we write the same value in the "see" tag value
+		# than in the cref value so that even if the cref is broken the
+		# text is displayed
 
 	def translate_class_reference(self, ref):
 		refStr = Translator.translate_reference(self, ref, absName=True)
-		return '<see cref="{0}" />'.format(refStr)
+		return '<see cref="{0}">{0}</see>'.format(refStr)
+	
+
+	def _translate_parameter_list(self, parameterList):
+		text = ''
+		if not self.isEndTagPlaced:
+			text += '</para>\n'
+			text += '</summary>\n'
+			self.isEndTagPlaced = True
+
+		for paramDesc in parameterList.parameters:
+			if self.displaySelfParam or not paramDesc.is_self_parameter():
+				desc = self._translate_description(paramDesc.desc)
+				desc = desc[0] if len(desc) > 0 else ''
+				text += ('<param name="{0}">{1}</param>\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
+		return text
+	
+	def _translate_section(self, section):
+		text =''
+		if not self.isEndTagPlaced:
+			text += '</para>\n'
+			text += '</summary>\n'
+			self.isEndTagPlaced = True
+
+		if section.kind == 'return':
+			section.kind = '<returns>{0}</returns>'
+		elif section.kind == 'warning':
+			section.kind = '<remarks>Warning : {0}</remarks> '
+		elif section.kind == 'note':
+			section.kind = '<remarks>Note : {0}</remarks>'
+		elif section.kind == 'see':
+			section.kind = '<remarks>See : {0}</remarks>'
+		else:
+			section.kind = section.kind + " : {0}"
+			logging.warning('SandCastle doc translate section pointing on an unknown object ({0})'.format(section.kind))
+
+		text += section.kind.format(self._translate_paragraph(section.paragraph))
+
+		return text
diff --git a/wrappers/cpp/doxygen.dox.in b/wrappers/cpp/doxygen.dox.in
index 8601ad5da4..e9687e7b74 100644
--- a/wrappers/cpp/doxygen.dox.in
+++ b/wrappers/cpp/doxygen.dox.in
@@ -15,7 +15,7 @@
  * - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++)
  * - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift)
  * - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java)
- * - C# (coming soon)
+ * - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs)
  * - Python (coming soon)
  *
  * Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it!
diff --git a/wrappers/csharp/CMakeLists.txt b/wrappers/csharp/CMakeLists.txt
index d5acfcaa42..620bb6299f 100644
--- a/wrappers/csharp/CMakeLists.txt
+++ b/wrappers/csharp/CMakeLists.txt
@@ -38,3 +38,65 @@ add_custom_target(linphonecs ALL DEPENDS LinphoneWrapper.cs)
 install(FILES "${CMAKE_CURRENT_BINARY_DIR}/LinphoneWrapper.cs"
         DESTINATION "${CMAKE_INSTALL_DATADIR}/linphonecs/"
 )
+
+if(ENABLE_DOC AND ENABLE_CSHARP_WRAPPER)
+	message(CHECK_START "Generate C# wrapper html site")
+	find_program(
+        DOCFX_EXECUTABLE
+        NAMES docfx
+    )
+
+	if(DOCFX_EXECUTABLE)
+		file(
+			COPY "${CMAKE_CURRENT_SOURCE_DIR}/docfx_project"
+			DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/" 
+		)
+		
+		# Check git describe to see if we are on a release or not
+		set(LINPHONE_STATE "snapshots")
+		execute_process(COMMAND ${GIT_EXECUTABLE} describe OUTPUT_VARIABLE GIT_DESC)
+		if(NOT GIT_DESC MATCHES ".*(alpha|beta).*")
+			set(LINPHONE_STATE "releases")
+		endif()
+		# Replace @LINPHONE_STATE@ and @LINPHONE_VERSION@ in site main page
+		configure_file(
+			"${CMAKE_CURRENT_BINARY_DIR}/docfx_project/index.md" 
+			"${CMAKE_CURRENT_BINARY_DIR}/docfx_project/index.md"
+		)
+		# Replace @LINPHONE_STATE@ and @LINPHONE_VERSION@ version.txt.in, this
+		# file will be used in doc upload
+		configure_file(
+			"${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in"
+			"${CMAKE_CURRENT_BINARY_DIR}/version.txt"
+		)
+
+		add_custom_command(OUTPUT "docfx_project/_site/index.html"
+			DEPENDS LinphoneWrapper.cs
+			COMMAND ${CMAKE_COMMAND} -E copy
+					"${CMAKE_CURRENT_BINARY_DIR}/LinphoneWrapper.cs"
+					"${CMAKE_CURRENT_BINARY_DIR}/docfx_project/src/LinphoneWrapper.cs"
+			COMMAND ${DOCFX_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/docfx.json"
+		)
+
+		add_custom_target(
+			linphonecs-doc-site 
+			ALL 
+			DEPENDS linphonecs "docfx_project/_site/index.html"
+		)
+
+		install(
+			FILES "${CMAKE_CURRENT_BINARY_DIR}/version.txt"
+			DESTINATION "${CMAKE_INSTALL_DATADIR}/"
+		)
+
+		install(
+			DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/docfx_project/_site/"
+			DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/liblinphone-${LINPHONE_VERSION}/cs"
+		)
+
+		message(CHECK_PASS "generation and copy configured")
+	else()
+		message(CHECK_FAIL "docfx wansn't found, no site generated")
+	
+	endif()
+endif()
diff --git a/wrappers/csharp/docfx_project/api/index.md b/wrappers/csharp/docfx_project/api/index.md
new file mode 100644
index 0000000000..69deb01cd6
--- /dev/null
+++ b/wrappers/csharp/docfx_project/api/index.md
@@ -0,0 +1 @@
+# Liblinphone C# API documentation
diff --git a/wrappers/csharp/docfx_project/docfx.json b/wrappers/csharp/docfx_project/docfx.json
new file mode 100644
index 0000000000..c21402f4e5
--- /dev/null
+++ b/wrappers/csharp/docfx_project/docfx.json
@@ -0,0 +1,44 @@
+{
+  "metadata": [
+    {
+      "src": [
+        {
+          "files": [
+            "src/**.cs"
+          ]
+        }
+      ],
+      "dest": "api",
+      "disableGitFeatures": false,
+      "disableDefaultFilter": false
+    }
+  ],
+  "build": {
+    "content": [
+      {
+        "files": [
+          "api/**.yml",
+          "api/index.md"
+        ]
+      },
+      {
+        "files": [
+          "toc.yml",
+          "*.md"
+        ]
+      }
+    ],
+    "dest": "_site",
+    "globalMetadataFiles": [],
+    "fileMetadataFiles": [],
+    "template": [
+      "statictoc"
+    ],
+    "postProcessors": [],
+    "markdownEngineName": "markdig",
+    "noLangKeyword": false,
+    "keepFileLink": false,
+    "cleanupCacheHistory": false,
+    "disableGitFeatures": false
+  }
+}
\ No newline at end of file
diff --git a/wrappers/csharp/docfx_project/index.md b/wrappers/csharp/docfx_project/index.md
new file mode 100644
index 0000000000..0cdcfa006d
--- /dev/null
+++ b/wrappers/csharp/docfx_project/index.md
@@ -0,0 +1,25 @@
+# Liblinphone Documentation
+
+## What is liblinphone
+
+Liblinphone is a high-level open source library that integrates all the SIP voice/video and instant messaging features into a single easy-to-use API. This is the VoIP SDK engine on which Linphone applications are based.
+
+Liblinphone combines our media processing and streaming toolkit (Mediastreamer2) with our user-agent library for SIP signaling (belle-sip). Liblinphone has support for a variety of languages, each one has its own reference documentation:
+
+ - C (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c)
+ - C++ (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/c++)
+ - Swift (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/swift)
+ - Java (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/java)
+ - C# (https://linphone.org/@LINPHONE_STATE@/docs/liblinphone/@LINPHONE_VERSION@/cs)
+ - Python (coming soon)
+
+Liblinphone is distributed under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html). Please understand the licencing details before using it!
+
+For any use of this library beyond the rights granted to you by the GPLv3 license, please [contact Belledonne Communications](https://www.linphone.org/contact).
+
+## CSharp tutorial for liblinphone
+
+You can find a step by step tutorial to use liblinphone in C# [here](https://gitlab.linphone.org/BC/public/tutorials)
+
+## See also
+http://www.linphone.org
diff --git a/wrappers/csharp/docfx_project/toc.yml b/wrappers/csharp/docfx_project/toc.yml
new file mode 100644
index 0000000000..159adc35a6
--- /dev/null
+++ b/wrappers/csharp/docfx_project/toc.yml
@@ -0,0 +1,4 @@
+- name: Api Documentation
+  href: api/
+  # Default was api/index.md but we prefer to jump directly to api/Linphone.html page
+  homepage: api/Linphone.html
diff --git a/wrappers/csharp/genwrapper.py b/wrappers/csharp/genwrapper.py
index c3f652d4a2..0c1a530d97 100644
--- a/wrappers/csharp/genwrapper.py
+++ b/wrappers/csharp/genwrapper.py
@@ -90,7 +90,8 @@ class CsharpTranslator:
 		methodDict = {}
 		methodDict['prototype'] = "static extern {return} {name}({params});".format(**methodElems)
 
-		methodDict['doc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		methodDict['briefDoc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True) if method.briefDescription is not None else None
+		methodDict['detailedDoc'] = method.detailedDescription.translate(self.docTranslator) if method.detailedDescription is not None else None
 
 		methodDict['has_impl'] = genImpl
 		if genImpl:
@@ -294,6 +295,10 @@ class CsharpTranslator:
 			listenerDict['delegate']['params_private'] += dllImportType + " " + argName
 
 		listenerDict['delegate']["c_name_setter"] = c_name_setter
+
+		listenerDict['delegate']['briefDoc'] = method.briefDescription.translate(self.docTranslator, tagAsBrief=True) if method.briefDescription is not None else None
+		listenerDict['delegate']['detailedDoc'] = method.detailedDescription.translate(self.docTranslator) if method.detailedDescription is not None else None
+		
 		return listenerDict
 
 ###########################################################################################################################################
@@ -301,7 +306,8 @@ class CsharpTranslator:
 	def translate_enum(self, enum):
 		enumDict = {}
 		enumDict['enumName'] = enum.name.translate(self.nameTranslator)
-		enumDict['doc'] = enum.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		enumDict['briefDoc'] = enum.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		enumDict['detailedDoc'] = enum.detailedDescription.translate(self.docTranslator)
 		enumDict['values'] = []
 		enumDict['isFlag'] = False
 		i = 0
@@ -309,7 +315,8 @@ class CsharpTranslator:
 		for enumValue in enum.enumerators:
 			enumValDict = {}
 			enumValDict['name'] = enumValue.name.translate(self.nameTranslator)
-			enumValDict['doc'] = enumValue.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+			enumValDict['briefDoc'] = enumValue.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+			enumValDict['detailedDoc'] = enumValue.detailedDescription.translate(self.docTranslator)
 			if isinstance(enumValue.value, int):
 				lastValue = enumValue.value
 				enumValDict['value'] = str(enumValue.value)
@@ -335,7 +342,8 @@ class CsharpTranslator:
 		classDict['isLinphoneFactory'] = classDict['className'] == "Factory"
 		classDict['isLinphoneCall'] = _class.name.to_camel_case() == "Call"
 		classDict['isLinphoneCore'] = _class.name.to_camel_case() == "Core"
-		classDict['doc'] = _class.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		classDict['briefDoc'] = _class.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		classDict['detailedDoc'] = _class.detailedDescription.translate(self.docTranslator)
 		classDict['dllImports'] = []
 
 		islistenable = _class.listenerInterface is not None
@@ -382,6 +390,8 @@ class CsharpTranslator:
 		interfaceDict['interfaceName'] = interface.name.translate(self.nameTranslator)
 		interfaceDict['set_user_data_name'] = interface.listenedClass.name.to_snake_case(fullName=True) + '_cbs_set_user_data'
 		interfaceDict['get_user_data_name'] = interface.listenedClass.name.to_snake_case(fullName=True) + '_cbs_get_user_data'
+		interfaceDict['briefDoc'] = interface.briefDescription.translate(self.docTranslator, tagAsBrief=True)
+		interfaceDict['detailedDoc'] = interface.detailedDescription.translate(self.docTranslator)
 
 		interfaceDict['methods'] = []
 		for method in interface.instanceMethods:
diff --git a/wrappers/csharp/version.txt.in b/wrappers/csharp/version.txt.in
new file mode 100644
index 0000000000..3b419d8d49
--- /dev/null
+++ b/wrappers/csharp/version.txt.in
@@ -0,0 +1,2 @@
+LINPHONE_VERSION=@LINPHONE_VERSION@
+LINPHONE_STATE=@LINPHONE_STATE@
\ No newline at end of file
diff --git a/wrappers/csharp/wrapper_impl.mustache b/wrappers/csharp/wrapper_impl.mustache
index 811cfbfaa4..83e64ec180 100644
--- a/wrappers/csharp/wrapper_impl.mustache
+++ b/wrappers/csharp/wrapper_impl.mustache
@@ -288,21 +288,37 @@ namespace Linphone
 #endregion
 
 #region Enums
+
 	{{#enums}}
 	{{#enum}}
-	{{#doc}}
+	/// <summary>
+	{{#briefDoc}}
+	{{#lines}}
+	/// {{{line}}}
+	{{/lines}}
+	{{/briefDoc}}
+	{{#detailedDoc}}
+	/// <para>
 	{{#lines}}
 	/// {{{line}}}
 	{{/lines}}
-	{{/doc}}{{#isFlag}}[Flags]{{/isFlag}}
+	{{/detailedDoc}}
+	{{#isFlag}}[Flags]{{/isFlag}}
 	public enum {{enumName}}
 	{
 		{{#values}}
-		{{#doc}}
+		/// <summary>
+		{{#briefDoc}}
+		{{#lines}}
+		/// {{{line}}}
+		{{/lines}}
+		{{/briefDoc}}
+		{{#detailedDoc}}
+		/// <para>
 		{{#lines}}
 		/// {{{line}}}
 		{{/lines}}
-		{{/doc}}
+		{{/detailedDoc}}
 		{{name}} = {{{value}}},
 		{{/values}}
 	}
@@ -314,6 +330,18 @@ namespace Linphone
 #region Listeners
 	{{#interfaces}}
 	{{#interface}}
+	/// <summary>
+	{{#briefDoc}}
+	{{#lines}}
+	/// {{{line}}}
+	{{/lines}}
+	{{/briefDoc}}
+	{{#detailedDoc}}
+	/// <para>
+	{{#lines}}
+	/// {{{line}}}
+	{{/lines}}
+	{{/detailedDoc}}
 	[StructLayout(LayoutKind.Sequential)]
 	public class {{interfaceName}} : LinphoneObject
 	{
@@ -342,7 +370,20 @@ namespace Linphone
 		[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
 		private delegate void {{name_private}}({{params_private}});
 
-		public delegate void {{name_public}}({{{params_public}}});
+		/// <summary>
+		{{#briefDoc}}
+		{{#lines}}
+		/// {{{line}}}
+		{{/lines}}
+		{{/briefDoc}}
+		{{#detailedDoc}}
+		/// <para>
+		{{#lines}}
+		/// {{{line}}}
+		{{/lines}}
+		{{/detailedDoc}}
+		{{#deprecated}}[Obsolete]
+    	{{/deprecated}}public delegate void {{name_public}}({{{params_public}}});
 		private {{name_private}} {{var_private}};
 		private {{name_public}} {{var_public}};
 
@@ -419,12 +460,18 @@ namespace Linphone
 #region Classes
 	{{#classes}}
 	{{#_class}}
-	{{#doc}}
+	/// <summary>
+	{{#briefDoc}}
 	{{#lines}}
 	/// {{{line}}}
 	{{/lines}}
-	{{/doc}}
-
+	{{/briefDoc}}
+	{{#detailedDoc}}
+	/// <para>
+	{{#lines}}
+	/// {{{line}}}
+	{{/lines}}
+	{{/detailedDoc}}
 	[StructLayout(LayoutKind.Sequential)]
 	public class {{className}} : LinphoneObject
 	{
@@ -554,11 +601,18 @@ namespace Linphone
 		{{/has_second_prototype}}
 
 		{{#has_property}}
-		{{#doc}}
+		/// <summary>
+		{{#briefDoc}}
+		{{#lines}}
+		/// {{{line}}}
+		{{/lines}}
+		{{/briefDoc}}
+		{{#detailedDoc}}
+		/// <para>
 		{{#lines}}
 		/// {{{line}}}
 		{{/lines}}
-		{{/doc}}
+		{{/detailedDoc}}
 		{{property_static}}public {{{property_return}}} {{property_name}}
 		{
 		{{#has_getter}}
@@ -628,11 +682,18 @@ namespace Linphone
 		{{/has_property}}
 		{{#has_impl}}
 		{{#impl}}
-		{{#doc}}
+		/// <summary>
+		{{#briefDoc}}
+		{{#lines}}
+		/// {{{line}}}
+		{{/lines}}
+		{{/briefDoc}}
+		{{#detailedDoc}}
+		/// <para>
 		{{#lines}}
 		/// {{{line}}}
 		{{/lines}}
-		{{/doc}}
+		{{/detailedDoc}}
 		public {{static}}{{override}}{{{type}}} {{name}}({{{args}}})
 		{
 			{{#is_string}}
-- 
GitLab