X11workbench Toolkit  1.0
context_help.c
1 // //
3 // _ _ _ _ //
4 // ___ ___ _ __ | |_ ___ __ __| |_ | |__ ___ | | _ __ ___ //
5 // / __|/ _ \ | '_ \ | __|/ _ \\ \/ /| __| | '_ \ / _ \| || '_ \ / __| //
6 // | (__| (_) || | | || |_| __/ > < | |_ | | | || __/| || |_) |_| (__ //
7 // \___|\___/ |_| |_| \__|\___|/_/\_\ \__|_____|_| |_| \___||_|| .__/(_)\___| //
8 // |_____| |_| //
9 // //
11 
12 
13 /*****************************************************************************
14 
15  X11workbench - X11 programmer's 'work bench' application and toolkit
16  Copyright (c) 2010-2019 by Bob Frazier (aka 'Big Bad Bombastic Bob')
17  all rights reserved
18 
19  DISCLAIMER: The X11workbench application and toolkit software are supplied
20  'as-is', with no warranties, either implied or explicit.
21 
22  BSD-like license:
23 
24  There is no restriction as to what you can do with this software, so long
25  as you include the above copyright notice and DISCLAIMER for any distributed
26  work that is linked with, equivalent to, or derived from any portion of this
27  software, along with this paragraph that explains the terms of the license if
28  the source is also being made available. "Linked with" includes the use of a
29  portion of any of the source and/or header files, or their compiled binary
30  output, as a part of your application or library. A "derived work"
31  describes a work that uses a significant portion of the source files or the
32  algorithms that are included with this software.
33 
34  EXCLUSIONS
35 
36  Specifically excluded from this requirement are files that were generated by
37  the software, or anything that is included with the software that is part of
38  another package (such as files that were created or added during the
39  'configure' process).
40 
41  DISTRIBUTION
42 
43  The license also covers the use of part or all of any of the X11 workbench
44  toolkit source or header files in your distributed application, in source or
45  binary form. If you do not ship the source, the above copyright statement
46  and DISCLAIMER is still required to be placed in a reasonably prominent
47  place, such as documentation, splash screens, and/or 'about the application'
48  dialog boxes.
49 
50  Use and distribution are in accordance with GPL, LGPL, and/or the above
51  BSD-like license. See COPYING and README.md files for more information.
52 
53  Additionally, this software, in source or binary form, and in whole or in
54  part, may be used by explicit permission from the author, without the need
55  of a license.
56 
57  Additional information at http://sourceforge.net/projects/X11workbench
58  and http://bombasticbob.github.io/X11workbench/
59 
60 ******************************************************************************/
61 
62 #include <stdio.h>
63 #include <stdlib.h>
64 #include <stdarg.h>
65 #include <unistd.h>
66 #include <memory.h>
67 #include <string.h>
68 #include <strings.h>
69 #include <signal.h>
70 #include <time.h>
71 #include <errno.h>
72 #include <fcntl.h>
73 #include <sys/stat.h>
74 
75 // project includes
76 #include "X11workbench.h"
77 #include "dialog_window.h"
78 #include "platform_helper.h"
79 #include "debug_helper.h"
80 #include "file_help.h"
81 #include "conf_help.h"
82 
83 #include "context_help.h"
84 
85 
86 static char * InternalMan2Html(const char *szTerm, const char *szText);
87 
88 
90 // ____ _ _ _ _ _ //
91 // / ___|___ _ __ | |_ _____ _| |_ | | | | ___| |_ __ //
92 // | | / _ \| '_ \| __/ _ \ \/ / __| | |_| |/ _ \ | '_ \ //
93 // | |__| (_) | | | | || __/> <| |_ | _ | __/ | |_) | //
94 // \____\___/|_| |_|\__\___/_/\_\\__| |_| |_|\___|_| .__/ //
95 // |_| //
97 
98 // CONTEXT SENSITIVE HELP NOTES using DOXYGEN GENERATED DOCS
99 
100 // doxygen generates doc/html/group__*.html files that will contain the desired documentation
101 //
102 // Each documented item has a tag similar to the following:
103 //
104 // <!-- doxytag: member="window_helper.h::WBSetInputFocus" ref="ga28d82cd699b08cf93278ae26c5ad4788" args="(Window wID)" -->
105 //
106 // jumping to the correct HTML link with the anchor set to the 'ref' value will open it up in doxygen, as
107 //
108 // file:///usr/local/share/X11workbench/doc/html/group__wcore.html#ga28d82cd699b08cf93278ae26c5ad4788
109 //
110 
111 
112 void DoContextSensitiveHelp(const char *szTerm)
113 {
114 char szDocFilePath[PATH_MAX * 2], szName[PATH_MAX];
115 char *p1, *p2, *p3;//, *p5;
116 const char *p4;
117 void *pSettings, *pDirList;
118 int i1;
119 unsigned long dwAttr;
120 FILE *pTemp;
121 WB_FILE_HANDLE hProcess;
122 static char szLineBuf[4096], szDoxyTag[PATH_MAX * 2 + 512], szHelpBrowser[PATH_MAX];
123 
124 
125  // step 1: get 'docs' directory from settings, and if there isn't a setting for it,
126  // use the current directory + "docs/html"
127 
128  bzero(szDocFilePath, sizeof(szDocFilePath));
129 
130 
131  pSettings = CHOpenConfFile(APP_NAME, CH_FLAGS_DEFAULT);
132 
133  if(pSettings)
134  {
135  if(0 >= CHGetConfFileString(pSettings, "paths", "browser", szHelpBrowser, sizeof(szHelpBrowser)))
136  {
137  WB_ERROR_PRINT("ERROR did not find 'paths' 'browser', using default\n");
138  goto find_url_opener;
139  }
140 // else
141 // {
142 // WB_ERROR_PRINT("TEMPORARY: %s - browser \"%s\"\n", __FUNCTION__, szHelpBrowser);
143 // }
144 
145  i1 = CHGetConfFileString(pSettings, "paths","documentation",szDocFilePath, sizeof(szDocFilePath) - 2);
146 
147  CHCloseConfFile(pSettings);
148  pSettings = NULL;
149  }
150  else
151  {
152 find_url_opener:
153 
154  i1 = 0;
155 
156  p1 = CHGetMimeDefaultApp("text/html");
157  p2 = p3 = NULL;
158 
159  if(!p1 || *p1 <= ' ')
160  {
161  if(p1)
162  {
163  WBFree(p1);
164  }
165 
166  p1 = CHGetMimeDefaultApp("x-scheme-handler/http"); // Mate uses this one
167  }
168 
169  if(p1 && *p1 > ' ')
170  {
171  if(strlen(p1) > 8 /* strlen(".desktop") */ &&
172  !memcmp(p1 + strlen(p1) - 8, ".desktop", 8)) // it's a '.desktop' file
173  {
174  p2 = p1;
175  p1 = CHGetDesktopFileInfo(p2, "Exec");
176  // NOTE: the 'Exec' string will have a '%f' or '%u' or similar in it. trim that.
177 
178  if(!p1)
179  {
180  p1 = p2; // restore original, hope it still works
181  }
182  else
183  {
184  WBFree(p2);
185  p2 = p1 + strlen(p1);
186 
187  while(p2 > p1 && *(p2 - 1) <= ' ')
188  {
189  *(--p2) = 0; // right-trim
190  }
191 
192  if(p2 >= p1 + 2 && *(p2 - 2) == '%' &&
193  (*(p2 - 1) == 'u' || *(p2 - 1) == 'U' || *(p2 - 1) == 'f' || *(p2 - 1) == 'F'))
194  {
195  p2 -= 2;
196  *p2 = 0; // trim off argument
197 
198  while(p2 > p1 && *(p2 - 1) <= ' ')
199  {
200  *(--p2) = 0; // right-trim
201  }
202  }
203 
204  // NOTE: for _NOW_ assume that there are no extra parameters [later I'll fix that...]
205  }
206  }
207 
208  strcpy(szHelpBrowser, p1);
209  WBFree(p1);
210 
211  p1 = NULL;
212  p2 = NULL;
213  }
214  else
215  {
216  if(p1)
217  {
218  WB_ERROR_PRINT("%s - \"%s\" - No default browser available!\n", __FUNCTION__, p1);
219 
220  WBFree(p1);
221  }
222  else
223  {
224  WB_ERROR_PRINT("%s - No default browser available!\n", __FUNCTION__);
225  }
226 
228  "Context Help", "No default browser available for displaying help");
229  return;
230  }
231  }
232 
233  if(i1 <= 0)
234  {
235  p1 = WBGetCanonicalPath("doc/html/");
236  if(p1)
237  {
238  strncpy(szDocFilePath, p1, sizeof(szDocFilePath) - 2);
239  WBFree(p1);
240  }
241  else
242  {
243  strcpy(szDocFilePath, "doc/html/");
244  }
245  }
246 
247  if(szDocFilePath[strlen(szDocFilePath) - 1] != '/')
248  {
249  strcat(szDocFilePath, "/");
250  }
251 
252 // WB_ERROR_PRINT("TEMPORARY: %s - szDocFilePath = \"%s\"\n", __FUNCTION__, szDocFilePath);
253 // WB_ERROR_PRINT(" %s - szTerm = \"%s\"\n", __FUNCTION__, szTerm);
254 
255  p1 = szDocFilePath + strlen(szDocFilePath);
256 
257  // now that THAT mess is over with, determine which file has the appropriate term in it,
258  // and invoke the default web browser to open that link. I'll do this using 'xdg-open'.
259 
260  strcpy(p1, "group__*.html");
261 
262  pDirList = WBAllocDirectoryList(szDocFilePath);
263 
264  *p1 = 0; // 'szDocFilePath' is once again, ONLY a path
265  szDoxyTag[0] = 0; // it will be a flag
266 
267  if(!pDirList)
268  {
269  WB_ERROR_PRINT("ERROR: %s no dir list \"%s\"\n", __FUNCTION__, szDocFilePath);
270  return;
271  }
272  else
273  {
274  while(!WBNextDirectoryEntry(pDirList, szName, sizeof(szName), &dwAttr))
275  {
276  if(S_ISREG(dwAttr))
277  {
278  strcpy(p1, szName);
279 
280 // WB_ERROR_PRINT("TEMPORARY: opening \"%s\"\n", szDocFilePath);
281 
282  pTemp = fopen(szDocFilePath, "r");
283  if(!pTemp)
284  {
285  WB_ERROR_PRINT("ERROR: %s - unable to open \"%s\"\n", __FUNCTION__, szDocFilePath);
286  }
287  else
288  {
289  *p1 = 0; // file name not needed now, but path name IS needed
290 
291  while(!feof(pTemp))
292  {
293  if(!fgets(szLineBuf, sizeof(szLineBuf), pTemp))
294  {
295  break;
296  }
297 
298  // THIS METHOD APPLIES TO Doxygen version 1.8.3.1 and later
299  // (it has also been tested with Doxygen 1.8.13.2)
300  // If you have an earlier version of doxygen, consider UPGRADING PLEASE!
301  // If you cannot upgrade Doxygen, and this API does not work with your
302  // generated documentation, consider downloading the pre-built documentation.
303 
304  if(NULL != (p2 = strstr(szLineBuf, "class=\"el\"")))
305  {
306  while(p2 > szLineBuf && *p2 != '<') // search for start of tag
307  {
308  p2--;
309  }
310 
311  if(p2 >= szLineBuf && *p2 == '<')
312  {
313  // parse the tag, find 'href'
314 
315  p4 = CHFindEndOfXMLTag(p2, -1); // XML parse helper
316 
317  if(*p4 == '>')
318  {
319  // the search term will be the 'anchor' term.
320 
321  if(!memcmp(p4 + 1, szTerm, strlen(szTerm)) &&
322  !memcmp(p4 + 1 + strlen(szTerm), "</a>", 4)) // ending tag
323  {
324  p3 = CHParseXMLTagContents(p2, p4 - p2);
325  if(p3)
326  {
327  // now that I have the tag, grab the 'href' member
328  for(p2=p3; *p2; p2 += strlen(p2) + 1)
329  {
330  if(!strncmp(p2, "href=", 5))
331  {
332  strcpy(szDoxyTag, "file://");
333  strcat(szDoxyTag, szDocFilePath); // the path only at this point
334  strcat(szDoxyTag, p2 + 5); // the 'href' text
335  break;
336  }
337  }
338 
339  WBFree(p3);
340  if(szDoxyTag[0])
341  {
342  break;
343  }
344  }
345  }
346  }
347  }
348  }
349 
350 #if 0 /* this is the OLD way - new method uses '<a class="el" href="">' tags */
351  if(strstr(szLineBuf, "doxytag"))
352  {
353  p2 = strstr(szLineBuf, szTerm);
354 
355  if(p2 && p2 > szLineBuf + 10 && *(p2 - 1) == ':' && *(p2 - 2) == ':' &&
356  (p2[strlen(szTerm)] == '"' || p2[strlen(szTerm)] == '\'')) // must be followed by a quote mark
357  {
358  // grab the entire XML tag
359  while(p2 > szLineBuf && (*p2 != '<' || p2[1] != '!' || p2[2] != '-' || p2[3] != '-'))
360  {
361  p2--;
362  }
363 
364  if(*p2 == '<' && p2[1] == '!' && p2[2] == '-' && p2[3] == '-') // doxytag comment block
365  {
366  p4 = CHFindEndOfXMLTag(p2 + 4, -1); // point past the '<!--' first, then find the end
367 
368  if(*p4 == '>')
369  {
370  p3 = CHParseXMLTagContents(p2 + 4, p4 - (p2 + 4));
371  if(p3)
372  {
373  // now that I have the tag, grab the 'ref' member
374  for(p2=p3; *p2; p2 += strlen(p2) + 1)
375  {
376  if(!strncmp(p2, "ref=", 4))
377  {
378  strcpy(szDoxyTag, "file://");
379  strcat(szDoxyTag, szDocFilePath);
380  strcat(szDoxyTag, "#");
381  strcat(szDoxyTag, p2 + 4);
382  break;
383  }
384  }
385 
386  WBFree(p3);
387  if(szDoxyTag[0])
388  {
389  break;
390  }
391 // TODO: with this much nesting, consider writing utility functions
392 // to do some of it and make the code more readable
393  }
394  }
395  }
396  }
397  }
398 #endif // 0
399  }
400 
401  fclose(pTemp);
402  pTemp = NULL; // by convention
403  }
404  }
405  }
406  }
407 
408  if(szDoxyTag[0]) // found the right file
409  {
410 // WB_ERROR_PRINT("TEMPORARY: %s spawn %s\n with \"%s\"\n\n",
411 // __FUNCTION__, szHelpBrowser, szDoxyTag);
412  hProcess = WBRunAsync(szHelpBrowser, szDoxyTag, NULL);
413 
414  // TODO: does '--new-instance' work properly?
415 
416  if(hProcess != WB_INVALID_FILE_HANDLE)
417  {
418  // TODO: display 'wait' cursor and wait for app window to appear ?
419 
420  return;
421  }
422 
423  goto fail_to_run_help;
424  }
425 
426  // TODO: other documentation
427 
428  // search for related language runtime library MAN pages
429  // command line: man -S 2:3 keyword - pipe output via man2html then open temp html and delete it
430 
431  // TODO: write 'man to HTML' mini-web-server?
432  // TODO: use embedded web client if webkit is available?
433 
434  p2 = WBRunResult("man", "-S", "2:3", szTerm, NULL);
435  if(!p2 || !*p2)
436  {
437  if(p2)
438  {
439  WBFree(p2);
440  p2 = NULL;
441  }
442 
443 fail_to_run_man2html:
444 // WB_ERROR_PRINT("TEMPORARY: fail to run man2html \"%s\"\n", p1);
445 
446  goto fail_to_run_help;
447  }
448 
449 // WB_ERROR_PRINT("TEMPORARY: man output \"%s\"\n", p2);
450 
451  // instead of calling an external program to convert, I wrote something
452  // that does the conversion as reliably as possible. external scripts
453  // sometimes 'pooch up' the HTML version of the man page, inject garbage,
454  // lose large chunks of text, or have issues with hyphenated words.
455  // it's possible MY code may exhibit similar problems, but I think I've
456  // nailed the issues without too much trouble. And, if I ever set up a
457  // web server for the help system, I'll fix linkage as well. That would be
458  // EVEN MORE interesting, actually... (and it's less troublesome than trying
459  // to get groff to work with man consistently across multiple platforms)
460 
461  p3 = InternalMan2Html(szTerm, p2);
462 
463  WBFree(p2);
464  p2 = NULL;
465 
466  if(!p3)
467  {
468  WB_ERROR_PRINT("Failed to run InternalMan2Html in %s\n", __FUNCTION__);
469  goto fail_to_run_man2html;
470  }
471 
472  p2 = WBTempFile(".html"); // temporary HTML file
473 
474  // write 'p3' to a temp html file now
475  if(!p2) // new temp file name
476  {
477  WBFree(p3);
478  p3 = NULL;
479 
480 // WB_ERROR_PRINT("TEMPORARY: here I am (2)\n");
481  goto fail_to_run_man2html;
482  }
483 
484  if(FBWriteFileFromBuffer(p2, p3, strlen(p3)) < 0)
485  {
486 // WB_ERROR_PRINT("TEMPORARY: here I am (3) p2=\"%s\"\n", p2);
487 
488  WBFree(p2);
489  WBFree(p3);
490  p2 = p3 = NULL;
491 
492  goto fail_to_run_man2html;
493  }
494 
495  // p2 contains the ".html" temp file name
496 
497  hProcess = WBRunAsync(szHelpBrowser, p2, NULL);
498 
499  // TODO: does '--new-instance' work properly?
500 
501 
502  WBFree(p2);
503  WBFree(p3);
504  p1 = p2 = p3 = NULL;
505 
506  if(hProcess != WB_INVALID_FILE_HANDLE)
507  {
508  // TODO: display 'wait' cursor and wait for app window to appear ?
509 
510  return;
511  }
512 
513 
514 
515 fail_to_run_help:
516 
517  p1 = WBCopyString("No help available for \"");
518  WBCatString(&p1, szTerm);
519  WBCatString(&p1, "\"");
520 
521  if(p1)
522  {
524  "Context Help", p1);
525 
526  WBFree(p1);
527  }
528 }
529 
530 static char * CreateNBSPString(const char *szRef, int nLen)
531 {
532 static const char szNBSP[]="&nbsp";
533 int i1, i2;
534 char *pRval;
535 
536 #define NBSP_TAB_WIDTH 8
537 
538  if(nLen < 0)
539  {
540  return NULL;
541  }
542 
543  pRval = WBAlloc(nLen * 8 * (sizeof(szNBSP) - 1) + 2);
544 
545  if(pRval)
546  {
547  char *p2 = pRval;
548  const char *p1 = szRef;
549 
550  for(i1=0, i2=0; i1 < nLen; p1++, i1++, i2 = (i2 + 1) % NBSP_TAB_WIDTH)
551  {
552  if(*p1 == '\t')
553  {
554  for(; i2 < NBSP_TAB_WIDTH; i2++)
555  {
556  memcpy(p2, szNBSP, sizeof(szNBSP) - 1);
557  p2 += sizeof(szNBSP) - 1;
558  }
559  i2 = NBSP_TAB_WIDTH - 1; // tab position
560  }
561  else
562  {
563  memcpy(p2, szNBSP, sizeof(szNBSP) - 1);
564  p2 += sizeof(szNBSP) - 1;
565  }
566  }
567 
568  *p2 = 0;
569  }
570 
571  return pRval;
572 }
573 
574 
575 
577 // __ __ _ _ _ _ _ _ _____ __ __ _ //
578 // | \/ | / \ | \ | | | |_ ___ | | | |_ _| \/ | | //
579 // | |\/| | / _ \ | \| | | __/ _ \ | |_| | | | | |\/| | | //
580 // | | | |/ ___ \| |\ | | || (_) | | _ | | | | | | | |___ //
581 // |_| |_/_/ \_\_| \_| \__\___/ |_| |_| |_| |_| |_|_____| //
582 // //
584 
585 static char * InternalMan2Html(const char *szTerm, const char *szText)
586 {
587 char *pRval, *p1, *p2, *pTemp, *pHyphenBuf = NULL;
588 const char *p3, *p4, *p5;
589 int i1, i2, cbLineLen, nTabTwist, bNoHyphen = 0;
590 static const char szNBSP[]="&nbsp;";
591 
592 
593  pRval = WBCopyString("<HTML><HEAD><TITLE>X11workbench Help - man ");
594  if(pRval)
595  {
596  WBCatString(&pRval, szTerm);
597 
598  if(pRval)
599  {
600  WBCatString(&pRval, "</TITLE></HEAD><BODY><TT>\r\n");
601  }
602  }
603 
604  if(!pRval)
605  {
606  return NULL;
607  }
608 
609  p3 = szText;
610 
611  while(*p3 && pRval)
612  {
613  // skip white space
614  p4 = p3;
615  nTabTwist = 0;
616 
617  while(*p3 && *p3 <= ' ')
618  {
619  if(*p3 == '\r' || *p3 == '\n' || *p3 == '\f')
620  {
621  // TODO: handle tabs?
622  pTemp = CreateNBSPString(p4, p3 - p4);
623  if(pTemp)
624  {
625  WBCatString(&pRval, pTemp);
626  WBFree(pTemp);
627  pTemp = NULL;
628  }
629 
630  if(pHyphenBuf)
631  {
632  // rare possibility of hyphenated text on a blank line
633  // but I want to indent it past the end of the white space
634  if(pRval)
635  {
636  WBCatString(&pRval, pHyphenBuf);
637  }
638 
639  WBFree(pHyphenBuf);
640  pHyphenBuf = NULL;
641  }
642 
643  p4 = p3; // to indicate I shouldn't copy anything
644  break; // it will fall through to the next part
645  }
646 
647  if(*p3 == '\t')
648  {
649  nTabTwist = 0; // reset after a tab
650  }
651  else
652  {
653  nTabTwist++;
654  }
655 
656  p3++;
657  }
658 
659  nTabTwist = nTabTwist % NBSP_TAB_WIDTH; // 'tab twist' for converting tabs
660 
661  if(pRval && p3 > p4)
662  {
663  pTemp = CreateNBSPString(p4, p3 - p4);
664  if(pTemp)
665  {
666  WBCatString(&pRval, pTemp);
667  WBFree(pTemp);
668  pTemp = NULL;
669  }
670 
671  p4 = p3;
672  }
673 
674  if(!pRval)
675  {
676  break;
677  }
678 
679  // at this point 'p3' and 'p4' point to the first non-white-space character
680  // and all of the white space (any any 'hyphen buf' stuff) is copied
681 
682  while(*p3 && *p3 != '\r' && *p3 != '\n' && *p3 != '\f')
683  {
684  p3++; // go to the end of the line
685  }
686 
687  cbLineLen = p3 - p4; // not including CR, LF, or FF
688 
689  if(*p3 == '\r' && p3[1] == '\n') // for now just check for <CRLF>
690  {
691  p3++;
692  }
693 
694  p3++;
695 
696  if(!cbLineLen || pHyphenBuf) // blank line
697  {
698  // if I have de-hyphenated text, I need to INSERT it into the buffer. There will be no tabs
699  // in it and for NOW I'll assume there's no bolding nor underlining [later I might have to
700  // check for this independently and I don't want to complicate this any more]
701 
702  if(pHyphenBuf)
703  {
704  nTabTwist = (nTabTwist + strlen(pHyphenBuf)) % NBSP_TAB_WIDTH;
705 
706  WBCatString(&pRval, pHyphenBuf);
707 
708  WBFree(pHyphenBuf);
709  pHyphenBuf = NULL;
710  }
711 
712  if(!cbLineLen)
713  {
714  if(pRval)
715  {
716  WBCatString(&pRval, "<br>\r\n");
717  }
718 
719  continue;
720  }
721  }
722 
723  // I need to look for backspaces and handle them as bold-text or underscores (depending)
724 
725 #define STRSEGCMP(X,Y) strncmp(X,Y,strlen(Y))
726 
727  if(!bNoHyphen &&
728  (!STRSEGCMP(p4, "SEE ALSO") ||
729  !STRSEGCMP(p4, "S\x08""SE\x08""EE\x08""E A\x08""AL\x08""LS\x08""SO\x08""O") ||
730  !STRSEGCMP(p4, "S\x08""SE\x08""EE\x08""E \x08 A\x08""AL\x08""LS\x08""SO\x08""O")))
731  {
732  bNoHyphen = 1;
733  }
734 
735  p5 = p4; // p4 is the beginning of my line, of 'cbLineLen' characters
736 
737  while(p5 < p3 && *p5 != '\x08' && *p5 != '<' && *p5 != '>') // backspace in the line? 'GT'? 'LT'?
738  {
739  p5++;
740  }
741 
742  if(p5 >= p3 || !cbLineLen) // no backspaces or zero-length line
743  {
744  if(cbLineLen)
745  {
746  // if there are tabs in the line I have to deal with this differently
747  p5 = p4;
748  i1 = 0;
749 
750  while(p5 < p3) // tabs?
751  {
752  if(*p5 == '\t')
753  {
754  i1++; // count them
755  }
756 
757  p5++;
758  }
759  if(!i1) // no tabs
760  {
761  WBCatStringN(&pRval, p4, cbLineLen);
762  }
763  else
764  {
765  // each tab converts to a variable number of spaces up to NBSP_TAB_WIDTH
766  // this is a little silly but it's probably the easiest way to make it work
767 
768  pTemp = WBAlloc(i1 * NBSP_TAB_WIDTH * (sizeof(szNBSP) - 1) + cbLineLen + 4);
769  if(!pTemp)
770  {
771  WBFree(pRval);
772  pRval = NULL;
773 
774  break; // buh-bye (error)
775  }
776 
777  for(p1=pTemp, p5 = p4, i1 = 0, i2=nTabTwist; i1 < cbLineLen;
778  i1++, p5++, i2 = (i2 + 1) % NBSP_TAB_WIDTH)
779  {
780  if(*p5 == '\t')
781  {
782  for(; i2 < NBSP_TAB_WIDTH; i2++)
783  {
784  memcpy(p1, szNBSP, sizeof(szNBSP) - 1);
785  p1 += sizeof(szNBSP) - 1;
786  }
787 
788  i2 = NBSP_TAB_WIDTH - 1; // so when it increments it will be zero
789  }
790  else
791  {
792  *(p1++) = *p5;
793  }
794  }
795 
796  *p1 = 0;
797 
798  WBCatString(&pRval, pTemp);
799 
800  WBFree(pTemp);
801  pTemp = NULL;
802  }
803 
804  // check for hyphenated text
805 
806  if(pRval)
807  {
808  p2 = pRval + strlen(pRval);
809 
810  if(bNoHyphen)
811  {
812  if(*(p2 - 1) == '-')
813  {
814  p2--;
815  *p2 = 0; // trim the dash
816 
817  while(p2 > pRval && *(p2 - 1) > ' ' && *(p2 - 1) != '.'
818  && *(p2 - 1) != ',' && *(p2 - 1) != ')' && *(p2 - 1) != ']' && *(p2 - 1) != '}')
819  {
820  p2--; // decrement until at the beginning of a word
821  }
822 
823  pHyphenBuf = WBCopyString(p2);
824  *p2 = 0; // trim hyphenated partial word off of the string
825 
826 // WB_ERROR_PRINT("TEMPORARY: (d) pHyphenBuf=\"%s\"\n", pHyphenBuf);
827  }
828  else if(p2 > pRval + 3 && *(p2 - 3) == '\xe2' && *(p2 - 2) == '\x80' && *(p2 - 1) == '\x90')
829  {
830  // the sequence <E2><80><90> is found on debian's man program, and since they
831  // define the gnu standard, it's likely to be elsewhere also
832 
833  p2 -= 3; // 3 character sequence (remove it)
834  *p2 = 0; // trim the 3 character sequence
835 
836  while(p2 > pRval && *(p2 - 1) > ' ' && *(p2 - 1) != '.'
837  && *(p2 - 1) != ',' && *(p2 - 1) != ')' && *(p2 - 1) != ']' && *(p2 - 1) != '}')
838  {
839  p2--; // decrement until at the beginning of a word
840  }
841 
842  pHyphenBuf = WBCopyString(p2);
843  *p2 = 0; // trim hyphenated partial word off of the string
844 
845 // WB_ERROR_PRINT("TEMPORARY: (e) pHyphenBuf=\"%s\"\n", pHyphenBuf);
846  }
847  }
848  else if(p2 > pRval + 3 && *(p2 - 3) == '\xe2' && *(p2 - 2) == '\x80' && *(p2 - 1) == '\x90')
849  {
850  // the sequence <E2><80><90> is found on debian's man program, and since they
851  // define the gnu standard, it's likely to be elsewhere also
852 
853  p2 -= 2; // 3 character sequence (remove it, replace with '-')
854  *(p2 - 1) = '-';
855  *p2 = 0; // trim the 3 character sequence
856 
857 // WB_ERROR_PRINT("TEMPORARY: (f) pHyphenBuf=\"%s\"\n", pHyphenBuf);
858  }
859  }
860  }
861 
862  if(pRval)
863  {
864  WBCatString(&pRval, "<br>\r\n");
865  }
866  }
867  else
868  {
869  p5 = p4;
870  i1 = 0;
871 
872  while(p5 < p3) // tabs?
873  {
874  if(*p5 == '\t')
875  {
876  i1++; // count them
877  }
878 
879  p5++;
880  }
881 
882  pTemp = WBAlloc(i1 * NBSP_TAB_WIDTH * (sizeof(szNBSP) - 1) + cbLineLen * 12 + 4); // way more than enough space (12 times the length)
883 
884  if(!pTemp)
885  {
886  WBFree(pRval);
887  pRval = NULL;
888  break;
889  }
890 
891  memcpy(pTemp, p4, cbLineLen);
892  pTemp[cbLineLen] = 0;
893 
894  p2 = pTemp;
895  i2 = nTabTwist;
896 
897  while(*p2)
898  {
899  if(*p2 == '\x8') // backspace at start of line?
900  {
901  strcpy(p2, p2 + 1); // just skip it
902  continue;
903  }
904 
905  if(p2[1] == '\x8') // next char is a backspace (this precludes line starting with a backspace
906  {
907  // there are 2 options possible - one is A<BS>A and the other is _<BS>A or A<BS>_ [either fine]
908 
909  if(*p2 == p2[2]) // BOLD
910  {
911  // Insert a <B> here and overwrite the first 2 characters
912 
913  memmove(p2 + 1, p2, strlen(p2) + 1); // 2 chars overwritten, move over 1
914  memcpy(p2, "<B>", 3);
915 
916  p2 += 3; // point to the original character
917 
918  // if the first character in the sequence is a '<' or '>' I have to deal with it FIRST
919  if(*p2 == '<' || *p2 == '>')
920  {
921  char cTemp = *p2;
922 
923  memmove(p2 + 3, p2, strlen(p2) + 1); // need room for '&gt;' or '&lt;' overwriting the original
924 
925  if(cTemp == '<')
926  {
927  memcpy(p2, "&lt;", 4);
928  }
929  else // if(cTemp == '>')
930  {
931  memcpy(p2, "&gt;", 4);
932  }
933 
934  p2 += 4; // increment past the tag
935  }
936  else
937  {
938  p2++; // increment past the character
939  }
940 
941  while(*p2 && p2[1] == '\x8' && *p2 == p2[2])
942  {
943  if(*p2 == '<' || *p2 == '>')
944  {
945  char cTemp = *p2;
946 
947  memmove(p2 + 1, p2, strlen(p2) + 1); // need room for '&gt;' or '&lt;' and minus 3 chars
948 
949  if(cTemp == '<')
950  {
951  memcpy(p2, "&lt;", 4);
952  }
953  else // if(cTemp == '>')
954  {
955  memcpy(p2, "&gt;", 4);
956  }
957 
958  p2 += 4; // increment past the tag
959  }
960  else
961  {
962  memcpy(p2, p2 + 2, strlen(p2 + 2) + 1); // skip the A<BS> and just have A
963  p2++;
964  }
965  }
966 
967  // now that I'm at the end of the sequence, insert </B>
968 
969  memmove(p2 + 4, p2, strlen(p2) + 1);
970  memcpy(p2, "</B>", 4);
971 
972  p2 += 4;
973  continue;
974  }
975  else if(*p2 == '_' || p2[2] == '_') // UNDERSCORE
976  {
977  // Insert a <U> here
978 
979  memmove(p2 + 1, p2, strlen(p2) + 1); // 2 chars overwritten, move over 1
980  if(*p2 != '_')
981  {
982  p2[2] = *p2; // move the character to underline "up there"
983  }
984  memcpy(p2, "<U>", 3);
985 
986  p2 += 3; // point to the original 'underscored' character
987 
988  // if the first character in the sequence is a '<' or '>' I have to deal with it FIRST
989  if(*p2 == '<' || *p2 == '>')
990  {
991  char cTemp = *p2;
992 
993  memmove(p2 + 3, p2, strlen(p2) + 1); // need room for '&gt;' or '&lt;' overwriting the original
994 
995  if(cTemp == '<')
996  {
997  memcpy(p2, "&lt;", 4);
998  }
999  else // if(cTemp == '>')
1000  {
1001  memcpy(p2, "&gt;", 4);
1002  }
1003 
1004  p2 += 4; // increment past the tag
1005  }
1006  else
1007  {
1008  p2++; // increment past the character
1009  }
1010 
1011 
1012  while(*p2 && p2[1] == '\x8' && (*p2 == '_' || p2[2] == '_'))
1013  {
1014  if(*p2 == '_')
1015  {
1016  *p2 = p2[2]; // move it "back down"
1017  }
1018 
1019  if(*p2 == '<' || *p2 == '>')
1020  {
1021  char cTemp = *p2;
1022 
1023  memmove(p2 + 1, p2, strlen(p2) + 1); // need room for '&gt;' or '&lt;' and minus 3 chars
1024 
1025  if(cTemp == '<')
1026  {
1027  memcpy(p2, "&lt;", 4);
1028  }
1029  else // if(cTemp == '>')
1030  {
1031  memcpy(p2, "&gt;", 4);
1032  }
1033 
1034  p2 += 4; // increment past the tag
1035  }
1036  else
1037  {
1038  memcpy(p2, p2 + 2, strlen(p2 + 2) + 1); // skip the A<BS> and just have A
1039  p2++;
1040  }
1041  }
1042 
1043  // now that I'm at the end of the sequence, insert </U>
1044 
1045  memmove(p2 + 4, p2, strlen(p2) + 1);
1046  memcpy(p2, "</U>", 4);
1047 
1048  p2 += 4;
1049  continue;
1050  }
1051  }
1052  else if(*p2 == '<' || *p2 == '>')
1053  {
1054  memmove(p2 + 3, p2, strlen(p2) + 1); // need room for '&gt;' or '&lt;'
1055 
1056  if(*p2 == '<')
1057  {
1058  memcpy(p2, "&lt;", 4);
1059  }
1060  else // if(*p2 == '>')
1061  {
1062  memcpy(p2, "&gt;", 4);
1063  }
1064 
1065  p2 += 3; // point at the ';' and loop
1066  }
1067 
1068  // once THAT has been done, check for a tab character and insert '&nbsp;'s
1069  if(*p2 == '\t')
1070  {
1071  p1 = p2; // current position - has a tab in it
1072  p2 += (sizeof(szNBSP) - 1) * (NBSP_TAB_WIDTH - i2); // next character to process, skips nbsp's
1073 
1074  if(p1[1])
1075  {
1076  memmove(p2, p1 + 1, strlen(p1 + 1) + 1); // don't copy the tab, but DO copy everything else (p1 + 1)
1077  }
1078  else
1079  {
1080  *p2 = 0; // end the string (I'm at the end)
1081  }
1082 
1083  for(; i2 < NBSP_TAB_WIDTH; i2++)
1084  {
1085  memcpy(p1, szNBSP, sizeof(szNBSP) - 1);
1086  p1 += sizeof(szNBSP) - 1;
1087  }
1088 
1089  i2 = 0; // no tab twist at the moment
1090  }
1091  else
1092  {
1093  p2++; // next character
1094  i2 = (i2 + 1) % NBSP_TAB_WIDTH; // "tab twist"
1095  }
1096  }
1097 
1098  // pTemp now contains the entire 'cooked' line. If I must trim off a trailing hyphen
1099  // put the text into 'pHyphenBuf'. Then create a line from the rest
1100 
1101  p2 = pTemp + strlen(pTemp);
1102 
1103  if(bNoHyphen)
1104  {
1105  if(*pTemp && *(p2 - 1) == '-') // hyphenated
1106  {
1107  p2--;
1108  *p2 = 0; // trim the dash
1109 
1110  while(p2 > pTemp && *(p2 - 1) > ' ' && *(p2 - 1) != '.'
1111  && *(p2 - 1) != ',' && *(p2 - 1) != ')' && *(p2 - 1) != ']' && *(p2 - 1) != '}')
1112  {
1113  p2--; // decrement until at the beginning of a word
1114  }
1115 
1116  pHyphenBuf = WBCopyString(p2);
1117  *p2 = 0; // trim hyphenated partial word off of the string
1118 
1119 // WB_ERROR_PRINT("TEMPORARY: (b) pHyphenBuf=\"%s\"\n", pHyphenBuf);
1120  }
1121  else if(p2 > pTemp + 3 && *(p2 - 3) == '\xe2' && *(p2 - 2) == '\x80' && *(p2 - 1) == '\x90')
1122  {
1123  // the sequence <E2><80><90> is found on debian's man program, and since they
1124  // define the gnu standard, it's likely to be elsewhere also
1125 
1126  p2 -= 3; // 3 character sequence (remove it)
1127  *p2 = 0; // trim the 3 character sequence
1128 
1129  while(p2 > pTemp && *(p2 - 1) > ' ' && *(p2 - 1) != '.'
1130  && *(p2 - 1) != ',' && *(p2 - 1) != ')' && *(p2 - 1) != ']' && *(p2 - 1) != '}')
1131  {
1132  p2--; // decrement until at the beginning of a word
1133  }
1134 
1135  pHyphenBuf = WBCopyString(p2);
1136  *p2 = 0; // trim hyphenated partial word off of the string
1137 
1138 // WB_ERROR_PRINT("TEMPORARY: (c) pHyphenBuf=\"%s\"\n", pHyphenBuf);
1139  }
1140  }
1141  else if(p2 > pTemp + 3 && *(p2 - 3) == '\xe2' && *(p2 - 2) == '\x80' && *(p2 - 1) == '\x90')
1142  {
1143  // the sequence <E2><80><90> is found on debian's man program, and since they
1144  // define the gnu standard, it's likely to be elsewhere also
1145 
1146  p2 -= 2; // 3 character sequence (remove it, replace with '-')
1147  *(p2 - 1) = '-';
1148  *p2 = 0; // trim the 3 character sequence
1149 
1150 // WB_ERROR_PRINT("TEMPORARY: (f) pHyphenBuf=\"%s\"\n", pHyphenBuf);
1151  }
1152 
1153  strcat(pTemp, "<br>\r\n");
1154 
1155  if(pRval)
1156  {
1157  WBCatString(&pRval, pTemp);
1158  }
1159 
1160  WBFree(pTemp);
1161  pTemp = NULL; // by convention
1162  }
1163  }
1164 
1165  if(pRval)
1166  {
1167  WBCatString(&pRval, "<br>\r\n<hr size=2>\r\n<p align=\"center\">Generated internally by X11workbench</p>\r\n"
1168  "</TT></BODY></HTML>\r\n");
1169  }
1170 
1171  return pRval;
1172 }
1173 
1174 
char * CHGetMimeDefaultApp(const char *szMimeType)
Get the default application for a particular MIME type.
Definition: conf_help.c:3110
char * WBTempFile(const char *szExt)
Get the name for a new, unique temporary file, creating the file in the process, and save its name fo...
void WBCatStringN(char **ppDest, const char *pSrc, unsigned int nMaxChars)
A simple utility that concatenates a string onto the end of a 0-byte terminated WBAlloc() string up t...
char * WBRunResult(const char *szAppName,...)
Run an application synchronously, returning 'stdout' output in a character buffer.
void * WBAllocDirectoryList(const char *szDirSpec)
Allocate a 'Directory List' object for a specified directory spec.
Definition: file_help.c:1187
const char * CHFindEndOfXMLTag(const char *pTagContents, int cbLength)
Parses contents of an XML tag to find the end of it.
Definition: conf_help.c:3077
void * CHOpenConfFile(const char *szAppName, int iFlags)
open configuration file for read/write, optionally creating it, based on application name
Definition: conf_help.c:643
Button Bits - OK button.
int CHGetConfFileString(void *hFile, const char *szSection, const char *szIdentifier, char *szData, int cbData)
obtain a string from a configuration file
Definition: conf_help.c:1242
'configuration helper' main header file for the X11 Work Bench Toolkit API
Icon - red stop sign.
Icon - white 'middle finger' on red triangle.
void CHCloseConfFile(void *pFile)
close configuration file opened by CHOpenConfFile(), but does NOT free memory resources
Definition: conf_help.c:850
void * WBAlloc(int nSize)
High performance memory sub-allocator 'allocate'.
WB_PROCESS_ID WBRunAsync(const char *szAppName,...)
Run an application asynchronously.
#define WB_ERROR_PRINT(...)
Preferred method of implementing an 'error level' debug message for all subsystems.
Definition: debug_helper.h:474
void WBFree(void *pBuf)
High performance memory sub-allocator 'free'.
int WBNextDirectoryEntry(void *pDirectoryList, char *szNameReturn, int cbNameReturn, unsigned long *pdwModeAttrReturn)
Obtain information about the next entry in a 'Directory List'.
Definition: file_help.c:1390
static __inline__ int FBWriteFileFromBuffer(const char *szFileName, const char *pBuf, unsigned int cbBuf)
Write a file from a regular buffer using a file name.
Definition: file_help.h:423
char * WBGetCanonicalPath(const char *szFileName)
Return the canonical path for a file name (similar to POSIX 'realpath()' funtion)
Definition: file_help.c:803
char * WBCopyString(const char *pSrc)
A simple utility that returns a WBAlloc() copy of a 0-byte terminated string.
#define CH_FLAGS_DEFAULT
Definition: conf_help.h:126
Definition file for platform-specific utility functions.
char * CHGetDesktopFileInfo(const char *szDesktopFile, const char *szInfo)
Get the default application for a particular MIME type.
Definition: conf_help.c:3158
int DLGMessageBox(int iType, Window wIDOwner, const char *szTitle, const char *szMessage)
Display a modal 'message box' dialog window with a specific title, message, and button combination.
Definition: dialog_impl.c:231
char * CHParseXMLTagContents(const char *pTagContents, int cbLength)
Parses contents of a single XML tag, returning as WBAlloc'd string list similar to environment string...
Definition: conf_help.c:2600
void WBCatString(char **ppDest, const char *pSrc)
A simple utility that concatenates a string onto the end of a 0-byte terminated WBAlloc() string.