#Region "Copyright © 2001-2003 Jean-Claude Manoli [jc@manoli.net]"
'
' * This software is provided 'as-is', without any express or implied warranty.
' * In no event will the author(s) be held liable for any damages arising from
' * the use of this software.
' * 
' * Permission is granted to anyone to use this software for any purpose,
' * including commercial applications, and to alter it and redistribute it
' * freely, subject to the following restrictions:
' * 
' *   1. The origin of this software must not be misrepresented; you must not
' *      claim that you wrote the original software. If you use this software
' *      in a product, an acknowledgment in the product documentation would be
' *      appreciated but is not required.
' * 
' *   2. Altered source versions must be plainly marked as such, and must not
' *      be misrepresented as being the original software.
' * 
' *   3. This notice may not be removed or altered from any source distribution.
'  
#End Region

Imports System.IO
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Reflection

Namespace Manoli.Utils.CSharpFormat
	''' <summary>
	'''	Provides a base implementation for all code formatters.
	''' </summary>
	''' <remarks>
	''' <para>
	''' To display the formatted code on your web site, the web page must 
	''' refer to a stylesheet that defines the formatting for the different 
	''' CSS classes generated by CSharpFormat:
	''' .csharpcode, pre, .rem, .kwrd, .str, .op, .preproc, .alt, .lnum.
	''' </para>
	''' <para>
	''' Note that if you have multi-line comments in your source code
	''' (like /* ... */), the "line numbers" or "alternate line background" 
	''' options will generate code that is not strictly HTML 4.01 compliant. 
	''' The code will still look good with IE5+ or Mozilla 0.8+. 
	''' </para>
	''' </remarks>
	Public MustInherit Class SourceFormat
		''' <summary/>
		Protected Sub New()
			_tabSpaces = 4
			_lineNumbers = False
			_alternate = False
			_embedStyleSheet = False
		End Sub

		Private _tabSpaces As Byte

		''' <summary>
		''' Gets or sets the tabs width.
		''' </summary>
		''' <value>The number of space characters to substitute for tab 
		''' characters. The default is <b>4</b>, unless overridden is a 
		''' derived class.</value>
		Public Property TabSpaces() As Byte
			Get
				Return _tabSpaces
			End Get
			Set(ByVal value As Byte)
				_tabSpaces = value
			End Set
		End Property

		Private _lineNumbers As Boolean

		''' <summary>
		''' Enables or disables line numbers in output.
		''' </summary>
		''' <value>When <b>true</b>, line numbers are generated. 
		''' The default is <b>false</b>.</value>
		Public Property LineNumbers() As Boolean
			Get
				Return _lineNumbers
			End Get
			Set(ByVal value As Boolean)
				_lineNumbers = value
			End Set
		End Property

		Private _alternate As Boolean

		''' <summary>
		''' Enables or disables alternating line background.
		''' </summary>
		''' <value>When <b>true</b>, lines background is alternated. 
		''' The default is <b>false</b>.</value>
		Public Property Alternate() As Boolean
			Get
				Return _alternate
			End Get
			Set(ByVal value As Boolean)
				_alternate = value
			End Set
		End Property

		Private _embedStyleSheet As Boolean

		''' <summary>
		''' Enables or disables the embedded CSS style sheet.
		''' </summary>
		''' <value>When <b>true</b>, the CSS &lt;style&gt; element is included 
		''' in the HTML output. The default is <b>false</b>.</value>
		Public Property EmbedStyleSheet() As Boolean
			Get
				Return _embedStyleSheet
			End Get
			Set(ByVal value As Boolean)
				_embedStyleSheet = value
			End Set
		End Property

		''' <overloads>Transform source code to HTML 4.01.</overloads>
		''' 
		''' <summary>
		''' Transforms a source code stream to HTML 4.01.
		''' </summary>
		''' <param name="source">Source code stream.</param>
		''' <returns>A string containing the HTML formatted code.</returns>
		Public Function FormatCode(ByVal source As Stream) As String
			Dim reader As New StreamReader(source)
			Dim s As String = reader.ReadToEnd()
			reader.Close()
			Return FormatCode(s, _lineNumbers, _alternate, _embedStyleSheet, False)
		End Function

		''' <summary>
		''' Transforms a source code string to HTML 4.01.
		''' </summary>
		''' <returns>A string containing the HTML formatted code.</returns>
		Public Function FormatCode(ByVal source As String) As String
			Return FormatCode(source, _lineNumbers, _alternate, _embedStyleSheet, False)
		End Function

		''' <summary>
		''' Allows formatting a part of the code in a different language,
		''' for example a JavaScript block inside an HTML file.
		''' </summary>
		Public Function FormatSubCode(ByVal source As String) As String
			Return FormatCode(source, False, False, False, True)
		End Function

		''' <summary>
		''' Gets the CSS stylesheet as a stream.
		''' </summary>
		''' <returns>A text <see cref="Stream"/> of the CSS definitions.</returns>
		Public Shared Function GetCssStream() As Stream
			Return System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("csharp.css")
		End Function

		''' <summary>
		''' Gets the CSS stylesheet as a string.
		''' </summary>
		''' <returns>A string containing the CSS definitions.</returns>
		Public Shared Function GetCssString() As String
			Dim reader As New StreamReader(GetCssStream())
			Return reader.ReadToEnd()
		End Function

		Private _codeRegex As Regex

		''' <summary>
		''' The regular expression used to capture language tokens.
		''' </summary>
		Protected Property CodeRegex() As Regex
			Get
				Return _codeRegex
			End Get
			Set(ByVal value As Regex)
				_codeRegex = value
			End Set
		End Property

		''' <summary>
		''' Called to evaluate the HTML fragment corresponding to each 
		''' matching token in the code.
		''' </summary>
		''' <param name="match">The <see cref="Match"/> resulting from a 
		''' single regular expression match.</param>
		''' <returns>A string containing the HTML code fragment.</returns>
		Protected MustOverride Function MatchEval(ByVal match As Match) As String

		'does the formatting job
		Private Function FormatCode(ByVal source As String, ByVal lineNumbers As Boolean, ByVal alternate As Boolean, ByVal embedStyleSheet As Boolean, ByVal subCode As Boolean) As String
			'replace special characters
			Dim sb As New StringBuilder(source)

			If Not subCode Then
				sb.Replace("&", "&amp;")
				sb.Replace("<", "&lt;")
				sb.Replace(">", "&gt;")
				sb.Replace(vbTab, String.Empty.PadRight(_tabSpaces))
			End If

			'color the code
			source = _codeRegex.Replace(sb.ToString(), New MatchEvaluator(AddressOf Me.MatchEval))

			sb = New StringBuilder()

			If embedStyleSheet Then
				sb.Append("<style type=""text/css"">" & vbLf)
				sb.Append(GetCssString())
				sb.Append("</style>" & vbLf)
			End If

			If lineNumbers OrElse alternate Then 'we have to process the code line by line
				If Not subCode Then
					sb.Append("<div class=""csharpcode"">" & vbLf)
				End If
				Dim reader As New StringReader(source)
				Dim i As Integer = 0
				Dim spaces As String = "    "
				Dim order As Integer
				Dim line As String
				line = reader.ReadLine()
				Do While line IsNot Nothing
					i += 1
					If alternate AndAlso ((i Mod 2) = 1) Then
						sb.Append("<pre class=""alt"">")
					Else
						sb.Append("<pre>")
					End If

					If lineNumbers Then
						order = CInt(Fix(Math.Log10(i)))
						sb.Append("<span class=""lnum"">" & spaces.Substring(0, 3 - order) & i.ToString() & ":  </span>")
					End If

					If line.Length = 0 Then
						sb.Append("&nbsp;")
					Else
						sb.Append(line)
					End If
					sb.Append("</pre>" & vbLf)
					line = reader.ReadLine()
				Loop
				reader.Close()
				If Not subCode Then
					sb.Append("</div>")
				End If
			Else
				'have to use a <pre> because IE below ver 6 does not understand 
				'the "white-space: pre" CSS value
				If Not subCode Then
					sb.Append("<pre class=""csharpcode"">" & vbLf)
				End If
				sb.Append(source)
				If Not subCode Then
					sb.Append("</pre>")
				End If
			End If

			Return sb.ToString()
		End Function

	End Class
End Namespace
