├── .gitattributes ├── .gitignore ├── DosHelp.sln ├── HelpBrowser ├── App.config ├── AppIcon.ico ├── HelpBrowser.csproj ├── MainForm.Designer.cs ├── MainForm.cs ├── MainForm.resx ├── Program.cs └── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings ├── HelpConvert ├── App.config ├── HelpConvert.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── LICENSE ├── QuickHelp ├── Compression │ ├── BitStream.cs │ ├── CompressionStream.cs │ ├── HuffmanStream.cs │ ├── HuffmanTree.cs │ ├── KeywordCompression.cs │ └── StreamExtensions.cs ├── Formatters │ ├── Default.css │ ├── EmbeddedHtmlFormatter.cs │ ├── HtmlFormatter.cs │ └── TextFormatter.cs ├── HelpDatabase.cs ├── HelpLine.cs ├── HelpSystem.cs ├── HelpTopic.cs ├── HelpUri.cs ├── Properties │ └── AssemblyInfo.cs ├── QuickHelp.csproj └── Serialization │ ├── BinaryHelpSerializer.cs │ ├── BufferReader.cs │ ├── Format.txt │ ├── Graphic437Encoding.cs │ ├── HelpCommand.cs │ ├── HelpFile.cs │ ├── SerializationOptions.cs │ └── StreamView.cs └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _*/ 2 | .vs/ 3 | 4 | ## Ignore Visual Studio temporary files, build results, and 5 | ## files generated by popular Visual Studio add-ons. 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.sln.docstates 11 | 12 | # Build results 13 | 14 | [Dd]ebug/ 15 | [Rr]elease/ 16 | x64/ 17 | build/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 22 | !packages/*/build/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | *_i.c 29 | *_p.c 30 | *.ilk 31 | *.meta 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.tmp_proj 44 | *.log 45 | *.vspscc 46 | *.vssscc 47 | .builds 48 | *.pidb 49 | *.log 50 | *.scc 51 | 52 | # Visual C++ cache files 53 | ipch/ 54 | *.aps 55 | *.ncb 56 | *.opensdf 57 | *.sdf 58 | *.cachefile 59 | 60 | # Visual Studio profiler 61 | *.psess 62 | *.vsp 63 | *.vspx 64 | 65 | # Guidance Automation Toolkit 66 | *.gpState 67 | 68 | # ReSharper is a .NET coding add-in 69 | _ReSharper*/ 70 | *.[Rr]e[Ss]harper 71 | 72 | # TeamCity is a build add-in 73 | _TeamCity* 74 | 75 | # DotCover is a Code Coverage Tool 76 | *.dotCover 77 | 78 | # NCrunch 79 | *.ncrunch* 80 | .*crunch*.local.xml 81 | 82 | # Installshield output folder 83 | [Ee]xpress/ 84 | 85 | # DocProject is a documentation generator add-in 86 | DocProject/buildhelp/ 87 | DocProject/Help/*.HxT 88 | DocProject/Help/*.HxC 89 | DocProject/Help/*.hhc 90 | DocProject/Help/*.hhk 91 | DocProject/Help/*.hhp 92 | DocProject/Help/Html2 93 | DocProject/Help/html 94 | 95 | # Click-Once directory 96 | publish/ 97 | 98 | # Publish Web Output 99 | *.Publish.xml 100 | 101 | # NuGet Packages Directory 102 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 103 | #packages/ 104 | 105 | # Windows Azure Build Output 106 | csx 107 | *.build.csdef 108 | 109 | # Windows Store app package directory 110 | AppPackages/ 111 | 112 | # Others 113 | sql/ 114 | *.Cache 115 | ClientBin/ 116 | [Ss]tyle[Cc]op.* 117 | ~$* 118 | *~ 119 | *.dbmdl 120 | *.[Pp]ublish.xml 121 | *.pfx 122 | *.publishsettings 123 | 124 | # RIA/Silverlight projects 125 | Generated_Code/ 126 | 127 | # Backup & report files from converting an old project file to a newer 128 | # Visual Studio version. Backup files are not needed, because we have git ;-) 129 | _UpgradeReport_Files/ 130 | Backup*/ 131 | UpgradeLog*.XML 132 | UpgradeLog*.htm 133 | 134 | # SQL Server files 135 | App_Data/*.mdf 136 | App_Data/*.ldf 137 | 138 | 139 | #LightSwitch generated files 140 | GeneratedArtifacts/ 141 | _Pvt_Extensions/ 142 | ModelManifest.xml 143 | 144 | # ========================= 145 | # Windows detritus 146 | # ========================= 147 | 148 | # Windows image file caches 149 | Thumbs.db 150 | ehthumbs.db 151 | 152 | # Folder config file 153 | Desktop.ini 154 | 155 | # Recycle Bin used on file shares 156 | $RECYCLE.BIN/ 157 | 158 | # Mac desktop service store files 159 | .DS_Store 160 | -------------------------------------------------------------------------------- /DosHelp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26430.12 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickHelp", "QuickHelp\QuickHelp.csproj", "{C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelpBrowser", "HelpBrowser\HelpBrowser.csproj", "{00E978CD-7420-40EA-91A2-18E0F3CFF5C1}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelpConvert", "HelpConvert\HelpConvert.csproj", "{469806C9-9B3B-4AA2-94D9-35A2143FC585}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FC1F06ED-D17E-4F95-94F2-B133245A89D1}" 13 | ProjectSection(SolutionItems) = preProject 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {00E978CD-7420-40EA-91A2-18E0F3CFF5C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {00E978CD-7420-40EA-91A2-18E0F3CFF5C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {00E978CD-7420-40EA-91A2-18E0F3CFF5C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {00E978CD-7420-40EA-91A2-18E0F3CFF5C1}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {469806C9-9B3B-4AA2-94D9-35A2143FC585}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {469806C9-9B3B-4AA2-94D9-35A2143FC585}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {469806C9-9B3B-4AA2-94D9-35A2143FC585}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {469806C9-9B3B-4AA2-94D9-35A2143FC585}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /HelpBrowser/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /HelpBrowser/AppIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fancidev/DosHelp/98182a9fad6dd95c87ce77e4f0286e7f8f862dfb/HelpBrowser/AppIcon.ico -------------------------------------------------------------------------------- /HelpBrowser/HelpBrowser.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {00E978CD-7420-40EA-91A2-18E0F3CFF5C1} 8 | WinExe 9 | Properties 10 | HelpBrowser 11 | HelpBrowser 12 | v2.0 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | Form 43 | 44 | 45 | MainForm.cs 46 | 47 | 48 | 49 | 50 | MainForm.cs 51 | 52 | 53 | ResXFileCodeGenerator 54 | Resources.Designer.cs 55 | Designer 56 | 57 | 58 | True 59 | Resources.resx 60 | True 61 | 62 | 63 | SettingsSingleFileGenerator 64 | Settings.Designer.cs 65 | 66 | 67 | True 68 | Settings.settings 69 | True 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | {c18c4ed1-e4b6-4dc3-b7cf-55053fec165b} 78 | QuickHelp 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | -------------------------------------------------------------------------------- /HelpBrowser/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace HelpBrowser 2 | { 3 | partial class MainForm 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.components = new System.ComponentModel.Container(); 32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 33 | this.lstTopics = new System.Windows.Forms.ListBox(); 34 | this.webBrowser1 = new System.Windows.Forms.WebBrowser(); 35 | this.txtSource = new System.Windows.Forms.TextBox(); 36 | this.splitContainer1 = new System.Windows.Forms.SplitContainer(); 37 | this.tabControl2 = new System.Windows.Forms.TabControl(); 38 | this.tabTopics = new System.Windows.Forms.TabPage(); 39 | this.tabContexts = new System.Windows.Forms.TabPage(); 40 | this.lstContexts = new System.Windows.Forms.ListBox(); 41 | this.tabErrors = new System.Windows.Forms.TabPage(); 42 | this.lstErrors = new System.Windows.Forms.ListBox(); 43 | this.panel1 = new System.Windows.Forms.Panel(); 44 | this.cbDatabases = new System.Windows.Forms.ComboBox(); 45 | this.btnAddArchive = new System.Windows.Forms.Button(); 46 | this.btnRemoveArchive = new System.Windows.Forms.Button(); 47 | this.tabControl1 = new System.Windows.Forms.TabControl(); 48 | this.tabHtml = new System.Windows.Forms.TabPage(); 49 | this.txtTopicTitle = new System.Windows.Forms.TextBox(); 50 | this.tabText = new System.Windows.Forms.TabPage(); 51 | this.txtNoFormat = new System.Windows.Forms.TextBox(); 52 | this.tabSource = new System.Windows.Forms.TabPage(); 53 | this.menuStrip1 = new System.Windows.Forms.MenuStrip(); 54 | this.mnuFile = new System.Windows.Forms.ToolStripMenuItem(); 55 | this.mnuFileOpen = new System.Windows.Forms.ToolStripMenuItem(); 56 | this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator(); 57 | this.mnuFileExit = new System.Windows.Forms.ToolStripMenuItem(); 58 | this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 59 | this.mnuViewUnresolvedLinks = new System.Windows.Forms.ToolStripMenuItem(); 60 | this.mnuViewErrors = new System.Windows.Forms.ToolStripMenuItem(); 61 | this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); 62 | this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); 63 | this.splitContainer1.Panel1.SuspendLayout(); 64 | this.splitContainer1.Panel2.SuspendLayout(); 65 | this.splitContainer1.SuspendLayout(); 66 | this.tabControl2.SuspendLayout(); 67 | this.tabTopics.SuspendLayout(); 68 | this.tabContexts.SuspendLayout(); 69 | this.tabErrors.SuspendLayout(); 70 | this.panel1.SuspendLayout(); 71 | this.tabControl1.SuspendLayout(); 72 | this.tabHtml.SuspendLayout(); 73 | this.tabText.SuspendLayout(); 74 | this.tabSource.SuspendLayout(); 75 | this.menuStrip1.SuspendLayout(); 76 | this.SuspendLayout(); 77 | // 78 | // lstTopics 79 | // 80 | this.lstTopics.Dock = System.Windows.Forms.DockStyle.Fill; 81 | this.lstTopics.FormattingEnabled = true; 82 | this.lstTopics.HorizontalScrollbar = true; 83 | this.lstTopics.ItemHeight = 15; 84 | this.lstTopics.Location = new System.Drawing.Point(3, 3); 85 | this.lstTopics.Margin = new System.Windows.Forms.Padding(4); 86 | this.lstTopics.Name = "lstTopics"; 87 | this.lstTopics.Size = new System.Drawing.Size(227, 293); 88 | this.lstTopics.TabIndex = 0; 89 | this.lstTopics.SelectedIndexChanged += new System.EventHandler(this.lstTopics_SelectedIndexChanged); 90 | // 91 | // webBrowser1 92 | // 93 | this.webBrowser1.Dock = System.Windows.Forms.DockStyle.Fill; 94 | this.webBrowser1.Location = new System.Drawing.Point(0, 23); 95 | this.webBrowser1.MinimumSize = new System.Drawing.Size(20, 20); 96 | this.webBrowser1.Name = "webBrowser1"; 97 | this.webBrowser1.Size = new System.Drawing.Size(481, 299); 98 | this.webBrowser1.TabIndex = 1; 99 | this.webBrowser1.Navigating += new System.Windows.Forms.WebBrowserNavigatingEventHandler(this.webBrowser1_Navigating); 100 | // 101 | // txtSource 102 | // 103 | this.txtSource.Dock = System.Windows.Forms.DockStyle.Fill; 104 | this.txtSource.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 105 | this.txtSource.Location = new System.Drawing.Point(3, 3); 106 | this.txtSource.Multiline = true; 107 | this.txtSource.Name = "txtSource"; 108 | this.txtSource.ReadOnly = true; 109 | this.txtSource.ScrollBars = System.Windows.Forms.ScrollBars.Both; 110 | this.txtSource.Size = new System.Drawing.Size(475, 318); 111 | this.txtSource.TabIndex = 2; 112 | this.txtSource.WordWrap = false; 113 | // 114 | // splitContainer1 115 | // 116 | this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill; 117 | this.splitContainer1.Location = new System.Drawing.Point(0, 24); 118 | this.splitContainer1.Name = "splitContainer1"; 119 | // 120 | // splitContainer1.Panel1 121 | // 122 | this.splitContainer1.Panel1.Controls.Add(this.tabControl2); 123 | this.splitContainer1.Panel1.Controls.Add(this.panel1); 124 | // 125 | // splitContainer1.Panel2 126 | // 127 | this.splitContainer1.Panel2.Controls.Add(this.tabControl1); 128 | this.splitContainer1.Size = new System.Drawing.Size(734, 350); 129 | this.splitContainer1.SplitterDistance = 241; 130 | this.splitContainer1.TabIndex = 4; 131 | // 132 | // tabControl2 133 | // 134 | this.tabControl2.Controls.Add(this.tabTopics); 135 | this.tabControl2.Controls.Add(this.tabContexts); 136 | this.tabControl2.Controls.Add(this.tabErrors); 137 | this.tabControl2.Dock = System.Windows.Forms.DockStyle.Fill; 138 | this.tabControl2.Location = new System.Drawing.Point(0, 23); 139 | this.tabControl2.Multiline = true; 140 | this.tabControl2.Name = "tabControl2"; 141 | this.tabControl2.SelectedIndex = 0; 142 | this.tabControl2.Size = new System.Drawing.Size(241, 327); 143 | this.tabControl2.TabIndex = 1; 144 | // 145 | // tabTopics 146 | // 147 | this.tabTopics.Controls.Add(this.lstTopics); 148 | this.tabTopics.Location = new System.Drawing.Point(4, 24); 149 | this.tabTopics.Name = "tabTopics"; 150 | this.tabTopics.Padding = new System.Windows.Forms.Padding(3); 151 | this.tabTopics.Size = new System.Drawing.Size(233, 299); 152 | this.tabTopics.TabIndex = 0; 153 | this.tabTopics.Text = "Topics"; 154 | this.tabTopics.UseVisualStyleBackColor = true; 155 | // 156 | // tabContexts 157 | // 158 | this.tabContexts.Controls.Add(this.lstContexts); 159 | this.tabContexts.Location = new System.Drawing.Point(4, 22); 160 | this.tabContexts.Name = "tabContexts"; 161 | this.tabContexts.Padding = new System.Windows.Forms.Padding(3); 162 | this.tabContexts.Size = new System.Drawing.Size(233, 303); 163 | this.tabContexts.TabIndex = 1; 164 | this.tabContexts.Text = "Contexts"; 165 | this.tabContexts.UseVisualStyleBackColor = true; 166 | // 167 | // lstContexts 168 | // 169 | this.lstContexts.Dock = System.Windows.Forms.DockStyle.Fill; 170 | this.lstContexts.FormattingEnabled = true; 171 | this.lstContexts.ItemHeight = 15; 172 | this.lstContexts.Location = new System.Drawing.Point(3, 3); 173 | this.lstContexts.Name = "lstContexts"; 174 | this.lstContexts.Size = new System.Drawing.Size(227, 297); 175 | this.lstContexts.TabIndex = 0; 176 | this.lstContexts.SelectedIndexChanged += new System.EventHandler(this.lstContexts_SelectedIndexChanged); 177 | // 178 | // tabErrors 179 | // 180 | this.tabErrors.Controls.Add(this.lstErrors); 181 | this.tabErrors.Location = new System.Drawing.Point(4, 22); 182 | this.tabErrors.Name = "tabErrors"; 183 | this.tabErrors.Size = new System.Drawing.Size(233, 303); 184 | this.tabErrors.TabIndex = 2; 185 | this.tabErrors.Text = "Errors"; 186 | this.tabErrors.UseVisualStyleBackColor = true; 187 | // 188 | // lstErrors 189 | // 190 | this.lstErrors.Dock = System.Windows.Forms.DockStyle.Fill; 191 | this.lstErrors.FormattingEnabled = true; 192 | this.lstErrors.ItemHeight = 15; 193 | this.lstErrors.Location = new System.Drawing.Point(0, 0); 194 | this.lstErrors.Name = "lstErrors"; 195 | this.lstErrors.Size = new System.Drawing.Size(233, 303); 196 | this.lstErrors.TabIndex = 0; 197 | this.lstErrors.SelectedIndexChanged += new System.EventHandler(this.lstErrors_SelectedIndexChanged); 198 | // 199 | // panel1 200 | // 201 | this.panel1.AutoSize = true; 202 | this.panel1.Controls.Add(this.cbDatabases); 203 | this.panel1.Controls.Add(this.btnAddArchive); 204 | this.panel1.Controls.Add(this.btnRemoveArchive); 205 | this.panel1.Dock = System.Windows.Forms.DockStyle.Top; 206 | this.panel1.Location = new System.Drawing.Point(0, 0); 207 | this.panel1.Name = "panel1"; 208 | this.panel1.Size = new System.Drawing.Size(241, 23); 209 | this.panel1.TabIndex = 2; 210 | // 211 | // cbDatabases 212 | // 213 | this.cbDatabases.Dock = System.Windows.Forms.DockStyle.Top; 214 | this.cbDatabases.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 215 | this.cbDatabases.FormattingEnabled = true; 216 | this.cbDatabases.Location = new System.Drawing.Point(0, 0); 217 | this.cbDatabases.Name = "cbDatabases"; 218 | this.cbDatabases.Size = new System.Drawing.Size(187, 23); 219 | this.cbDatabases.TabIndex = 1; 220 | this.cbDatabases.SelectedIndexChanged += new System.EventHandler(this.cbDatabases_SelectedIndexChanged); 221 | // 222 | // btnAddArchive 223 | // 224 | this.btnAddArchive.Dock = System.Windows.Forms.DockStyle.Right; 225 | this.btnAddArchive.Location = new System.Drawing.Point(187, 0); 226 | this.btnAddArchive.Name = "btnAddArchive"; 227 | this.btnAddArchive.Size = new System.Drawing.Size(27, 23); 228 | this.btnAddArchive.TabIndex = 3; 229 | this.btnAddArchive.Text = "+"; 230 | this.btnAddArchive.UseVisualStyleBackColor = true; 231 | this.btnAddArchive.Click += new System.EventHandler(this.btnAddArchive_Click); 232 | // 233 | // btnRemoveArchive 234 | // 235 | this.btnRemoveArchive.Dock = System.Windows.Forms.DockStyle.Right; 236 | this.btnRemoveArchive.Location = new System.Drawing.Point(214, 0); 237 | this.btnRemoveArchive.Name = "btnRemoveArchive"; 238 | this.btnRemoveArchive.Size = new System.Drawing.Size(27, 23); 239 | this.btnRemoveArchive.TabIndex = 2; 240 | this.btnRemoveArchive.Text = "-"; 241 | this.btnRemoveArchive.UseVisualStyleBackColor = true; 242 | this.btnRemoveArchive.Click += new System.EventHandler(this.btnRemoveArchive_Click); 243 | // 244 | // tabControl1 245 | // 246 | this.tabControl1.Alignment = System.Windows.Forms.TabAlignment.Bottom; 247 | this.tabControl1.Controls.Add(this.tabHtml); 248 | this.tabControl1.Controls.Add(this.tabText); 249 | this.tabControl1.Controls.Add(this.tabSource); 250 | this.tabControl1.Dock = System.Windows.Forms.DockStyle.Fill; 251 | this.tabControl1.Location = new System.Drawing.Point(0, 0); 252 | this.tabControl1.Name = "tabControl1"; 253 | this.tabControl1.SelectedIndex = 0; 254 | this.tabControl1.Size = new System.Drawing.Size(489, 350); 255 | this.tabControl1.TabIndex = 2; 256 | // 257 | // tabHtml 258 | // 259 | this.tabHtml.Controls.Add(this.webBrowser1); 260 | this.tabHtml.Controls.Add(this.txtTopicTitle); 261 | this.tabHtml.Location = new System.Drawing.Point(4, 4); 262 | this.tabHtml.Name = "tabHtml"; 263 | this.tabHtml.Size = new System.Drawing.Size(481, 322); 264 | this.tabHtml.TabIndex = 0; 265 | this.tabHtml.Text = "HTML"; 266 | this.tabHtml.UseVisualStyleBackColor = true; 267 | // 268 | // txtTopicTitle 269 | // 270 | this.txtTopicTitle.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 271 | this.txtTopicTitle.Dock = System.Windows.Forms.DockStyle.Top; 272 | this.txtTopicTitle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 273 | this.txtTopicTitle.Location = new System.Drawing.Point(0, 0); 274 | this.txtTopicTitle.Margin = new System.Windows.Forms.Padding(0); 275 | this.txtTopicTitle.Name = "txtTopicTitle"; 276 | this.txtTopicTitle.ReadOnly = true; 277 | this.txtTopicTitle.Size = new System.Drawing.Size(481, 23); 278 | this.txtTopicTitle.TabIndex = 2; 279 | this.txtTopicTitle.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 280 | // 281 | // tabText 282 | // 283 | this.tabText.Controls.Add(this.txtNoFormat); 284 | this.tabText.Location = new System.Drawing.Point(4, 4); 285 | this.tabText.Name = "tabText"; 286 | this.tabText.Size = new System.Drawing.Size(481, 324); 287 | this.tabText.TabIndex = 2; 288 | this.tabText.Text = "Text"; 289 | this.tabText.UseVisualStyleBackColor = true; 290 | // 291 | // txtNoFormat 292 | // 293 | this.txtNoFormat.Dock = System.Windows.Forms.DockStyle.Fill; 294 | this.txtNoFormat.Font = new System.Drawing.Font("Courier New", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 295 | this.txtNoFormat.Location = new System.Drawing.Point(0, 0); 296 | this.txtNoFormat.Multiline = true; 297 | this.txtNoFormat.Name = "txtNoFormat"; 298 | this.txtNoFormat.ReadOnly = true; 299 | this.txtNoFormat.ScrollBars = System.Windows.Forms.ScrollBars.Both; 300 | this.txtNoFormat.Size = new System.Drawing.Size(481, 324); 301 | this.txtNoFormat.TabIndex = 0; 302 | this.txtNoFormat.WordWrap = false; 303 | // 304 | // tabSource 305 | // 306 | this.tabSource.Controls.Add(this.txtSource); 307 | this.tabSource.Location = new System.Drawing.Point(4, 4); 308 | this.tabSource.Name = "tabSource"; 309 | this.tabSource.Padding = new System.Windows.Forms.Padding(3); 310 | this.tabSource.Size = new System.Drawing.Size(481, 324); 311 | this.tabSource.TabIndex = 1; 312 | this.tabSource.Text = "Source"; 313 | this.tabSource.UseVisualStyleBackColor = true; 314 | // 315 | // menuStrip1 316 | // 317 | this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 318 | this.mnuFile, 319 | this.viewToolStripMenuItem}); 320 | this.menuStrip1.Location = new System.Drawing.Point(0, 0); 321 | this.menuStrip1.Name = "menuStrip1"; 322 | this.menuStrip1.Size = new System.Drawing.Size(734, 24); 323 | this.menuStrip1.TabIndex = 5; 324 | this.menuStrip1.Text = "menuStrip1"; 325 | // 326 | // mnuFile 327 | // 328 | this.mnuFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 329 | this.mnuFileOpen, 330 | this.toolStripMenuItem1, 331 | this.mnuFileExit}); 332 | this.mnuFile.Name = "mnuFile"; 333 | this.mnuFile.Size = new System.Drawing.Size(37, 20); 334 | this.mnuFile.Text = "&File"; 335 | // 336 | // mnuFileOpen 337 | // 338 | this.mnuFileOpen.Name = "mnuFileOpen"; 339 | this.mnuFileOpen.Size = new System.Drawing.Size(112, 22); 340 | this.mnuFileOpen.Text = "&Open..."; 341 | this.mnuFileOpen.Click += new System.EventHandler(this.mnuFileOpen_Click); 342 | // 343 | // toolStripMenuItem1 344 | // 345 | this.toolStripMenuItem1.Name = "toolStripMenuItem1"; 346 | this.toolStripMenuItem1.Size = new System.Drawing.Size(109, 6); 347 | // 348 | // mnuFileExit 349 | // 350 | this.mnuFileExit.Name = "mnuFileExit"; 351 | this.mnuFileExit.Size = new System.Drawing.Size(112, 22); 352 | this.mnuFileExit.Text = "E&xit"; 353 | this.mnuFileExit.Click += new System.EventHandler(this.mnuFileExit_Click); 354 | // 355 | // viewToolStripMenuItem 356 | // 357 | this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 358 | this.mnuViewUnresolvedLinks, 359 | this.mnuViewErrors}); 360 | this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; 361 | this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); 362 | this.viewToolStripMenuItem.Text = "&View"; 363 | // 364 | // mnuViewUnresolvedLinks 365 | // 366 | this.mnuViewUnresolvedLinks.Name = "mnuViewUnresolvedLinks"; 367 | this.mnuViewUnresolvedLinks.Size = new System.Drawing.Size(172, 22); 368 | this.mnuViewUnresolvedLinks.Text = "&Unresolved Links..."; 369 | this.mnuViewUnresolvedLinks.Click += new System.EventHandler(this.mnuViewUnresolvedLinks_Click); 370 | // 371 | // mnuViewErrors 372 | // 373 | this.mnuViewErrors.Name = "mnuViewErrors"; 374 | this.mnuViewErrors.Size = new System.Drawing.Size(172, 22); 375 | this.mnuViewErrors.Text = "&Errors..."; 376 | this.mnuViewErrors.Click += new System.EventHandler(this.mnuViewErrors_Click); 377 | // 378 | // openFileDialog1 379 | // 380 | this.openFileDialog1.Filter = "DOS help files|*.hlp|QuickHelp markup source|*.txt"; 381 | this.openFileDialog1.Multiselect = true; 382 | this.openFileDialog1.Title = "Open DOS Help Files"; 383 | // 384 | // contextMenuStrip1 385 | // 386 | this.contextMenuStrip1.Name = "contextMenuStrip1"; 387 | this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4); 388 | // 389 | // MainForm 390 | // 391 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 392 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 393 | this.ClientSize = new System.Drawing.Size(734, 374); 394 | this.Controls.Add(this.splitContainer1); 395 | this.Controls.Add(this.menuStrip1); 396 | this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 397 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 398 | this.MainMenuStrip = this.menuStrip1; 399 | this.Margin = new System.Windows.Forms.Padding(4); 400 | this.Name = "MainForm"; 401 | this.Text = "DOS Help Viewer"; 402 | this.WindowState = System.Windows.Forms.FormWindowState.Maximized; 403 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed); 404 | this.Load += new System.EventHandler(this.MainForm_Load); 405 | this.splitContainer1.Panel1.ResumeLayout(false); 406 | this.splitContainer1.Panel1.PerformLayout(); 407 | this.splitContainer1.Panel2.ResumeLayout(false); 408 | this.splitContainer1.ResumeLayout(false); 409 | this.tabControl2.ResumeLayout(false); 410 | this.tabTopics.ResumeLayout(false); 411 | this.tabContexts.ResumeLayout(false); 412 | this.tabErrors.ResumeLayout(false); 413 | this.panel1.ResumeLayout(false); 414 | this.tabControl1.ResumeLayout(false); 415 | this.tabHtml.ResumeLayout(false); 416 | this.tabHtml.PerformLayout(); 417 | this.tabText.ResumeLayout(false); 418 | this.tabText.PerformLayout(); 419 | this.tabSource.ResumeLayout(false); 420 | this.tabSource.PerformLayout(); 421 | this.menuStrip1.ResumeLayout(false); 422 | this.menuStrip1.PerformLayout(); 423 | this.ResumeLayout(false); 424 | this.PerformLayout(); 425 | 426 | } 427 | 428 | #endregion 429 | 430 | private System.Windows.Forms.ListBox lstTopics; 431 | private System.Windows.Forms.WebBrowser webBrowser1; 432 | private System.Windows.Forms.TextBox txtSource; 433 | private System.Windows.Forms.SplitContainer splitContainer1; 434 | private System.Windows.Forms.TabControl tabControl1; 435 | private System.Windows.Forms.TabPage tabHtml; 436 | private System.Windows.Forms.TabPage tabSource; 437 | private System.Windows.Forms.TextBox txtNoFormat; 438 | private System.Windows.Forms.TabControl tabControl2; 439 | private System.Windows.Forms.TabPage tabTopics; 440 | private System.Windows.Forms.TabPage tabContexts; 441 | private System.Windows.Forms.ListBox lstContexts; 442 | private System.Windows.Forms.TabPage tabText; 443 | private System.Windows.Forms.MenuStrip menuStrip1; 444 | private System.Windows.Forms.ToolStripMenuItem mnuFile; 445 | private System.Windows.Forms.ToolStripMenuItem mnuFileOpen; 446 | private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1; 447 | private System.Windows.Forms.ToolStripMenuItem mnuFileExit; 448 | private System.Windows.Forms.OpenFileDialog openFileDialog1; 449 | private System.Windows.Forms.TextBox txtTopicTitle; 450 | private System.Windows.Forms.ContextMenuStrip contextMenuStrip1; 451 | private System.Windows.Forms.ToolStripMenuItem viewToolStripMenuItem; 452 | private System.Windows.Forms.ToolStripMenuItem mnuViewUnresolvedLinks; 453 | private System.Windows.Forms.ToolStripMenuItem mnuViewErrors; 454 | private System.Windows.Forms.TabPage tabErrors; 455 | private System.Windows.Forms.Panel panel1; 456 | private System.Windows.Forms.ComboBox cbDatabases; 457 | private System.Windows.Forms.Button btnAddArchive; 458 | private System.Windows.Forms.Button btnRemoveArchive; 459 | private System.Windows.Forms.ListBox lstErrors; 460 | } 461 | } 462 | 463 | -------------------------------------------------------------------------------- /HelpBrowser/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace HelpBrowser 6 | { 7 | static class Program 8 | { 9 | /// 10 | /// The main entry point for the application. 11 | /// 12 | [STAThread] 13 | static void Main() 14 | { 15 | Application.EnableVisualStyles(); 16 | Application.SetCompatibleTextRenderingDefault(false); 17 | Application.Run(new MainForm()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HelpBrowser/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HelpBrowser")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HelpBrowser")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("79244f21-2a46-4f7b-ae67-b140dfb3bf29")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /HelpBrowser/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18034 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace HelpBrowser.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("HelpBrowser.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /HelpBrowser/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /HelpBrowser/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.18034 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace HelpBrowser.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | 26 | [global::System.Configuration.UserScopedSettingAttribute()] 27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 28 | public global::System.Collections.Specialized.StringCollection OpenFileNames { 29 | get { 30 | return ((global::System.Collections.Specialized.StringCollection)(this["OpenFileNames"])); 31 | } 32 | set { 33 | this["OpenFileNames"] = value; 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /HelpBrowser/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HelpConvert/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /HelpConvert/HelpConvert.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {469806C9-9B3B-4AA2-94D9-35A2143FC585} 8 | Exe 9 | Properties 10 | HelpConvert 11 | HelpConvert 12 | v2.0 13 | 512 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {c18c4ed1-e4b6-4dc3-b7cf-55053fec165b} 48 | QuickHelp 49 | 50 | 51 | 52 | 59 | -------------------------------------------------------------------------------- /HelpConvert/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using QuickHelp; 5 | using QuickHelp.Formatters; 6 | using QuickHelp.Serialization; 7 | 8 | namespace HelpConvert 9 | { 10 | class Program 11 | { 12 | static void Main(string[] args) 13 | { 14 | Convert(args, false); 15 | 16 | #if DEBUG 17 | Console.WriteLine("Press any key to continue..."); 18 | Console.ReadKey(); 19 | #endif 20 | } 21 | 22 | static void PrintUsage() 23 | { 24 | Console.WriteLine("HelpConvert input-file [input-file ...]"); 25 | } 26 | 27 | static void Convert(string[] fileNames, bool isDryRun) 28 | { 29 | HelpSystem system = new HelpSystem(); 30 | BatchHtmlFormatter converter = new BatchHtmlFormatter(system); 31 | 32 | foreach (string fileName in fileNames) 33 | { 34 | var decoder = new BinaryHelpDeserializer(); 35 | foreach (HelpDatabase database in decoder.LoadDatabases(fileName)) 36 | system.Databases.Add(database); 37 | } 38 | 39 | // export HTML 40 | foreach (HelpDatabase database in system.Databases) 41 | { 42 | // TODO: check invalid chars in database name 43 | string htmlPath = database.Name.Replace('.', '_'); 44 | Directory.CreateDirectory(htmlPath); 45 | 46 | int topicIndex = 0; 47 | foreach (HelpTopic topic in database.Topics) 48 | { 49 | string html = converter.FormatTopic(topic); 50 | string htmlFileName = Path.Combine(htmlPath, string.Format("T{0:X4}.html", topicIndex)); 51 | if (!isDryRun) 52 | { 53 | using (StreamWriter writer = new StreamWriter(htmlFileName, false, Encoding.UTF8)) 54 | { 55 | writer.Write(html); 56 | } 57 | } 58 | topicIndex++; 59 | } 60 | 61 | // Create contents.html. 62 | HelpTopic topic1 = system.ResolveUri(database, new HelpUri("h.contents")); 63 | if (topic1 != null && topic1.Database == database) 64 | { 65 | if (!isDryRun) 66 | { 67 | using (StreamWriter writer = new StreamWriter(Path.Combine(htmlPath, "Contents.html"))) 68 | { 69 | writer.WriteLine("", 70 | topic1.TopicIndex); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | 77 | static string GetDatabasePath(HelpDatabase database) 78 | { 79 | string path = database.Name.Replace('.', '_'); 80 | Directory.CreateDirectory(path); 81 | return path; 82 | } 83 | } 84 | 85 | class BatchHtmlFormatter : HtmlFormatter 86 | { 87 | readonly HelpSystem system; 88 | 89 | public BatchHtmlFormatter(HelpSystem system) 90 | { 91 | base.FixLinks = true; 92 | this.system = system; 93 | } 94 | 95 | protected override string FormatUri(HelpTopic source, HelpUri uri) 96 | { 97 | switch (uri.Type) 98 | { 99 | case HelpUriType.Context: 100 | case HelpUriType.GlobalContext: 101 | case HelpUriType.LocalContext: 102 | { 103 | HelpTopic target = system.ResolveUri(source.Database, uri); 104 | if (target != null) 105 | { 106 | if (target.Database == source.Database) 107 | { 108 | return string.Format("T{0:X4}.html", target.TopicIndex); 109 | } 110 | else 111 | { 112 | return string.Format("../{0}/T{1:X4}.html", 113 | GetDatabasePath(target.Database), 114 | target.TopicIndex); 115 | } 116 | } 117 | else 118 | { 119 | Console.WriteLine("Warning: cannot resolve context string '{0}'", uri); 120 | } 121 | } 122 | break; 123 | 124 | case HelpUriType.LocalTopic: 125 | return string.Format("T{0:X4}.html", uri.TopicIndex); 126 | 127 | case HelpUriType.Command: 128 | case HelpUriType.File: 129 | default: 130 | // TODO: would be better if we have the source location. 131 | Console.WriteLine("Warning: cannot convert link: {0}", uri); 132 | break; 133 | } 134 | return "?" + uri.ToString(); 135 | } 136 | 137 | static string GetDatabasePath(HelpDatabase database) 138 | { 139 | string path = database.Name.Replace('.', '_'); 140 | Directory.CreateDirectory(path); 141 | return path; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /HelpConvert/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("HelpConvert")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("HelpConvert")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a2d32d66-6dd2-4bbf-a238-e9e719544a6b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /QuickHelp/Compression/BitStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace QuickHelp.Compression 5 | { 6 | /// 7 | /// Represents a stream that reads and writes bits (from MSB to LSB) to 8 | /// and from a base stream as bytes of value zero and one. 9 | /// 10 | /// 11 | /// This class only accesses the base stream when necessary and does not 12 | /// prefetch reads or buffer writes. 13 | /// 14 | class BitStream : Stream 15 | { 16 | readonly Stream m_baseStream; 17 | int m_currentByte; 18 | int m_numBitsAvailable; 19 | 20 | public BitStream(Stream baseStream) 21 | { 22 | if (baseStream == null) 23 | throw new ArgumentNullException(nameof(baseStream)); 24 | 25 | m_baseStream = baseStream; 26 | m_currentByte = 0; 27 | m_numBitsAvailable = 0; 28 | } 29 | 30 | public override bool CanRead 31 | { 32 | get { return m_baseStream.CanRead; } 33 | } 34 | 35 | public override bool CanSeek 36 | { 37 | get { return m_baseStream.CanSeek; } 38 | } 39 | 40 | public override bool CanWrite 41 | { 42 | get { return m_baseStream.CanWrite; } 43 | } 44 | 45 | public override long Length 46 | { 47 | get { return m_baseStream.Length * 8; } 48 | } 49 | 50 | public override long Position 51 | { 52 | get { return m_baseStream.Position * 8 - m_numBitsAvailable; } 53 | set 54 | { 55 | if (value < 0) 56 | throw new ArgumentOutOfRangeException(nameof(value)); 57 | long bitPosition = value; 58 | long bytePosition = bitPosition / 8; 59 | m_baseStream.Position = bytePosition; 60 | m_numBitsAvailable = (int)(bytePosition * 8 - bitPosition); 61 | // numBitsAvailable may be negative and this is intended. 62 | } 63 | } 64 | 65 | public override void Flush() 66 | { 67 | m_baseStream.Flush(); 68 | } 69 | 70 | public override int Read(byte[] buffer, int offset, int count) 71 | { 72 | return StreamExtensions.ReadBytes(this, buffer, offset, count); 73 | } 74 | 75 | /// 76 | /// Reads the next bit from the base stream as a byte of value 0 or 1. 77 | /// 78 | /// 79 | /// 1 if the bit is set; 0 if the bit is reset; -1 if EOF. 80 | /// 81 | public override int ReadByte() 82 | { 83 | if (m_numBitsAvailable <= 0) 84 | { 85 | m_currentByte = m_baseStream.ReadByte(); 86 | if (m_currentByte < 0) 87 | return -1; 88 | m_numBitsAvailable += 8; 89 | } 90 | return (m_currentByte >> --m_numBitsAvailable) & 1; 91 | } 92 | 93 | public override long Seek(long offset, SeekOrigin origin) 94 | { 95 | switch (origin) 96 | { 97 | case SeekOrigin.Begin: 98 | this.Position = offset; 99 | break; 100 | case SeekOrigin.Current: 101 | this.Position += offset; 102 | break; 103 | case SeekOrigin.End: 104 | this.Position = this.Length + offset; 105 | break; 106 | default: 107 | throw new ArgumentOutOfRangeException(nameof(origin)); 108 | } 109 | return this.Position; 110 | } 111 | 112 | public override void SetLength(long value) 113 | { 114 | throw new NotSupportedException( 115 | "BitStream does not support SetLength()."); 116 | } 117 | 118 | public override void Write(byte[] buffer, int offset, int count) 119 | { 120 | StreamExtensions.WriteBytes(this, buffer, offset, count); 121 | } 122 | 123 | public override void WriteByte(byte value) 124 | { 125 | if (value != 0 && value != 1) 126 | throw new ArgumentOutOfRangeException(nameof(value)); 127 | 128 | int newByte = m_currentByte; 129 | int numBitsRemaining = m_numBitsAvailable; 130 | int seekOffset = -1; 131 | 132 | if (numBitsRemaining <= 0) 133 | { 134 | newByte = m_baseStream.ReadByte(); 135 | if (newByte < 0) 136 | { 137 | // Writing past the end of the stream is allowed in case 138 | // the base stream supports extending the stream on write. 139 | seekOffset = 0; 140 | newByte = 0; 141 | } 142 | numBitsRemaining += 8; 143 | } 144 | 145 | --numBitsRemaining; 146 | newByte = newByte 147 | & ~(1 << numBitsRemaining) 148 | | (value << numBitsRemaining); 149 | 150 | if (seekOffset != 0) 151 | m_baseStream.Seek(seekOffset, SeekOrigin.Current); 152 | 153 | try 154 | { 155 | m_baseStream.WriteByte((byte)newByte); 156 | } 157 | finally 158 | { 159 | if (seekOffset != 0) 160 | m_baseStream.Seek(-seekOffset, SeekOrigin.Current); 161 | } 162 | 163 | m_currentByte = newByte; 164 | m_numBitsAvailable = numBitsRemaining; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /QuickHelp/Compression/CompressionStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace QuickHelp.Compression 7 | { 8 | /// 9 | /// Represents a stream with dictionary substitution and run-length 10 | /// compression. This stream is used by compressed QuickHelp databases. 11 | /// 12 | /// 13 | /// This stream class is not buffered; that is, it does not read more 14 | /// bytes from the base stream than requested. Therefore it is suitable 15 | /// for detecting errors in the base stream. 16 | /// 17 | /// TODO: dispose the base stream 18 | class CompressionStream : Stream 19 | { 20 | readonly Stream baseStream; 21 | readonly byte[][] dictionary; 22 | readonly byte[] buffer = new byte[512]; 23 | int bufferStart = 0; 24 | int bufferEnd = 0; 25 | 26 | public CompressionStream(Stream baseStream, byte[][] dictionary) 27 | { 28 | if (baseStream == null) 29 | throw new ArgumentNullException(nameof(baseStream)); 30 | if (dictionary == null) 31 | dictionary = new byte[0][]; 32 | 33 | this.baseStream = baseStream; 34 | this.dictionary = dictionary; 35 | } 36 | 37 | private byte ReadArgumentByte() 38 | { 39 | int b = baseStream.ReadByte(); 40 | if (b < 0) 41 | { 42 | throw new EndOfStreamException("Missing argument for control byte."); 43 | } 44 | return (byte)b; 45 | } 46 | 47 | private void FillBuffer(int maxBytes) 48 | { 49 | System.Diagnostics.Debug.Assert(bufferStart >= bufferEnd); 50 | 51 | bufferStart = 0; 52 | bufferEnd = 0; 53 | 54 | // Fill buffer until its size is greater than 256. Since a 55 | // control byte may at most encode 256 bytes, this ensures 56 | // we can always store the decoded data in the buffer. 57 | while (bufferEnd <= 256 && bufferEnd < maxBytes) 58 | { 59 | int b = baseStream.ReadByte(); 60 | if (b < 0) // EOF 61 | break; 62 | 63 | if (b < 0x10 || b > 0x1A) // not a control byte 64 | { 65 | buffer[bufferEnd++] = (byte)b; 66 | } 67 | else if (b == 0x1A) // 0x1A, ESCAPE-BYTE 68 | { 69 | byte escapeByte = ReadArgumentByte(); 70 | buffer[bufferEnd++] = escapeByte; 71 | } 72 | else if (b == 0x19) // 0x19, REPEAT-BYTE, REPEAT-COUNT 73 | { 74 | byte repeatByte = ReadArgumentByte(); 75 | byte repeatCount = ReadArgumentByte(); 76 | for (int i = 0; i < repeatCount; i++) 77 | buffer[bufferEnd++] = repeatByte; 78 | } 79 | else if (b == 0x18) // 0x18, SPACE-COUNT 80 | { 81 | byte spaceCount = ReadArgumentByte(); 82 | for (int i = 0; i < spaceCount; i++) 83 | buffer[bufferEnd++] = 0x20; 84 | } 85 | else // dictionary entry index 86 | { 87 | byte dictIndexLowByte = ReadArgumentByte(); 88 | int dictIndex = ((b & 3) << 8) | dictIndexLowByte; 89 | if (dictIndex >= dictionary.Length) 90 | { 91 | throw new InvalidDataException("Dictionary index is out of range."); 92 | } 93 | 94 | byte[] dictEntry = dictionary[dictIndex]; 95 | Array.Copy(dictEntry, 0, buffer, bufferEnd, dictEntry.Length); 96 | bufferEnd += dictEntry.Length; 97 | 98 | // Append space if bit 2 is set. 99 | if ((b & 4) != 0) 100 | buffer[bufferEnd++] = 0x20; 101 | } 102 | } 103 | } 104 | 105 | // TODO: do not implement ReadFull logic here. 106 | public override int Read(byte[] buffer, int offset, int count) 107 | { 108 | int actual = 0; 109 | while (count > 0) 110 | { 111 | if (bufferStart >= bufferEnd) 112 | { 113 | FillBuffer(count); // do not fill unrestrictedly, because 114 | // the underlying huffman stream has 115 | // no way to tell EOF, and may return 116 | // invalid control bytes. 117 | if (bufferStart >= bufferEnd) // EOF 118 | break; 119 | } 120 | 121 | int n = Math.Min(count, bufferEnd - bufferStart); 122 | Array.Copy(this.buffer, bufferStart, buffer, offset, n); 123 | bufferStart += n; 124 | offset += n; 125 | count -= n; 126 | actual += n; 127 | } 128 | return actual; 129 | } 130 | 131 | #region Stream Members 132 | 133 | public override bool CanRead 134 | { 135 | get { return true; } 136 | } 137 | 138 | public override bool CanWrite 139 | { 140 | get { return false; } 141 | } 142 | 143 | public override bool CanSeek 144 | { 145 | get { return false; } 146 | } 147 | 148 | public override long Position 149 | { 150 | get { throw new NotSupportedException(); } 151 | set { throw new NotSupportedException(); } 152 | } 153 | 154 | public override long Length 155 | { 156 | get { throw new NotSupportedException(); } 157 | } 158 | 159 | public override void SetLength(long value) 160 | { 161 | throw new NotSupportedException(); 162 | } 163 | 164 | public override void Write(byte[] buffer, int offset, int count) 165 | { 166 | throw new NotSupportedException(); 167 | } 168 | 169 | public override void Flush() 170 | { 171 | throw new NotSupportedException(); 172 | } 173 | 174 | public override long Seek(long offset, SeekOrigin origin) 175 | { 176 | throw new NotSupportedException(); 177 | } 178 | 179 | #endregion 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /QuickHelp/Compression/HuffmanStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace QuickHelp.Compression 5 | { 6 | /// 7 | /// Represents a huffman-encoded stream. Only supports decoding. 8 | /// 9 | /// 10 | /// This stream class is unbuffered; that is, it does not pre-read any 11 | /// byte that is not necessary from the base stream. Therefore it is 12 | /// suitable for detecting errors in the base stream. 13 | /// 14 | sealed class HuffmanStream : Stream 15 | { 16 | readonly HuffmanTree huffmanTree; 17 | readonly BitStream bitStream; 18 | 19 | public HuffmanStream(Stream baseStream, HuffmanTree huffmanTree) 20 | { 21 | if (baseStream == null) 22 | throw new ArgumentNullException(nameof(baseStream)); 23 | if (huffmanTree == null) 24 | throw new ArgumentNullException(nameof(huffmanTree)); 25 | 26 | this.bitStream = new BitStream(baseStream); 27 | this.huffmanTree = huffmanTree; 28 | } 29 | 30 | public override int Read(byte[] buffer, int offset, int count) 31 | { 32 | return StreamExtensions.ReadBytes(this, buffer, offset, count); 33 | } 34 | 35 | public override int ReadByte() 36 | { 37 | HuffmanDecoder decoder = new HuffmanDecoder(this.huffmanTree); 38 | while (!decoder.HasValue) 39 | { 40 | int bit = bitStream.ReadByte(); 41 | if (bit < 0) // EOF 42 | return -1; 43 | decoder.Push(bit != 0); 44 | } 45 | return decoder.Value; 46 | } 47 | 48 | #region Stream Members 49 | 50 | public override bool CanRead 51 | { 52 | get { return true; } 53 | } 54 | 55 | public override bool CanWrite 56 | { 57 | get { return false; } 58 | } 59 | 60 | public override bool CanSeek 61 | { 62 | get { return false; } 63 | } 64 | 65 | public override void Flush() 66 | { 67 | throw new NotSupportedException(); 68 | } 69 | 70 | public override long Length 71 | { 72 | get { throw new NotSupportedException(); } 73 | } 74 | 75 | public override long Position 76 | { 77 | get { throw new NotSupportedException(); } 78 | set { throw new NotSupportedException(); } 79 | } 80 | 81 | public override long Seek(long offset, SeekOrigin origin) 82 | { 83 | throw new NotSupportedException(); 84 | } 85 | 86 | public override void SetLength(long value) 87 | { 88 | throw new NotSupportedException(); 89 | } 90 | 91 | public override void Write(byte[] buffer, int offset, int count) 92 | { 93 | throw new NotSupportedException(); 94 | } 95 | 96 | #endregion 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /QuickHelp/Compression/HuffmanTree.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | 5 | namespace QuickHelp.Compression 6 | { 7 | using HuffmanTreeNode = BinaryTreeNode; 8 | 9 | class HuffmanTreeNodeData 10 | { 11 | public byte Symbol; // for leaf node only 12 | public int Index; // used by serialization only 13 | // public int Cost; // used by tree building only 14 | } 15 | 16 | /// 17 | /// Represents a huffman tree that encodes a subset of the symbols 0-255. 18 | /// 19 | /// 20 | /// A huffman tree is a proper binary tree that encodes symbols in its 21 | /// leaf nodes. A proper binary tree is a binary tree where every node 22 | /// has either two children or no child. 23 | /// 24 | /// For completeness this class supports two special cases: (i) an empty 25 | /// tree, where no symbol is encoded, and (ii) a tree with a single node, 26 | /// where the unique symbol is emitted without consuming any input. 27 | /// 28 | /// Use the helper class HuffmanDecoder to decode a symbol. 29 | /// 30 | public class HuffmanTree 31 | { 32 | internal HuffmanTreeNode Root { get; set; } 33 | 34 | internal int SymbolCount { get; set; } 35 | 36 | public bool IsEmpty 37 | { 38 | get { return Root == null; } 39 | } 40 | 41 | public bool IsSingular 42 | { 43 | get { return Root != null && Root.IsLeaf; } 44 | } 45 | 46 | public static HuffmanTree Deserialize(BinaryReader reader) 47 | { 48 | if (reader == null) 49 | throw new ArgumentNullException(nameof(reader)); 50 | 51 | List nodeValues = new List(511); 52 | while (true) 53 | { 54 | Int16 nodeValue; 55 | try 56 | { 57 | nodeValue = reader.ReadInt16(); 58 | } 59 | catch (EndOfStreamException) 60 | { 61 | throw new InvalidDataException("Invalid Huffman tree: missing terminating NULL."); 62 | } 63 | if (nodeValue == 0) 64 | break; 65 | nodeValues.Add(nodeValue); 66 | } 67 | return InternalDeserialize(nodeValues.ToArray()); 68 | } 69 | 70 | /// 71 | /// Deserializes a huffman tree. 72 | /// 73 | /// 74 | /// See Format.txt for the serialized format of a huffman tree. 75 | /// 76 | private static HuffmanTree InternalDeserialize(Int16[] nodeValues) 77 | { 78 | #if DEBUG 79 | System.Diagnostics.Debug.Assert(nodeValues != null); 80 | #endif 81 | if (nodeValues.Length == 0) 82 | return new HuffmanTree(); 83 | 84 | int n = nodeValues.Length; 85 | if (n % 2 == 0) 86 | throw new InvalidDataException("Invalid Huffman tree: expecting an odd number of nodes."); 87 | 88 | HuffmanTreeNode[] nodes = new HuffmanTreeNode[n]; 89 | nodes[0] = new HuffmanTreeNode(); 90 | 91 | bool[] symbolExists = new bool[256]; 92 | for (int i = 0; i < n; i++) 93 | { 94 | HuffmanTreeNode node = nodes[i]; 95 | if (node == null) 96 | throw new InvalidDataException("Invalid Huffman tree: orphaned node encountered."); 97 | 98 | node.Value = new HuffmanTreeNodeData(); 99 | 100 | short nodeValue = nodeValues[i]; 101 | if (nodeValue < 0) // leaf; symbol stored in low byte 102 | { 103 | byte symbol = (byte)nodeValue; 104 | if (symbolExists[symbol]) 105 | { 106 | throw new InvalidDataException("Invalid Huffman tree: symbol already encoded."); 107 | } 108 | node.Value.Symbol = symbol; 109 | symbolExists[symbol] = true; 110 | } 111 | else // right-child (1 bit) follows, left-child (0 bit) encoded 112 | { 113 | int child0 = nodeValue / 2; 114 | int child1 = i + 1; 115 | if (!(child1 < child0 && child0 < n)) 116 | throw new InvalidDataException("Invalid Huffman tree: expected child node location."); 117 | if (nodes[child0] != null || nodes[child1] != null) 118 | throw new InvalidDataException("Invalid Huffman tree: cycle detected."); 119 | 120 | nodes[child0] = new HuffmanTreeNode(); 121 | nodes[child1] = new HuffmanTreeNode(); 122 | node.LeftChild = nodes[child0]; 123 | node.RightChild = nodes[child1]; 124 | } 125 | } 126 | 127 | return new HuffmanTree 128 | { 129 | Root = nodes[0], 130 | SymbolCount = n / 2 131 | }; 132 | } 133 | 134 | public void Serialize(BinaryWriter writer) 135 | { 136 | if (writer == null) 137 | throw new ArgumentNullException(nameof(writer)); 138 | 139 | Int16[] words = InternalSerialize(); 140 | 141 | // Append extra word at the end for terminating NULL. 142 | byte[] bytes = new byte[words.Length * 2 + 2]; 143 | Buffer.BlockCopy(words, 0, bytes, 0, words.Length * 2); 144 | writer.Write(bytes); 145 | } 146 | 147 | private Int16[] InternalSerialize() 148 | { 149 | int n = (this.SymbolCount == 0) ? 0 : 2 * this.SymbolCount - 1; 150 | Int16[] sequence = new Int16[n]; 151 | if (n == 0) 152 | return sequence; 153 | 154 | int index = n; 155 | foreach (HuffmanTreeNode node in Root.PostOrderTraverse()) 156 | { 157 | node.Value.Index = --index; 158 | if (node.IsLeaf) 159 | sequence[index] = (Int16)(0x8000 | node.Value.Symbol); 160 | else 161 | sequence[index] = (Int16)(node.LeftChild.Value.Index * 2); 162 | } 163 | #if DEBUG 164 | System.Diagnostics.Debug.Assert(index == 0); 165 | #endif 166 | return sequence; 167 | } 168 | 169 | #if false 170 | public void Dump() 171 | { 172 | Dump(0, 0); 173 | } 174 | 175 | internal void Dump(int nodeIndex, int depth) 176 | { 177 | System.Diagnostics.Debug.Write(nodeIndex.ToString("000")); 178 | 179 | 180 | if (IsLeaf) 181 | { 182 | //byte b = node.Symbol; 183 | //if (b > 32 && b < 127) 184 | // System.Diagnostics.Debug.WriteLine("=" + (char)b); 185 | //else 186 | // System.Diagnostics.Debug.WriteLine("=" + b.ToString("X2")); 187 | System.Diagnostics.Debug.WriteLine(string.Format("={0}", Value)); 188 | } 189 | else 190 | { 191 | //System.Diagnostics.Debug.Write("-"); 192 | //if (LeftChild!=null) 193 | //LeftChild Dump(node.OneBitChildIndex, depth + 1); 194 | 195 | //System.Diagnostics.Debug.Write(new string(' ', 4 * (depth + 1))); 196 | //Dump(node.ZeroBitChildIndex, depth + 1); 197 | } 198 | } 199 | #endif 200 | } 201 | 202 | #if false 203 | /// 204 | /// Represents a read-only sequence of bits. 205 | /// 206 | public class BitBuffer 207 | { 208 | private readonly byte[] m_buffer; 209 | private readonly int m_startBitIndex; 210 | private readonly int m_bitCount; 211 | 212 | /// 213 | /// Creates a bit sequence from the underlying byte buffer. The bits 214 | /// are stored in big-endian order, i.e. from MSB to LSB. 215 | /// 216 | public BitBuffer(byte[] buffer, int startBitIndex, int bitCount) 217 | { 218 | if (buffer == null) 219 | throw new ArgumentNullException(nameof(buffer)); 220 | if (!(startBitIndex >= 0 && startBitIndex <= buffer.Length * 8)) 221 | throw new ArgumentOutOfRangeException(nameof(startBitIndex)); 222 | if (!(bitCount >= 0 && bitCount <= buffer.Length * 8 - startBitIndex)) 223 | throw new ArgumentOutOfRangeException(nameof(bitCount)); 224 | 225 | m_buffer = buffer; 226 | m_startBitIndex = startBitIndex; 227 | m_bitCount = bitCount; 228 | } 229 | 230 | public bool this[int bitIndex] 231 | { 232 | get 233 | { 234 | if (!(bitIndex >= 0 && bitIndex < m_bitCount)) 235 | throw new ArgumentOutOfRangeException(nameof(bitIndex)); 236 | int k = m_startBitIndex + bitIndex; 237 | return (m_buffer[k / 8] >> (7 - (k % 8))) != 0; 238 | } 239 | } 240 | } 241 | #endif 242 | 243 | /// 244 | /// Helper class to decode a single symbol from a huffman tree. 245 | /// 246 | /// 247 | /// This class should be used like the following: 248 | /// 249 | /// HuffmanDecoder decoder = new HuffmanDecoder(tree); 250 | /// while (!decoder.HasValue) decoder.Push([next bit in the input]); 251 | /// Console.WriteLine(decoder.Value); 252 | /// 253 | /// Two special cases should be handled with care. If the huffman tree 254 | /// is empty, HasValue always contains false and calling 255 | /// Push() results in an exception. If the huffman tree contains 256 | /// a single node, HasValue is true upon construction and 257 | /// the unique symbol is available without consuming any bit. In this 258 | /// case caller must know the decoded data length a-priori in order not 259 | /// to run into an infinite loop. 260 | /// 261 | public struct HuffmanDecoder 262 | { 263 | private HuffmanTreeNode m_node; 264 | 265 | public HuffmanDecoder(HuffmanTree huffmanTree) 266 | { 267 | if (huffmanTree == null) 268 | throw new ArgumentNullException(nameof(huffmanTree)); 269 | m_node = huffmanTree.Root; 270 | } 271 | 272 | public bool HasValue 273 | { 274 | get { return m_node != null && m_node.IsLeaf; } 275 | } 276 | 277 | public byte Value 278 | { 279 | get 280 | { 281 | if (!this.HasValue) 282 | throw new InvalidOperationException("Decoder does not have a value."); 283 | return m_node.Value.Symbol; 284 | } 285 | } 286 | 287 | public void Push(bool bit) 288 | { 289 | if (m_node == null) 290 | throw new InvalidOperationException("Cannot walk an empty tree."); 291 | if (m_node.IsLeaf) 292 | throw new InvalidOperationException("Cannot walk further from a leaf."); 293 | 294 | m_node = bit ? m_node.RightChild : m_node.LeftChild; 295 | } 296 | } 297 | 298 | /// 299 | /// Represents a node in a binary tree. 300 | /// 301 | internal class BinaryTreeNode 302 | { 303 | public T Value { get; set; } 304 | 305 | public BinaryTreeNode LeftChild { get; set; } 306 | 307 | public BinaryTreeNode RightChild { get; set; } 308 | 309 | /// 310 | /// Returns true if this node has no child. 311 | /// 312 | public bool IsLeaf 313 | { 314 | get { return LeftChild == null && RightChild == null; } 315 | } 316 | 317 | public IEnumerable> PostOrderTraverse() 318 | { 319 | if (LeftChild != null) 320 | { 321 | foreach (var node in LeftChild.PostOrderTraverse()) 322 | yield return node; 323 | } 324 | if (RightChild != null) 325 | { 326 | foreach (var node in RightChild.PostOrderTraverse()) 327 | yield return node; 328 | } 329 | yield return this; 330 | } 331 | } 332 | } -------------------------------------------------------------------------------- /QuickHelp/Compression/KeywordCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace QuickHelp.Compression 6 | { 7 | /// 8 | /// Maintains a list of keywords used for keyword compression (also known 9 | /// as dictionary substitution). 10 | /// 11 | /// 12 | /// The number of keywords must not exceed 1024. 13 | /// 14 | public class KeywordList : List 15 | { 16 | 17 | } 18 | 19 | internal static class KeywordListSerializer 20 | { 21 | public static KeywordList Deserialize(byte[] buffer) 22 | { 23 | if (buffer == null) 24 | throw new ArgumentNullException(nameof(buffer)); 25 | 26 | // Serialized sequentially as length-prefixed string. 27 | KeywordList keywords = new KeywordList(); 28 | using (BinaryReader reader = new BinaryReader(new MemoryStream(buffer))) 29 | { 30 | while (reader.BaseStream.Position < reader.BaseStream.Length) 31 | { 32 | byte length = reader.ReadByte(); 33 | byte[] keyword = reader.ReadBytes(length); 34 | keywords.Add(keyword); 35 | } 36 | } 37 | return keywords; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /QuickHelp/Compression/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace QuickHelp.Compression 5 | { 6 | static class StreamExtensions 7 | { 8 | public static int ReadBytes( 9 | Stream stream, byte[] buffer, int offset, int count) 10 | { 11 | if (stream == null) 12 | throw new ArgumentNullException(nameof(stream)); 13 | if (buffer == null) 14 | throw new ArgumentNullException(nameof(buffer)); 15 | if (offset < 0 || offset > buffer.Length) 16 | throw new ArgumentOutOfRangeException(nameof(offset)); 17 | if (count < 0 || count > buffer.Length - offset) 18 | throw new ArgumentOutOfRangeException(nameof(count)); 19 | 20 | for (int i = 0; i < count; i++) 21 | { 22 | int value = stream.ReadByte(); 23 | if (value < 0) 24 | return i; 25 | buffer[offset + i] = (byte)value; 26 | } 27 | return count; 28 | } 29 | 30 | public static void WriteBytes( 31 | Stream stream, byte[] buffer, int offset, int count) 32 | { 33 | if (stream == null) 34 | throw new ArgumentNullException(nameof(stream)); 35 | if (buffer == null) 36 | throw new ArgumentNullException(nameof(buffer)); 37 | if (offset < 0 || offset > buffer.Length) 38 | throw new ArgumentOutOfRangeException(nameof(offset)); 39 | if (count < 0 || count > buffer.Length - offset) 40 | throw new ArgumentOutOfRangeException(nameof(count)); 41 | 42 | for (int i = 0; i < count; i++) 43 | { 44 | stream.WriteByte(buffer[offset + i]); 45 | } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /QuickHelp/Formatters/Default.css: -------------------------------------------------------------------------------- 1 | .help-content { 2 | font-family: Consolas; 3 | font-size: 12pt; 4 | } 5 | 6 | .help-content a:link { 7 | color: blue; 8 | text-decoration: none; 9 | } 10 | 11 | .help-content a:visited { 12 | color: blue; 13 | } 14 | 15 | .help-content a:hover { 16 | color: blue; 17 | text-decoration: underline; 18 | } 19 | 20 | .help-content a:active { 21 | color: blue; 22 | } -------------------------------------------------------------------------------- /QuickHelp/Formatters/EmbeddedHtmlFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Reflection; 3 | 4 | namespace QuickHelp.Formatters 5 | { 6 | /// 7 | /// Provides methods to format a help topic as HTML suitable for embedding 8 | /// in a WebBrowser control. 9 | /// 10 | public class EmbeddedHtmlFormatter : HtmlFormatter 11 | { 12 | private static string s_styleSheet = LoadStyleSheet(); 13 | 14 | private static string LoadStyleSheet() 15 | { 16 | Assembly assembly = Assembly.GetExecutingAssembly(); 17 | string resourceName = "QuickHelp.Formatters.Default.css"; 18 | using (Stream stream = assembly.GetManifestResourceStream(resourceName)) 19 | using (StreamReader reader = new StreamReader(stream)) 20 | { 21 | return reader.ReadToEnd(); 22 | } 23 | } 24 | 25 | protected override string FormatUri(HelpTopic topic, HelpUri uri) 26 | { 27 | return "?" + Escape(uri.ToString()); 28 | } 29 | 30 | protected override string GetStyleSheet() 31 | { 32 | return string.Format(" \n", s_styleSheet); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /QuickHelp/Formatters/HtmlFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QuickHelp.Formatters 6 | { 7 | /// 8 | /// Abstract class that provides methods to format a help topic as HTML. 9 | /// 10 | public abstract class HtmlFormatter 11 | { 12 | /// 13 | /// Gets or sets a flag that controls whether to fix links in the 14 | /// output. 15 | /// 16 | /// 17 | /// If this property is set to true, the renderer excludes 18 | /// the enclosing pair of ◄ and ► from the link. 19 | /// 20 | public bool FixLinks { get; set; } 21 | 22 | /// 23 | /// Formats the given help topic as HTML and returns the HTML source. 24 | /// 25 | public string FormatTopic(HelpTopic topic) 26 | { 27 | if (topic == null) 28 | throw new ArgumentNullException(nameof(topic)); 29 | 30 | StringBuilder html = new StringBuilder(); 31 | html.AppendLine(""); 32 | html.AppendLine(" "); 33 | html.AppendLine(string.Format(" {0}", Escape(topic.Title))); 34 | html.AppendLine(" "); 35 | html.Append(GetStyleSheet()); 36 | html.AppendLine(" "); 37 | html.AppendLine(" "); 38 | 39 | html.Append("
");
 40 |             for (int i = 0; i < topic.Lines.Count; i++)
 41 |             {
 42 |                 FormatLine(html, topic, topic.Lines[i]);
 43 |                 if (i < topic.Lines.Count - 1)
 44 |                     html.AppendLine();
 45 |             }
 46 |             html.AppendLine("
"); 47 | 48 | html.AppendLine(" "); 49 | html.AppendLine(""); 50 | return html.ToString(); 51 | } 52 | 53 | /// 54 | /// Formats a help line as HTML and returns the HTML source. 55 | /// 56 | /// 57 | /// This method produces properly structured HTML. That is, it avoids 58 | /// markup such as 59 | /// 60 | /// ............... 61 | /// 62 | /// The generated HTML is not the most compact possible, but is quite 63 | /// compact in practice. 64 | /// 65 | /// For a formal discussion about unpaired tags, see 66 | /// http://www.w3.org/html/wg/drafts/html/master/syntax.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser 67 | /// 68 | private void FormatLine(StringBuilder html, HelpTopic topic, HelpLine line) 69 | { 70 | if (this.FixLinks) 71 | { 72 | line = FixLine(line); 73 | } 74 | for (int index = 0; index < line.Length;) 75 | { 76 | index = FormatLineSegment(html, topic, line, index); 77 | } 78 | } 79 | 80 | private static HelpLine FixLine(HelpLine line) 81 | { 82 | TextAttribute[] attributes = new TextAttribute[line.Length]; 83 | for (int i = 0; i < line.Length; i++) 84 | { 85 | if (line.Text[i] == '►' && 86 | line.Attributes[i].Link != null && 87 | (i == line.Length - 1 || line.Attributes[i + 1].Link == null)) 88 | { 89 | attributes[i] = new TextAttribute(line.Attributes[i].Style, null); 90 | } 91 | else 92 | { 93 | attributes[i] = line.Attributes[i]; 94 | } 95 | } 96 | return new HelpLine(line.Text, attributes); 97 | } 98 | 99 | private int FormatLineSegment(StringBuilder html, HelpTopic topic, HelpLine line, int startIndex) 100 | { 101 | HelpUri link = line.Attributes[startIndex].Link; 102 | if (link != null) 103 | { 104 | html.AppendFormat("", FormatUri(topic, link)); 105 | } 106 | 107 | Stack openTags = new Stack(); 108 | int index = startIndex; 109 | while (index < line.Length && line.Attributes[index].Link == link) 110 | { 111 | TextAttribute oldAttrs = (index == startIndex) ? 112 | TextAttribute.Default : line.Attributes[index - 1]; 113 | TextAttribute newAttrs = line.Attributes[index]; 114 | TextStyle stylesToAdd = newAttrs.Style & ~oldAttrs.Style; 115 | TextStyle stylesToRemove = oldAttrs.Style & ~newAttrs.Style; 116 | 117 | while (stylesToRemove != TextStyle.None) 118 | { 119 | TextStyle top = openTags.Pop(); 120 | FormatRemovedStyles(html, top); 121 | if ((stylesToRemove & top) != 0) 122 | { 123 | stylesToRemove &= ~top; 124 | } 125 | else 126 | { 127 | stylesToAdd |= top; 128 | } 129 | } 130 | 131 | if ((stylesToAdd & TextStyle.Bold) != 0) 132 | { 133 | html.Append(""); 134 | openTags.Push(TextStyle.Bold); 135 | } 136 | if ((stylesToAdd & TextStyle.Italic) != 0) 137 | { 138 | html.Append(""); 139 | openTags.Push(TextStyle.Italic); 140 | } 141 | if ((stylesToAdd & TextStyle.Underline) != 0) 142 | { 143 | html.Append(""); 144 | openTags.Push(TextStyle.Underline); 145 | } 146 | 147 | html.Append(Escape("" + line.Text[index])); 148 | index++; 149 | } 150 | 151 | while (openTags.Count > 0) 152 | { 153 | FormatRemovedStyles(html, openTags.Pop()); 154 | } 155 | if (link != null) 156 | { 157 | html.Append(""); 158 | } 159 | 160 | return index; 161 | } 162 | 163 | /// 164 | /// Formats the given help uri for the HTML href attribute. 165 | /// 166 | protected abstract string FormatUri(HelpTopic topic, HelpUri uri); 167 | 168 | private static void FormatRemovedStyles( 169 | StringBuilder html, TextStyle change) 170 | { 171 | if ((change & TextStyle.Bold) != 0) 172 | html.Append(""); 173 | if ((change & TextStyle.Italic) != 0) 174 | html.Append(""); 175 | if ((change & TextStyle.Underline) != 0) 176 | html.Append(""); 177 | } 178 | 179 | /// 180 | /// Gets an HTML fragment to be inserted into the head section of the 181 | /// HTML output. The default implementation returns an empty string. 182 | /// 183 | protected virtual string GetStyleSheet() 184 | { 185 | return ""; 186 | } 187 | 188 | public static string Escape(string s) 189 | { 190 | return System.Security.SecurityElement.Escape(s); 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /QuickHelp/Formatters/TextFormatter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace QuickHelp.Formatters 5 | { 6 | /// 7 | /// Provides methods to format a help topic as plain text. 8 | /// 9 | public class TextFormatter 10 | { 11 | public static string FormatTopic(HelpTopic topic) 12 | { 13 | if (topic == null) 14 | throw new ArgumentNullException(nameof(topic)); 15 | 16 | StringBuilder sb = new StringBuilder(); 17 | foreach (HelpLine line in topic.Lines) 18 | { 19 | sb.AppendLine(line.Text); 20 | } 21 | return sb.ToString(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /QuickHelp/HelpDatabase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace QuickHelp 6 | { 7 | /// 8 | /// Represents a help database that contains a collection of help topics. 9 | /// 10 | /// 11 | /// This class is the logical representation of a help database. The 12 | /// BinaryHelpSerializer class handles the serialization of the 13 | /// help database on disk. 14 | /// 15 | public class HelpDatabase 16 | { 17 | private readonly HelpTopicCollection topics; 18 | private readonly SortedDictionary contextMap; 19 | private readonly bool isCaseSensitive; 20 | private readonly string name; 21 | 22 | /// 23 | /// Creates a help database with the given name. The name is used to 24 | /// resolve external context strings. 25 | /// 26 | public HelpDatabase(string name) 27 | : this(name, false) 28 | { 29 | } 30 | 31 | /// 32 | /// Creates a help database with the given name, and specifies whether 33 | /// context strings are case sensitive. 34 | /// 35 | /// Name of the database 36 | /// 37 | public HelpDatabase(string name, bool isCaseSensitive) 38 | { 39 | if (name == null) 40 | throw new ArgumentNullException(nameof(name)); 41 | 42 | StringComparer stringComparer = isCaseSensitive ? 43 | StringComparer.InvariantCulture : 44 | StringComparer.InvariantCultureIgnoreCase; 45 | this.contextMap = new SortedDictionary(stringComparer); 46 | this.isCaseSensitive = isCaseSensitive; 47 | this.name = name; 48 | this.topics = new HelpTopicCollection(this); 49 | } 50 | 51 | /// 52 | /// Gets the name of the help database. This name is used to resolve 53 | /// external context strings. The name is case-insensitive. 54 | /// 55 | public string Name 56 | { 57 | get { return name; } 58 | } 59 | 60 | /// 61 | /// Gets or sets a flag that indicates whether context strings are 62 | /// case-sensitive when resolving links. 63 | /// 64 | public bool IsCaseSensitive 65 | { 66 | get { return isCaseSensitive; } 67 | } 68 | 69 | /// 70 | /// Gets the collection of topics in this database. 71 | /// 72 | public HelpTopicCollection Topics 73 | { 74 | get { return topics; } 75 | } 76 | 77 | /// 78 | /// Gets a list of the context strings defined in this database. 79 | /// 80 | public IEnumerable ContextStrings 81 | { 82 | get { return contextMap.Keys; } 83 | } 84 | 85 | /// 86 | /// Associates a context string with a topic in this database. 87 | /// Multiple context strings may be associated with a single topic. 88 | /// 89 | /// The context string. 90 | /// Zero-based topic index. 91 | /// Whether the context string is treated as case-sensitive 92 | /// is controlled by the IsCaseSensitive property. 93 | public void AddContext(string contextString, int topicIndex) 94 | { 95 | if (contextString == null) 96 | throw new ArgumentNullException(nameof(contextString)); 97 | if (topicIndex < 0) 98 | throw new IndexOutOfRangeException(nameof(topicIndex)); 99 | 100 | contextMap[contextString] = topicIndex; 101 | } 102 | 103 | /// 104 | /// Finds the topic associated with the given context string. 105 | /// 106 | /// The context string to resolve. 107 | /// The help topic associated with the given context string, 108 | /// or null if the context string cannot be resolved. 109 | /// Whether the context string is treated as case-sensitive 110 | /// is controlled by the IsCaseSensitive property. 111 | public HelpTopic ResolveContext(string contextString) 112 | { 113 | if (contextString == null) 114 | throw new ArgumentNullException("contextString"); 115 | 116 | int topicIndex; 117 | if (contextMap.TryGetValue(contextString, out topicIndex)) 118 | return topics[topicIndex]; 119 | else 120 | return null; 121 | } 122 | } 123 | 124 | public class HelpTopicCollection : IList 125 | { 126 | private readonly HelpDatabase m_database; 127 | private readonly List m_topics = new List(); 128 | 129 | internal HelpTopicCollection(HelpDatabase database) 130 | { 131 | if (database == null) 132 | throw new ArgumentNullException(nameof(database)); 133 | m_database = database; 134 | } 135 | 136 | private void Attach(HelpTopic topic) 137 | { 138 | if (topic != null) 139 | { 140 | if (topic.Database != null) 141 | { 142 | throw new InvalidOperationException("Topic is already part of a database."); 143 | } 144 | topic.Database = m_database; 145 | } 146 | } 147 | 148 | private void Detach(HelpTopic topic) 149 | { 150 | if (topic != null) 151 | { 152 | if (topic.Database != m_database) 153 | { 154 | throw new InvalidOperationException("Topic to detach is not part of this database."); 155 | } 156 | topic.Database = null; 157 | } 158 | } 159 | 160 | public HelpTopic this[int index] 161 | { 162 | get { return m_topics[index]; } 163 | set 164 | { 165 | if (m_topics[index] != value) 166 | { 167 | Attach(value); 168 | Detach(m_topics[index]); 169 | m_topics[index] = value; 170 | } 171 | } 172 | } 173 | 174 | public int Count 175 | { 176 | get { return m_topics.Count; } 177 | } 178 | 179 | public bool IsReadOnly 180 | { 181 | get { return false; } 182 | } 183 | 184 | public void Add(HelpTopic topic) 185 | { 186 | Attach(topic); 187 | m_topics.Add(topic); 188 | } 189 | 190 | public void Clear() 191 | { 192 | foreach (HelpTopic topic in m_topics) 193 | { 194 | Detach(topic); 195 | } 196 | m_topics.Clear(); 197 | } 198 | 199 | public bool Contains(HelpTopic topic) 200 | { 201 | return m_topics.Contains(topic); 202 | } 203 | 204 | public void CopyTo(HelpTopic[] array, int arrayIndex) 205 | { 206 | m_topics.CopyTo(array, arrayIndex); 207 | } 208 | 209 | public IEnumerator GetEnumerator() 210 | { 211 | return m_topics.GetEnumerator(); 212 | } 213 | 214 | public int IndexOf(HelpTopic topic) 215 | { 216 | return m_topics.IndexOf(topic); 217 | } 218 | 219 | public void Insert(int index, HelpTopic topic) 220 | { 221 | Attach(topic); 222 | m_topics.Insert(index, topic); 223 | } 224 | 225 | public bool Remove(HelpTopic topic) 226 | { 227 | if (m_topics.Remove(topic)) 228 | { 229 | Detach(topic); 230 | return true; 231 | } 232 | return false; 233 | } 234 | 235 | public void RemoveAt(int index) 236 | { 237 | HelpTopic topic = m_topics[index]; 238 | m_topics.RemoveAt(index); 239 | Detach(topic); 240 | } 241 | 242 | IEnumerator IEnumerable.GetEnumerator() 243 | { 244 | return ((IEnumerable)m_topics).GetEnumerator(); 245 | } 246 | } 247 | } -------------------------------------------------------------------------------- /QuickHelp/HelpLine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace QuickHelp 6 | { 7 | /// 8 | /// Represents a line of text in a help topic, along with formatting and 9 | /// hyperlink information. 10 | /// 11 | public class HelpLine 12 | { 13 | readonly string text; 14 | readonly TextAttribute[] attributes; 15 | 16 | public HelpLine(string text, TextAttribute[] attributes) 17 | { 18 | if (text == null) 19 | throw new ArgumentNullException(nameof(text)); 20 | if (attributes == null) 21 | throw new ArgumentNullException(nameof(attributes)); 22 | if (text.Length != attributes.Length) 23 | throw new ArgumentException("text and attributes must have the same length."); 24 | 25 | this.text = text; 26 | this.attributes = attributes; 27 | } 28 | 29 | public HelpLine(string text) 30 | { 31 | if (text == null) 32 | throw new ArgumentNullException(nameof(text)); 33 | 34 | this.text = text; 35 | this.attributes = new TextAttribute[text.Length]; 36 | } 37 | 38 | public int Length 39 | { 40 | get { return text.Length; } 41 | } 42 | 43 | /// 44 | /// Gets the text in this line without any formatting information. 45 | /// This is the text displayed on the screen. 46 | /// 47 | public string Text 48 | { 49 | get { return text; } 50 | } 51 | 52 | /// 53 | /// Gets the attribute of each character in the line. 54 | /// 55 | public TextAttribute[] Attributes 56 | { 57 | get { return attributes; } 58 | } 59 | 60 | public IEnumerable Links 61 | { 62 | get 63 | { 64 | HelpUri lastLink = null; 65 | foreach (TextAttribute a in attributes) 66 | { 67 | if (a.Link != lastLink) 68 | { 69 | if (a.Link != null) 70 | yield return a.Link; 71 | lastLink = a.Link; 72 | } 73 | } 74 | } 75 | } 76 | 77 | public override string ToString() 78 | { 79 | return this.text; 80 | } 81 | } 82 | 83 | public struct TextAttribute 84 | { 85 | readonly TextStyle style; 86 | readonly HelpUri link; 87 | 88 | public TextAttribute(TextStyle style, HelpUri link) 89 | { 90 | this.style = style; 91 | this.link = link; 92 | } 93 | 94 | public TextStyle Style 95 | { 96 | get { return style; } 97 | } 98 | 99 | public HelpUri Link 100 | { 101 | get { return link; } 102 | } 103 | 104 | public override string ToString() 105 | { 106 | if (link == null) 107 | return style.ToString(); 108 | else 109 | return style.ToString() + "; " + link.ToString(); 110 | } 111 | 112 | public static readonly TextAttribute Default = new TextAttribute(); 113 | } 114 | 115 | [Flags] 116 | public enum TextStyle 117 | { 118 | None = 0, 119 | Bold = 1, 120 | Italic = 2, 121 | Underline = 4, 122 | } 123 | 124 | public class HelpLineBuilder 125 | { 126 | readonly StringBuilder textBuilder; 127 | readonly List attrBuilder; 128 | 129 | public HelpLineBuilder(int capacity) 130 | { 131 | this.textBuilder = new StringBuilder(capacity); 132 | this.attrBuilder = new List(capacity); 133 | } 134 | 135 | public int Length 136 | { 137 | get { return textBuilder.Length; } 138 | } 139 | 140 | public void Append(string s, int index, int count, TextStyle styles) 141 | { 142 | textBuilder.Append(s, index, count); 143 | for (int i = 0; i < count; i++) 144 | attrBuilder.Add(new TextAttribute(styles, null)); 145 | } 146 | 147 | public void Append(char c, TextStyle styles) 148 | { 149 | textBuilder.Append(c); 150 | attrBuilder.Add(new TextAttribute(styles, null)); 151 | } 152 | 153 | public void AddLink(int index, int count, HelpUri link) 154 | { 155 | for (int i = index; i < index + count; i++) 156 | { 157 | attrBuilder[i] = new TextAttribute(attrBuilder[i].Style, link); 158 | } 159 | } 160 | 161 | public HelpLine ToLine() 162 | { 163 | return new HelpLine(textBuilder.ToString(), attrBuilder.ToArray()); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /QuickHelp/HelpSystem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace QuickHelp 5 | { 6 | /// 7 | /// Manages multiple cross-referenced help databases. 8 | /// 9 | public class HelpSystem 10 | { 11 | readonly List databases = new List(); 12 | 13 | /// 14 | /// Gets the collection of help databases in this library. 15 | /// 16 | public List Databases 17 | { 18 | get { return databases; } 19 | } 20 | 21 | /// 22 | /// Finds a database with the given name, ignoring case. 23 | /// 24 | /// Name of the database to find. 25 | /// The help database, or null if not found. 26 | public HelpDatabase FindDatabase(string name) 27 | { 28 | if (name == null) 29 | throw new ArgumentNullException(nameof(name)); 30 | 31 | foreach (HelpDatabase database in databases) 32 | { 33 | if (database.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)) 34 | return database; 35 | } 36 | return null; 37 | } 38 | 39 | /// 40 | /// Resolves the given uri in the current help system. 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// The topic pointed to by the uri, or null if one cannot be 46 | /// found. 47 | /// 48 | public HelpTopic ResolveUri(HelpDatabase referrer, HelpUri uri) 49 | { 50 | if (uri == null) 51 | throw new ArgumentNullException(nameof(uri)); 52 | 53 | HelpUriType uriType = uri.Type; 54 | if (uriType == HelpUriType.LocalTopic) 55 | { 56 | if (referrer != null) 57 | { 58 | int topicIndex = uri.TopicIndex; 59 | if (topicIndex >= 0 && topicIndex < referrer.Topics.Count) 60 | return referrer.Topics[topicIndex]; 61 | } 62 | } 63 | else if (uriType == HelpUriType.GlobalContext) 64 | { 65 | string dbName = uri.DatabaseName; 66 | HelpDatabase db = FindDatabase(dbName); 67 | if (db != null) 68 | return db.ResolveContext(uri.ContextString); 69 | } 70 | else if (uriType == HelpUriType.LocalContext) 71 | { 72 | if (referrer != null) 73 | return referrer.ResolveContext(uri.ContextString); 74 | } 75 | else if (uriType == HelpUriType.Context) 76 | { 77 | if (referrer != null) 78 | { 79 | HelpTopic topic = referrer.ResolveContext(uri.ContextString); 80 | if (topic != null) 81 | return topic; 82 | } 83 | foreach (HelpDatabase db in databases) 84 | { 85 | HelpTopic topic = db.ResolveContext(uri.ContextString); 86 | if (topic != null) 87 | return topic; 88 | } 89 | return null; 90 | } 91 | return null; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /QuickHelp/HelpTopic.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace QuickHelp 5 | { 6 | /// 7 | /// Represents a help topic that contains formatted text and links. 8 | /// 9 | public class HelpTopic 10 | { 11 | private readonly List lines = new List(); 12 | private readonly List m_snippets = new List(); 13 | private readonly List m_references = new List(); 14 | 15 | public HelpTopic() 16 | { 17 | } 18 | 19 | /// 20 | /// Gets the database that contains this help topic. 21 | /// 22 | public HelpDatabase Database { get; internal set; } 23 | 24 | /// 25 | /// Gets the zero-based index of this topic within its containing database. 26 | /// 27 | /// TODO: remove this field 28 | public int TopicIndex 29 | { 30 | get 31 | { 32 | if (this.Database != null) 33 | return this.Database.Topics.IndexOf(this); 34 | else 35 | return -1; 36 | } 37 | } 38 | 39 | /// 40 | /// Gets or sets the title of the topic. 41 | /// 42 | public string Title { get; set; } 43 | 44 | /// 45 | /// Gets or sets the default window height in number of lines. 46 | /// 47 | public int WindowHeight { get; set; } 48 | 49 | /// 50 | /// Gets or sets the number of rows to freeze at the top of the help 51 | /// screen. 52 | /// 53 | public int FreezeHeight { get; set; } 54 | 55 | #if true 56 | /// 57 | /// Gets or sets the source of this topic. If the topic is read from 58 | /// a text file, the object is a string; if the topic is read from a 59 | /// binary file, the object is a byte[]. 60 | /// 61 | public object Source { get; set; } 62 | #endif 63 | 64 | /// 65 | /// Gets the collection of lines in this topic. 66 | /// 67 | public List Lines 68 | { 69 | get { return lines; } 70 | } 71 | 72 | public override string ToString() 73 | { 74 | if (this.Title == null) 75 | return "(Untitled Topic)"; 76 | else 77 | return this.Title; 78 | } 79 | 80 | /// 81 | /// Gets or sets a flag that indicates whether the topic should be 82 | /// treated as a list of topics. 83 | /// 84 | /// 85 | /// If this value is true, each line in the topic is treated 86 | /// as a list item and is supposed to point to a topic. If the line 87 | /// contains a link, that link points to the target. Otherwise, the 88 | /// first string terminated by two spaces or a newline character is 89 | /// treated as a context string that points to the target. If no such 90 | /// string exists, the first word is treated as a context string. 91 | /// 92 | public bool IsList { get; set; } 93 | 94 | /// 95 | /// Gets or sets a flag that instructs the help viewer to turn off 96 | /// special processing of certain characters. 97 | /// 98 | public bool IsRaw { get; set; } 99 | 100 | /// 101 | /// Gets or sets a flag that indicates the topic should not be 102 | /// displayed in the help viewer because it contains commands or 103 | /// internal information. 104 | /// 105 | public bool IsHidden { get; set; } 106 | 107 | /// 108 | /// Gets or sets the command to execute. 109 | /// 110 | public string ExecuteCommand { get; set; } 111 | 112 | /// 113 | /// Gets or sets the category of the topic. May be null if the 114 | /// topic is not assigned to any category. 115 | /// 116 | public string Category { get; set; } 117 | 118 | /// 119 | /// Gets or sets a flag that instructs the help viewer to display the 120 | /// topic in a popup window instead of in a regular, scrollable 121 | /// window. 122 | /// 123 | public bool IsPopup { get; set; } 124 | 125 | /// 126 | /// Gets the collection of help snippets in this topic. 127 | /// 128 | public List Snippets 129 | { 130 | get { return m_snippets; } 131 | } 132 | 133 | /// 134 | /// Gets or sets the previous topic in navigation. 135 | /// 136 | /// 137 | /// This is used by QuickHelp to skip large number of hidden or popup 138 | /// topics. If not specified, the previous topic in the sequence is 139 | /// used. 140 | /// 141 | public HelpUri Predecessor { get; set; } 142 | 143 | /// 144 | /// Gets or sets the next topic in navigation. 145 | /// 146 | /// 147 | /// This is used by QuickHelp to skip large number of hidden or popup 148 | /// topics. If not specified, the next topic in the sequence is used. 149 | /// 150 | public HelpUri Successor { get; set; } 151 | 152 | /// 153 | /// Gets the collection of references for the topic. Each reference 154 | /// is represented by a context string. 155 | /// 156 | public List References 157 | { 158 | get { return m_references; } 159 | } 160 | } 161 | 162 | /// 163 | /// Represents a range of lines in a help topic with a name. 164 | /// 165 | /// 166 | /// A snippet is called a "paste" in QuickHelp terms. It is typically 167 | /// used to point to a section of code that can be pasted. 168 | /// 169 | public class HelpSnippet 170 | { 171 | /// 172 | /// Gets or sets the name of the snippet. 173 | /// 174 | public string Name { get; set; } 175 | 176 | /// 177 | /// Gets or sets the zero-based line number of the first line of the 178 | /// snippet. 179 | /// 180 | public int StartLine { get; set; } 181 | 182 | /// 183 | /// Gets or sets the zero-based line number of the line just past the 184 | /// end of the snippet. 185 | /// 186 | public int EndLine { get; set; } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /QuickHelp/HelpUri.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | 4 | namespace QuickHelp 5 | { 6 | /// 7 | /// Refers to a location within a help system. 8 | /// 9 | /// 10 | /// A help uri may take one of the following formats, and is resolved in 11 | /// that order: 12 | /// 13 | /// @LXXXX -- where XXXX is a hexidecimal number with the higest bit set 14 | /// Display the topic with index (XXXX & 0x7FFF) in the local 15 | /// database. 16 | /// 17 | /// @contextstring 18 | /// Display the topic associated with "@contextstring". Only the 19 | /// local database is searched for the context. 20 | /// 21 | /// !command 22 | /// Execute the command specified after the exclamation point (!). 23 | /// The command is case sensitive. Commands are application-specific. 24 | /// 25 | /// filename! 26 | /// Display filename as a single topic. The specified file must be a 27 | /// text file no larger than 64K. 28 | /// 29 | /// helpfile!contextstring 30 | /// Search helpfile for contextstring and display the associated 31 | /// topic. Only the specified Help database or physical Help file is 32 | /// searched for the context. 33 | /// 34 | /// contextstring 35 | /// Display the topic associated with contextstring. The context 36 | /// string is first searched for in the local database; if it is 37 | /// not found, it is searched for in other databases in the help 38 | /// system, and the first match is returned. 39 | /// 40 | public class HelpUri 41 | { 42 | readonly string target; 43 | 44 | /// 45 | /// Creates a uri that points to a topic in the local database. This 46 | /// is called "local context" in QuickHelp terms. 47 | /// 48 | /// Zero-based topic index. 49 | /// 50 | /// If topic index is less than 0 or greater than or equal to 0x8000. 51 | /// 52 | public HelpUri(int topicIndex) 53 | { 54 | if (topicIndex < 0 || topicIndex >= 0x8000) 55 | throw new ArgumentOutOfRangeException(nameof(topicIndex)); 56 | 57 | this.target = string.Format("@L{0:X4}", topicIndex | 0x8000); 58 | } 59 | 60 | /// 61 | /// Creates a uri directly. 62 | /// 63 | public HelpUri(string target) 64 | { 65 | if (target == null) 66 | throw new ArgumentNullException(nameof(target)); 67 | 68 | this.target = target; 69 | } 70 | 71 | /// 72 | /// Gets the type of location this uri refers to. 73 | /// 74 | public HelpUriType Type 75 | { 76 | get 77 | { 78 | if (target == "") 79 | return HelpUriType.None; 80 | 81 | if (target.StartsWith("@")) 82 | { 83 | if (TopicIndex >= 0) 84 | return HelpUriType.LocalTopic; 85 | else 86 | return HelpUriType.LocalContext; 87 | } 88 | 89 | if (target.StartsWith("!")) 90 | return HelpUriType.Command; 91 | else if (target.EndsWith("!")) 92 | return HelpUriType.File; 93 | else if (target.Contains("!")) 94 | return HelpUriType.GlobalContext; 95 | else 96 | return HelpUriType.Context; 97 | } 98 | } 99 | 100 | /// 101 | /// Gets the topic index specified by this uri, or -1 if this uri 102 | /// does not specify a topic index. 103 | /// 104 | public int TopicIndex 105 | { 106 | get 107 | { 108 | if (target.Length == 6 && target.StartsWith("@L")) 109 | { 110 | int topicIndexOr8000; 111 | if (Int32.TryParse( 112 | target.Substring(2), 113 | NumberStyles.AllowHexSpecifier, 114 | CultureInfo.InvariantCulture, 115 | out topicIndexOr8000) && 116 | (topicIndexOr8000 & 0x8000) != 0) 117 | { 118 | return topicIndexOr8000 & 0x7FFF; 119 | } 120 | } 121 | return -1; 122 | } 123 | } 124 | 125 | /// 126 | /// Gets the database name component of the url. 127 | /// 128 | /// 129 | /// The text to the left of the first '!' in the uri, or null 130 | /// if the uri does not contain any '!'. 131 | /// 132 | public string DatabaseName 133 | { 134 | get 135 | { 136 | int k = target.IndexOf('!'); 137 | if (k <= 0) 138 | return null; 139 | else 140 | return target.Substring(0, k); 141 | } 142 | } 143 | 144 | /// 145 | /// Gets the context string component of the url. 146 | /// 147 | /// 148 | /// The text to the right of the first '!', or the entire target if 149 | /// the uri does not contain any '!'. 150 | /// 151 | public string ContextString 152 | { 153 | get 154 | { 155 | int k = target.IndexOf('!'); 156 | if (k < 0) 157 | return target; 158 | else 159 | return target.Substring(k + 1); 160 | } 161 | } 162 | 163 | public string Target 164 | { 165 | get { return target; } 166 | } 167 | 168 | public override string ToString() 169 | { 170 | return target; 171 | } 172 | } 173 | 174 | /// 175 | /// Specifies the type of a help uri. 176 | /// 177 | public enum HelpUriType 178 | { 179 | /// 180 | /// The uri is empty. 181 | /// 182 | None = 0, 183 | 184 | /// 185 | /// The uri specifies a command to be executed. 186 | /// 187 | Command = 1, 188 | 189 | /// 190 | /// The uri contains a topic index to be resolved in the local 191 | /// database. 192 | /// 193 | LocalTopic = 2, 194 | 195 | /// 196 | /// The uri contains a context string to be resolved in the local 197 | /// database. 198 | /// 199 | LocalContext = 3, 200 | 201 | /// 202 | /// The uri contains a database name and a context string; the 203 | /// context string must be resolved in that database. 204 | /// 205 | GlobalContext = 4, 206 | 207 | /// 208 | /// The uri contains a context string that can be resolved in any 209 | /// database in the help system, with the local database searched 210 | /// first. 211 | /// 212 | Context = 5, 213 | 214 | /// 215 | /// The uri contains a database name to be displayed as a single 216 | /// help topic. 217 | /// 218 | File = 6, 219 | } 220 | 221 | #if false 222 | public class HelpLocation 223 | { 224 | HelpTopic Topic; 225 | ushort LineNumber; // zero-based 226 | byte ColumnNumber; // zero-based 227 | } 228 | #endif 229 | } 230 | -------------------------------------------------------------------------------- /QuickHelp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("QuickHelp")] 9 | [assembly: AssemblyDescription("QuickHelp File Decoder")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("QuickHelp")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f483fa86-81e4-42b0-955c-79fda02acb01")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /QuickHelp/QuickHelp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C18C4ED1-E4B6-4DC3-B7CF-55053FEC165B} 8 | Library 9 | Properties 10 | QuickHelp 11 | QuickHelp 12 | v2.0 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 72 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/BufferReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.IO; 5 | 6 | namespace QuickHelp.Serialization 7 | { 8 | /// 9 | /// Provides methods to read typed data from a byte buffer. This class is 10 | /// similar in functionality to a BinaryReader backed by a MemoryStream, 11 | /// but provides additional methods with a lightweight implementation. 12 | /// 13 | /// TODO: a BufferReader should keep track of context information (like 14 | /// line and column) so that it can be used to indicate location of any 15 | /// error. 16 | public class BufferReader 17 | { 18 | readonly Encoding encoding; 19 | readonly byte[] buffer; 20 | readonly int endIndex; 21 | int index; 22 | 23 | public BufferReader(byte[] buffer) 24 | : this(buffer, 0, buffer.Length, Encoding.UTF8) 25 | { 26 | } 27 | 28 | public BufferReader(byte[] buffer, Encoding encoding) 29 | : this(buffer, 0, buffer.Length, encoding) 30 | { 31 | } 32 | 33 | public BufferReader(byte[] buffer, int index, int count) 34 | : this(buffer, index, count, Encoding.UTF8) 35 | { 36 | } 37 | 38 | public BufferReader(byte[] buffer, int index, int count, Encoding encoding) 39 | { 40 | if (buffer == null) 41 | throw new ArgumentNullException("buffer"); 42 | if (index < 0 || index > buffer.Length) 43 | throw new ArgumentOutOfRangeException("index"); 44 | if (count < 0 || count > buffer.Length - index) 45 | throw new ArgumentOutOfRangeException("count"); 46 | if (encoding == null) 47 | throw new ArgumentNullException("encoding"); 48 | 49 | this.buffer = buffer; 50 | this.index = index; 51 | this.endIndex = index + count; 52 | this.encoding = encoding; 53 | } 54 | 55 | public bool IsEOF 56 | { 57 | get { return index >= endIndex; } 58 | } 59 | 60 | public byte ReadByte() 61 | { 62 | if (index >= endIndex) 63 | throw new EndOfStreamException(); 64 | return buffer[index++]; 65 | } 66 | 67 | public UInt16 ReadUInt16() 68 | { 69 | if (index + 2 > endIndex) 70 | throw new EndOfStreamException(); 71 | int value = buffer[index] | (buffer[index + 1] << 8); 72 | index += 2; 73 | return (UInt16)value; 74 | } 75 | 76 | public string ReadNullTerminatedString() 77 | { 78 | int k = Array.IndexOf(buffer, (byte)0, index, endIndex - index); 79 | if (k == -1) 80 | throw new EndOfStreamException(); 81 | 82 | string s = encoding.GetString(buffer, index, k - index); 83 | index = k + 1; 84 | return s; 85 | } 86 | 87 | public string ReadFixedLengthString(int length) 88 | { 89 | if (length < 0) 90 | throw new ArgumentOutOfRangeException("length"); 91 | if (length > endIndex - index) 92 | throw new EndOfStreamException(); 93 | 94 | string s = encoding.GetString(buffer, index, length); 95 | index += length; 96 | return s; 97 | } 98 | 99 | public BufferReader ReadBuffer(int length) 100 | { 101 | if (length < 0) 102 | throw new ArgumentOutOfRangeException("length"); 103 | if (length > endIndex - index) 104 | throw new EndOfStreamException(); 105 | 106 | BufferReader subReader = new BufferReader(buffer, index, length, encoding); 107 | index += length; 108 | return subReader; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/Format.txt: -------------------------------------------------------------------------------- 1 | QuickHelp Binary Format 2 | ======================= 3 | 4 | This document describes the binary format of a QuickHelp .HLP file. 5 | 6 | 7 | Overview 8 | -------- 9 | 10 | A QuickHelp .HLP file comprises the following sections: 11 | 12 | +------------------------+ 13 | | Signature | 14 | +------------------------+ 15 | | File Header | 16 | +------------------------+ 17 | | Topic Index | 18 | +------------------------+ 19 | | Context Strings | 20 | +------------------------+ 21 | | Context Map | 22 | +------------------------+ 23 | | Keywords | 24 | +------------------------+ 25 | | Huffman Tree | 26 | +------------------------+ 27 | | Topic[0] Text | 28 | +------------------------+ 29 | | ... | 30 | +------------------------+ 31 | | Topic[N-1] Text | 32 | +------------------------+ 33 | 34 | Each section is described in detail below. 35 | 36 | Note 1: All numeric fields are stored in little-endian order unless specified 37 | otherwise. 38 | 39 | Note 2: Multiple help databases may be concatenated and stored in a single 40 | file. In this document, we assume a single database for convenience. 41 | 42 | 43 | Signature 44 | --------- 45 | 46 | An HLP file starts with the two-byte signature: 0x4C, 0x4E. 47 | 48 | 49 | File Header 50 | ----------- 51 | 52 | Following the signature is the File Header section, which is a structure with 53 | the following fields: 54 | 55 | Range Type Field Meaning 56 | ---------------------------------------------------------------------------- 57 | 02-04 WORD Version always 2 58 | 04-06 WORD Attributes bit 0: case-sensitivity of context 59 | strings 60 | bit 1: prevent the database from being 61 | decoded by the HELPMAKE utility 62 | bits 2-15: reserved; always 0 63 | 06-07 BYTE ControlCharacter usually ':'; 0FFh also seen 64 | 07-08 BYTE (padding 1) reserved; always 0 65 | 08-0A WORD TopicCount number of topics in the database 66 | 0A-0C WORD ContextCount number of context strings in the 67 | database 68 | 0C-0E WORD DisplayWidth width of the help viewer in characters 69 | 0E-10 WORD PredefinedCtxCount number of predefined context strings; 70 | usually 0 71 | 10-1E BYTE[] DatabaseName database name used to resolve external 72 | links; NULL-terminated and NULL-padded 73 | 1E-22 DWORD (reserved 1) always 0 74 | 22-26 DWORD TopicIndex offset of the Topic Index section 75 | 26-2A DWORD ContextStringsOffset offset of the Context Strings section 76 | 2A-2E DWORD ContextMapOffset offset of the Context Mapping section 77 | 2E-32 DWORD KeywordsOffset offset of the Keywords section; 78 | 0 if keyword compression is not used 79 | 32-36 DWORD HuffmanTreeOffset offset of the Huffman Tree section; 80 | 0 if Huffman compression is not used 81 | 36-3A DWORD TopicTextOffset offset of the start of topic texts 82 | 3A-3E DWORD (reserved 2) always 0 83 | 3E-42 DWORD (reserved 3) always 0 84 | 42-46 DWORD DatabaseSize size of the help database in bytes 85 | 86 | 87 | Topic Index 88 | ----------- 89 | 90 | The Topic Index section comprises (TopicCount + 1) DWORD integers. The first 91 | TopicCount integers specify the offset of the corresponding topic texts 92 | relative to the beginning of the help database. The last integer specifies 93 | the offset just past the end of the last topic. 94 | 95 | (N = TopicCount) 96 | +--------------------+ 97 | | TopicOffset[0] | (DWORD) offset of the first topic 98 | +--------------------+ 99 | | ... | ... 100 | +--------------------+ 101 | | TopicOffset[N-1] | (DWORD) offset of the last topic 102 | +--------------------+ 103 | | TopicOffset[N] | (DWORD) indicates the end of the last topic; 104 | +--------------------+ should be equal to DatabaseSize. 105 | 106 | The size (in bytes) of topic k can be found by computing the difference 107 | between TopicOffset[k] and TopicOffset[k+1]. 108 | 109 | 110 | Context Strings 111 | --------------- 112 | 113 | The Context Strings section comprises (ContextCount) NULL-terminated context 114 | strings. The context strings are not sorted. 115 | 116 | (N = ContextCount) 117 | +--------------------+ 118 | | ContextString[0] | first context string (NULL-terminated) 119 | +--------------------+ 120 | | ... | ... 121 | +--------------------+ 122 | | ContextString[N-1] | last context string (NULL-terminated) 123 | +--------------------+ 124 | 125 | 126 | Context Map 127 | ----------- 128 | 129 | The Context Map section comprises (ContextCount) WORD integers. Each integer 130 | specifies the index of the topic to which the corresponding context string 131 | resolves, and must be within the range 0 to (TopicCount-1) inclusive. 132 | 133 | (N = ContextCount) 134 | +-------------------+ 135 | | ContextMap[0] | (WORD) index of the topic that the first context 136 | +-------------------+ string points to 137 | | ... | ... 138 | +-------------------+ 139 | | ContextMap[N-1] | (WORD) index of the topic that the last context 140 | +-------------------+ string points to 141 | 142 | 143 | Keywords 144 | -------- 145 | 146 | The Keywords section contains a list of frequently used words in the topics. 147 | It is used by the keyword compression (dictionary substitution) pass. This 148 | section does not exist if keyword compression is not used for this database. 149 | 150 | Each word is prefixed by a byte that specifies the length of the word in 151 | bytes, not counting the prefix itself. The word is NOT NULL-terminated. 152 | 153 | (N = implied) 154 | +-------------+ 155 | | Word[0] | first word in dictionary (length-prefixed string) 156 | +-------------+ 157 | | ... | ... 158 | +-------------+ 159 | | Word[N-1] | last word in dictionary (length-prefixed string) 160 | +-------------+ 161 | 162 | The words are sorted in lexicographical order. The length prefix is not taken 163 | into account when sorting. 164 | 165 | There can be at most 1024 (10 bits) words in the dictionary; but there can be 166 | fewer. The number of words is not explicitly specified and must be implied by 167 | reading the entire section. 168 | 169 | 170 | Huffman Tree 171 | ------------ 172 | 173 | The Huffman Tree section contains the huffman tree used by the Huffman 174 | compression pass. This section does not exist if Huffman compression is not 175 | used for this database. 176 | 177 | Each node in a huffman tree is either a leaf node or an internal node. A leaf 178 | node represents a symbol valued from 0 to 255. An internal node must have two 179 | children and encodes a bit in the huffman code: the left child encodes 0 and 180 | the right child encodes 1. 181 | 182 | 511 nodes are sufficient to encode all 256 symbols. If the tree contains less 183 | than 511 nodes, only a subset of the 256 symbols are encoded. 184 | 185 | The Huffman tree is compactly stored in an array of WORD integers, where each 186 | integer represents a node. The array is terminated by an extra WORD of value 187 | zero. The nodes plus the terminating 0 WORD should match the length of the 188 | section specified by [HuffmanTreeOffset, TopicTextOffset). 189 | 190 | The serialized format of the Huffman tree is as follows. Denote the nodes by 191 | Node[0] through Node[N] where N <= 511 and Node[N] == 0. Then: 192 | 193 | - A leaf node, Node[i], has its highest bit set to 1, and the symbol it 194 | represents is stored in the low byte of Node[i]. 195 | 196 | - An internal node, Node[i], has its highest is set to 0, and 197 | o its right child (1 bit) is Node[i+1]; 198 | o its left child (0 bit) is Node[Node[i]/2]. 199 | 200 | - The root node is Node[0]. 201 | 202 | Note that this format is not specific to a Huffman tree; it can be used to 203 | serialize any proper binary tree. 204 | 205 | The above node numbering scheme can be generated by performing a post-order 206 | traversal of the tree and number the node from N to 1 as they are visited. 207 | 208 | 209 | Topic Text 210 | ---------- 211 | 212 | Following the meta data sections are N blocks of topic text. Each topic is 213 | separately compressed. The compression method is described below. 214 | 215 | Topic text is compiled, compressed, and encoded in three steps: 216 | 217 | Step 1. Topic text is compiled from QuickHelp markup format to binary 218 | format. 219 | 220 | Step 2. The binary text is compressed using keyword compression and 221 | run-length encoding. 222 | 223 | Step 3. The compressed text is encoded with Huffman coding and stored in 224 | the help database. 225 | 226 | The following diagram illustrates the encoding procedure. 227 | 228 | +=====================+ 229 | | Markup Topic Text | 230 | +=====================+ 231 | | 232 | | [1] compile to binary format 233 | v 234 | +=====================+ 235 | | Binary Topic Text | 236 | +=====================+ 237 | | 238 | | [2] keyword compression and 239 | | run-length encoding 240 | v 241 | +=====================+ 242 | | Compact Topic Text | 243 | +=====================+ 244 | | 245 | | [3] Huffman encoding 246 | v 247 | +=====================+ 248 | | Encoded Topic Text | 249 | +=====================+ 250 | 251 | 252 | Step 1: Compile QuickHelp markup format to binary format 253 | -------------------------------------------------------- 254 | 255 | The QuickHelp markup format is described in detail in MASM documentation, 256 | Chapter 18 - Creating Help Files With HELPMAKE. 257 | 258 | In QuickHelp binary format, each line is represented by two parts: 259 | 1. Text 260 | 2. Styles and links 261 | 262 | "Text" is the characters displayed on the screen, stripped of any formatting 263 | information. 264 | 265 | "Styles" associate each character with one or more of the following styles: 266 | bold, italic, and underline. In QuickHelp viewer, the styles are rendered as 267 | follows: 268 | 269 | Value Style Foreground Color Background Color 270 | --------------------------------------------------------------------- 271 | 0 Normal (default) white black 272 | 1 Bold highlighted white black 273 | 2 Italic green black 274 | 3 Bold+Italic cyan black 275 | 4 Underline red black 276 | 5 Bold+Underline highlighted white cyan 277 | 6 Italic+Underline white black 278 | 7 Bold+Italic+Underline black black 279 | 280 | "Links" associate a range of characters in a line with a link target. The link 281 | target must take one of the following forms: 282 | 283 | 1. a context string that matches a context in the current help database or 284 | another help database, or 285 | 286 | 2. a 16-bit integer with the highest bit set, whose lowest 15 bits specifies 287 | a topic index in the current help database. 288 | 289 | Links are defined orthogonal to styles. Links must not overlap. 290 | 291 | With this model in mind, below we describe the QuickHelp binary format. 292 | 293 | Each topic is first split into lines. Colon commands (see MASM docs for a 294 | detailed description) are treated as plain text when stored. Each line 295 | consists of a text block and an attribute block, like below: 296 | 297 | +-----------------+ 298 | | TextBlockLen(X) | 1 byte number of bytes in text block, including 299 | +-----------------+ the "TextBlockLen" byte 300 | . . 301 | . TextBlockData . X-1 bytes characters in the line, stripped of any 302 | . . formatting information 303 | +-----------------+ 304 | | AttrBlockLen(Y) | 1 byte number of bytes in attribute block, 305 | +-----------------+ including the "AttrBlockLen" byte 306 | . . 307 | . AttrBlockData . Y-1 bytes character style and link information; see 308 | . . below. 309 | +-----------------+ 310 | 311 | "TextBlockLen" and "AttrBlockLen" must be greater than zero. 312 | 313 | "TextBlockData" is the plain text to display. Each byte corresponds to an 314 | ASCII or Extended ASCII character; on Windows, this is code page 437. Note, 315 | however, that characters 0-31 are rendered as graphic characters instead of 316 | interpreted as control characters when displayed in QuickHelp; this means 317 | that a further mapping must be performed after transforming using CP-437. 318 | 319 | "AttrBlockData" comprises a mandatory "Styles" part followed by an optional 320 | "Links" part. 321 | 322 | If no link exists in a line, the format of "AttrBlockData" is 323 | 324 | (Y-1) bytes 325 | +==========+ 326 | | Styles | 327 | +==========+ 328 | 329 | If links are present in a line, the format of "AttrBlockData" is 330 | 331 | (? bytes) 1 byte (? bytes) --> total (Y-1) bytes 332 | +==========+------+=========+ 333 | | Styles | 0xFF | Links | 334 | +==========+------+=========+ 335 | 336 | "Styles" is an alternating list of "chunk length" and "style", as follows: 337 | 338 | +--------------+ 339 | | ChunkLen 0 | 1 byte 340 | +--------------+ 341 | | Style 1 | 1 byte 342 | +--------------+ 343 | | ChunkLen 1 | 1 byte 344 | +--------------+ 345 | | Style 2 | 1 byte 346 | +--------------+ 347 | | ChunkLen 2 | 1 byte 348 | +--------------+ 349 | ~ ... ~ ... 350 | +--------------+ 351 | 352 | This list always starts with a ChunkLen[0] field. The default style applies 353 | to the first (ChunkLen[0]) characters in the line. The next ChunkLen[1] 354 | characters apply the style defined in Style[1]. Following that, the next 355 | ChunkLen[2] characters apply the style defined in Style[2]; and so on. 356 | 357 | Each Style byte comprises the following bits: 358 | 359 | 7 6 5 4 3 2 1 0 360 | +---+---+---+---+---+---+---+---+ 361 | | 0 | 0 | 0 | 0 | 0 | U | I | B | 362 | +---+---+---+---+---+---+---+---+ 363 | |_______________| | | |---- Bold 364 | | | |-------- Italic 365 | reserved; |------------ Underline 366 | must be 0. 367 | 368 | Each Style[i] field replaces the previous style; the style bits are not merged 369 | or toggled. 370 | 371 | "Links" is an array of variable-length records, where each record defines a 372 | link in the line. The format of a record is: 373 | 374 | +--------------+ 375 | | StartIndex | 1 byte ONE-based index of the first character in the 376 | +--------------+ link, inclusive. 377 | | EndIndex | 1 byte ONE-based index of the last character in the 378 | +--------------+ link, inclusive. 379 | | Context | ? bytes NULL-terminated context string that specifies 380 | | String | the link target, or an empty string to indicate 381 | +--------------+ that TopicIndex should be used as the target. 382 | | TopicIndex | WORD Optional; present only if ContextString is empty 383 | +--------------+ 384 | 385 | 386 | Step 2: Keyword compression and run-length encoding 387 | --------------------------------------------------- 388 | 389 | The compressed data is a byte stream that has the following format. 390 | 391 | Each byte in the compressed stream is either a control byte or a value byte. 392 | This is determined as follows: 393 | 394 | Byte 00 - 0F : value byte 395 | Byte 10 - 1A : control byte 396 | Byte 1B - FF : value byte 397 | 398 | During decoding, value bytes are copied as is to the output, unless they 399 | follow a control byte and is treated as an argument. See below. 400 | 401 | There are eleven control bytes, valued from 0x10 (16) to 0x1A (26). A control 402 | byte takes one or two bytes as its argument. The format of each control byte 403 | is summarized below. 404 | 405 | Hex Dec Control Byte Argument Byte 1 Argument Byte 2 406 | 407 | +-----------------+-----------------+ 408 | 10-17 16-23 | 0 0 0 1 0 A D D | D D D D D D D D | 409 | | S 9 8 | 7 6 5 4 3 2 1 0 | 410 | +-----------------+-----------------+ 411 | 412 | +-----------------+-----------------+ 413 | 18 24 | 0 0 0 1 1 0 0 0 | SPACE-COUNT | 414 | +-----------------+-----------------+ 415 | 416 | +-----------------+-----------------+-----------------+ 417 | 19 25 | 0 0 0 1 1 0 0 1 | REPEAT-BYTE | REPEAT-LENGTH | 418 | +-----------------+-----------------+-----------------+ 419 | 420 | +-----------------+-----------------+ 421 | 1A 26 | 0 0 0 1 1 0 1 0 | ESCAPE-BYTE | 422 | +-----------------+-----------------+ 423 | 424 | Control bytes 10h-17h encode a dictionary entry index. The index, D, is 425 | specified by the lowest 2 bits of the control byte, followed by the 8 bits 426 | of the argument byte that follows. (This gives 10 bits available; hence the 427 | dictionary can contain no more than 1024 entries.) The dictionary entry is 428 | copied to the output. If the AS (Append-Space) bit is 1, a space (ASCII 32) 429 | is appended to the output. 430 | 431 | Control byte 18h encodes a run of spaces (ASCII 32). The number of spaces is 432 | specified by the argument byte that follows. This many spaces are appended to 433 | the output. 434 | 435 | Control byte 19h encodes a run of bytes. The byte to repeat is given by the 436 | first argument, and the run-length is given by the second argument. That many 437 | bytes are repeated and appended to the output. 438 | 439 | Control byte 1Ah escapes the next byte (argument) in the compressed stream. 440 | The argument is written as is to the output. This is necessary to output a 441 | byte in the range 10h to 1Ah. 442 | 443 | 444 | Step 3: Huffman coding 445 | ---------------------- 446 | 447 | The resulting binary data from Step 2 is encoded by a huffman coder. The 448 | huffman tree encodes 256 symbols (i.e. byte value 0 - 255). There is no limit 449 | on the number of bits used to encode each symbol. 450 | 451 | +----------+==============+ 452 | | OUTLEN | BIT STREAM | 453 | +----------+==============+ 454 | 455 | OUTLEN is the number of bytes in the binary topic data that is produced by 456 | Step 1. Note that it is NOT the compressed data produced by Step 2. 457 | 458 | BIT STREAM is a bit stream that contains the huffman-encoded data from Step 2. 459 | For each byte in the stream, the bits are written to (and read from) starting 460 | from the MOST significant bit of that byte; there may be extra, unused bits at 461 | the end of the last byte. This is why we need the OUTLEN field. 462 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/Graphic437Encoding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | namespace QuickHelp.Serialization 5 | { 6 | public class Graphic437Encoding : Encoding 7 | { 8 | private static readonly Encoding CP437 = Encoding.GetEncoding(437); 9 | private const string GraphicCharacters = "\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼"; 10 | 11 | public static bool IsControlCharacter(char c) 12 | { 13 | return (c < 32) || (c == 127); 14 | } 15 | 16 | public static bool ContainsControlCharacter(string s) 17 | { 18 | if (s == null) 19 | throw new ArgumentNullException(nameof(s)); 20 | 21 | for (int i = 0; i < s.Length; i++) 22 | { 23 | if (IsControlCharacter(s[i])) 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | public static void SubstituteControlCharacters(char[] chars) 30 | { 31 | if (chars == null) 32 | throw new ArgumentNullException(nameof(chars)); 33 | 34 | SubstituteControlCharacters(chars, 0, chars.Length); 35 | } 36 | 37 | public static void SubstituteControlCharacters(char[] chars, int index, int count) 38 | { 39 | if (chars == null) 40 | throw new ArgumentNullException(nameof(chars)); 41 | if (index < 0 || index > chars.Length) 42 | throw new ArgumentOutOfRangeException(nameof(index)); 43 | if (count < 0 || count > chars.Length - index) 44 | throw new ArgumentOutOfRangeException(nameof(count)); 45 | 46 | for (int i = index; i < index + count; i++) 47 | { 48 | if (chars[i] < 32) 49 | chars[i] = GraphicCharacters[chars[i]]; 50 | else if (chars[i] == 127) 51 | chars[i] = '⌂'; 52 | } 53 | } 54 | 55 | public static string SubstituteControlCharacters(string s) 56 | { 57 | if (s == null) 58 | return null; 59 | 60 | if (!ContainsControlCharacter(s)) 61 | return s; 62 | 63 | char[] chars = s.ToCharArray(); 64 | SubstituteControlCharacters(chars); 65 | return new string(chars); 66 | } 67 | 68 | public override int GetByteCount(char[] chars, int index, int count) 69 | { 70 | return CP437.GetByteCount(chars, index, count); 71 | } 72 | 73 | public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) 74 | { 75 | return CP437.GetBytes(chars, charIndex, charCount, bytes, byteIndex); 76 | } 77 | 78 | public override int GetCharCount(byte[] bytes, int index, int count) 79 | { 80 | return CP437.GetCharCount(bytes, index, count); 81 | } 82 | 83 | public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) 84 | { 85 | int charCount = CP437.GetChars(bytes, byteIndex, byteCount, chars, charIndex); 86 | SubstituteControlCharacters(chars, charIndex, charCount); 87 | return charCount; 88 | } 89 | 90 | public override int GetMaxByteCount(int charCount) 91 | { 92 | return CP437.GetMaxByteCount(charCount); 93 | } 94 | 95 | public override int GetMaxCharCount(int byteCount) 96 | { 97 | return CP437.GetMaxCharCount(byteCount); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/HelpCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace QuickHelp.Serialization 5 | { 6 | /// 7 | /// Specifies a dot or colon command in serialized help content. 8 | /// 9 | /// 10 | /// Because dot or colon commands are only relevant in serialized format, 11 | /// this enum and related classes are internal to the assembly. 12 | /// 13 | enum HelpCommand 14 | { 15 | /// 16 | /// Special value that indicates the absence of a command. 17 | /// 18 | None = 0, 19 | 20 | /// 21 | /// Lists the category in which the current topic appears and its 22 | /// position in the list of topics. The category name is used by the 23 | /// QuickHelp Categories command, which displays the list of topics. 24 | /// Supported only by QuickHelp. 25 | /// 26 | [HelpCommandFormat(".category", ":c", "string")] 27 | Category, 28 | 29 | /// 30 | /// Indicates that the topic cannot be displayed. Use this command to 31 | /// hide command topics and other internal information. 32 | /// 33 | [HelpCommandFormat(".command", ":x", null)] 34 | Command, 35 | 36 | /// 37 | /// Takes a string as parameter, which is a comment that appears only 38 | /// in the source file. Comments are not inserted in the database and 39 | /// are not restored during decoding. 40 | /// 41 | [HelpCommandFormat(".comment", null, "string")] 42 | [HelpCommandFormat("..", null, "string")] 43 | Comment, 44 | 45 | /// 46 | /// Takes a string as parameter. The string defines a context. 47 | /// 48 | [HelpCommandFormat(".context", null, "string")] 49 | Context, 50 | 51 | /// 52 | /// Ends a paste section. See the .paste command. Supported only by 53 | /// QuickHelp. 54 | /// 55 | [HelpCommandFormat(".end", ":e", null)] 56 | End, 57 | 58 | /// 59 | /// Executes the specified command. For example, 60 | /// .execute Pmark context represents a jump to the specified context 61 | /// at the specified mark. See the .mark command. 62 | /// 63 | [HelpCommandFormat(".execute", ":y", "command")] 64 | Execute, 65 | 66 | /// 67 | /// Locks the first numlines lines at the top of the screen. These 68 | /// lines do not move when the text is scrolled. 69 | /// 70 | [HelpCommandFormat(".freeze", ":z", "numlines")] 71 | Freeze, 72 | 73 | /// 74 | /// Sets the default window size for the topic in topiclength lines. 75 | /// 76 | [HelpCommandFormat(".length", ":l", "topiclength")] 77 | Length, 78 | 79 | /// 80 | /// Tells HELPMAKE to reset the line number to begin at number for 81 | /// subsequent lines of the input file. Line numbers appear in 82 | /// HELPMAKE error messages. See .source. The .line command is not 83 | /// inserted in the Help database and is not restored during decoding. 84 | /// 85 | [HelpCommandFormat(".line", null, "number")] 86 | Line, 87 | 88 | /// 89 | /// Indicates that the current topic contains a list of topics. Help 90 | /// displays a highlighted line; you can choose a topic by moving the 91 | /// highlighted line over the desired topic and pressing ENTER. If the 92 | /// line contains a coded link, Help looks up that link. If it does 93 | /// not contain a link, Help looks within the line for a string 94 | /// terminated by two spaces or a newline character and looks up that 95 | /// string. Otherwise, Help looks up the first word. 96 | /// 97 | [HelpCommandFormat(".list", ":i", null)] 98 | List, 99 | 100 | /// 101 | /// Defines a mark immediately preceding the following line of text. The 102 | /// marked line shows a script command where the display of a topic 103 | /// begins. The name identifies the mark. The column is an integer value 104 | /// specifying a column location within the marked line. Supported only 105 | /// by QuickHelp. 106 | /// 107 | [HelpCommandFormat(".mark", ":m", "name [[column]]")] 108 | Mark, 109 | 110 | /// 111 | /// Tells the Help reader to look up the next topic using context 112 | /// instead of the topic that physically follows it in the file. 113 | /// You can use this command to skip large blocks of .command or 114 | /// .popup topics. 115 | /// 116 | [HelpCommandFormat(".next", ":>", "context")] 117 | Next, 118 | 119 | /// 120 | /// Begins a paste section. The pastename appears in the QuickHelp 121 | /// Paste menu. Supported only by QuickHelp. 122 | /// 123 | [HelpCommandFormat(".paste", ":p", "pastename")] 124 | Paste, 125 | 126 | /// 127 | /// Tells the Help reader to display the current topic as a popup 128 | /// window instead of as a normal, scrollable topic. Supported only 129 | /// by QuickHelp. 130 | /// 131 | [HelpCommandFormat(".popup", ":g", null)] 132 | Popup, 133 | 134 | /// 135 | /// Tells the Help reader to look up the previous topic using context 136 | /// instead of the topic that physically precedes it in the file. You 137 | /// can use this command to skip large blocks of .command or .popup 138 | /// topics. 139 | /// 140 | [HelpCommandFormat(".previous", ":<", "context")] 141 | Previous, 142 | 143 | /// 144 | /// Turns off special processing of certain characters by the Help 145 | /// reader. 146 | /// 147 | [HelpCommandFormat(".raw", ":u", null)] 148 | Raw, 149 | 150 | /// 151 | /// Tells the Help reader to display the topic in the Reference menu. 152 | /// You can list multiple topics; separate each additional topic with 153 | /// a comma. A .ref command is not affected by the /W option. If no 154 | /// topic is specified, QuickHelp searches the line immediately 155 | /// following for a See or See Also reference; if present, the 156 | /// reference must be the first word on the line. Supported only by 157 | /// QuickHelp. 158 | /// 159 | [HelpCommandFormat(".ref", ":r", "topic[[, topic]]")] 160 | Ref, 161 | 162 | /// 163 | /// Tells HELPMAKE that subsequent topics come from filename. HELPMAKE 164 | /// error messages contain the name and line number of the input file. 165 | /// The .source command tells HELPMAKE to use filename in the message 166 | /// instead of the name of the input file and to reset the line number 167 | /// to 1. This is useful when you concatenate several sources to form 168 | /// the input file. See .line. The .source command is not inserted in 169 | /// the Help database and is not restored during decoding. 170 | /// 171 | [HelpCommandFormat(".source", null, "filename")] 172 | Source, 173 | 174 | /// 175 | /// Defines text as the name or title to be displayed in place of the 176 | /// context string if the application Help displays a title. This 177 | /// command is always the first line in the context unless you also 178 | /// use the .length or .freeze commands. 179 | /// 180 | [HelpCommandFormat(".topic", ":n", "text")] 181 | Topic, 182 | } 183 | 184 | [AttributeUsage(AttributeTargets.All, AllowMultiple=true)] 185 | class HelpCommandFormatAttribute : Attribute 186 | { 187 | readonly string dotCommand; 188 | readonly string colonCommand; 189 | readonly string parameterFormat; 190 | 191 | public HelpCommandFormatAttribute( 192 | string dotCommand, string colonCommand, string parameterFormat) 193 | { 194 | if (dotCommand == null) 195 | throw new ArgumentNullException(nameof(dotCommand)); 196 | if (dotCommand.Length == 0 || dotCommand[0] != '.') 197 | throw new ArgumentException("Dot command must start with a dot.", nameof(dotCommand)); 198 | 199 | if (colonCommand == null) 200 | throw new ArgumentNullException(nameof(colonCommand)); 201 | if (colonCommand.Length == 0 || colonCommand[0] != ':') 202 | throw new ArgumentException("Colon command must start with a colon.", nameof(colonCommand)); 203 | 204 | if (parameterFormat == null) 205 | throw new ArgumentNullException(nameof(parameterFormat)); 206 | 207 | this.dotCommand = dotCommand; 208 | this.colonCommand = colonCommand; 209 | this.parameterFormat = parameterFormat; 210 | } 211 | } 212 | 213 | // TODO: rename Converter to something else 214 | static class HelpCommandConverter 215 | { 216 | /// 217 | /// Parses a line for a command and executes the command if present. 218 | /// 219 | /// 220 | /// true if the line represents a command and is executed; 221 | /// false if the line does not represent a command. Throws an 222 | /// exception if the line represents an unknown or invalid command. 223 | /// 224 | public static bool ProcessCommand( 225 | string line, char controlCharacter, HelpTopic topic) 226 | { 227 | HelpCommand command; 228 | string parameter; 229 | // if (!ParseCommand(line, controlCharacter, out command, out parameter)) 230 | if (!ParseColonCommand(line, controlCharacter, out command, out parameter)) 231 | return false; 232 | 233 | ExecuteCommand(command, parameter, topic); 234 | return true; 235 | } 236 | 237 | /// 238 | /// Processes a command within the given topic. 239 | /// 240 | /// 241 | /// true if the command is successfully processed; false 242 | /// if the command is not supported or if the syntax is invalid. 243 | /// 244 | private static void ExecuteCommand( 245 | HelpCommand command, string parameter, HelpTopic topic) 246 | { 247 | switch (command) 248 | { 249 | case HelpCommand.Category: 250 | topic.Category = parameter; 251 | break; 252 | 253 | case HelpCommand.Command: 254 | topic.IsHidden = true; 255 | break; 256 | 257 | case HelpCommand.End: 258 | if (topic.Snippets.Count > 0) 259 | { 260 | topic.Snippets[topic.Snippets.Count - 1].EndLine 261 | = topic.Lines.Count; 262 | } 263 | break; 264 | 265 | case HelpCommand.Execute: 266 | // TODO: there could be multiple execute commands. 267 | topic.ExecuteCommand = parameter; 268 | break; 269 | 270 | case HelpCommand.Freeze: 271 | topic.FreezeHeight = Int32.Parse(parameter); 272 | break; 273 | 274 | case HelpCommand.Length: 275 | topic.WindowHeight = Int32.Parse(parameter); 276 | break; 277 | 278 | case HelpCommand.List: 279 | topic.IsList = true; 280 | break; 281 | 282 | case HelpCommand.Mark: 283 | // TODO: to be implemented 284 | System.Diagnostics.Debug.WriteLine(string.Format( 285 | "**** NOT IMPLEMENTED **** HelpCommand.Mark @ Line {0} of Topic {1} ({2}): {3}", 286 | topic.Lines.Count, topic.TopicIndex, topic.Title, parameter)); 287 | break; 288 | 289 | case HelpCommand.Next: 290 | topic.Successor = new HelpUri(parameter); 291 | break; 292 | 293 | case HelpCommand.Paste: 294 | { 295 | HelpSnippet snippet = new HelpSnippet(); 296 | snippet.Name = parameter; 297 | snippet.StartLine = topic.Lines.Count; 298 | snippet.EndLine = topic.Lines.Count; 299 | topic.Snippets.Add(snippet); 300 | } 301 | break; 302 | 303 | case HelpCommand.Popup: 304 | topic.IsPopup = true; 305 | break; 306 | 307 | case HelpCommand.Previous: 308 | topic.Predecessor = new HelpUri(parameter); 309 | break; 310 | 311 | case HelpCommand.Raw: 312 | topic.IsRaw = true; 313 | break; 314 | 315 | case HelpCommand.Ref: 316 | if (string.IsNullOrEmpty(parameter)) 317 | { 318 | // TODO: The references are in the following 319 | // lines until the next blank line. We don't 320 | // handle this for the moment. 321 | } 322 | else 323 | { 324 | string[] references = parameter.Split(','); 325 | foreach (string reference in references) 326 | { 327 | string contextString = reference.Trim(); 328 | if (!string.IsNullOrEmpty(contextString)) 329 | topic.References.Add(contextString); 330 | } 331 | } 332 | break; 333 | 334 | case HelpCommand.Topic: 335 | topic.Title = parameter; 336 | break; 337 | 338 | default: 339 | throw new NotImplementedException(); 340 | } 341 | } 342 | 343 | private static bool ParseCommand( 344 | string line, char controlCharacter, 345 | out HelpCommand command, out string parameters) 346 | { 347 | command = HelpCommand.None; 348 | parameters = ""; 349 | 350 | if (string.IsNullOrEmpty(line)) 351 | return false; 352 | else if (line[0] == '.') 353 | return ParseDotCommand(line, out command, out parameters); 354 | else if (line[0] == controlCharacter) 355 | return ParseColonCommand(line, controlCharacter, out command, out parameters); 356 | else 357 | return false; 358 | } 359 | 360 | private static bool ParseColonCommand( 361 | string line, char controlCharacter, 362 | out HelpCommand command, out string parameters) 363 | { 364 | command = HelpCommand.None; 365 | parameters = ""; 366 | 367 | if (string.IsNullOrEmpty(line) || line[0] != controlCharacter) 368 | return false; 369 | 370 | if (line.Length < 2) 371 | throw new ArgumentException("Colon command must not be blank."); 372 | 373 | if (!ColonCommandToHelpCommandMapping.TryGetValue(line[1], out command)) 374 | throw new ArgumentException("Colon command is not recognized."); 375 | 376 | parameters = line.Substring(2); 377 | return true; 378 | } 379 | 380 | private static bool ParseDotCommand( 381 | string line, out HelpCommand command, out string parameters) 382 | { 383 | command = HelpCommand.None; 384 | parameters = ""; 385 | 386 | if (string.IsNullOrEmpty(line) || line[0] != '.') 387 | return false; 388 | 389 | string dotCommand; 390 | int k = line.IndexOf(' '); 391 | if (k < 0) 392 | { 393 | dotCommand = line.Substring(1); 394 | parameters = ""; 395 | } 396 | else 397 | { 398 | dotCommand = line.Substring(1, k - 1); 399 | parameters = line.Substring(k + 1); 400 | } 401 | 402 | char colonCommand; 403 | if (DotCommandToColonCommandMapping.TryGetValue(dotCommand, out colonCommand)) 404 | { 405 | command = ColonCommandToHelpCommandMapping[colonCommand]; 406 | return true; 407 | } 408 | 409 | // Process source-only dot commands that do not have an equivalent 410 | // colon command. 411 | switch (dotCommand) 412 | { 413 | case "comment": 414 | case ".": 415 | command = HelpCommand.Comment; 416 | break; 417 | case "context": 418 | command = HelpCommand.Context; 419 | break; 420 | case "source": 421 | command = HelpCommand.Source; 422 | break; 423 | default: 424 | throw new ArgumentException("Dot command is not recognized."); 425 | } 426 | return true; 427 | } 428 | 429 | private static readonly Dictionary 430 | DotCommandToColonCommandMapping = new Dictionary 431 | { 432 | { "category", 'c' }, 433 | { "command", 'x' }, 434 | { "end", 'e' }, 435 | { "execute", 'y' }, 436 | { "freeze", 'z' }, 437 | { "length", 'l' }, 438 | { "list", 'i' }, 439 | { "mark", 'm' }, 440 | { "next", '>' }, 441 | { "paste", 'p' }, 442 | { "popup", 'g' }, 443 | { "previous", '<' }, 444 | { "raw", 'u' }, 445 | { "ref", 'r' }, 446 | { "topic", 'n' }, 447 | }; 448 | 449 | private static readonly Dictionary 450 | ColonCommandToHelpCommandMapping = new Dictionary 451 | { 452 | { '<', HelpCommand.Previous }, 453 | { '>', HelpCommand.Next }, 454 | { 'c', HelpCommand.Category }, 455 | { 'e', HelpCommand.End }, 456 | { 'g', HelpCommand.Popup }, 457 | { 'i', HelpCommand.List }, 458 | { 'l', HelpCommand.Length }, 459 | { 'm', HelpCommand.Mark }, 460 | { 'n', HelpCommand.Topic }, 461 | { 'p', HelpCommand.Paste }, 462 | { 'r', HelpCommand.Ref }, 463 | { 'u', HelpCommand.Raw }, 464 | { 'x', HelpCommand.Command }, 465 | { 'y', HelpCommand.Execute }, 466 | { 'z', HelpCommand.Freeze }, 467 | }; 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/HelpFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace QuickHelp.Serialization 4 | { 5 | class BinaryHelpFileHeader 6 | { 7 | public UInt16 Version; 8 | public HelpFileAttributes Attributes; 9 | public byte ControlCharacter; 10 | public byte Padding1; 11 | public UInt16 TopicCount; 12 | public UInt16 ContextCount; 13 | public byte DisplayWidth; 14 | public byte Padding2; 15 | public UInt16 Padding3; 16 | public string DatabaseName; 17 | public int Reserved1; 18 | public int TopicIndexOffset; 19 | public int ContextStringsOffset; 20 | public int ContextMapOffset; 21 | public int KeywordsOffset; 22 | public int HuffmanTreeOffset; 23 | public int TopicTextOffset; 24 | public int Reserved2; 25 | public int Reserved3; 26 | public int DatabaseSize; 27 | } 28 | 29 | [Flags] 30 | enum HelpFileAttributes : ushort 31 | { 32 | None = 0, 33 | 34 | /// 35 | /// Indicates that the context strings in the archive are 36 | /// case-sensitive. 37 | /// 38 | CaseSensitive = 1, 39 | 40 | /// 41 | /// Indicates that the help archive may not be decoded by the 42 | /// HELPMAKE utility. 43 | /// 44 | Locked = 2, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/SerializationOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using QuickHelp.Compression; 4 | 5 | namespace QuickHelp.Serialization 6 | { 7 | /// 8 | /// Contains options to control the serialization process. 9 | /// 10 | public class SerializationOptions 11 | { 12 | private readonly List m_keywords = new List(); 13 | 14 | /// 15 | /// Gets or sets the serialized format. 16 | /// 17 | public SerializationFormat Format { get; set; } 18 | 19 | public char ControlCharacter { get; set; } 20 | 21 | /// 22 | /// Gets or sets the compression level. 23 | /// 24 | /// 25 | /// On serialization, if Format is Automatic or Binary, this value 26 | /// controls the compression level in the serialized .HLP file. If 27 | /// Format is Markup, this value is ignored. 28 | /// 29 | /// On deserialization, this property is set to the actual compression 30 | /// level used in the input. 31 | /// 32 | public CompressionFlags Compression { get; set; } 33 | 34 | /// 35 | /// Gets or sets a list of keywords used for keyword compression. 36 | /// 37 | /// 38 | /// On serialization, if keyword compression is enabled, the 39 | /// serializer uses the dictionary specified by this property if it 40 | /// is not null, or computes the dictionary on the fly and 41 | /// updates this property if it is null. 42 | /// 43 | /// On deserialization, the serializer sets this property to the 44 | /// actual dictionary used in the input or null if the source 45 | /// does not use keyword compression. 46 | /// 47 | public byte[][] Keywords { get; set; } 48 | 49 | /// 50 | /// Gets or sets the Huffman tree used for Huffman compression. 51 | /// 52 | /// 53 | /// On serialization, if Huffman compression is enabled, the 54 | /// serializer uses the Huffman tree specified by this property if it 55 | /// is not null, or computes a Huffman tree on-the-fly and 56 | /// updates this property if it is null. 57 | /// 58 | /// On deserialization, the serializer sets this property to the 59 | /// actual Huffman tree used in the input, or null if the 60 | /// source does not use Huffman compression. 61 | /// 62 | public HuffmanTree HuffmanTree { get; set; } 63 | } 64 | 65 | /// 66 | /// Specifies the serialized format of a help database. 67 | /// 68 | public enum SerializationFormat 69 | { 70 | /// 71 | /// On deserialization, automatically detect the input format. On 72 | /// serialization, use Binary format. 73 | /// 74 | Automatic = 0, 75 | 76 | /// 77 | /// Specifies the binary format (with .HLP extension). 78 | /// 79 | Binary = 1, 80 | 81 | /// 82 | /// Specifies the markup format (with .SRC extension). 83 | /// 84 | Markup = 2, 85 | } 86 | 87 | [Flags] 88 | public enum CompressionFlags 89 | { 90 | None = 0, 91 | RunLength = 1, 92 | Keyword = 2, 93 | ExtendedKeyword = 4, 94 | Huffman = 8, 95 | All = RunLength | Keyword | ExtendedKeyword | Huffman 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /QuickHelp/Serialization/StreamView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace QuickHelp.Serialization 5 | { 6 | /// 7 | /// Represents a view on an underlying base stream with different position 8 | /// and length. 9 | /// 10 | class StreamView : Stream 11 | { 12 | private readonly Stream m_baseStream; 13 | private readonly long m_length; 14 | private long m_position; 15 | 16 | public StreamView(Stream baseStream, long length) 17 | : this(baseStream, length, 0) 18 | { 19 | } 20 | 21 | /// 22 | /// Creates a view on a stream. 23 | /// 24 | public StreamView(Stream baseStream, long length, long position) 25 | { 26 | if (baseStream == null) 27 | throw new ArgumentNullException(nameof(baseStream)); 28 | if (length < 0) 29 | throw new ArgumentOutOfRangeException(nameof(length)); 30 | if (!(position >= 0 && position <= length)) 31 | throw new ArgumentOutOfRangeException(nameof(position)); 32 | 33 | m_baseStream = baseStream; 34 | m_length = length; 35 | m_position = position; 36 | } 37 | 38 | public override long Length 39 | { 40 | get { return m_length; } 41 | } 42 | 43 | public override int Read(byte[] buffer, int offset, int count) 44 | { 45 | if (buffer == null) 46 | throw new ArgumentNullException(nameof(buffer)); 47 | if (!(offset >= 0 && offset <= buffer.Length)) 48 | throw new ArgumentOutOfRangeException(nameof(offset)); 49 | if (!(count >= 0 && offset + count <= buffer.Length)) 50 | throw new ArgumentOutOfRangeException(nameof(count)); 51 | 52 | if (count > m_length - m_position) 53 | count = (int)(m_length - m_position); 54 | if (count == 0) 55 | return 0; 56 | 57 | // TODO: read full or throw exception 58 | int actual = m_baseStream.Read(buffer, offset, count); 59 | m_position += actual; 60 | return actual; 61 | } 62 | 63 | public override bool CanRead 64 | { 65 | get { return m_baseStream.CanRead; } 66 | } 67 | 68 | public override bool CanWrite 69 | { 70 | get { return false; } 71 | } 72 | 73 | public override bool CanSeek 74 | { 75 | get { return false; } 76 | } 77 | 78 | public override void Flush() 79 | { 80 | throw new NotSupportedException(); 81 | } 82 | 83 | public override long Position 84 | { 85 | get { return m_position; } 86 | set { throw new NotSupportedException(); } 87 | } 88 | 89 | public override long Seek(long offset, SeekOrigin origin) 90 | { 91 | throw new NotSupportedException(); 92 | } 93 | 94 | public override void Write(byte[] buffer, int offset, int count) 95 | { 96 | throw new NotImplementedException(); 97 | } 98 | 99 | public override void SetLength(long value) 100 | { 101 | throw new NotSupportedException(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This project provides a .Net library and several utility programs to view MS-DOS (.HLP) help files, 2 | making them accessible in a modern environment. 3 | 4 | **Highlights** 5 | 6 | [QuickHelp Format Description](https://raw.githubusercontent.com/fancidev/DosHelp/master/QuickHelp/Serialization/Format.txt) 7 | describes the format of a QuickHelp .HLP file. 8 | 9 | [QuickHelp Library](https://github.com/fancidev/DosHelp/tree/master/QuickHelp) is a .NET 2.0 library 10 | that enables you to read QuickHelp files. 11 | 12 | [HelpBrowser](https://github.com/fancidev/DosHelp/tree/master/HelpBrowser) is a program (requires 13 | .NET 2.0) that allows the user to browse the contents in a QuickHelp file. 14 | 15 | [HelpConvert](https://github.com/fancidev/DosHelp/tree/master/HelpConvert) is a command line utility 16 | (requires .NET 2.0) that converts a QuickHelp file to a set of cross-referenced HTML pages suitable 17 | for viewing in a browser. 18 | 19 | **Reference** 20 | 21 | *Creating Help Files With HELPMAKE* (Chapter 18 of MASM Documentation, Environment and Tools) 22 | 23 | *Microsoft Professional Advisor, Library Reference* --------------------------------------------------------------------------------