内部网站搜索引擎






4.56/5 (71投票s)
2004年1月29日
16分钟阅读

901441

31324
站点搜索引擎可以搜索整个页面以及动态页面中的匹配关键字或短语,并计算关键字或短语在页面中出现的次数,然后将匹配度最高的搜索结果排在前面显示。
- 下载最新演示项目(VB.NET) - 67 Kb
- 下载演示项目(读写XML,VB.NET) - 88 Kb
- 下载最新演示项目(无需 Visual Studio .NET) - 52 Kb
- 下载旧版演示项目(C#) - 28 Kb
引言
搜索引擎模块将搜索整个页面以及动态页面中匹配的关键字或短语,并计算关键字或短语在页面中出现的次数,然后将匹配度最高的搜索结果排在前面显示。该模块将搜索所有你可以在 web.config 文件中指定文件扩展名的文件。你不希望被搜索的文件或文件夹也可以在 web.config 文件中指定,以便不对这些文件和文件夹进行搜索。现在你还可以选择你喜欢的编码。
本更新的文章包含本地化和增强代码的技巧。
注意:它最适合小型站点。你也可以通过使用正则表达式修改此代码以在内部爬行页面。对于大型站点,你需要定期将数据写入 XML 文件,然后再从 XML 文件读取。我在该部分的末尾提供了一些技巧。我还包括了一个读取和写入 XML 的演示项目。
背景
站点搜索引擎可帮助用户追踪他感兴趣的页面。在我从事 ASP.NET 项目时,我需要添加站点搜索模块。我有一个 ASP 版的,但没有 .NET 版的。因此,这个站点搜索引擎应运而生。我的第一个版本只是一个单独的 Web 窗体,我没有充分利用面向对象 .NET 语言的全部功能。在业余时间,我重写了我的代码,以最大限度地利用面向对象语言。对于这篇文章,我根据经验和不同作者建议的最佳实践,进一步增强了我的设计。
来自中国北京的宋涛先生向我咨询了如何将该模块转换为中文。在他的帮助下,我增强了代码以支持其他语言。此外,一些用户在将 SiteSearch.aspx 放在根目录下时遇到了文章错误。我修改了代码以纠正此错误。
源代码概述
站点搜索引擎的结构如下
类
定义类和创建类实例的能力是任何面向对象语言最重要的功能之一。在下一节中,我们将看到我们在搜索模块中使用的类。
类名 | 描述 |
SiteSearch |
Web 窗体的类,用户可以在其中搜索站点中的特定词语。 |
Searches.CleanHtml |
清理 HTMl 内容的类 |
Searches.FileContent |
从 HTML 文件中获取内容的类 |
Searches.Page |
存储页面数据的类 |
Searches.PagesDataset |
创建和存储数据集结果的类 |
Searches.Site |
读取站点配置的类 |
Searches.UserSearch |
存储每个用户搜索信息的类 |
SiteSearch.aspx
Web 窗体是 Microsoft .NET 计划中令人兴奋的新功能之一。SiteSearch.aspx 是一个 Web 窗体,也是搜索模块的起始页。
Web 窗体页面由页面(ASPX 文件)和代码隐藏文件(.aspx.cs 文件或 .aspx.vb 文件)组成。我们的 Web 窗体包含 SiteSearch.aspx 和 SiteSearch.aspx.vb。我将同时处理它们,并重点介绍 Web 窗体的主要元素。
ASP.NET 是一个事件驱动的编程环境。我们将在下一节中看到一些事件处理程序和方法。
Page_Load
服务器控件加载到 Page
对象上,此时视图状态信息可用。Page_Load
事件检查 sSite 是否为 null,并将 Session("Site")
变量分配给它。
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
If IsNothing(sSite) Then
sSite = Session("Site")
End If
End Sub
srchbtn_Click
搜索按钮事件在点击搜索按钮时触发。在这里,我们编写代码来更改控件设置或在页面上显示文本。在这里,我们检查搜索是否包含文本,然后调用 SearchSite
方法。调用 DisplayContent()
为 Web 页面中的不同控件赋值。
'*********************************************************
'
' srchbtn_Click event
'
' Add code to this event.
'
'**********************************************************
Private Sub srchbtn_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles srchbtn.Click
Dim strSearchWords As String
'If there is no words entered by the user to search for
'then don't carryout the file search routine
pnlSearchResults.Visible = False
strSearchWords = Trim(Request.Params("search"))
If Not strSearchWords.Equals("") Then
Searchs.Site.ApplicationPath = String.Format("http://{0}{1}",
Request.ServerVariables("HTTP_HOST"), Request.ApplicationPath)
sSite = SearchSite(strSearchWords)
Session("Site") = sSite
dgrdPages.CurrentPageIndex = 0
DisplayContent()
End If
End Sub
DisplayContent
调用 DisplayContent()
为 Web 页面中的不同控件赋值。DataGrid
的内容通过调用 BindDataGrid
方法设置。ViewState("SortExpression")
用于存储排序表达式。
'*********************************************************************
'
' DisplayContent method
'
' The data is bound to the respective fields.
'
'*********************************************************************
Private Sub DisplayContent()
If Not IsNothing(sSite.PageDataset) Then
pnlSearchResults.Visible = True
lblSearchWords.Text = sSite.SearchWords
If ViewState("SortExpression") Is Nothing Then
ViewState("SortExpression") = "MatchCount Desc"
End If
BindDataGrid(ViewState("SortExpression"))
lblTotalFiles.Text = sSite.TotalFilesSearched
lblFilesFound.Text = sSite.TotalFilesFound
End If
End Sub
搜索
搜索的主要调用发生在此方法中。稍后将介绍的 UserSearch
类存储了整个搜索信息和搜索结果。创建 UserSearch
对象,即 srchSite
,并为其 SearchWords
和 SearchCriteria
等属性赋值。同时调用 srchSite.Search
方法。
'************************************************************
'
' SearchSite method
'
' The sSite.PageDataset is used to populate the datagrid.
'
'************************************************************
Private Function SearchSite(ByVal strSearch_
As String) As Searchs.UserSearch
Dim srchSite As Searchs.UserSearch
srchSite = New Searchs.UserSearch()
'Read in all the search words into one variable
srchSite.SearchWords = strSearch
If Phrase.Checked Then
srchSite.SearchCriteria = Searchs.SearchCriteria.Phrase
ElseIf AllWords.Checked Then
srchSite.SearchCriteria = Searchs.SearchCriteria.AllWords
ElseIf AnyWords.Checked Then
srchSite.SearchCriteria = Searchs.SearchCriteria.AnyWords
End If
srchSite.Search(Server.MapPath("./"))
Return srchSite
End Function
DataGrid
DataGrid
控件呈现一个多列、完全模板化的网格,它是所有数据绑定控件中最通用的。此外,DataGrid
控件是 ASP.NET 中进行数据报告的首选控件。因此,我使用它来显示搜索结果。由于本文的重点是内部搜索引擎,我将只简要概述此处使用的 DataGrid
的功能。
数据绑定
数据绑定是从数据源检索数据并将其动态关联到视觉元素属性的过程。由于 DataGrid
同时处理(或至少在内存中保留)更多项,因此您应该将 DataGrid
显式关联到数据集合,即数据源。
DataGrid
的内容通过其 DataSource
属性设置。所有搜索结果都存储在 sSite.PageDataset.Tables("Pages")
中。因此,DataGrid
的内容被设置为 dvwPages
,即 sSite.PageDataset.Tables("Pages").DefaultView
。每次页面加载时都会调用 BindDataGrid
方法。
'************************************************************
'
' BindDataGrid method
'
' The sSite.PageDataset is used to populate the datagrid.
'
'************************************************************
Private Sub BindDataGrid(ByVal strSortField As String)
Dim dvwPages As DataView
dvwPages = sSite.PageDataset.Tables("Pages").DefaultView
dvwPages.Sort = strSortField
dgrdPages.DataSource = dvwPages
dgrdPages.DataBind()
End Sub
该控件能够自动生成基于数据源结构的列。自动生成是 DataGrid
的默认行为,但您可以通过一个名为 AutoGenerateColumns
的 Boolean
属性来控制此行为。当您希望控件仅显示添加到 Columns
集合中的列时,将该属性设置为 False
。当您希望控件添加数据源所需的尽可能多的列时,将其设置为 True
(默认)。自动生成不允许您指定标题文本,也不提供文本格式。因此,我在这里将其设置为 False
。您通常使用 <columns>
标签在 <asp:datagrid>
服务器控件的主体内绑定列。
<Columns>
<asp:TemplateColumn>
<ItemTemplate>
<%# DisplayTitle(Container.DataItem( "Title" ), _
Container.DataItem( "Path" )) %>
<br>
<%# Container.DataItem( "Description" ) %>
<br>
<span class="Path">
<%# String.Format("{0} - {1}kb", DisplayPath( _
Container.DataItem( "Path" )) , _
Container.DataItem( "Size" ))%>
</span>
<br>
<br>
</ItemTemplate>
</asp:TemplateColumn>
</Columns>
使用 DisplayTitle
方法和 DisplayPath
方法来显示 DataGrid
列中自定义的信息。
'****************************************
'
' DisplayTitle method
'
' Display title of searched pages
'
'****************************************
Protected Function DisplayTitle(ByVal Title _
As String, ByVal Path As String) As String
Return String.Format("<A href="{1}">{0}</A>", Title, Path)
End Function
'****************************************
'
' DisplayPath method
'
' Path of the file is returned
'
'****************************************
Protected Function DisplayPath(ByVal Path As String) As String
Return String.Format("{0}{1}/{2}", _
Request.ServerVariables("HTTP_HOST"), _
Request.ApplicationPath, Path)
End Function
分页
与 DataList
控件不同,DataGrid
控件支持数据分页,即能够将显示的数据源行分成页面。我们数据源的大小很容易超出页面空间。因此,为了保持服务器的可伸缩性并为用户提供更易于访问的页面,一次只显示几行。要启用 DataGrid
控件的分页,您需要告知控件。您可以通过 AllowPaging
属性来完成此操作。
分页栏是 DataGrid
控件提供的一个有趣且互补的功能,让用户可以轻松地从一页移动到另一页。分页栏是显示在 DataGrid
控件底部的一行,其中包含指向可用页面的链接。当您单击其中任何一个链接时,控件会自动触发 PageIndexChanged
事件并相应地更新页面索引。当页面索引更改时,将调用 dgrdPages_PageIndexChanged
。
'*****************************************************************
'
' dgrdPages_PageIndexChanged event
'
' The CurrentPageIndex is Assigned the page index value.
' The datagrid is then populated using the BindDataGrid function.
'
'*****************************************************************
Protected Sub dgrdPages_PageIndexChanged(ByVal s As Object, _
ByVal e As DataGridPageChangedEventArgs) _
Handles dgrdPages.PageIndexChanged
dgrdPages.CurrentPageIndex = e.NewPageIndex
DisplayContent()
End Sub
您可以使用 PagerStyle
属性的 Mode
属性来控制分页栏。Mode
属性的值来自 PagerMode
枚举。在这里,我们选择了一系列详细的数字按钮,每个按钮指向一个特定的页面。
<PagerStyle CssClass="GridPager" Mode="NumericPages"></PagerStyle>
排序
DataGrid
控件实际上不排序行,但只要底层数据源的排序功能足够,它就提供了良好的排序支持。数据源始终负责根据用户通过 DataGrid
控件的用户界面选择的排序表达式返回排序后的记录集。通过将 AllowSorting
属性设置为 True
来触发内置排序机制。
调用 dgrdPages_SortCommand
来对 DataGrid
进行排序。SortCommand
事件处理程序通过 DataGridSortCommandEventArgs
类提供的 SortExpression
属性了解排序表达式。在我们的代码中,排序信息之所以能够持久化,是因为它存储在页面 ViewState
集合的一个槽中。
注意:在我的页面中,我禁用了标题,但如果显示了标题,您可以使用它来对 DataGrid
进行排序。
'*****************************************************************
'
' dgrdAdditionalItems_SortCommand event
'
' The ViewState( "SortExpression" ) is Assigned
' the sort expression value.
' The datagrid is then populated using the BindDataGrid function.
'
'*****************************************************************
Protected Sub dgrdPages_SortCommand(ByVal s As Object, _
ByVal e As DataGridSortCommandEventArgs) _
Handles dgrdPages.SortCommand
ViewState("SortExpression") = e.SortExpression
DisplayContent()
End Sub
Page.vb
Page
对象的作用是存储与网站每个页面相关的数据。
Page
类定义了以下属性
Path |
存储文件的路径 |
标题 |
存储 HTML 标题标签中的文本 |
关键词 |
存储 HTML meta keywords 标签中的文本 |
描述 |
存储 HTML meta description 标签中的文本 |
目录 |
存储 HTML 页面中的文本 |
Matchcount |
存储在 HTML 页面中找到的匹配项 |
'***************************************************
'
' Size Property
'
' Assign and retrieve size of the file
'
'***********************************************
Public Property Size() As Decimal
Get
Return m_size
End Get
Set(ByVal Value As Decimal)
m_size = Value
End Set
End Property
'***************************************************
'
' Path Property
'
' Assign and retrieve path of the file
'
'***********************************************
Public Property Path() As String
Get
Return m_path
End Get
Set(ByVal Value As String)
m_path = Value
End Set
End Property
'***********************************************
'
' Title Property
'
'Assign and retrieve title of the file
'
'***********************************************
Public Property Title() As String
Get
Return m_title
End Get
Set(ByVal Value As String)
m_title = Value
End Set
End Property
'***********************************************
'
' Keywords Property
'
' Assign and retrieve Keywords
' (meta tags) of the file
'
'***********************************************
Public Property Keywords() As String
Get
Return m_keywords
End Get
Set(ByVal Value As String)
m_keywords = Value
End Set
End Property
'***********************************************
'
' Description Property
'
' Assign and retrieve description
' (meta tags) of the file
'
'***********************************************
Public Property Description() As String
Get
Return m_description
End Get
Set(ByVal Value As String)
m_description = Value
End Set
End Property
'***********************************************
'
' Contents Property
'
' Assign and retrieve contents of the file
'
'***********************************************
Public Property Contents() As String
Get
Return m_contents
End Get
Set(ByVal Value As String)
m_contents = Value
End Set
End Property
'***********************************************
'
' Contents Property
'
' Assign and retrieve MatchCount of the file
'
'***********************************************
Public Property MatchCount() As Integer
Get
Return m_matchcount
End Get
Set(ByVal Value As Integer)
m_matchcount = Value
End Set
End Property
Page
类有两个私有方法和两个公共方法。它定义了以下方法
CheckFileInfo 方法
这是一个公共方法,用于检查标题、描述和内容是否存在。如果标题文本为空,则将其设置为默认值“无标题”。如果描述文本为空,则将其设置为文件内容或默认值“此页面没有可用描述”。
'*************************************************
'
' CheckFileInfo method
'
' Subroutine to the check the file contains
' title and decription
'
'*************************************************
Public Sub CheckFileInfo()
'If the page contains no title then Page Title
' variable the appropriate message to display
If IsNothing(m_title) Or m_title.Trim().Equals("") Then
m_title = "No Title"
End If
'If the page contains no title then Page Description
'variable the appropriate message to display
If IsNothing(m_description) Or _
m_description.Trim().Equals("") Then
If IsNothing(m_contents) Or _
m_contents.Trim().Equals("") Then
m_description = _
"There is no description available for this page"
Else
If m_contents.Length > 200 Then
m_description = m_contents.Substring(0, 200)
Else
m_description = m_contents
End If
End If
End If
End Sub
Search 方法
Search
方法是一个公共方法,它根据搜索条件调用 SearchPhrase
和 SearchWords
方法。SearchPhrase
方法搜索短语,而 SearchWords
搜索所有或任意单词。这两种方法都调用 SearchPattern
方法,该方法使用正则表达式搜索文件。
'*******************************************
'
' Search method
'
' Subroutine to the search file
'
'*******************************************
Public Sub Search(ByVal strSearchWords As String, _
ByVal SrchCriteria As SearchCriteria)
'If the user has choosen to search by phrase
If SrchCriteria = SearchCriteria.Phrase Then
SearchPhrase(strSearchWords)
'Else the search is either by all or any words
Else
SearchWords(strSearchWords, SrchCriteria)
End If
End Sub
'******************************************************
'
' SearchPhrase method
'
' Subroutine to the search file
'
'******************************************************
Private Sub SearchPhrase(ByVal strSearchWords As String)
Dim mtches As MatchCollection
mtches = SearchPattern(strSearchWords)
'Check to see if the phrase has been found
If mtches.Count > 0 Then
'Get the number of times the phrase is matched
m_matchcount = mtches.Count
End If
End Sub
'**************************************************
'
' SearchWords method
'
' Subroutine to the search file
'
'**************************************************
Private Sub SearchWords(ByVal strSearchWords As String, _
ByVal SrchCriteria As SearchCriteria)
Dim intSearchLoopCounter As Integer
Dim sarySearchWord As String()
'Array to hold the words to be searched for
Dim mtches As MatchCollection
'Split each word to be searched up and place in an array
sarySearchWord = Split(Trim(strSearchWords), " ")
'Loop round to search for each word to be searched
For intSearchLoopCounter = 0 To UBound(sarySearchWord)
'Set the pattern to search for
mtches = SearchPattern(sarySearchWord(_
intSearchLoopCounter))
If SrchCriteria = SearchCriteria.AnyWords Then
m_matchcount = m_matchcount + mtches.Count
ElseIf SrchCriteria = SearchCriteria.AllWords Then
'Check to see if any of the words have been found
If mtches.Count > 0 Then
'Get the number of times the search word is matched
If m_matchcount = 0 Or (m_matchcount > 0 _
And m_matchcount > mtches.Count) Then
m_matchcount = mtches.Count
End If
Else
'If the search word is not found then set the
'search found variable back to false as one of
'the words has not been found
m_matchcount = 0
Exit Sub
End If
End If
Next
End Sub
转义字符 \b 是一个特例。在正则表达式中,\b 表示单词边界(\w 和 \W 字符之间),除非在 [] 字符类中,此时 \b 指的是退格字符。在替换模式中,\b 始终表示退格。
当我们使用 UTF-8 以外的编码时,可能需要删除单词边界。
'****************************************************
'
' SearchPattern method
'
' Subroutine to the search file
'
'****************************************************
Private Function SearchPattern( _
ByVal strSearchWord As String) As MatchCollection
Dim regexp As Regex
Dim strPattern
'Set the pattern to search for
regexp = New Regex("", RegexOptions.IgnoreCase)
'Search the file for the phrase
If Searchs.Site.Encoding.Equals("utf-8") Then
strPattern = "\b{0}\b"
Else
strPattern = "{0}"
End If
Return regexp.Matches(m_contents, String.Format(strPattern, _
strSearchWord), RegexOptions.IgnoreCase)
End Function
UserSearch.vb
它包含以下属性
SearchCriteria |
用户选择的搜索在此存储和检索 |
SearchWords |
用户使用的搜索词在此存储和检索 |
TotalFilesSearched |
此处读取搜索的总文件数 |
TotalFilesFound |
此处读取搜索到的总文件数 |
'**********************************************************
'
' SearchCriteria Property
'
' Assign and retrieve SearchCriteria of the site
'
'**********************************************************
Public Property SearchCriteria() As Searchs.SearchCriteria
Get
Return m_searchCriteria
End Get
Set(ByVal Value As Searchs.SearchCriteria)
m_searchCriteria = Value
End Set
End Property
'**********************************************************
'
' SearchWords Property
'
'Assign and retrieve SearchWords of the site
'
'**********************************************************
Public Property SearchWords() As String
Get
Return m_searchWords
End Get
Set(ByVal Value As String)
m_searchWords = Value
End Set
End Property
'**********************************************************
'
' TotalFilesSearched Property
'
' Retrieve TotalFilesSearched of the site
'
'**********************************************************
Public ReadOnly Property TotalFilesSearched() As Integer
Get
Return m_totalFilesSearched
End Get
End Property
'**********************************************************
'
' TotalFilesFound Property
'
' Retrieve TotalFilesFound of the site
'
'**********************************************************
Public ReadOnly Property TotalFilesFound() As Integer
Get
Return m_totalFilesFound
End Get
End Property
'**********************************************************
'
' PageDataset Shared Property
'
' Retrieve data of the entire site of the site
'
'**********************************************************
Public ReadOnly Property PageDataset() As DataSet
Get
Return m_dstPages
End Get
End Property
Search 方法
搜索的实际处理从这里开始。此处创建用于存储搜索结果的 DataSet
,并调用 ProcessDirectory
方法。
'********************************************
'
' Search Method
'
' Search the entire site
'
'********************************************
Public Function Search(ByVal targetDirectory As String) As DataSet
'If the site is in English then use the server HTML encode method
If Searchs.Site.EnglishLanguage = True Then
'Replace any HTML tags with the HTML codes
'for the same characters (stops people entering HTML tags)
m_searchWords = m_page.Server.HtmlEncode(m_searchWords)
'If the site is not english just change the script tags
Else
'Just replace the script tag <> with HTML encoded < and >
m_searchWords = Replace(m_searchWords, "<", "<", 1, -1, 1)
m_searchWords = Replace(m_searchWords, ">", ">", 1, -1, 1)
End If
If m_dstPages Is Nothing Then
m_dstPages = Searchs.PagesDataset.Create()
End If
ProcessDirectory(targetDirectory)
Return m_dstPages
End Function
ProcessDirectory 方法
ProcessDirectory
遍历所有文件并调用 ProcessFile
方法。之后,它还会遍历子目录并调用自身。
'*********************************************
'
' ProcessDirectory Method
'
' Files in the directories are searched
'
'********************************************
Private Sub ProcessDirectory(ByVal targetDirectory As String)
Dim fileEntries As String()
Dim subdirectoryEntries As String()
Dim filePath As String
Dim subdirectory As String
fileEntries = Directory.GetFiles(targetDirectory)
' Process the list of files found in the directory
For Each filePath In fileEntries
m_totalFilesSearched += 1
ProcessFile(filePath)
Next filePath
subdirectoryEntries = Directory.GetDirectories(targetDirectory)
' Recurse into subdirectories of this directory
For Each subdirectory In subdirectoryEntries
'Check to make sure the folder about to be searched
'is not a barred folder if it is then don't search
If Not InStr(1, Searchs.Site.BarredFolders, _
Path.GetFileName(subdirectory), vbTextCompare) > 0 Then
'Call the search sub prcedure to search the web site
ProcessDirectory(subdirectory)
End If
Next subdirectory
End Sub 'ProcessDirectory
ProcessFile 方法
ProcessFile
调用 GetInfo
,它返回 Searchs.Page
对象,该对象包含特定文件的所有信息。之后,它检查 matchcount
是否大于 0,并调用 CheckFileInfo
来清理 Page
对象中存储的信息。然后,它将文件存储在 PagesDataset
中。
'*******************************************************
'
' ProcessFile Method
'
' Real logic for processing found files would go here.
'
'*******************************************************
Private Sub ProcessFile(ByVal FPath As String)
Dim srchFile As Searchs.Page
srchFile = GetInfo(FPath)
If Not IsNothing(srchFile) Then
srchFile.Search(m_searchWords, m_searchCriteria)
If srchFile.MatchCount > 0 Then
m_totalFilesFound += 1
'Response.Write(srchFile.Contents)
srchFile.CheckFileInfo()
Searchs.PagesDataset.StoreFile(m_dstPages, srchFile)
End If
End If
End Sub 'ProcessFile
GetInfo 方法
GetInfo
方法的主要任务是获取文件数据。它调用共享方法 Searchs.FileContent.GetFileInfo
,该方法完成了大部分工作。
'*****************************************************************
'
' GetInfo Method
'
' File data is picked in this method
'
'*****************************************************************
Private Function GetInfo(ByVal FPath As String) As Searchs.Page
Dim fileInform As New FileInfo(FPath)
Dim sr As StreamReader
Dim srchFile As New Searchs.Page()
Dim strBldFile As New StringBuilder()
Dim strFileURL As String 'Holds the path to the file on the site
'Check the file extension to make sure the file
'is of the extension type to be searched
If InStr(1, Searchs.Site.FilesTypesToSearch, _
fileInform.Extension, vbTextCompare) > 0 Then
'm_page.Trace.Warn("File ext.", fileInform.Extension)
'Check to make sure the file about to be searched
'is not a barred file if it is don't search the file
If Not InStr(1, Searchs.Site.BarredFiles, _
Path.GetFileName(FPath), vbTextCompare) > 0 Then
'm_page.Trace.Warn("File", FPath)
If Not File.Exists(FPath) Then
'm_page.Trace.Warn("Error", _
'String.Format("{0} does not exist.", FPath))
'Add throw excetion here
'
'
Return Nothing
End If
Searchs.FileContent.GetFileInfo(FPath, srchFile)
Return srchFile
End If
End If
Return Nothing
End Function
FileContent.vb
GetFileInfo 方法
此处检索页面中的数据块。通过调用 GetStaticFileContent
方法,如果文件是静态的,则从源读取文件内容。如果文件是动态的,则通过 GetDynamicFileContent
方法从服务器检索内容。标题信息从标题标签中检索,描述和关键字从 meta 标签中检索,方法是调用 GetMetaContent
方法。通过调用 Searchs.CleanHtml.Clean
方法,从 HTML 页面中剥离文件内容。
'**********************************************
'
' GetFileInfo Method
'
' File data is picked in this method
'
'**********************************************
Public Shared Sub GetFileInfo(ByVal FPath As String, _
ByVal srchFile As Searchs.Page)
Dim fileInform As New FileInfo(FPath)
Dim strBldFile As New StringBuilder()
Dim fileSize As Decimal = fileInform.Length \ 1024
srchFile.Size = fileSize
GetFilePath(FPath, srchFile)
If InStr(1, Searchs.Site.DynamicFilesTypesToSearch, _
fileInform.Extension, vbTextCompare) > 0 Then
m_page.Trace.Warn("Path", String.Format("{0}/{1}", "", _
srchFile.Path))
GetDynamicFileContent(srchFile)
Else
GetStaticFileContent(FPath, srchFile)
End If
If Not srchFile.Contents.Equals("") Then
srchFile.Contents = sr.ReadToEnd()
'Read in the title of the file
srchFile.Title = GetMetaContent(srchFile.Contents,_
"<title>", "</title>")
'm_page.Trace.Warn("Page Title", strPageTitle)
'Read in the description meta tag of the file
srchFile.Description = GetMetaContent(srchFile.Contents,_
"<meta name=""description"" content=""", ",""">")
'm_page.Trace.Warn("Page Desc", strPageDescription)
'Read in the keywords of the file
srchFile.Keywords = GetMetaContent(srchFile.Contents,_
"<meta name=""keywords"" content=""", ",""">")
'm_page.Trace.Warn("Page Keywords", strPageKeywords)
srchFile.Contents = _
Searchs.CleanHtml.Clean(srchFile.Contents)
srchFile.Contents = _
strBldFile.AppendFormat("{0} {1} {2} {3}", _
srchFile.Contents, srchFile.Description, _
srchFile.Keywords, srchFile.Title).ToString.Trim()
'm_page.Trace.Warn("File Info", strBldFile.ToString)
End If
End Sub
'******************************************************
'
' GetStaticFileContent Method
'
' File Content is picked in this method
'
'*******************************************************
Private Shared Sub GetStaticFileContent(_
ByVal FPath As String, ByVal srchFile As Searchs.Page)
Dim sr As StreamReader
If Searchs.Site.Encoding.Equals("utf-8") Then
sr = File.OpenText(FPath)
Else
sr = New StreamReader(FPath, _
Encoding.GetEncoding(Searchs.Site.Encoding))
End If
Try
srchFile.Contents = sr.ReadToEnd()
sr.Close()
Catch ex As Exception
m_page.Trace.Warn("Error", ex.Message)
srchFile.Contents = ex.Message
End Try
End Sub
GetDynamicFileContent
GetDynamicFileContent
根据编码分支到 GetDynamicFileContentOther
或 GetDynamicFileContentUTF
方法。
'*********************************************************************
'
' GetDynamicFileContent Method
'
' File Content is picked in this method
'
'*********************************************************************
Private Shared Sub GetDynamicFileContent(ByVal srchFile As Searchs.Page)
Dim wcMicrosoft As System.Net.WebClient
If Searchs.Site.Encoding.Equals("utf-8") Then
GetDynamicFileContentUTF(srchFile)
Else
GetDynamicFileContentOther(srchFile)
End If
End Sub
System.Net.WebClient
提供用于通过 URI 标识的资源发送数据和接收数据的常用方法。我们使用 DownloadData
来从资源下载数据并返回一个字节数组。
以公共语言运行时为目标的应用程序使用编码将字符表示从本机字符方案(Unicode)映射到其他方案。应用程序使用解码将字符从非本机方案(非 Unicode)映射到本机方案。System.Text 命名空间提供了允许您对字符进行编码和解码的类。
'****************************************************************
'
' GetDynamicFileContentOther Method
'
' File Content is picked in this method
' according to the encoding provided
'
'****************************************************************
Private Shared Sub GetDynamicFileContentOther( _
ByVal srchFile As Searchs.Page)
Dim wcMicrosoft As System.Net.WebClient
Dim fileEncoding As System.Text.Encoding
Try
fileEncoding = System.Text.Encoding.GetEncoding(_
Searchs.Site.Encoding)
srchFile.Contents = fileEncoding.GetString( _
wcMicrosoft.DownloadData(String.Format("{0}/{1}", _
Searchs.Site.ApplicationPath, srchFile.Path)))
Catch ex As System.Net.WebException
m_page.Trace.Warn("Error", ex.Message)
srchFile.Contents = ex.Message
Catch ex As System.Exception
m_page.Trace.Warn("Error", ex.Message)
srchFile.Contents = ex.Message
End Try
End Sub
UTF8Encoding
类使用 UCS Transformation Format,8 位形式(UTF-8)对 Unicode 字符进行编码。此编码支持所有 Unicode 字符值和代理项。
'*********************************************************************
'
' GetDynamicFileContentUTF Method
'
' File Content is picked in this method according to the utf-8 encoding
'
'*********************************************************************
Private Shared Sub GetDynamicFileContentUTF( _
ByVal srchFile As Searchs.Page)
Dim wcMicrosoft As System.Net.WebClient
Dim objUTF8Encoding As UTF8Encoding
Try
wcMicrosoft = New System.Net.WebClient()
objUTF8Encoding = New UTF8Encoding()
srchFile.Contents = objUTF8Encoding.GetString( _
wcMicrosoft.DownloadData(String.Format("{0}/{1}", _
Searchs.Site.ApplicationPath, srchFile.Path)))
Catch ex As System.Net.WebException
m_page.Trace.Warn("Error", ex.Message)
srchFile.Contents = ex.Message
Catch ex As System.Exception
m_page.Trace.Warn("Error", ex.Message)
srchFile.Contents = ex.Message
End Try
End Sub
GetFilePath 方法
GetFilePath
方法将本地文件夹路径转换为反映网站 URL。
'*****************************************
'
' GetFilePath Method
'
' File path is modfied to be displayed
' as hyperlink in this method
'
'*****************************************
Private Shared Sub GetFilePath(ByVal strFileURL As String,_
ByVal srchFile As Searchs.Page)
'Turn the server path to the file into a URL path to the file
strFileURL = Replace(strFileURL, m_page.Server.MapPath("./"), "")
'Replace the NT backslash with the internet
'forward slash in the URL to the file
strFileURL = Replace(strFileURL, "\", "/")
'Encode the file name and path into the URL code method
strFileURL = m_page.Server.UrlEncode(strFileURL)
'Just incase it's encoded any backslashes
strFileURL = Replace(strFileURL.Trim(), _
"%2f", "/", vbTextCompare)
srchFile.Path = strFileURL
m_page.Trace.Warn("Url", srchFile.Path)
End Sub
GetMetaContent 方法
GetMetaContent
方法使用正则表达式来剥离标签并获取所需信息。
'************************************************
'
' GetMetaContent Method
'
' Metacontent is stripped in this method
'
'************************************************
Private Shared Function GetMetaContent(ByVal strFile As String, _
ByVal strMetaStart As String, ByVal strMetaEnd As String) As String
'List the text between the title tags:
Dim regexp As Regex
Dim strMeta As String
Dim strPattern As String
Dim strInPattern As String
'If no description or keywords are found then you may be
'using http-equiv= instead of name= in your meta tags
If InStr(1, LCase(strFile), strMetaStart, 1) = 0 _
And InStr(strMetaStart, "name=") Then
'Swap name= for http-equiv=
strMetaStart = Replace(strMetaStart, "name=", "http-equiv=")
End If
'Build Pattern
strInPattern = "((.|\n)*?)"
strPattern = String.Format("{0}{1}{2}", _
strMetaStart, strInPattern, strMetaEnd)
regexp = New Regex(strPattern, RegexOptions.IgnoreCase)
'Match Pattern
strMeta = regexp.Match(strFile).ToString
'Build Pattern
strInPattern = "(.*?)"
strPattern = String.Format("{0}{1}{2}", _
strMetaStart, strInPattern, strMetaEnd)
'Get Pattern content
strMeta = regexp.Replace(strMeta, strPattern,_
"$1", RegexOptions.IgnoreCase)
Return strMeta
End Function
PagesDataset.vb
此类用于创建和构建 DataSet
。它包含两个方法StoreFile
。Create
方法创建用于存储搜索结果的 DataSet
,而 Storefile
负责将记录添加到 DataSet
中的 DataTable
。
'*******************************************************
'
' Create Method - Shared method
'
' Creates a datset for the pages and returns the result
'
'********************************************************
Public Shared Function Create() As DataSet
'Objects are defined
Dim pgDataSet As New DataSet()
Dim keys(1) As DataColumn
'Table is created and added to table collection
pgDataSet.Tables.Add(New DataTable("Pages"))
'Schema of table is defined
pgDataSet.Tables("Pages").Columns.Add("PageId", _
System.Type.GetType("System.Int32"))
pgDataSet.Tables("Pages").Columns.Add("Title",_
System.Type.GetType("System.String"))
pgDataSet.Tables("Pages").Columns.Add("Description", _
System.Type.GetType("System.String"))
pgDataSet.Tables("Pages").Columns.Add("Path", _
System.Type.GetType("System.String"))
pgDataSet.Tables("Pages").Columns.Add("MatchCount", _
System.Type.GetType("System.Int32"))
pgDataSet.Tables("Pages").Columns.Add("Size", _
System.Type.GetType("System.Decimal"))
'PageId is defined as indentity
pgDataSet.Tables("Pages").Columns("PageID").AutoIncrement = True
pgDataSet.Tables("Pages").Columns("PageID").AutoIncrementSeed = 1
'PageId is defined as the primary key
keys(0) = pgDataSet.Tables("Pages").Columns("PageId")
pgDataSet.Tables("Pages").PrimaryKey = keys
Return pgDataSet
End Function
'********************************************************
'
' StoreFile Method - Shared method
'
' Creates a datset for the pages and returns the result
'
'********************************************************
Public Shared Sub StoreFile(ByVal dstPgs As DataSet,_
ByVal srchPg As Searchs.Page)
'Objects are defined
Dim pageRow As DataRow
'New row is created
pageRow = dstPgs.Tables("Pages").NewRow()
'Data is added
pageRow("Title") = srchPg.Title
pageRow("Description") = srchPg.Description
pageRow("Path") = srchPg.Path
pageRow("MatchCount") = srchPg.MatchCount
pageRow("Size") = srchPg.Size
'Row is added to the dataset
dstPgs.Tables("Pages").Rows.Add(pageRow)
End Sub
CleanHtml.vb
CleanHtml
类包含一个公共共享方法,该方法使用正则表达式清理 HTML 内容。
'*****************************************************
'
' CleanFileContent Method
'
' Subroutine to the clean the file of html content
'
'*****************************************************
Public Shared Function Clean(ByVal Contents As String) As String
Dim regexp As Regex
Dim strPattern As String
strPattern = ""
regexp = New Regex(strPattern, RegexOptions.IgnoreCase)
Contents = regexp.Replace(Contents, _
"<(select|option|script|style|title)(.*?)" & _
">((.|\n)*?)</(SELECT|OPTION|SCRIPT|STYLE|TITLE)>",_
" ", RegexOptions.IgnoreCase)
Contents = regexp.Replace(Contents, "&(nbsp|quot|copy);", "")
'Contents = regexp.Replace(Contents, "<[^>]*>", "")
Contents = regexp.Replace(Contents, "<([\s\S])+?>",_
" ", RegexOptions.IgnoreCase).Replace(" ", " ")
'Contents = regexp.Replace(Contents, "<[^<>]+>",_
" ", RegexOptions.IgnoreCase)
'Contents = regexp.Replace("(<(\w+)[^>]*?>(.*?)</\1>", "$1")
Contents = regexp.Replace(Contents, "\W", " ")
'Trace.Warn("File Contents", Contents)
Return Contents
End Function
Site.vb
Site
类包含共享属性,用于存储整个站点的配置。这些属性通过 ConfigurationSettings.AppSettings
从 web.config 文件获取其值。
以下是站点类的属性
FilesTypesToSearch |
返回您想要搜索的文件类型 |
DynamicFilesTypesToSearch |
返回要搜索的动态文件 |
BarredFolders |
返回受限制的文件夹 |
EnglishLanguage |
返回一个 Boolean 值,表示语言是否为英语。 |
编码 |
返回站点的编码 |
BarredFiles |
返回受限制的文件 |
ApplicationPath |
分配并返回应用程序的路径 |
'*************************************************
'
' FilesTypesToSearch ReadOnly Property
'
' Retrieve FilesTypesToSearch of the site
'
'*************************************************
Public Shared ReadOnly Property FilesTypesToSearch() As String
Get
Return ConfigurationSettings.AppSettings(
"FilesTypesToSearch")
End Get
End Property
'*************************************************
'
' DynamicFilesTypesToSearch ReadOnly Property
'
' Retrieve FilesTypesToSearch of the site
'
'*************************************************
Public Shared ReadOnly Property DynamicFilesTypesToSearch() As String
Get
Return ConfigurationSettings.AppSettings(_
"DynamicFilesTypesToSearch")
End Get
End Property
'*************************************************
'
' BarredFolders ReadOnly Property
'
' Retrieve BarredFolders of the site
'
'*************************************************
Public Shared ReadOnly Property BarredFolders() As String
Get
Return ConfigurationSettings.AppSettings("BarredFolders")
End Get
End Property
'*************************************************
'
' BarredFiles ReadOnly Property
'
' Retrieve BarredFiles of the site
'
'*************************************************
Public Shared ReadOnly Property BarredFiles() As String
Get
Return ConfigurationSettings.AppSettings("BarredFiles")
End Get
End Property
'*************************************************
'
' EnglishLanguage Property
'
' Retrieve EnglishLanguage of the site
'
'*************************************************
Public Shared ReadOnly Property EnglishLanguage() As String
Get
Return ConfigurationSettings.AppSettings("EnglishLanguage")
End Get
End Property
'*********************************************************************
'
' Encoding Property
'
' Retreive Encoding of the site
'
'*********************************************************************
Public Shared ReadOnly Property Encoding() As String
Get
Return ConfigurationSettings.AppSettings("Encoding")
End Get
End Property
'**********************************************************
'
' ApplicationPath Property
'
'Assign and retrieve ApplicationPath of the site
'
'**********************************************************
Public Property ApplicationPath() As String
Get
Return m_ApplicationPath
End Get
Set(ByVal Value As String)
m_ApplicationPath = Value
End Set
End Property
Web.config
ASP.NET 配置系统具有可扩展的基础结构,它允许您在 ASP.NET 应用程序首次部署时定义配置设置,以便您可以随时添加或修改配置设置,而对运行中的 Web 应用程序和服务器的影响最小。多个名为 Web.config 的配置文件可以出现在 ASP.NET Web 应用程序服务器上的多个目录中。每个 Web.config 文件都将其配置设置应用于其自身的目录及其下的所有子目录。如前所述,站点配置可以在 web.config 文件中指定。
<appSettings>
<!-- Place the names of the files types you want searching
in the following line separated by commas -->
<add key="FilesTypesToSearch" value=".htm,.html,.asp,.shtml,.aspx"
/>
<!-- Place the names of the dynamic files types you want
searching in the following line separated by commas -->
<add key="DynamicFilesTypesToSearch" value=".asp,.shtml,.aspx" />
<!-- Place the names of the folders you don't
want searched in the following line separated by commas-->
<add key="BarredFolders"
value="aspnet_client,_private,_vti_cnf,_vti_log,_vti_pvt,
_vti_script,_vti_txt,cgi_bin,_bin,bin,_notes,images,scripts"
/>
<!-- Place the names of the files you don't want searched in the
following line separated by commas include the file extension-->
<add key="BarredFiles"
value="localstart.asp,iisstart.asp,AssemblyInfo.vb,
Global.asax,Global.asax.vb,SiteSearch.aspx"
/>
<!-- Set this boolean to False if you are not using
an English language web site-->
<add key="EnglishLanguage" value="True" />
<!-- Set this to the Encoding of the web site-->
<add key="Encoding" value="utf-8" />
</appSettings>
如何集成
该应用程序已在根目录中与 Web 窗体 SiteSearch.aspx 一起进行了测试。所以我的建议是您也这样做。之后,您可以尝试将其移动到任何子文件夹。我所有的类都放在了 components 文件夹中。您可以将它们移动到任何您喜欢的文件夹。
注意
- 对于那些没有 Visual Studio .Net 的用户
- 从链接“下载最新演示项目(无需 Visual studio.net)”下载
- 将 SearchDotnet.dll 放在根目录的 bin 文件夹中。
- 将 SiteSearch.aspx 和 web.config 放在根目录中。
- 使用 XML 版本
- 从链接“下载演示项目(读写 XML,VB.net)”下载
- 项目包含以下文件。
- AdminSearch.aspx 用于向文件写入 XML。
- SiteSearch.aspx 用于搜索文件。
- 我所有的类都放在了 components 文件夹中。
错误
当应用程序放在根目录下时,您可能会遇到以下错误。远程服务器返回了一个错误: (401) 未授权。
或者
远程服务器返回了一个错误: (500) 内部服务器错误。
此错误是由于
- 如果服务器返回 (401) 未授权,则应用程序由于权限不足而无法读取文件。
- 如果服务器返回 (500) 内部服务器错误,则它试图读取的页面返回了一个错误。该应用程序试图读取的页面要么有错误,要么需要参数,因此返回错误。
按照以下步骤纠正错误
- 在 Web.config 中,确保
BarredFolders
列表是全面的
aspnet_client,_private,_vti_cnf, _vti_log,_vti_pvt,_vti_script,_vti_txt, cgi_bin,_bin,bin,_notes,images,scripts - 确保 BarredFiles 列表是全面的,并且包含 localstart.asp,iisstart.asp
全球化
搜索引擎模块可以轻松进行本地化。为此,我们将学习如何将其转换为中文。
Web.config
XML 声明必须作为文档的第一行出现,前面没有任何其他内容,包括空格。
文档映射中的 XML 声明包含以下内容:<?xml version="1.0" encoding="Your Encoding" ?>。默认情况下,Visual Studio 使用 utf-8 编码,需要将其更改为您想要的编码。在这里,我们将更改为 gb2312。因此,XML 声明需要修改如下。
English
<?xml version="1.0" encoding="utf-8" ?>
Chinese
<?xml version="1.0" encoding="gb2312" ?>
requestEncoding
和 responseEncoding
指定了每个传入请求和传出响应的假定编码。默认编码是 UTF-8,在 .NET Framework 安装时创建的 Machine.config 文件中的 <globalization> 标记中指定。如果在 Machine.config 或 Web.config 文件中未指定编码,则编码默认为计算机的区域选项区域设置。我们需要更改 requestEncoding
和 responseEncoding
来反映编码更改。
English
<globalization requestEncoding="utf-8" responseEncoding="utf-8" />
Chinese
<globalization requestEncoding="gb2312" responseEncoding="gb2312" />
为了避免在编码更改时构建代码,我们需要将编码键添加到 appsettings
。
<!-- Set this to the Encoding of the web site-->
<add key="Encoding" value="gb2312" />
还将 English Language 键更改为 false。
<!-- Set this boolean to False if you are not using
an English language web site-->
<add key="EnglishLanguage" value="True" />
SiteSearch.aspx
最后但并非最不重要的一点是,必须在页面指令中添加 codepage
属性。
English
<%@ Page Language="vb" Trace="False" AutoEventWireup="false"
Codebehind="SiteSearch.aspx.vb"
Inherits="SearchDotnet.SiteSearch" debug="false" %>
Chinese
<%@ Page Language="vb" Trace="False" AutoEventWireup="false"
Codebehind="SiteSearch.aspx.vb" Inherits="SearchDotnet.SiteSearch"
debug="false" codePage="936" %>
增强代码
该应用程序适用于小型网站。对于大型网站,代码可以进一步增强。实际上,您需要定期将数据写入数据库(例如 XML 文件),然后再从中读取。我将提供一些实现此目的的技巧。(我现在包含了一个读取和写入 XML 的演示项目。)
(1) 在我的代码中,我使用正则表达式搜索和过滤数据。您需要使用以下方法将整个数据(未过滤的数据)写入 XML 文件。
Private Shared Sub WriteXmlToFile(ByVal thisDataSet As DataSet)
If thisDataSet Is Nothing Then
Return
End If
thisDataSet.WriteXml(XMLFile)
End Sub
(2) 之后,您需要从文件中读取 XML,将其保存到共享数据集中,例如 Searchs.Site.PageDataset.Tables("Pages").
Private Shared Function ReadXmlFromFile() As DataSet
' Create a new DataSet.
Dim newDataSet As New DataSet("New DataSet")
' Read the XML document back in.
' Create new FileStream to read schema with.
Dim fsReadXml As New System.IO.FileStream(XMLFile,
System.IO.FileMode.Open)
' Create an XmlTextReader to read the file.
Dim myXmlReader As New System.Xml.XmlTextReader(fsReadXml)
' Read the XML document into the DataSet.
newDataSet.ReadXml(myXmlReader)
' Close the XmlTextReader
myXmlReader.Close()
Return newDataSet
End Function
(3) 对于每个搜索,您以后需要使用 PageDataset.Tables
的 Select 方法根据搜索结果进行过滤。Filter dataset 可能看起来像这样。FillDataset
方法包含创建并向数据库添加搜索结果(DataRow
数组)的逻辑。
Private Sub FiterPagesDatset()
Dim strExpr As String
Dim foundRows As DataRow()
Dim Field() As String = {
"Title", "Description", "Keywords", "Contents"}
strExpr = SomeFunction 'Your function to build the query.
foundRows = Searchs.Site.PageDataset.Tables(
"Pages").Select(strExpr)
FillDataset(foundRows)
End Sub
(4) 将过滤后的结果存储在另一个数据集中,并使用它来显示结果。
关注点
当我从事该项目时,问题是如何显示结果。DataGrid
是我的选择,因为我们可以利用它许多其他列表控件所不具备的功能。一旦解决了我的问题,下一个问题是如何将内容传递给 DataGrid
。DataSet
是唯一的替代方案。在我进一步工作,在将信息存储到 DataSet
之前,我不得不来回处理页面的大量信息。我决定使用站点对象来存储信息。
其中一位作者提出了以下最佳实践
- 类应该很小,大约有半打方法和属性。
- 方法应该简短,同样大约半打行。
在仔细分析代码并牢记最佳实践后,我将代码重新设计为现在的样子。
历史
- 修改代码以读取动态页面。
- 增强代码以支持全球化。
- 我还包含了一个读取和写入 XML 的演示项目。