3 using System.Collections;
5 using System.Text.RegularExpressions;
7 namespace HtmlHelp.ChmDecoding
10 /// The class <c>HHKParser</c> implements a parser for HHK contents files.
12 internal sealed class HHKParser
15 /// regular expressions for replacing the sitemap boundary tags
17 private static string RE_ULOpening = @"\<ul\>"; // will be replaced by a '(' for nested parsing
18 private static string RE_ULClosing = @"\</ul\>"; // will be replaced by a ')' for nested parsing
23 private static string RE_ULBoundaries = @"\<ul\>(?<innerText>.*)\</ul\>";
25 /// Matching the nested tree structure.
27 private static string RE_NestedBoundaries = @"\( (?> [^()]+ | \( (?<DEPTH>) | \) (?<-DEPTH>) )* (?(DEPTH)(?!)) \)";
29 /// Matching object-tags
31 private static string RE_ObjectBoundaries = @"\<object(?<innerText>.*?)\</object\>";
33 /// Matching param tags
35 private static string RE_ParamBoundaries = @"\<param(?<innerText>.*?)\>";
37 /// Extracting tag attributes
39 private const string RE_QuoteAttributes = @"( |\t)*(?<attributeName>[\-a-zA-Z0-9]*)( |\t)*=( |\t)*(?<attributeTD>[\""\'])?(?<attributeValue>.*?(?(attributeTD)\k<attributeTD>|([\s>]|.$)))";
42 /// private regular expressionobjects
44 private static Regex ulRE;
45 private static Regex NestedRE;
46 private static Regex ObjectRE;
47 private static Regex ParamRE;
48 private static Regex AttributesRE;
51 /// Parses a HHK file and returns an ArrayList with the index tree
53 /// <param name="hhkFile">string content of the hhk file</param>
54 /// <param name="chmFile">CHMFile instance</param>
55 /// <returns>Returns an ArrayList with the index tree</returns>
56 public static ArrayList ParseHHK(string hhkFile, CHMFile chmFile)
58 ArrayList indexList = new ArrayList();
60 ulRE = new Regex(RE_ULBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
61 NestedRE = new Regex(RE_NestedBoundaries, RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
62 ObjectRE = new Regex(RE_ObjectBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
63 ParamRE = new Regex(RE_ParamBoundaries, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
64 AttributesRE = new Regex(RE_QuoteAttributes, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
66 int innerTextIdx = ulRE.GroupNumberFromName("innerText");
68 if( ulRE.IsMatch(hhkFile, 0) )
70 Match m = ulRE.Match(hhkFile, 0);
72 if( ObjectRE.IsMatch(hhkFile, 0) ) // first object block contains information types and categories
74 Match mO = ObjectRE.Match(hhkFile, 0);
75 int iOTxt = ObjectRE.GroupNumberFromName("innerText");
77 string globalText = mO.Groups[iOTxt].Value;
79 ParseGlobalSettings( globalText, chmFile );
82 string innerText = m.Groups["innerText"].Value;
84 innerText = innerText.Replace("(", "(");
85 innerText = innerText.Replace(")", ")");
86 innerText = Regex.Replace(innerText, RE_ULOpening, "(", RegexOptions.IgnoreCase);
87 innerText = Regex.Replace(innerText, RE_ULClosing, ")", RegexOptions.IgnoreCase);
89 ParseTree( innerText, null, indexList, chmFile );
96 /// Recursively parses a sitemap tree
98 /// <param name="text">content text</param>
99 /// <param name="parent">Parent for all read items</param>
100 /// <param name="arrNodes">arraylist which receives the extracted nodes</param>
101 /// <param name="chmFile">CHMFile instance</param>
102 private static void ParseTree( string text, IndexItem parent, ArrayList arrNodes, CHMFile chmFile )
104 string strPreItems="", strPostItems="";
105 string innerText = "";
109 while( NestedRE.IsMatch(text, nIndex) )
111 Match m = NestedRE.Match(text, nIndex);
113 innerText = m.Value.Substring( 1, m.Length-2);
115 strPreItems = text.Substring(nIndex,m.Index-nIndex);
117 ParseItems(strPreItems, parent, arrNodes, chmFile);
119 if((arrNodes.Count>0) && (innerText.Length > 0) )
121 IndexItem p = ((IndexItem)(arrNodes[arrNodes.Count-1]));
122 ParseTree( innerText, p, arrNodes, chmFile );
125 nIndex = m.Index+m.Length;
130 strPostItems = text.Substring(nIndex, text.Length-nIndex);
131 ParseItems(strPostItems, parent, arrNodes, chmFile);
133 else if( nIndex < text.Length-1)
135 strPostItems = text.Substring(nIndex, text.Length-nIndex);
136 ParseTree(strPostItems, parent, arrNodes, chmFile);
142 /// Parses nodes from the text
144 /// <param name="itemstext">text containing the items</param>
145 /// <param name="parentItem">parent index item</param>
146 /// <param name="arrNodes">arraylist where the nodes should be added</param>
147 /// <param name="chmFile">CHMFile instance</param>
148 private static void ParseItems( string itemstext, IndexItem parentItem, ArrayList arrNodes, CHMFile chmFile)
150 int innerTextIdx = ObjectRE.GroupNumberFromName("innerText");
151 int innerPTextIdx = ParamRE.GroupNumberFromName("innerText");
153 // get group-name indexes
154 int nameIndex = AttributesRE.GroupNumberFromName("attributeName");
155 int valueIndex = AttributesRE.GroupNumberFromName("attributeValue");
156 int tdIndex = AttributesRE.GroupNumberFromName("attributeTD");
158 int nObjStartIndex = 0;
159 int nLastObjStartIndex = 0;
160 string sKeyword = "";
162 while( ObjectRE.IsMatch(itemstext, nObjStartIndex) )
164 Match m = ObjectRE.Match(itemstext, nObjStartIndex);
166 string innerText = m.Groups[innerTextIdx].Value;
168 IndexItem idxItem = new IndexItem();
174 string paramTitle = "";
175 string paramLocal = "";
178 while( ParamRE.IsMatch(innerText, nParamIndex) )
180 Match mP = ParamRE.Match(innerText, nParamIndex);
182 string innerP = mP.Groups[innerPTextIdx].Value;
184 string paramName = "";
185 string paramValue = "";
190 while( AttributesRE.IsMatch( innerP, nAttrIdx ) )
192 Match mA = AttributesRE.Match(innerP, nAttrIdx);
194 string attributeName = mA.Groups[nameIndex].Value;
195 string attributeValue = mA.Groups[valueIndex].Value;
196 string attributeTD = mA.Groups[tdIndex].Value;
198 if(attributeTD.Length > 0)
200 // delete the trailing textqualifier
201 if( attributeValue.Length > 0)
203 int ltqi = attributeValue.LastIndexOf( attributeTD );
207 attributeValue = attributeValue.Substring(0,ltqi);
212 if( attributeName.ToLower() == "name")
214 paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values
218 if( attributeName.ToLower() == "value")
220 paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values
222 while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/'))
223 paramValue = paramValue.Substring(0,paramValue.Length-1);
226 nAttrIdx = mA.Index+mA.Length;
229 if( nNameCnt == 1) // first "Name" param = keyword
233 if(parentItem != null)
234 sKeyword = parentItem.KeyWordPath + ",";
236 string sOldKW = sKeyword;
238 sKeyword += paramValue;
240 IndexItem idxFind = FindByKeyword(arrNodes, sKeyword);
248 if( sKeyword.Split(new char[] {','}).Length > 1 )
250 idxItem.CharIndex = sKeyword.Length - paramValue.Length;
254 sKeyword = paramValue;
256 idxItem.CharIndex = 0;
259 idxItem.KeyWordPath = sKeyword;
260 idxItem.Indent = sKeyword.Split(new char[] {','}).Length - 1;
261 idxItem.IsSeeAlso = false;
269 if( (nNameCnt > 2) && (paramName.ToLower()=="name") )
272 IndexTopic idxTopic = new IndexTopic(paramTitle, paramLocal, chmFile.CompileFile, chmFile.ChmFilePath);
274 idxItem.Topics.Add( idxTopic );
280 switch(paramName.ToLower())
285 paramTitle = paramValue;
289 paramLocal = paramValue.Replace("../", "").Replace("./", "");
291 case "type": // information type assignment for item
293 idxItem.InfoTypeStrings.Add( paramValue );
297 idxItem.AddSeeAlso(paramValue);
298 idxItem.IsSeeAlso = true;
304 nParamIndex = mP.Index+mP.Length;
310 IndexTopic idxTopic = new IndexTopic(paramTitle, paramLocal, chmFile.CompileFile, chmFile.ChmFilePath);
312 idxItem.Topics.Add( idxTopic );
318 idxItem.ChmFile = chmFile;
319 arrNodes.Add( idxItem );
321 nLastObjStartIndex = nObjStartIndex;
322 nObjStartIndex = m.Index+m.Length;
327 /// Searches an index-keyword in the index list
329 /// <param name="indexList">index list to search</param>
330 /// <param name="Keyword">keyword to find</param>
331 /// <returns>Returns an <see cref="IndexItem">IndexItem</see> instance if found, otherwise null.</returns>
332 private static IndexItem FindByKeyword(ArrayList indexList, string Keyword)
334 foreach(IndexItem curItem in indexList)
336 if( curItem.KeyWordPath == Keyword)
344 /// Parses the very first <OBJECT> tag in the sitemap file and extracts
345 /// information types and categories.
347 /// <param name="sText">text of the object tag</param>
348 /// <param name="chmFile">CHMFile instance</param>
349 private static void ParseGlobalSettings(string sText, CHMFile chmFile)
351 int innerPTextIdx = ParamRE.GroupNumberFromName("innerText");
353 // get group-name indexes
354 int nameIndex = AttributesRE.GroupNumberFromName("attributeName");
355 int valueIndex = AttributesRE.GroupNumberFromName("attributeValue");
356 int tdIndex = AttributesRE.GroupNumberFromName("attributeTD");
362 // 1... inclusinve info type name
363 // 2... exclusive info type name
364 // 3... hidden info type name
365 // 4... category name
366 // 5... incl infotype name for category
367 // 6... excl infotype name for category
368 // 7... hidden infotype name for category
372 string sDescription = "";
373 string curCategory = "";
375 while( ParamRE.IsMatch(sText, nParamIndex) )
377 Match mP = ParamRE.Match(sText, nParamIndex);
379 string innerP = mP.Groups[innerPTextIdx].Value;
381 string paramName = "";
382 string paramValue = "";
386 while( AttributesRE.IsMatch( innerP, nAttrIdx ) )
388 Match mA = AttributesRE.Match(innerP, nAttrIdx);
390 string attributeName = mA.Groups[nameIndex].Value;
391 string attributeValue = mA.Groups[valueIndex].Value;
392 string attributeTD = mA.Groups[tdIndex].Value;
394 if(attributeTD.Length > 0)
396 // delete the trailing textqualifier
397 if( attributeValue.Length > 0)
399 int ltqi = attributeValue.LastIndexOf( attributeTD );
403 attributeValue = attributeValue.Substring(0,ltqi);
408 if( attributeName.ToLower() == "name")
410 paramName = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values
413 if( attributeName.ToLower() == "value")
415 paramValue = HttpUtility.HtmlDecode(attributeValue); // for unicode encoded values
417 while((paramValue.Length>0)&&(paramValue[paramValue.Length-1] == '/'))
418 paramValue = paramValue.Substring(0,paramValue.Length-1);
422 nAttrIdx = mA.Index+mA.Length;
425 switch(paramName.ToLower())
427 case "savetype": // inclusive information type name
432 case "savetypedesc": // description of information type
434 InformationTypeMode mode = InformationTypeMode.Inclusive;
435 sDescription = paramValue;
438 mode = InformationTypeMode.Inclusive;
440 mode = InformationTypeMode.Exclusive;
442 mode = InformationTypeMode.Hidden;
444 if( chmFile.GetInformationType( sName ) == null)
446 // check if the HtmlHelpSystem already holds such an information type
447 if( chmFile.SystemInstance.GetInformationType( sName ) == null)
449 // info type not found yet
451 InformationType newType = new InformationType(sName, sDescription, mode);
452 chmFile.InformationTypes.Add(newType);
456 InformationType sysType = chmFile.SystemInstance.GetInformationType( sName );
457 chmFile.InformationTypes.Add( sysType );
463 case "saveexclusive": // exclusive information type name
468 case "savehidden": // hidden information type name
473 case "category": // category name
479 case "categorydesc": // category description
481 sDescription = paramValue;
483 if( chmFile.GetCategory( sName ) == null)
485 // check if the HtmlHelpSystem already holds such a category
486 if( chmFile.SystemInstance.GetCategory( sName ) == null)
489 Category newCat = new Category(sName, sDescription);
490 chmFile.Categories.Add(newCat);
494 Category sysCat = chmFile.SystemInstance.GetCategory( sName );
495 chmFile.Categories.Add( sysCat );
501 case "type": // inclusive information type which is member of the previously read category
506 case "typedesc": // description of type for category
508 sDescription = paramValue;
509 Category cat = chmFile.GetCategory( curCategory );
514 InformationType infoType = chmFile.GetInformationType( sName );
516 if( infoType != null)
518 if( !cat.ContainsInformationType(infoType))
520 infoType.SetCategoryFlag(true);
521 cat.AddInformationType(infoType);
528 case "typeexclusive": // exclusive information type which is member of the previously read category
533 case "typehidden": // hidden information type which is member of the previously read category
546 nParamIndex = mP.Index+mP.Length;