/// <summary> /// Selects a node identified by its chm file and local /// </summary> /// <param name="col">treenode collection to search</param> /// <param name="chmFile">chm filename</param> /// <param name="local">local of the toc item</param> private void SelectTOCItem(TreeNodeCollection col, string chmFile, string local) { foreach (TreeNode curNode in col) { HtmlHelpTocItem curItem = curNode.Tag as HtmlHelpTocItem; if (curItem != null) { if ((curItem.Local == local) || (("/" + curItem.Local) == local)) { if (chmFile.Length > 0) { if (curItem.AssociatedFile.ChmFilePath == chmFile) { tocTreeView.SelectedNode = curNode; return; } } else { tocTreeView.SelectedNode = curNode; return; } } } SelectTOCItem(curNode.Nodes, chmFile, local); } }
/// <summary> /// Parses a HHC file and returns an list with the table of contents (TOC) tree /// </summary> /// <param name="hhcFile">string content of the hhc file</param> /// <param name="chmFile">CHMFile instance</param> /// <returns>Returns an list with the table of contents (TOC) tree</returns> public static List <HtmlHelpTocItem> ParseHHC(string hhcFile, CHMFile chmFile) { _lastTopicItem = null; _mergeItems = null; // clear merged item list List <HtmlHelpTocItem> tocList = new List <HtmlHelpTocItem>(); ulRE = new Regex(RE_ULBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); NestedRE = new Regex(RE_NestedBoundaries, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); ObjectRE = new Regex(RE_ObjectBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); ParamRE = new Regex(RE_ParamBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); AttributesRE = new Regex(RE_QuoteAttributes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline); int innerTextIdx = ulRE.GroupNumberFromName("innerText"); if (ulRE.IsMatch(hhcFile, 0)) { Match m = ulRE.Match(hhcFile, 0); int nFirstUL = 0; nFirstUL = hhcFile.ToLower().IndexOf("<ul>"); if (nFirstUL == -1) { nFirstUL = hhcFile.ToLower().IndexOf("<il>"); } if (ObjectRE.IsMatch(hhcFile, 0)) // first object block contains information types and categories { Match mO = ObjectRE.Match(hhcFile, 0); int iOTxt = ObjectRE.GroupNumberFromName("innerText"); string globalText = mO.Groups[iOTxt].Value; if (mO.Groups[iOTxt].Index <= nFirstUL) { ParseGlobalSettings(globalText, chmFile); } } // parse toc tree string innerText = m.Groups["innerText"].Value; innerText = innerText.Replace("(", "("); innerText = innerText.Replace(")", ")"); innerText = Regex.Replace(innerText, RE_ULOpening, "(", RegexOptions.IgnoreCase); innerText = Regex.Replace(innerText, RE_ULClosing, ")", RegexOptions.IgnoreCase); ParseTree(innerText, null, tocList, chmFile); } return(tocList); }
/// <summary> /// Recursively parses a sitemap tree /// </summary> /// <param name="text">content text</param> /// <param name="parent">Parent for all read items</param> /// <param name="arrNodes">arraylist which receives the extracted nodes</param> /// <param name="chmFile">CHMFile instance</param> private static void ParseTree(string text, HtmlHelpTocItem parent, IList <HtmlHelpTocItem> arrNodes, CHMFile chmFile) { string strPreItems = "", strPostItems = ""; string innerText = ""; int nIndex = 0; while (NestedRE.IsMatch(text, nIndex)) { Match m = NestedRE.Match(text, nIndex); innerText = m.Value.Substring(1, m.Length - 2); strPreItems = text.Substring(nIndex, m.Index - nIndex); ParseItems(strPreItems, parent, arrNodes, chmFile); if ((arrNodes.Count > 0) && (innerText.Length > 0)) { HtmlHelpTocItem p = arrNodes[arrNodes.Count - 1]; ParseTree(innerText, p, p.Children, chmFile); } nIndex = m.Index + m.Length; } if (nIndex == 0) { strPostItems = text.Substring(nIndex, text.Length - nIndex); ParseItems(strPostItems, parent, arrNodes, chmFile); } else if (nIndex < text.Length - 1) { strPostItems = text.Substring(nIndex, text.Length - nIndex); ParseTree(strPostItems, parent, arrNodes, chmFile); } }
/// <summary> /// Called if the user has collapsed a tree item /// </summary> /// <param name="sender">event sender</param> /// <param name="e">event parameter</param> private void tocTreeView_AfterCollapse(object sender, System.Windows.Forms.TreeViewEventArgs e) { HtmlHelpTocItem curItem = (HtmlHelpTocItem)(e.Node.Tag); if (curItem != null) { if (HtmlHelpSystem.UseHH2TreePics) { if (curItem.ImageIndex <= 15) { e.Node.ImageIndex = curItem.ImageIndex; e.Node.SelectedImageIndex = curItem.ImageIndex; } } else { if (curItem.ImageIndex < 8) { e.Node.ImageIndex = curItem.ImageIndex; e.Node.SelectedImageIndex = curItem.ImageIndex; } } } }
/// <summary> /// Parses tree nodes from the text /// </summary> /// <param name="itemstext">text containing the items</param> /// <param name="parent">Parent for all read items</param> /// <param name="arrNodes">arraylist where the nodes should be added</param> /// <param name="chmFile">CHMFile instance</param> private static void ParseItems(string itemstext, HtmlHelpTocItem parent, IList <HtmlHelpTocItem> arrNodes, CHMFile chmFile) { int innerTextIdx = ObjectRE.GroupNumberFromName("innerText"); int innerPTextIdx = ParamRE.GroupNumberFromName("innerText"); // get group-name indexes int nameIndex = AttributesRE.GroupNumberFromName("attributeName"); int valueIndex = AttributesRE.GroupNumberFromName("attributeValue"); int tdIndex = AttributesRE.GroupNumberFromName("attributeTD"); int nObjStartIndex = 0; while (ObjectRE.IsMatch(itemstext, nObjStartIndex)) { Match m = ObjectRE.Match(itemstext, nObjStartIndex); string innerText = m.Groups[innerTextIdx].Value; HtmlHelpTocItem tocItem = new HtmlHelpTocItem(); tocItem.TocMode = DataMode.TextBased; tocItem.AssociatedFile = chmFile; tocItem.Parent = parent; // read parameters int nParamIndex = 0; while (ParamRE.IsMatch(innerText, nParamIndex)) { Match mP = ParamRE.Match(innerText, nParamIndex); string innerP = mP.Groups[innerPTextIdx].Value; string paramName = ""; string paramValue = ""; int nAttrIdx = 0; while (AttributesRE.IsMatch(innerP, nAttrIdx)) { Match mA = AttributesRE.Match(innerP, nAttrIdx); string attributeName = mA.Groups[nameIndex].Value; string attributeValue = mA.Groups[valueIndex].Value; string attributeTD = mA.Groups[tdIndex].Value; if (attributeTD.Length > 0) { // delete the trailing textqualifier if (attributeValue.Length > 0) { int ltqi = attributeValue.LastIndexOf(attributeTD); if (ltqi >= 0) { attributeValue = attributeValue.Substring(0, ltqi); } } } if (attributeName.ToLower() == "name") { paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values } if (attributeName.ToLower() == "value") { paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values // delete trailing / while ((paramValue.Length > 0) && (paramValue[paramValue.Length - 1] == '/')) { paramValue = paramValue.Substring(0, paramValue.Length - 1); } } nAttrIdx = mA.Index + mA.Length; } tocItem.Params[paramName] = paramValue; switch (paramName.ToLower()) { case "name": { tocItem.Name = paramValue; }; break; case "local": { tocItem.Local = paramValue.Replace("../", "").Replace("./", ""); }; break; case "imagenumber": { tocItem.ImageIndex = Int32.Parse(paramValue); tocItem.ImageIndex -= 1; int nFolderAdd = 0; if ((chmFile != null) && (chmFile.ImageTypeFolder)) { // get the value which should be added, to display folders instead of books if (HtmlHelpSystem.UseHH2TreePics) { nFolderAdd = 8; } else { nFolderAdd = 4; } } if (tocItem.ImageIndex % 2 != 0) { if (tocItem.ImageIndex == 1) { tocItem.ImageIndex = 0; } } if (HtmlHelpSystem.UseHH2TreePics) { if (tocItem.ImageIndex == 0) { tocItem.ImageIndex = HtmlHelpTocItem.STD_FOLDER_HH2 + nFolderAdd; } } }; break; case "merge": // this item contains topics or a full TOC from a merged CHM { tocItem.MergeLink = paramValue; // "register" this item as merge-link if (_mergeItems == null) { _mergeItems = new List <HtmlHelpTocItem>(); } _mergeItems.Add(tocItem); }; break; case "type": // information type assignment for item { tocItem.InfoTypeStrings.Add(paramValue); }; break; } nParamIndex = mP.Index + mP.Length; } tocItem.ChmFile = chmFile.ChmFilePath; if (tocItem.MergeLink.Length > 0) { if (_lastTopicItem != null) { tocItem.Parent = _lastTopicItem; _lastTopicItem.Children.Add(tocItem); } else { arrNodes.Add(tocItem); } } else { _lastTopicItem = tocItem; arrNodes.Add(tocItem); } nObjStartIndex = m.Index + m.Length; } }
/// <summary> /// Shows help for a specific keyword /// </summary> /// <param name="namespaceFilter">namespace filter (used for merged files)</param> /// <param name="hlpNavigator">navigator value</param> /// <param name="keyword">keyword</param> /// <param name="url">url</param> void IHelpViewer.ShowHelp(string namespaceFilter, HelpNavigator hlpNavigator, string keyword, string url) { switch (hlpNavigator) { case HelpNavigator.AssociateIndex: { HtmlHelpIndexItem foundIdx = _reader.Index.SearchIndex(keyword, IndexType.AssiciativeLinks); if (foundIdx != null) { if (foundIdx.Topics.Count > 0) { HtmlHelpIndexTopic topic = foundIdx.Topics[0]; if (topic.Local.Length > 0) { NavigateBrowser(topic.URL); } } } } break; case HelpNavigator.Find: { this.Cursor = Cursors.WaitCursor; this.helpSearch.SetSearchText(keyword); DataTable dtResults = _reader.PerformSearch(keyword, 500, true, false); this.helpSearch.SetResults(dtResults); this.Cursor = Cursors.Arrow; this.helpSearch.Focus(); } break; case HelpNavigator.Index: { ((IHelpViewer)this).ShowHelpIndex(url); } break; case HelpNavigator.KeywordIndex: { HtmlHelpIndexItem foundIdx = _reader.Index.SearchIndex(keyword, IndexType.KeywordLinks); if (foundIdx != null) { if (foundIdx.Topics.Count == 1) { HtmlHelpIndexTopic topic = foundIdx.Topics[0]; if (topic.Local.Length > 0) { NavigateBrowser(topic.URL); } } else if (foundIdx.Topics.Count > 1) { this.helpIndex.SelectText(foundIdx.IndentKeyWord); } } this.helpIndex.Focus(); } break; case HelpNavigator.TableOfContents: { HtmlHelpTocItem foundTOC = _reader.HtmlHelpToc.SearchTopic(keyword); if (foundTOC != null) { if (foundTOC.Local.Length > 0) { NavigateBrowser(foundTOC.Url); } } this.helpTocTree.Focus(); } break; case HelpNavigator.Topic: { HtmlHelpTocItem foundTOC = _reader.HtmlHelpToc.SearchTopic(keyword); if (foundTOC != null) { if (foundTOC.Local.Length > 0) { NavigateBrowser(foundTOC.Url); } } } break; } }
/// <summary> /// Decodes the binary file data and fills the internal properties /// </summary> /// <returns>true if succeeded</returns> private bool DecodeData() { _toc = new List <HtmlHelpTocItem>(); _offsetTable = new Hashtable(); bool bRet = true; MemoryStream memStream = new MemoryStream(_binaryFileData); BinaryReader binReader = new BinaryReader(memStream); int nCurOffset = 0; _offset2028 = binReader.ReadInt32(); _offset16structs = binReader.ReadInt32(); _numberOf16structs = binReader.ReadInt32(); _offsetOftopics = binReader.ReadInt32(); binReader.BaseStream.Seek(_offset2028, SeekOrigin.Begin); if (RecursivelyBuildTree(ref binReader, _offset2028, _toc, null)) { binReader.BaseStream.Seek(_offset16structs, SeekOrigin.Begin); nCurOffset = (int)binReader.BaseStream.Position; for (int i = 0; i < _numberOf16structs; i++) { int tocOffset = binReader.ReadInt32(); int sqNr = binReader.ReadInt32(); int topOffset = binReader.ReadInt32(); int hhctopicIdx = binReader.ReadInt32(); nCurOffset = (int)binReader.BaseStream.Position; int topicIdx = -1; // if the topic offset is within the range of the stream // and is >= the offset of the first topic dword if ((topOffset < (binReader.BaseStream.Length - 4)) && (topOffset >= _offsetOftopics)) { // read the index of the topic for this item binReader.BaseStream.Seek(topOffset, SeekOrigin.Begin); topicIdx = binReader.ReadInt32(); binReader.BaseStream.Seek(nCurOffset, SeekOrigin.Begin); HtmlHelpTocItem item = (HtmlHelpTocItem)_offsetTable[tocOffset.ToString()]; if (item != null) { if ((topicIdx < _associatedFile.TopicsFile.TopicTable.Count) && (topicIdx >= 0)) { TopicEntry te = (TopicEntry)(_associatedFile.TopicsFile.TopicTable[topicIdx]); if ((te != null) && (item.TopicOffset < 0)) { item.TopicOffset = te.EntryOffset; } } } } } } return(bRet); }
/// <summary> /// Recursively reads the binary toc tree from the file /// </summary> /// <param name="binReader">reference to binary reader</param> /// <param name="NodeOffset">offset of the first node in the current level</param> /// <param name="level">arraylist of TOCItems for the current level</param> /// <param name="parentItem">parent item for the item</param> /// <returns>Returns true if succeeded</returns> private bool RecursivelyBuildTree(ref BinaryReader binReader, int NodeOffset, IList <HtmlHelpTocItem> level, HtmlHelpTocItem parentItem) { bool bRet = true; int nextOffset = 0; int nReadOffset = (int)binReader.BaseStream.Position; binReader.BaseStream.Seek(NodeOffset, SeekOrigin.Begin); do { int nCurOffset = (int)binReader.BaseStream.Position; int unkn1 = binReader.ReadInt16(); // unknown int unkn2 = binReader.ReadInt16(); // unknown int flag = binReader.ReadInt32(); int nFolderAdd = 0; if ((_associatedFile != null) && (_associatedFile.ImageTypeFolder)) { // get the value which should be added, to display folders instead of books if (HtmlHelpSystem.UseHH2TreePics) { nFolderAdd = 8; } else { nFolderAdd = 4; } } int nFolderImgIdx = (HtmlHelpSystem.UseHH2TreePics ? (HtmlHelpTocItem.STD_FOLDER_HH2 + nFolderAdd) : (HtmlHelpTocItem.STD_FOLDER_HH1 + nFolderAdd)); int nFileImgIdx = (HtmlHelpSystem.UseHH2TreePics ? HtmlHelpTocItem.STD_FILE_HH2 : HtmlHelpTocItem.STD_FILE_HH1); int stdImage = ((flag & 0x4) != 0) ? nFolderImgIdx : nFileImgIdx; int stringOffset = binReader.ReadInt32(); int ParentOffset = binReader.ReadInt32(); nextOffset = binReader.ReadInt32(); int firstChildOffset = 0; int unkn3 = 0; if ((flag & 0x4) != 0) { firstChildOffset = binReader.ReadInt32(); unkn3 = binReader.ReadInt32(); // unknown } HtmlHelpTocItem newItem = new HtmlHelpTocItem(); newItem.ImageIndex = stdImage; newItem.Offset = nCurOffset; newItem.OffsetNext = nextOffset; newItem.AssociatedFile = _associatedFile; newItem.TocMode = DataMode.Binary; newItem.Parent = parentItem; if ((flag & 0x08) == 0) { // toc item doesn't have a local value (=> stringOffset = offset of strings file) newItem.Name = _associatedFile.StringsFile[stringOffset]; } else { // this item has a topic entry (=> stringOffset = index of topic entry) if ((stringOffset < _associatedFile.TopicsFile.TopicTable.Count) && (stringOffset >= 0)) { TopicEntry te = (TopicEntry)(_associatedFile.TopicsFile.TopicTable[stringOffset]); if (te != null) { newItem.TopicOffset = te.EntryOffset; } } } _offsetTable[nCurOffset.ToString()] = newItem; // if this item has children (firstChildOffset > 0) if (firstChildOffset > 0) { bRet &= RecursivelyBuildTree(ref binReader, firstChildOffset, newItem.Children, newItem); } level.Add(newItem); if (nCurOffset != nextOffset) { binReader.BaseStream.Seek(nextOffset, SeekOrigin.Begin); } }while(nextOffset != 0); binReader.BaseStream.Seek(nReadOffset, SeekOrigin.Begin); return(bRet); }
/// <summary> /// Standard constructor /// </summary> /// <param name="item">toc item associated with the event</param> public TocEventArgs(HtmlHelpTocItem item) { _tocItem = item; }