'************************************************** ' FILE : BBCodeParser.vb ' AUTHOR : Paulo Santos ' CREATION : 4/29/2009 11:03:53 AM ' COPYRIGHT : Copyright © 2009 ' PJ on Development ' All Rights Reserved. ' ' Description: ' TODO: Add file description ' ' Change log: ' 0.1 4/29/2009 11:03:53 AM ' Paulo Santos ' Created. '*************************************************** Imports System.Text.RegularExpressions Imports System.Diagnostics.CodeAnalysis ''' ''' The parser of ''' Public NotInheritable Class BBCodeParser(Of TContext As Class) Private __Factory As BBCodeElementFactory(Of TContext) Private __Configuration As BBCodeConfiguration(Of TContext) Private Shared ReadOnly __ConfigSerializer As New System.Xml.Serialization.XmlSerializer(GetType(BBCodeConfiguration(Of TContext))) Private Shared ReadOnly __Tokenizer As Tokenization.Tokenizer = PrepareTokenizer() ''' Initializes an instance of the class. ''' This is the default constructor for this class. Public Sub New() End Sub ''' ''' Gets the dictionary of elements to be replaced by the . ''' Public ReadOnly Property Dictionary() As BBCodeElementDictionary Get If (__Configuration Is Nothing) Then __Configuration = New BBCodeConfiguration(Of TContext)() End If Return __Configuration.Dictionary End Get End Property ''' ''' Gets the dictionary of types created by the parser. ''' Public ReadOnly Property ElementTypes() As BBCodeElementTypeDictionary(Of TContext) Get If (__Configuration Is Nothing) Then __Configuration = New BBCodeConfiguration(Of TContext)() End If Return __Configuration.ElementTypes End Get End Property #Region " LoadConfiguration Methods " ''' ''' Loads the configuration from the specified filename. ''' ''' The name of the file to read the dictionary from. Public Sub LoadConfiguration(ByVal fileName As String) If (String.IsNullOrEmpty(fileName)) Then Throw New ArgumentNullException("fileName") End If Using fileStream As New IO.FileStream(fileName, IO.FileMode.Open) LoadConfiguration(fileStream) End Using End Sub ''' ''' Loads the configuration from the specified . ''' ''' A to read the dictionary from. Public Sub LoadConfiguration(ByVal stream As IO.Stream) LoadConfiguration(New IO.StreamReader(stream, Text.Encoding.UTF8, True)) End Sub ''' ''' Loads the configuration from the specified . ''' ''' The to read the dictionary from. Public Sub LoadConfiguration(ByVal reader As IO.TextReader) Dim dic = __ConfigSerializer.Deserialize(reader) If (dic IsNot Nothing) Then __Configuration = dic End If End Sub #End Region #Region " SaveConfiguration Methods " ''' ''' Saves the conficuration to the specified file. ''' ''' The name of the file to save the dictionary. Public Sub SaveConfiguration(ByVal fileName As String) If (String.IsNullOrEmpty(fileName)) Then Throw New ArgumentNullException("fileName") End If Using fileStream As New IO.FileStream(fileName, IO.FileMode.Create) SaveConfiguration(fileStream) End Using End Sub ''' ''' Saves the conficuration to the specified . ''' ''' The to save the dictionary. Public Sub SaveConfiguration(ByVal stream As IO.Stream) SaveConfiguration(New IO.StreamWriter(stream, Text.Encoding.UTF8)) End Sub ''' ''' Saves the conficuration to the specified . ''' ''' The to save the dictionary. Public Sub SaveConfiguration(ByVal writer As IO.TextWriter) __ConfigSerializer.Serialize(writer, __Configuration) End Sub #End Region #Region " Parse Methods " ''' ''' Parses the specified text, returning a collection of . ''' ''' The text to be parsed. ''' A containing the parsed text. Public Function Parse(ByVal text As String) As BBCodeDocument(Of TContext) Using reader As New IO.StringReader(text) Return Parse(reader) End Using End Function ''' ''' Parses the specified stream, returning a collection of . ''' ''' The to be parsed. ''' A containing the parsed . Public Function Parse(ByVal stream As IO.Stream) As BBCodeDocument(Of TContext) Return Parse(stream, Text.Encoding.UTF8) End Function ''' ''' Parses the specified stream, returning a collection of . ''' ''' The to be parsed. ''' The encoding of the stream. ''' A containing the parsed . Public Function Parse(ByVal stream As IO.Stream, ByVal encoding As Text.Encoding) As BBCodeDocument(Of TContext) Return Parse(New IO.StreamReader(stream, encoding)) End Function ''' ''' Parses the specified , returning a collection of . ''' ''' The to be parsed. ''' A containing the parsed . Public Function Parse(ByVal reader As IO.TextReader) As BBCodeDocument(Of TContext) Dim doc = New BBCodeDocument(Of TContext)(Me) Dim rootElement As New BBCodeElement(Of TContext)(Me) Dim currentElement As BBCodeElement(Of TContext) = rootElement Dim tk As Tokenization.Token Dim sb As New Text.StringBuilder() Dim sbText As New Text.StringBuilder() Do While (reader.Peek() <> -1) Dim line As String = reader.ReadLine() & vbCrLf sbText.AppendLine(line) Do '* '* Get the next token '* tk = Tokenizer.GetToken(line) If (tk Is Nothing) Then Exit Do End If Dim tag = New BBCodeTag(tk.Value) ParseElement(rootElement, currentElement, tk, sb, tag) Loop Loop '* '* Add the text node '* If (sb.Length > 0) Then currentElement.Nodes.Add(New BBCodeText(Of TContext)(sb.ToString())) End If '* '* Add the nodes to the document '* doc.Nodes.AddRange(rootElement.Nodes) '* '* Sets the source text '* doc.SetText(sbText.ToString()) Return doc End Function #End Region ''' ''' Gets the . ''' Private Shared ReadOnly Property Tokenizer() As Tokenization.Tokenizer Get Return __Tokenizer End Get End Property ''' ''' Gets the . ''' Private ReadOnly Property Factory() As BBCodeElementFactory(Of TContext) Get If (__Factory Is Nothing) Then __Factory = New BBCodeElementFactory(Of TContext)(Me) End If Return __Factory End Get End Property Private Sub ParseElement(ByVal rootElement As BBCodeElement(Of TContext), ByRef currentElement As BBCodeElement(Of TContext), ByVal token As Tokenization.Token, ByVal sb As Text.StringBuilder, ByVal tag As BBCodeTag) '* '* Check the token Type '* Select Case token.RuleType Case 0, -1 '* '* Empty tag or char '* sb.Append(token.Value) Case 1 '* '* Closing tag '* ParseClosingTag(rootElement, currentElement, token, sb, tag) Case 2, 3, 4 '* '* Value Tag, Parametrized Tag, Generic Tag '* ParseTag(currentElement, sb, tag) End Select End Sub Private Sub ParseTag(ByRef currentElement As BBCodeElement(Of TContext), ByVal sb As Text.StringBuilder, ByVal tag As BBCodeTag) '* '* Add the text previous to the current element '* If (sb.Length > 0) Then currentElement.Nodes.Add(New BBCodeText(Of TContext)(sb.ToString())) sb.Remove(0, sb.Length) End If '* '* Add the new element to the list of nodes '* Dim el = Factory.CreateElement(tag.Name, tag.Paramters) currentElement.Nodes.Add(el) '* '* Change the current element, if it requires an closing tag '* If (el.RequireClosingTag) Then currentElement = el End If End Sub Private Shared Sub ParseClosingTag(ByVal rootElement As BBCodeElement(Of TContext), ByRef currentElement As BBCodeElement(Of TContext), ByVal token As Tokenization.Token, ByVal sb As Text.StringBuilder, ByVal tag As BBCodeTag) '* '* Check if the closing tag is closing a previously open tag '* If currentElement.RequireClosingTag AndAlso (String.CompareOrdinal(currentElement.Name, tag.Name) = 0) Then '* '* Add the inner text '* If (sb.Length > 0) Then currentElement.Nodes.Add(New BBCodeText(Of TContext)(sb.ToString())) sb.Remove(0, sb.Length) End If '* '* Move up a level '* currentElement = If(currentElement.Parent, rootElement) Else '* '* Adds to the text '* sb.Append(token.Value) End If End Sub Private Shared Function PrepareTokenizer() As Tokenization.Tokenizer Dim tk As New Tokenization.Tokenizer '* '* Prepares the BBCode Grammar '* With tk '* '* Define the grammar macros '* AddTokenizerBaseMacros(tk) '* '* Define the grammar rules '* .AddRule("EmptyTag", 0, "\[{w}\]") .AddRule("ClosingTag", 1, "\[/{name}\]") .AddRule("ValueTag", 2, "\[{param}\]") .AddRule("ParamsTag", 3, "\[{name}{params}\]") .AddRule("Tag", 4, "\[[^ \t\r\n\f\]]+?\]") .AddRule("Char", -1, ".") End With Return tk End Function End Class