using System;
using System.IO;
using System.Collections;
using System.Data;
using HtmlHelp.ChmDecoding;
namespace HtmlHelp
{
///
/// The class HtmlHelpSystem implements the main object for reading chm files
///
public sealed class HtmlHelpSystem
{
///
/// Private shared instance of current HtmlHelpSystem class
///
private static HtmlHelpSystem _current=null;
///
/// Internal member storing the attached files
///
private ArrayList _chmFiles = new ArrayList();
///
/// Internal member storing a merged table of contents
///
private TableOfContents _toc = new TableOfContents();
///
/// Internal member storing a merged index
///
private Index _index = new Index();
///
/// URL prefix for specifying a chm destination
///
private static string _urlPrefix = "ms-its:";
///
/// Internal flag specifying if the system should use the tree-images list
/// from HtmlHelp2. If false the standard CHM-Viewer pics will be used.
///
private static bool _useHH2TreePics = false;
///
/// Internal member storing the read information types
///
private ArrayList _informationTypes = new ArrayList();
///
/// Internal member storing the read categories
///
private ArrayList _categories = new ArrayList();
///
/// Gets/Sets the url prefix for specifying a chm destination
///
public static string UrlPrefix
{
get { return _urlPrefix; }
set { _urlPrefix = value; }
}
public CHMStream.CHMStream BaseStream
{
get
{
CHMFile chm=(CHMFile)_chmFiles[0];
return chm.BaseStream;
}
}
///
/// Gets/Sets the flag specifying if the system should use the tree-images list
/// from HtmlHelp2. If false the standard CHM-Viewer pics will be used.
///
public static bool UseHH2TreePics
{
get { return _useHH2TreePics; }
set { _useHH2TreePics = value; }
}
///
/// Gets the current HtmlHelpSystem instance
///
public static HtmlHelpSystem Current
{
get
{
return _current;
}
}
///
/// Standard constructor
///
public HtmlHelpSystem() : this("")
{
}
///
/// Constructor of the reader class
///
/// chm file to attach with the reader
public HtmlHelpSystem(string chmFile)
{
_current = this;
OpenFile(chmFile);
}
///
/// Opens a chm file and creates
///
/// full file path of the chm file to open
/// If you call this method, all existing merged files will be cleared.
public void OpenFile(string chmFile)
{
OpenFile(chmFile, null);
}
///
/// Opens a chm file and creates
///
/// full file path of the chm file to open
/// dumping info
/// If you call this method, all existing merged files will be cleared.
public void OpenFile(string chmFile, DumpingInfo dmpInfo)
{
if( File.Exists(chmFile ) )
{
_chmFiles.Clear();
_toc.Clear();
_index.Clear();
_informationTypes.Clear();
_categories.Clear();
CHMFile newFile = new CHMFile(this, chmFile, dmpInfo);
_toc = new TableOfContents( newFile.TOC );
_index = new Index( newFile.IndexKLinks, newFile.IndexALinks );
_chmFiles.Add(newFile);
// add all infotypes and categories of the read file to this system instance
MergeFileInfoTypesCategories(newFile);
// check if the file has a merged files list
if( newFile.MergedFiles.Length > 0 )
{
// extract the path of the chm file (usually merged files are in the same path)
FileInfo fi = new FileInfo(chmFile);
string sPath = fi.DirectoryName;
for(int i=0; i 0)
{
if(sFile[1] != ':') // no full path setting
{
sFile = Path.Combine(sPath, sFile);
}
MergeFile(sFile, dmpInfo, true);
}
}
// if (newFile.MergLinks.Count>0)
// RecalculateMergeLinks(newFile);
RemoveMergeLinks(); // clear all merge-links which have no target !
}
}
}
///
/// Merges a chm file to the current help contents
///
/// full file path of the chm file to merge
public void MergeFile(string chmFile)
{
MergeFile(chmFile, null);
}
///
/// Merges a chm file to the current help contents
///
/// full file path of the chm file to merge
/// dumping info
public void MergeFile(string chmFile, DumpingInfo dmpInfo)
{
MergeFile(chmFile, dmpInfo, false);
}
///
/// Merges a chm file to the current help contents
///
/// full file path of the chm file to merge
/// dumping info
/// true if the merge is done because a merged file list
/// was found in the previously loaded CHM.
internal void MergeFile(string chmFile, DumpingInfo dmpInfo, bool mergedFileList)
{
if( File.Exists(chmFile ) )
{
if( _chmFiles.Count == 1)
{
// if we open the first file, we directly point into the toc and index of this file.
// So that we don't merge the new toc's indexe's into the first file, we have to
// clone the internal arraylists first to a new instance of the toc/index holder classes.
ArrayList atoc = _toc.TOC;
ArrayList alinks = _index.ALinks;
ArrayList klinks = _index.KLinks;
_toc = new TableOfContents();
_index = new Index();
_toc.MergeToC( atoc );
_index.MergeIndex( alinks, IndexType.AssiciativeLinks );
_index.MergeIndex( klinks, IndexType.KeywordLinks );
}
CHMFile newFile = new CHMFile(this, chmFile, dmpInfo);
if(mergedFileList) // if we've called this method due to a merged file list merge
{
RecalculateMergeLinks(newFile);
_toc.MergeToC( newFile.TOC, _chmFiles );
_index.MergeIndex( newFile.IndexALinks, IndexType.AssiciativeLinks );
_index.MergeIndex( newFile.IndexKLinks, IndexType.KeywordLinks );
_chmFiles.Add(newFile);
// add all infotypes and categories of the read file to this system instance
MergeFileInfoTypesCategories(newFile);
}
else
{
_toc.MergeToC( newFile.TOC, _chmFiles );
_index.MergeIndex( newFile.IndexALinks, IndexType.AssiciativeLinks );
_index.MergeIndex( newFile.IndexKLinks, IndexType.KeywordLinks );
_chmFiles.Add(newFile);
// add all infotypes and categories of the read file to this system instance
MergeFileInfoTypesCategories(newFile);
// check if the file has a merged files list
if( newFile.MergedFiles.Length > 0 )
{
// extract the path of the chm file (usually merged files are in the same path)
FileInfo fi = new FileInfo(chmFile);
string sPath = fi.DirectoryName;
for(int i=0; i 0)
{
if(sFile[1] != ':') // no full path setting
{
sFile = Path.Combine(sPath, sFile);
}
MergeFile(sFile, dmpInfo, true);
}
}
RemoveMergeLinks(); // clear all merge-links which have no target !
}
}
}
}
///
/// Checks all Merg-links read till now. Checks if the merg-link points to the
/// file currentFile. If yes the link will be replaced by the contents of the
/// merged file.
///
/// Current CHMFile instance
internal void RecalculateMergeLinks(CHMFile currentFile)
{
foreach(CHMFile curFile in _chmFiles)
{
if( curFile.MergLinks.Count > 0)
{
for(int i=0; i 3) // merge info contains path name
{
sFName = sSplit[0] + ":" + sSplit[1];
sTarget = sSplit[3];
}
else if( sSplit.Length == 3)// merge info contains only file name
{
FileInfo fi = new FileInfo(currentFile.ChmFilePath);
string sPath = fi.DirectoryName;
string sFile = sSplit[0];
if(sFile.Length > 0)
{
if(sFile[1] != ':') // no full path setting
{
sFile = Path.Combine(sPath, sFile);
}
}
sFName = sFile;
sTarget = sSplit[2];
}
ArrayList arrToc = null;
if( (sFName.Length>0) && (sTarget.Length>0) )
{
// if this link points into the current file
if( sFName.ToLower() == currentFile.ChmFilePath.ToLower() )
{
if(sTarget.ToLower().IndexOf(".hhc") >= 0)
{
string sfCheck = sTarget;
// remove prefixing ./
while( (sfCheck[0]=='.') || (sfCheck[0]=='/') )
{
sfCheck = sfCheck.Substring(1);
}
if( currentFile.ContentsFile.ToLower() != sfCheck )
{
arrToc = currentFile.ParseHHC( sTarget );
if( arrToc.Count > 0)
{
}
}
else
{
arrToc = currentFile.TOC;
}
// target points to a complete TOC
int nCnt = 0;
foreach(TOCItem chkItem in arrToc)
{
if(nCnt == 0)
{
curItem.AssociatedFile = currentFile;
curItem.Children = chkItem.Children;
curItem.ChmFile = currentFile.ChmFilePath;
curItem.ImageIndex = chkItem.ImageIndex;
curItem.Local = chkItem.Local;
curItem.MergeLink = chkItem.MergeLink;
curItem.Name = chkItem.Name;
curItem.TocMode = chkItem.TocMode;
curItem.TopicOffset = chkItem.TopicOffset;
MarkChildrenAdded(chkItem.Children, curFile.MergLinks);
}
else
{
ArrayList checkList = null;
if(curItem.Parent != null)
checkList = curItem.Parent.Children;
else
checkList = curFile.TOC;
int nIdx = checkList.IndexOf(curItem);
if((nIdx+nCnt)>checkList.Count)
checkList.Add(chkItem);
else
checkList.Insert(nIdx+nCnt, chkItem);
curFile.MergLinks.Add(chkItem);
MarkChildrenAdded(chkItem.Children, curFile.MergLinks);
}
nCnt++;
}
}
else
{
// target points to a single topic
TOCItem chkItem = currentFile.GetTOCItemByLocal(sTarget);
if(chkItem != null)
{
curItem.AssociatedFile = currentFile;
curItem.Children = chkItem.Children;
curItem.ChmFile = currentFile.ChmFilePath;
curItem.ImageIndex = chkItem.ImageIndex;
curItem.Local = chkItem.Local;
curItem.MergeLink = chkItem.MergeLink;
curItem.Name = chkItem.Name;
curItem.TocMode = chkItem.TocMode;
curItem.TopicOffset = chkItem.TopicOffset;
curFile.MergLinks.Add(chkItem);
MarkChildrenAdded(chkItem.Children, curFile.MergLinks);
}
}
}
}
}
}
}
}
///
/// Adds sub-items of an TOC-entry to the merg-linked list.
/// This will mark this item as "added" during the extra merge run
/// of the HtmlHelpSystem class.
///
/// TOCItem list
/// Arraylist which holds the merged-items
internal void MarkChildrenAdded(ArrayList tocs, ArrayList merged)
{
foreach(TOCItem curItem in tocs)
{
if(!merged.Contains(curItem))
{
merged.Add(curItem);
MarkChildrenAdded(curItem.Children, merged);
}
}
}
///
/// Removes merge-links from the toc of files which were not loaded
///
internal void RemoveMergeLinks()
{
foreach(CHMFile curFile in _chmFiles)
{
if( curFile.MergLinks.Count > 0)
{
while(curFile.MergLinks.Count > 0)
{
TOCItem curItem = curFile.MergLinks[0] as TOCItem;
if(curItem.MergeLink.Length > 0)
curFile.RemoveTOCItem(curItem);
curFile.MergLinks.RemoveAt(0);
}
}
}
}
///
/// Merges the information types and categories read by the CHMFile instance
/// into the system instance
///
/// file instance
private void MergeFileInfoTypesCategories(CHMFile chmFile)
{
if(chmFile.HasInformationTypes)
{
for(int i=0; i
/// Removes the information types and categories read by the CHMFile instance
///
/// file instance
private void RemoveFileInfoTypesCategories(CHMFile chmFile)
{
if(chmFile.HasInformationTypes)
{
for(int i=0; i
/// Removes a chm file from the internal file collection
///
/// full file path of the chm file to remove
public void RemoveFile(string chmFile)
{
int nIdx = -1;
CHMFile removeInstance=null;
foreach(CHMFile curFile in _chmFiles)
{
nIdx++;
if( curFile.ChmFilePath.ToLower() == chmFile.ToLower() )
{
removeInstance = curFile;
break;
}
}
if(nIdx >= 0)
{
_toc.Clear(); // forces a rebuild of the merged toc
_index.Clear(); // force a rebuild of the merged index
RemoveFileInfoTypesCategories(removeInstance);
_chmFiles.RemoveAt(nIdx);
}
}
///
/// Closes all files and destroys TOC/index
///
public void CloseAllFiles()
{
for(int i=0; i < _chmFiles.Count; i++)
{
CHMFile curFile = _chmFiles[i] as CHMFile;
_chmFiles.RemoveAt(i);
curFile.Dispose();
i--;
}
_chmFiles.Clear();
_toc.Clear();
_index.Clear();
_informationTypes.Clear();
_categories.Clear();
}
///
/// Gets an array of loaded chm files.
///
public CHMFile[] FileList
{
get
{
CHMFile[] ret = new CHMFile[ _chmFiles.Count ];
for(int i=0;i<_chmFiles.Count;i++)
ret[i] = (CHMFile)_chmFiles[i];
return ret;
}
}
///
/// Returns true if the HtmlHelpSystem instance contains 1 or more information types
///
public bool HasInformationTypes
{
get { return (_informationTypes.Count>0); }
}
///
/// Returns true if the HtmlHelpSystem instance contains 1 or more categories
///
public bool HasCategories
{
get { return (_categories.Count>0); }
}
///
/// Gets an ArrayList of InformationType items
///
public ArrayList InformationTypes
{
get { return _informationTypes; }
}
///
/// Gets an ArrayList of Category items
///
public ArrayList Categories
{
get { return _categories; }
}
///
/// Gets the information type specified by its name
///
/// name of the information type to receive
/// Returns the Instance for the name or null if not found
public InformationType GetInformationType(string name)
{
if(HasInformationTypes)
{
for(int i=0; i<_informationTypes.Count;i++)
{
InformationType iT = _informationTypes[i] as InformationType;
if(iT.Name == name)
return iT;
}
}
return null;
}
///
/// Gets the category specifiyd by its name
///
/// name of the category
/// Returns the Instance for the name or null if not found
public Category GetCategory(string name)
{
if(HasCategories)
{
for(int i=0; i<_categories.Count;i++)
{
Category cat = _categories[i] as Category;
if(cat.Name == name)
return cat;
}
}
return null;
}
///
/// Gets the default topic
///
public string DefaultTopic
{
get
{
if( _chmFiles.Count > 0 )
{
foreach(CHMFile curFile in _chmFiles)
{
if( curFile.DefaultTopic.Length > 0)
{
return curFile.FormURL( curFile.DefaultTopic );
}
}
}
return "about:blank";
}
}
///
/// Gets a merged table of contents of all opened chm files
///
public TableOfContents TableOfContents
{
get
{
if( _chmFiles.Count > 0 )
{
if( _toc.Count() <= 0)
{
// merge toc of files
foreach(CHMFile curFile in _chmFiles)
{
_toc.MergeToC( curFile.TOC );
}
}
}
return _toc;
}
}
///
/// Gets a merged index of all opened chm files
///
public Index Index
{
get
{
if( _chmFiles.Count > 0 )
{
if( (_index.Count(IndexType.KeywordLinks)+_index.Count(IndexType.AssiciativeLinks)) <= 0)
{
// merge index files
foreach(CHMFile curFile in _chmFiles)
{
_index.MergeIndex( curFile.IndexKLinks, IndexType.KeywordLinks);
_index.MergeIndex( curFile.IndexALinks, IndexType.AssiciativeLinks);
}
}
}
return _index;
}
}
///
/// Gets a flag if the current instance offers a table of contents
///
public bool HasTableOfContents
{
get
{
return (TableOfContents.Count() > 0);
}
}
///
/// Gets a flag if the current instance offers an index
///
public bool HasIndex
{
get
{
return (HasALinks || HasKLinks);
}
}
///
/// Gets a flag if the index holds klinks
///
public bool HasKLinks
{
get
{
return (_index.Count(IndexType.KeywordLinks) > 0);
}
}
///
/// Gets a flag if the index holds alinks
///
public bool HasALinks
{
get
{
return (_index.Count(IndexType.AssiciativeLinks) > 0);
}
}
///
/// Gets a flag if the current instance supports fulltext searching
///
public bool FullTextSearch
{
get
{
bool bRet = false;
foreach(CHMFile curFile in _chmFiles)
{
bRet |= curFile.FullTextSearch;
}
return bRet;
}
}
///
/// Performs a full-text search over the chm files
///
/// words to search
/// true if partial word should be matched also
/// ( if this is true a search of 'support' will match 'supports', otherwise not )
/// true if titles only
/// A DataTable containing the search hits
public DataTable PerformSearch(string words, bool partialMatches, bool titleOnly)
{
return PerformSearch(words, -1, partialMatches, titleOnly);
}
///
/// Performs a full-text search over the chm files
///
/// words to search
/// maximal number of hits to return
/// true if partial word should be matched also
/// ( if this is true a search of 'support' will match 'supports', otherwise not )
/// true if titles only
/// A DataTable containing the search hits
public DataTable PerformSearch(string words, int MaxResults, bool partialMatches, bool titleOnly)
{
if( ! FullTextSearch )
return null;
DataTable dtResult = null;
int nCnt = 0;
foreach(CHMFile curFile in _chmFiles)
{
if(nCnt == 0)
{
if(curFile.FullTextSearchEngine.CanSearch)
{
if(curFile.FullTextSearchEngine.Search(words, MaxResults, partialMatches, titleOnly))
{
dtResult = curFile.FullTextSearchEngine.Hits;
dtResult.DefaultView.Sort = "Rating DESC";
}
}
}
else
{
if(curFile.FullTextSearchEngine.CanSearch)
{
if(curFile.FullTextSearchEngine.Search(words, MaxResults, partialMatches, titleOnly))
{
DataTable table = curFile.FullTextSearchEngine.Hits;
// append rows from 2nd file
foreach(DataRow curRow in table.Rows)
{
dtResult.ImportRow( curRow );
}
dtResult.DefaultView.Sort = "Rating DESC";
// adjust max hits
if(MaxResults >= 0)
{
if(dtResult.DefaultView.Count > MaxResults)
{
for(int i=MaxResults-1; i