|
|
|
|
'**************************************************
|
|
|
|
|
' FILE : BBCodeParser.vb
|
|
|
|
|
' AUTHOR : Paulo Santos
|
|
|
|
|
' CREATION : 4/29/2009 11:03:53 AM
|
|
|
|
|
' COPYRIGHT : Copyright <EFBFBD> 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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' The parser of
|
|
|
|
|
''' </summary>
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
''' <summary>Initializes an instance of the <see cref="BBCodeParser" /> class.
|
|
|
|
|
''' This is the default constructor for this class.</summary>
|
|
|
|
|
Public Sub New()
|
|
|
|
|
End Sub
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Gets the dictionary of elements to be replaced by the <see cref="BBCodeParser"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Gets the dictionary of types created by the parser.
|
|
|
|
|
''' </summary>
|
|
|
|
|
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 "
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Loads the configuration from the specified filename.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="fileName">The name of the file to read the dictionary from.</param>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Loads the configuration from the specified <see cref="IO.Stream"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="stream">A <see cref="IO.Stream"/> to read the dictionary from.</param>
|
|
|
|
|
Public Sub LoadConfiguration(ByVal stream As IO.Stream)
|
|
|
|
|
LoadConfiguration(New IO.StreamReader(stream, Text.Encoding.UTF8, True))
|
|
|
|
|
End Sub
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Loads the configuration from the specified <see cref="IO.TextReader"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="reader">The <see cref="IO.TextReader"/> to read the dictionary from.</param>
|
|
|
|
|
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 "
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Saves the conficuration to the specified file.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="fileName">The name of the file to save the dictionary.</param>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Saves the conficuration to the specified <see cref="IO.Stream"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="stream">The <see cref="IO.Stream"/> to save the dictionary.</param>
|
|
|
|
|
Public Sub SaveConfiguration(ByVal stream As IO.Stream)
|
|
|
|
|
SaveConfiguration(New IO.StreamWriter(stream, Text.Encoding.UTF8))
|
|
|
|
|
End Sub
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Saves the conficuration to the specified <see cref="IO.TextWriter"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="writer">The <see cref="IO.TextWriter"/> to save the dictionary.</param>
|
|
|
|
|
Public Sub SaveConfiguration(ByVal writer As IO.TextWriter)
|
|
|
|
|
__ConfigSerializer.Serialize(writer, __Configuration)
|
|
|
|
|
End Sub
|
|
|
|
|
|
|
|
|
|
#End Region
|
|
|
|
|
|
|
|
|
|
#Region " Parse Methods "
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Parses the specified text, returning a collection of <see cref="BBCodeNode"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="text">The text to be parsed.</param>
|
|
|
|
|
''' <returns>A <see cref="BBCodeNodeCollection"/> containing the parsed text.</returns>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Parses the specified stream, returning a collection of <see cref="BBCodeNode"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="stream">The <see cref="IO.Stream"/> to be parsed.</param>
|
|
|
|
|
''' <returns>A <see cref="BBCodeNodeCollection"/> containing the parsed <see cref="IO.Stream"/>.</returns>
|
|
|
|
|
Public Function Parse(ByVal stream As IO.Stream) As BBCodeDocument(Of TContext)
|
|
|
|
|
Return Parse(stream, Text.Encoding.UTF8)
|
|
|
|
|
End Function
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Parses the specified stream, returning a collection of <see cref="BBCodeNode"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="stream">The <see cref="IO.Stream"/> to be parsed.</param>
|
|
|
|
|
''' <param name="encoding">The encoding of the stream.</param>
|
|
|
|
|
''' <returns>A <see cref="BBCodeNodeCollection"/> containing the parsed <see cref="IO.Stream"/>.</returns>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Parses the specified <see cref="IO.TextReader"/>, returning a collection of <see cref="BBCodeNode"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
''' <param name="reader">The <see cref="IO.TextReader"/> to be parsed.</param>
|
|
|
|
|
''' <returns>A <see cref="BBCodeNodeCollection"/> containing the parsed <see cref="IO.TextReader"/>.</returns>
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Gets the <see cref="Tokenization.Tokenizer"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
Private Shared ReadOnly Property Tokenizer() As Tokenization.Tokenizer
|
|
|
|
|
Get
|
|
|
|
|
Return __Tokenizer
|
|
|
|
|
End Get
|
|
|
|
|
End Property
|
|
|
|
|
|
|
|
|
|
''' <summary>
|
|
|
|
|
''' Gets the <see cref="BBCodeElementFactory"/>.
|
|
|
|
|
''' </summary>
|
|
|
|
|
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
|