Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Parallel

Building a Hypertext System


JUN90: BUILDING A HYPERTEXT SYSTEM

This article contains the following executables: GESSNER.EXE

Rick Gessner

Rick is developing artificial life software at Anthrobotics in Tempe, Arizona. He can be reached at 1311 W. Baseline #2145, Tempe, AZ 85283, or on CompuServe at 71521, 1226.


There has been so much talk lately about object-oriented programming that it's easy to forget that the terms "Hypertext and Hypermedia" came into vogue just a year or so ago. Of course, hypertext is not a new concept; it's been around since the 1960s when Ted Nelson first introduced the concept as part of his Xanadu project. Popular use of hypertext systems has increased dramatically in recent years. Software companies such as Borland International and Microsoft, for instance, have designed interactive help components for their products using the hypertext model.

The advantage of hypertext becomes evident when you consider the inherent disadvantages of the traditional information medium -- the printed page. The presentation of information in written form is explicitly linear. Structurally, details embodied by the text are intended to move from the general to the specific, while still remaining focused on a given subject. As a result, the medium is confined and contextually inflexible.

Hypertext retains the capacity for the high information content provided by print media, but removes contextual limitations. In contrast to the objectively linear nature of the printed page, a hypertext system grants the user "subjective linearity." This means that rather than having to follow the explicit course of a document, the user can choose to explore an exponential array of contextual alternatives. In other words, the user is free to explore any information path in the document. From the programmer's perspective, this approach removes the burden of deciding the order in which large quantities of information (such as those found in help systems) will be presented. As long as the data is related contextually within the hypertext system, the user can access the information in the manner that he or she sees fit. (One less headache for us!)

The purpose of this article is to demystify hypertext systems, and to show you how to take advantage of the hypertext paradigm in your own programs. Along the way, we'll also touch on a couple of general-purpose programming tricks.

And Away We Go...

A wise old programmer once taught me that good programming is as much about the process of building system tools (providing tools for ourselves) as it is about the process of building applications (providing tools for others). The hypertext system presented here was designed to provide both kinds of tools: A system tool for creating and editing "hyperdata files" (the data files used in a hypertext system), and an application tool that allows users to browse through those files.

The hypertext system presented in this article is a page-oriented, text-only system with embedded "hot-links" to related information. (The terms "page" and "screen" are synonymous.) By activating any of these hot-links, the user can conveniently move from one item to another, and from one help page to another. Consider the screen in Figure 1. A hypertext page can be comprised of any text that you want in the page. The embedded, highlighted regions (shown in outline) represent the hot-links, which are available to the user as a method of navigation. By using the arrow keys to move the selection bar (shown in inverse video) between hot-links and then pressing the Return key, the user can activate any page.

Figure 1: Sample hypertext dialogue

  Help

  The File Menu is used to Open, Close, Save and
  Create Files.  Files may be selected from within any available Directory
  or Drive.

  Other options that are available from this menu are:
    OS Shell                  Directory
    Change Dir                Quit

  Related topics:

    Other Menus               Main Index
    Help on Help

You might have noticed that the word "Directory" appears twice on the page shown in Figure 1. The first time this word appears, the context of the word represents a noun that means "The logical grouping of files within a subdirectory." The second time the word "Directory" appears, it is used as a verb that means "Display the contents of the current directory." The same word appears twice, in a different context each time. From the user's perspective, the context is everything -- it is the basis for the direction that the user takes while using the system. From a systems perspective, the contextual meaning of hyperdata files is nonexistent; it's all just arbitrary data. Herein lies the beauty of hypertext. A user's perception of context can be exploited by using only elementary computing techniques.

Given an Infinite Time, and an Infinite Number of Monkeys ...

There are probably as many different ways to write a hypertext system as there are ways to pronounce "Bjarne Stroustrup." Obviously, you need a data structure to store the textual data that will eventually be displayed, as well as a way to store and manipulate hot-links. Upon first consideration, a programmer might build data structures that look like the structure shown in Example 1.

Example 1: A typical data structure

Const   ScreenWidth = 60;
     LinesPerScreen = 15;
        MaxHotLinks = 25;  {Or any other number you want}

Type       {heres a place to store the screen text}
    ScreenTextBuffer = Array[  1..ScreenWidth,
                    1..LinesPerScreen] of Char;

    HotLinkRecord = Record
        Startpos,        {col pos where link occurs}
        LineNum  : Integer; {row/line number where link occurs}
        LinkPage : Integer;   {page number to activate for link}
    end;

        {now put it all together}
    OnePage = Record
        TheText  : ScreenTextBuffer;
        TheLinks : Array[1..MaxHotLinks] of HotLinkRecord;
    end;

Of course, there is nothing manifestly wrong with this approach. (At least that's what I told myself when I did it.) Well, almost nothing wrong. A key disadvantage to this approach is that you must predefine how many hot-links a page can have, and then either preallocate that number of links per page or create a linked list to store the links. The use of an array results in the inefficient use of memory. If you allocate too few elements, you limit the number of hot-links per page; the allocation of too many elements wastes RAM. The use of link lists is not much better, because these lists require the use of additional routines to manage both the linked lists themselves, and disk I/O to and from the linked lists. What's more, if a doubly linked list is utilized, then each node requires 8 bytes just for list pointers -- and 8 bytes is more memory than each hot-link record requires. Either way, you have unnecessary overhead.

Another disadvantage with the approach just described is that you have to create two separate editors if you want the system to be fully interactive. The first editor allows the user to edit the text that appears on each page, the second would be used to allow the user to manipulate the hot-link data structures. Moreover, the additional complications of the process of managing the hot-link data itself arise. For example, should the hot-link keywords that appear on each page be unique? If so, then the editor must be able to prevent the user from entering duplicate hot-link keywords. Eventually, it becomes apparent that there must be a simpler way to implement the system.

The answer? What if there were a way to embed the hot-links into the text itself? If this were possible, then the only factor limiting the number of links per page would be the size of the page itself. Another advantage of the technique of embedding hot-links into the text is that you would only need one editor. The only requirement of this editor would be that it must be able to distinguish hot-links from normal text. Other than that, a simple line editor would suffice. (Just wait, you'll see.)

The inner working of this type of system is so simple and elegant that it's almost laughable. The entire program -- support routines and all -- is just over 500 lines of code. It just goes to show you that the best solution is not always the most difficult to produce.

The system is divisible into two components, each of which is incorporated into a unit called "HyprText.Pas" (Listing One, page 86). The first component handles all of the user interaction required to set up the hyperdata files; this component is basically a screen editor. The second component is called a "hypertext engine," this component combines the information from the hyperdata files together with user input in order to navigate the pages of hypertext.

Already mentioned, the system works by combining the textual data that appears on a given hypertext page with the links that interconnect one page to another. An example of how it's done will make the trick obvious.

Let's assume that the user has typed the lines shown in Figure 2 for a given hypertext page, using our built-in hypertext editor. The characters typed by the user are stored in the Help_Record data structure, which is defined as listed in Example 2.

Figure 2: Sample hypertext screen

  Help

  The File Menu is used to Open, Close, Save and
  Create Files.  Files may be selected from within any available Directory
  or Drive.

  Other options that are available from this menu are:

     OS Shell                  Directory
     Change Dir                Quit

  Related topics:

     Other Menus               Main Index
     Help on Help

Example 2: Defining the Help_Record data structure

  Const   MaxLinesPerPage = 15;
          MaxLineWidth    = 60;
  Type        Help Record = Record
          HelpLines : Array[1..MaxLinesPerPage] of String [100];
                      {Leave room for links}
      end;

As you can see, the Help_Record data type is very sparse; in fact, it's an array of strings. The reason for using a record structure is to facilitate disk I/O, and nothing more.

Here comes the linking trick. Let's presume that a hypothetical user wants to create a hot-link for the term "OS-Shell" (Figure 2) to page 5 in our hyperdata file. (Setting and removing a link is accomplished by pressing the F2 key.) The pseudocode description of the algorithm for placing a link is shown in Figure 3. The process begins by determining if the cursor is currently positioned on a valid (non-space) character. If so, then the next step is to find the first and last characters in the current phrase. The high bit of each character in the phrase is then set, by adding $80 to the value of each character. The last step is to add a "hot-link token" to the end of the phrase. A hot-link token is comprised of a null byte followed by the hot-link page number.

Figure 3: Pseudocode description of the algorithm for placing a link

  If Cursor is on a valid alpha character ([Succ(' ').. '} '])then
    Begin
      Start := Find 1st char in the phrase;
      Stop    := Find last char in the phrase;
      For CurrentChar := Start to Stop do
      Set Char[CurrentChar] High-Bit;
      Attach link
    end

Returning to our example, the user has decided to create a link on the phrase "OS Shell" to page 5 in the hyperdata file. Before the link, the characters on line 8 look like those shown in Example 3. After the link is performed, the high bits are set, and the link information is appended to the end of the phrase, line 8 looks like the code in Example 4.

Example 3: Before linking, the characters on line 8 look like this

  Helplines [8] = [' OS Shell  Main Index ']  {As a string}

       = [ $20, $20, $20, $20,        {As Hex}
           $4F, $53, $20, $53, $68, $6C, $6C]

Example 4: After linking, the characters on line 8 look like this

  = [ $20,$20,$20,$20,
        $CF, $D3, $A0, $D3,
        $E8, $EC, $EC, $00,$05]

Notice that the ordinal value of each of the characters in the phase has been incremented by $80. Also, notice that a hot-link token has been added to the end of the phrase ($00,$05). A null ($00) value is used as a delimiter, and it is immediately followed by a byte that contains the link page number. The use of a byte to store the page numbers limits the system to a maximum of 255 pages of help, which is equal to the greatest value that can be stored in a single byte. It is possible to eliminate this limitation by promoting the byte to a word or a longint, as long as you watch out for nulls. (Nulls are used as the unique hot-link delimiter.)

You may be wondering why the system both adds hot-link tokens and sets the high bit of linked terms. There are two reasons. First, setting the high bits simplifies the task of detecting which words are linked and which are not. Second, this step makes the process of displaying each page easier to do, because the display routines can check for the presence or absence of the high bit, and then select the appropriate display colors accordingly.

The only task that remains to be solved is the step of unlinking a previously linked phrase. The solution is simple. When the user requests a link for a given word, the system first determines if the word has already been linked. It makes this determination by comparing the ordinal value of the character that is currently loaded beneath the cursor (at the cursor insert position). If the value of the current character is greater than $80, then the character must already be linked. In this case, the process just described is reversed -- the high bit of each character in the phrase is cleared, returning it to its original ASCII value. Last of all, the hot-link token that was previously appended to the end of the phrase is removed. Voila back to normal!

The Hypertext Editor

The hyperdata file-editor routines appear in the HyprText unit (Listing One) lines 77 - 288. The editor provides common routines for horizontal and vertical cursor movement, backspace, and character/line deletion. It's nothing more than a simple, screen-oriented line editor that can deal with both embedded hot-link tokens and characters that have their high bits set.

In the process of dealing with an ASCII character that has the high bit set is very simple. Because the only data allowed during data input are valid ASCII characters, we can be sure (corrupted data files notwithstanding) that any character with an ASCII value greater than 127 is part of a hot-linked phrase. To properly display this data, all that is required is to reduce the decimal value of the characters in question by $80. The routine Show_HelpLine, and its ancillary routine Write_Char, handle that task.

Show_HelpLine is called by Show_Help_Page to display each line of text. Show_HelpLine performs two related tasks. First, it determines the color attribute with which each character is displayed. This determination gives the system the capability to show hot-linked items in a different color and/or intensity than the color or intensity of normal text. Second, Show_HelpLine calls Write_Char, which decodes each ASCII character and reduces the character's value by $80 if necessary before displaying that character. That's all there is to it.

To understand how the editor deals with embedded hot-links, consider how a line editor works. Given a line of ASCII text, the editing routine enters a loop. The user is permitted to enter valid characters, which are inserted into the line at the current cursor position. Editing commands are provided to allow the user to quickly alter the input data via such steps as deleting all of the characters from the cursor to the end of the line. The routine normally exits when the user presses a valid terminating keystroke, which is often just a press of the Return key.

The editor works in the same way. If a valid character is entered by the user, the character is inserted into the line at the cursor position, or at least at what appears to the user to be the cursor position. Remember that each line may contain hot-link tokens, which consists of data that are not actually seen by the user. What appears to the user to be column 10 for example, might actually be character 16 or character 20 once hot-link tokens are taken into account.

Consider Example 5. Let's presume that the cursor is located in column 17, just above the letter "M" in the word "Main Index." Also, presume that no hot-links are present in the line. In this case, the actual character number in the string is the same as the column number that appears to the user. In our example, if the user presses any valid character, that character is inserted into the line at column 17. Now let's say that the user previously linked the phrase "OS Shell," and that the cursor again is located above the letter "M," as shown in Example 6.

Example 5: The actual character number in the string is the same as the column number that the user sees

  Helplines[8] = [' OS Shell Main Index '];
                                ^
                             (Character 17)

Example 6: The phrase "OS Shell" was previously linked; cursor is on the letter "M"

  HelpLines [8] = [' OS Shell<xx> Main Index '];
                   ^    ^
                (Hot Link) (Character 19)

Just as before, the cursor appears to the user to be located in column 17, but the editor knows better. The actual character number is 19; the difference is due to the fact that a 2-byte hot-link token has been inserted into the line after the phrase "OS Shell." If the user presses any valid character at this point, then the character is inserted into the string as character 19. From the user's viewpoint, the result is identical to the process just described for the case where no embedded hot-links are present.

Two routines worth mentioning are Determine_Actual_Line_Pos and Link_Count. Determine_Actual_Line_Posconverts the visual column number that appears to the user (and is tracked by the editor) to the actual column position just described. Link_Count returns the number of hot-links found on a given line. This routine works by counting the number of null characters -- our hot-link token delimiters -- in a line. All of the work required to handle the process of editing text that contains embedded hot-links can be performed by these two routines, making the inclusion of additional editing routines much easier.

The editor itself is called and managed by the routine Help_Editor, which provides the necessary setup, file-handling, and page-manipulation routines required by the editor itself. Help_Editor also provides a simple screen display and an editing-command help window for the user -- the latter is an excellent place for you to add your user-interface touches.

The discussion of the design considerations of the editor brings up an important point not only with respect to our hypertext system, but to user interface design in general. It doesn't make a bit of difference what is going on beneath the surface, as long as that activity does not interfere with the user. Case in point: Compare word processing on the Macintosh with word processing on the PC. On the Mac, the user never explicitly inserts font controls into a document. Instead, the user just selects the font they want from a menu and the rest is handled by the Mac beneath the surface. On the PC, the user practically has to learn a foreign language in order to instruct a word processor to change fonts mid-paragraph.

The Hypertext Engine

A hypertext engine is a collection of routines that permit a user to access the data contained in hyperdata files. Functionally, a hypertext engine is equivalent to a random-access file browser; it permits the user to scan records (or pages, in this case) in any order. The only difference with respect to a hypertext engine is that the user never directly specifies a record/page number. Instead, the user moves from one page to another by the selection of hot-links; the hot-links contain the new page numbers. The page number makes no difference whatsoever to the user -- the user need be concerned only with the context of the hot-link keywords and the user's own interest in pursuing a given contextual flow.

The hypertext engine itself is shown in Listing One in lines 290 - 319 and 367 - 548. Listed first are the I/O routines that read and write the hyperdata files created and used by the system. Because these routines operate on simple binary files, and because binary file I/O is both easily understood and well-documented elsewhere, a discussion of these routines is omitted here.

The main task performed by the hypertext engine is to provide a user interface that permits the user to navigate hyperdata files. The engine creates the illusion of a highlighted selection bar for the user. A related task performed by the hypertext engine is to track the user's navigation of the hyperdata file. The process of tracking is required in order to allow the user to explore any contextual path to and then back out of that path to the original starting point. The engine handles this process by maintaining a local stack that tracks both the hypertext pages that the user visits and the order of the user's visit to the pages. (The Stack data structure is shown in Listing One, lines 438 - 442 and 444.)

The technique of tracking the user's navigation around the system is not very difficult. Once the user selects a hot-link, the page number contained in the hot-link is pushed onto the local stack. If the user decides to back out of a path by pressing the PgUp key, then each preceding page number is popped off the local stack (which, in effect, reverses the user's path). This process continues until the root has been found, the user chooses to move down another path, or the stack is empty.

In addition to engine's ability to track the user's navigation around the system, another very useful feature is provided by the engine as a result of its local stack. The programmer can specify a starting page and a home page, thereby priming the local stack. This feature allows you to jump into the hyperdata file at any point and still permit your user to return to a main index or a home page. For example, let's say that you have written a database application and you've used this hypertext system as the basis of your pop-up help system. Further, let's say that the user has requested help on sorting records. As the programmer, you want the help system to first show the page concerned with sorting. This step is accomplished by passing a valid page number to the hypertext engine to be used as the starting page on the topic of sorting. A home page, usually used for a main index, can also be specified as a startup parameter for the engine.

One last point of interest is the method used to find embedded hot-links on a given page. The technique used for embedding hot-links directly into the data for each page has already been discussed. But because the system does not keep master hot-link tables, the links must be decoded at run time. Once a page has been loaded into memory and displayed, the recursive routines Find_Next_Link and Find_Prev_Link handle the decoding for you. Find_Next_Link accepts a line and column number for the current page, and then searches the page from that coordinate to the end, looking for an embedded link. Find_Prev_Link works in the same manner, except that it starts at a given coordinate and searches backwards toward the top of the page. The two routines allow you to start at any point on the screen, and finding the next or the previous hot-link. Once the new hot-link is found, it's highlighted by the selection bar, and can be invoked by a press of the Return key. Pages without hot-links terminate the current contextual path.

The Supporting Cast

The other routines in Listing One (see lines 32 - 75) are purely to provide support. The Make_String procedure creates a string of a given length from any ASCII character. Draw_Box draws single or double-lined boxes using ASCII line-drawing characters and the coordinates provided as parameters. (All simple stuff.) Read_KeyBoard accepts user input, one character at a time, and detects when the user presses the Ctrl and Alt keys.

Using the HyprText Unit

The code in Example 7 shows how easy it is to use the HyprText unit in your own programs. Line 9 shows a call to the editor, Help_Editor, that passes the name of the hyperdata file to be used. Once the user is finished editing the file, the program calls the procedure Do_Help on line 10, and passes to this procedure the name of the hyperdata file to be used. The other parameters passed to Do_Help specify the starting and home page numbers used to prime the stack (as described earlier).

Example 7: Code to incorporate the HyprText unit in other programs

   1 Program HelpTest;
   2 Uses Crt, HyprHelp;
   3 Var FileName : String [80];
   4 Begin
   5  ClrScr;
   6  {If you want to save the current text screen, do it here.}
   7  Write (TEnter the file name: U);
   8  Readln(FileName);
   9  Help_Editor (FileName);
  10  Do_Help (FileName,1,1);
  11  {Restore the text screen here, if you saved it before}   12 end.

If you want to use this system for pop-up help inside your own programs, you may want to add routines to save the text screen before calling Do_Help, and to restore the text screen once Do_Help terminates. Also, to keep the length of the listing to a minimum, I omitted most of the error-checking steps that a commercial application should have. You may want to add more error-checking routines to meet your own needs.

The Epilog

Well, there you have it -- a hypertext system that is simple to program and simple to use. I hope that this system offers a useful addition to your programming toolbox.

I am sure that you can think of additional uses and enhancements to this system. What about converting this system to a graphics-oriented environment, such as the BGI? What about adding sound hot-links? Or adding a utility that reads text files, searches for key-words, builds an index, and then automatically builds hyperdata files? You could also add compression routines to reduce the storage requirements of hyperdata files.

Nobody ever said that programming the PC was supposed to be dull!

_BUILDING A HYPERTEXT SYSTEM_ by Rick Gessner

[LISTING ONE]

<a name="0137_0016">

 {$V-} {$F+} {$O+}   { Written By:  Rick Gessner, 1989. }

 Unit HyprText;
 {-------} Interface {-----------------------------------------------}

 PROCEDURE Help_Editor(FileName: String);
 PROCEDURE Do_help(FileName: String; GoPage,HomePage: Word);
 {-------} Implementation {------------------------------------------}
 Uses Crt;

 CONST HelpColor        : Array[False..true] of Byte =
                             ( Black*16+White,     {Used for normal text}
                               Magenta*16+Yellow); {Used for hot-link text.}
       NormalColor      : Byte = Black*16+White;   {Used to draw screen info.}
       BoldColor        : Byte = White*16+Black-Blink;  {Used for select bar.}
       Header           : String[50] = ' HyperText System [1.0] ';
       MaxLinesPerPage  = 15;
       MaxLineWidth     = 57;

       PGUP    = 'I';      PGDN    = 'Q';      UpArrow = 'H'; {Edit keys}
       DnArrow = 'P';      LArrow  = 'K';      RArrow  = 'M';
       ESC     = #27;      HomeKey = 'G';      EndKey  = 'O';
       RETURN  = ;       BkSpc   = #8;       NULL    = #0;
       Tab     = #9;       F2      = '<';      DelKey  = 'S';
 Type  HelpRecord = Record  {The main structure for our hypertext files}
           HelpLines : Array[1..MaxLinesPerPage] of String[100];
       end; {String length MUST be > than MaxLineWidth to store hot-links!}
 Var   HelpRec    : HelpRecord;
       HelpFile   : File of HelpRecord;
       Alt,Ctrl,CommandKey : Boolean;
{--------------------------------------------------------------------}
 FUNCTION Make_String(Ch   : Char; Size : Integer) : String;
 Var S: string;
 Begin
     S[0] := Chr(Size);                  { Set length byte = SIZE.          }
     FillChar(S[1],Size,Ch);             { Fill the string with chr(CH).    }
     Make_String:= S;                    { and return the string as function}
 end; {Make String }                     { value.                           }
{--------------------------------------------------------------------}
 PROCEDURE Draw_Box(topx,topy,botx,boty: Byte; Color,Width: byte);
 Type BoxPos = (TopL,TopR,BotL,BotR,Top,Bot,LSide,RSide);
 Var Y       : Integer;
 Const  Boxchar : Array[1..2,TopL..RSide] of char =
    (( 'Z','?','@','Y','D','D','3','3'),  { ASCII chars for single line box }
     ( 'I',';','H','<','M','M',':',':')); { ASCII chars for double line box }
 Begin
   TextAttr:=Color;
   If Not (Width in [1,2]) then Width:=1; { Make sure width value is OK   }
   Gotoxy(TopX,TopY);    { First, draw the top line of the box...}
   Write( BoxChar[Width,TopL]+Make_String(BoxChar[width,top],BotX-TopX-1)+
                                                        BoxChar[Width,TopR]);
   For Y:=TopY+1 to BotY-1 do
   Begin                 { Second, draw the middle lines of the box...}
           Gotoxy(TopX,Y);
           Write( BoxChar[Width,LSide],BoxChar[Width,RSide]:BotX-TopX);
   end;
   GotoXY(TopX,BotY);    { Third, draw the bottom line of the box. }
   Write( BoxChar[Width,BotL]+Make_String(BoxChar[width,top],BotX-TopX-1)+
                                                         BoxChar[Width,BotR])
 end; {Draw Box}
{--------------------------------------------------------------------}
 FUNCTION Read_KeyBoard: Char;   {Routine to get keystrokes from user}
 Const  CtrlMask  = $04;
        AltMask   = $08;
 Var    KBDFlag   : Byte Absolute $0040:$0017;
 Begin
  Read_KeyBoard:=ReadKey;
  CommandKey := ((KBDFlag AND AltMask) <> 0) or ((KBDFlag AND CtrlMask) <> 0);
  ALT  := (KBDFlag AND AltMask) <> 0; CTRL := (KBDFlag AND CtrlMask) <> 0;
  If KeyPressed Then
  Begin
          Read_Keyboard := ReadKey; {Just in case user pressed modified key}
          CommandKey := True;
   end;
 end; {Read_Keyboard}
{--------------------------------------------------------------------}
  PROCEDURE Show_HelpLine(X,Y,StartBold,EndBold: Integer; Var Line: String);
  Var I,J: Integer;
       PROCEDURE Write_Char(Ch: Char);
       Begin
            If Ord(Ch)>127 then Ch:=Chr(Ord(Ch)-128); {Clear high bit}
            If Ord(Ch)>27 then Write(Ch) else Inc(i);
       end;
  Begin
       TextAttr:=HelpColor[False];
       Window(X,Y,59,Y); ClrEOL; Window(1,1,80,25); {Prepare for output}
       Gotoxy(X,Y); I:=1;
       While I<=Length(Line) do                       {Do each char in line}
       Begin
            TextAttr:=HelpColor[Ord(Line[i])>128];    {Set proper color}
            If I in [StartBold..EndBold] then TextAttr:=BoldColor;
            Write_Char(Line[i]);
            Inc(i);
       end;
  end; {Show helpline}
 {-------------------------------------------------------------------}
  PROCEDURE Show_Help_Page(X,Y: Integer; Var HelpRec: HelpRecord);
  Var I: Integer;
  Begin
       Window(X+1,Y+1,X+56,Y+MaxLinesPerPage+1); ClrScr; Window(1,1,80,25);
       For I:=1 to MaxLinesPerPage do
           Show_HelpLine(X,Y+I,0,0,HelpRec.HelpLines[I]);
  end; {Show help page}
 {-------------------------------------------------------------------}
  FUNCTION Determine_Actual_Line_Pos(Var Line: String; LinePos: Integer):
                                                                     Integer;
  Var I,J: Integer;  {Convert visual edit column to actual char. position,}
  Begin              {by skipping over embedded hot links.}
       I:=0; J:=1;
       While (J<=Length(Line)) and (I<>LinePos) do
       Begin
            If Line[j]<>Null then   {Null is used as delimiter}
               Inc(i) else Inc(j,2);
            Inc(j);
       end;
       Determine_Actual_Line_Pos:=J;
  end; {Determine actual line pos}
 {-------------------------------------------------------------------}
  FUNCTION Link_Count(Var Line: String): Integer;
  Var I,Count: Integer;  { Returns 2*#nulls in line, used to convert }
  Begin                  { from actual byte pos. to visual byte pos., }
       Count:=0;         { during data input. }
       For I:=1 to Length(Line) do
           If Line[i]=Null then Inc(Count,2);
       Link_Count:=Count;
  end; {Link count}
 {-------------------------------------------------------------------}
  FUNCTION Input_HelpPage(X,Y: Byte; Var AHelpRec: HelpRecord): Char;
  Var   Ch          : Char;   { The main editing routine in this system.  }
        PageNum     : Byte;   { It is really just a page-oriented line    }
        I,J,                  { editor that knows how to jump over 2-byte }
        LinePos,              { hot-links.                                }
        RealLinePos,          { If you add editing options, don't forget  }
        LineNum     : Integer;{ take the embedded hot-links into account! }

        PROCEDURE Delete_Linked_Char(Var Line: String; LinePos: Integer);
        Var I,J: Integer;
        Begin
             LinePos:=Pred(Determine_Actual_Line_Pos(Line,LinePos));
             If Ord(Line[LinePos])>127 then  {Were on a linked item}
             Begin
              I:=LinePos;
              While ((Ord(Line[I-1])>127) and (I>1)) do Dec(i);
              J:=LinePos;                           {Next find end of link}
                  While ((Ord(Line[J+1])>127) and (I<Length(Line))) do Inc(J);
                  Delete(Line,LinePos,  {Delete all of item + link if necc.}
                         1+(2*Ord(J=I)));
             end;
        end; {Delete linked char}
  Begin
       Show_Help_Page(X,Y,AHelpRec);  {Display this page }
       LinePos:=1; RealLinePos:=1;    {Now do a little init stuff.}
       LineNum:=1;
       With AHelpRec do               {Now enter main edit loop...}
       Repeat
             Show_HelpLine(X,Y+LineNum,0,0,HelpLines[LineNum]);
             Gotoxy(X+LinePos-1,Y+LineNum);
             Repeat Ch:=Read_KeyBoard Until Ch <> Null;
             If CommandKey then
                Case Ch of
                 ^Y     : If RealLinePos<=Length(HelpLines[LineNum]) then
                          Begin    { ^Y = Delete to end of line. }
                             If (RealLinePos=1) then HelpLines[LineNum]:=''
                             else
                             Begin
                                While HelpLines[LineNum,RealLinePos]<>Null do
                                 Delete(HelpLines[LineNum],RealLinePos,1);
                                If HelpLines[LineNum,RealLinePos]=Null then
                                 Delete(HelpLines[LineNum],RealLinePos+2,255)
                              end
                         end;
                 F2     : Begin { F2 = Add/Remove hot-link.}
                                J:=RealLinePos;
                                While (j>0) and (HelpLines[LineNum,j]<>' ')
                                                                   do Dec(j);
                                Inc(j);
                                If Ord(HelpLines[Linenum,j]) in [28..127] then
                                Repeat {Now get a valid page # to jump to...}
                                     Gotoxy(3,24); Write('Link Page: ');
                                     Readln(PageNum);
                                     Gotoxy(3,24); ClrEOL;
                                Until (PageNum>0) and (PageNum<256);
                                While (HelpLines[LineNum,j]<>' ') and
                                         (j<=Length(HelpLines[LineNum])) and
                                      (HelpLines[LineNum,j]<>Null) do
                                Begin
                                     HelpLines[LineNum,j]:=Chr(Ord(HelpLines
                                                           [LineNum,j])+128);
                                     Inc(j);
                                end;
                             If Ord(HelpLines[LineNum,J-1]) in [28..127] then
                             Delete(HelpLines[LineNum],J,2) else
                             Insert(Null+Chr(PageNum),HelpLines[LineNum],j);
                           end;
                 LArrow : If RealLinePos>1 then  {Move cursor left 1 char.}
                       Begin
                          Dec(linePos);
                          RealLinePos:=Pred(Determine_Actual_Line_Pos
                                              (HelpLines[LineNum],LinePos));
                       end;
                 RArrow : If RealLinePos<=Length(HelpLines[LineNum]) then
                           Begin                 {Move cursor right 1 char.}
                                Inc(LinePos);
                                If RealLinePos<Length(HelpLines[LineNum]) then
                                   Inc (RealLinePos,
                                1+Ord(HelpLines[LineNum,RealLinePos+1]=Null)*2)
                                else Inc(realLinePos)
                           end;
                 DnArrow: If LineNum<MaxLinesPerPage then
                      Begin                 {Move down 1 line.}
                         Inc(LineNum);
                         If LinePos<=Length(HelpLines[LineNum]) then
                           RealLinePos:=Pred(Determine_Actual_Line_Pos
                                               (HelpLines[LineNum],LinePos))
                         else
                          Begin
                           RealLinePos:=Succ(Length(HelpLines[LineNum]));
                           LinePos:=RealLinePos-Link_Count(HelpLines[LineNum]);
                          end;
                      end;
                 UpArrow: If LineNum>1 then      {Move up 1 line.}
                      Begin
                         Dec(LineNum);
                         If LinePos<=Length(HelpLines[LineNum]) then
                           RealLinePos:=Pred(Determine_Actual_Line_Pos
                                               (HelpLines[LineNum],LinePos))
                         else
                        Begin
                         RealLinePos:=Succ(Length(HelpLines[LineNum]));
                         LinePos:=RealLinePos-Link_Count(HelpLines[LineNum]);
                        end;
                      end;
                 HomeKey: Begin                 {Move to 1 char. in line.}
                                LinePos:=1;
                                RealLinePos:=LinePos;
                           end;
                 EndKey : Begin                 {Move to end of line.}
                        RealLinePos:=Succ(Length(HelpLines[LineNum]));
                        LinePos:=RealLinePos-Link_Count(HelpLines[LineNum]);
                        end;
                 DelKey : If (RealLinePos<=Length(HelpLines[LineNum])) then
                        Begin                 {Delete a character.}
                         If (HelpLines[LineNum,RealLinePos]) in [' '..'}']
                           then Delete(HelpLines[LineNum],RealLinePos,1) else
                         Delete_Linked_Char(HelpLines[LineNum],LinePos);
                         RealLinePos:=Pred(Determine_Actual_Line_Pos
                                              (HelpLines[LineNum],LinePos));
                          end;
                end else
                Case Ch of
                 Return: If LineNum<MaxLinesPerPage then  {Move down 1 line.}
                          Begin
                               Inc(LineNum); LinePos:=1; RealLinePos:=1;
                          end;
                    Tab : Begin    {Tab right 10 chars.}
                        If RealLinePos+10<=Length(HelpLines[LineNum])+1 then
                               Inc(RealLinePos,10) else
                         RealLinePos:=Length(HelpLines[LineNum])+1;
                         LinePos:=RealLinePos-Link_Count(HelpLines[LineNum]);
                           end;
                  BkSpc  : If RealLinePos>1 then  {Backspace over prev. char.}
                       Begin
                        If HelpLines[LineNum,RealLinePos-1] in [' '..'}'] then
                              Begin
                                  Delete(HelpLines[LineNum],RealLinePos-1,1);
                                  Dec(RealLinePos);
                                  Dec(LinePos)
                              end else
                          Begin
                            Delete_Linked_Char(HelpLines[LineNum],LinePos-1);
                                  Dec(LinePos);
                                  RealLinePos:=Pred(Determine_Actual_Line_
                                           Pos(HelpLines[LineNum],LinePos));
                                end;
                          end;
               ' '..'}' : If Length(HelpLines[LineNum])<MaxLineWidth then
                           Begin     {Insert a valid Ascii char.}
                            If (Ord(HelpLines[LIneNum,RealLinePos])>127) and
                               (RealLinePos<=Length(HelpLines[Linenum])) then
                                 Ch:=Chr(Ord(Ch)+128);
                             Insert(Ch,HelpLines[LineNum],RealLinePos);
                             Inc(RealLinePos);
                             Inc(LinePos); Ch:=#255;
                           end;
             end;
       Until CH in [ESC,PGUp,PgDn]; {ESC=Quit;PGUp=Prev page;PgDn=Next Page.}
       Input_HelpPage:=Ch;
  end; {Input helppage}
{--------------------------------------------------------------------}
  FUNCTION Read_Helprec(Var AHelpRec: HelpRecord; RecNum: Integer ): Integer;
  Var I : Integer;
  Begin
       FillChar(AHelprec,SizeOf(AHelprec),0); {$I-} {Hyperdata file read rec}
       If FileSize(HelpFile)<RecNum then exit;      {routine. Includes just }
       Seek(helpfile,RecNum-1);                     {enough error checking  }
       Read(helpfile,AHelpRec);                     {to be considered safe. }
       Read_HelpRec:=IOResult; {$I+}
  end; {Read helprec}
{--------------------------------------------------------------------}
  FUNCTION Write_HelpRec(Var AHelpRec: HelpRecord; RecNum: Integer): Integer;
  Begin {$I-}
       Seek(helpfile,RecNum-1);         {Hyperdata file write rec routine.}
       Write(helpfile,AHelpRec); {$I+}  {This routine also contains just  }
       Write_HelpRec:=IOresult;         {enough error checking to be      }
  end; {Write helprec}                  {considered safe.                 }
{--------------------------------------------------------------------}
  FUNCTION Open_HelpFile(FileName: String): Integer;
  Var result: Integer;
  Begin
       Assign(HelpFile,FileName); {$I-}  {Opens hyperdata file specified}
       Reset(HelpFile);                  {as "FileName".  If the file   }
       result:=IOResult;                 {doesnt exist, then it will be }
       If Result=2 then                  {created.                      }
       Begin                             {Error checking is limited, but}
            ReWrite(HelpFile);           {enough to be safe.            }
            Result:=IOResult;
       end;
       Open_HelpFile:=Result;
  end; {open helpfile}
{--------------------------------------------------------------------}
 PROCEDURE Help_Editor(FileName: String);
 Const  HelpMsgs  = 13;
        HelpData  : Array[1..HelpMsgs] of String[17] =
                 ( 'Editing Keys: ',          '-------------',
                   'F2   : Link (+/-)',       '^Y   : Del EOLine',
                   'Bkspc: Del left',         'Del  : Del char',
                   Chr(10)+'Movement keys: ', '--------------',
                   Chr(24)+Chr(25)+Chr(27)+Chr(26)+', '+Chr(17)+Chr(217)+',',
                   'Tab, Home, End',          'PgUp : Prev page',
                   'PgDn : Next page',        Chr(10)+'ESC to quit.');
 Var I,HelpRecNum: Integer;
     AHelpRec    : HelpRecord;
     Ch          : Char;
     Result      : Integer;
 Begin
      Result:=Open_HelpFile(FileName);     {Open the specified file.}
      If Result=0 then                     {Continue only if no error.}
      Begin
           TextAttr := NormalColor;
           Draw_Box(1,3,80,23,NormalColor,1);
           Draw_Box(2,4,60,22,NormalColor,2);
           Gotoxy(61,4);
           For I:=1 to HelpMsgs do
           Begin
                Gotoxy(62,WhereY+1); Write(HelpData[i]);
           end;
           HelpRecNum:=1;
           Gotoxy(40-(Length(Header) div 2),3); Writeln(Header);
           Gotoxy(4,2); Writeln('File: ',FileName);
           Repeat
                 Gotoxy(4,4); Writeln(' Reading  ');
                 Result:=Read_HelpRec(AHelpRec,HelpRecNum);
                 Gotoxy(4,4); Writeln('Page: ',HelpRecNum:3);
                  Ch:=Input_HelpPage(3,4,AHelpRec);
                 Result:=Write_HelpRec(AHelpRec,HelpRecNum);
                 Gotoxy(4,4); Writeln(' Writing  ');
                 Case Ch of
                      PgUp : If helpRecNum>1 then Dec(HelpRecNum);
                      PgDn : If HelpRecNum < 255 then Inc(HelpRecNum);
                 end;
           Until Ch=ESC;
     end else  {Report the opening error...}
      Writeln('ERROR: ',Result,' opening ',FileName,'. Unable to continue.');
     {$I-} Close(HelpFile); Result:=IOresult; {$I+}
 end; {Help editor}
{--------------------------------------------------------------------}
 FUNCTION Find_Next_Link( Var X,Y: Integer; EndX,EndY: Integer;
                          Var AHelpRec: HelpRecord): Boolean;
 Var OrigX,OrigY,Col,                {Recursive routine used to find a }
     Row,StartCol,StopCol: Integer;  {hot-link on the page after the   }
 Begin                               {current page position (X,Y).     }
      Find_Next_Link:=False;
      {First, search from current pos to end of page...}
      For Row:=Y to EndY do
      Begin
           If Row<>Y then StartCol:=1 else StartCol:=X;
           If Row<>EndY then StopCol:=Length(AhelpRec.HelpLines[Row])
              else StopCol:=EndX;
           If AhelpRec.HelpLines[Row]<>'' then
           For Col:=StartCol to StopCol do
               If (AHelpRec.HelpLines[Row,Col]=Null) then
               Begin
                    Find_Next_Link:=True;
                    X:=Col; Y:=Row;
                    Exit;  {make a quick getaway!}
               end;
      end;
      {ok, search from top of page to the startpos}
      If X+Y>2 then
      Begin
           Col:=1; Row:=1;
           If Find_Next_link(Col,Row,Pred(X),Y,AHelpRec) then
           Begin
                X:=Col; Y:=Row; Find_Next_Link:=true;
           end
      end;
 end; {find next link}
{--------------------------------------------------------------------}
 FUNCTION Find_Prev_Link( Var X,Y: Integer; EndX,EndY: Integer;
                          Var AHelpRec: HelpRecord): Boolean;
 Var OrigX,OrigY,Col,                 {Recursive routine used to find a }
     Row,StartCol,StopCol: Integer;   {hot-link on the page prev. to the}
 Begin                                {current page pos. (X,Y).         }
      Find_Prev_Link:=False;
      {First, search from current pos to top of page...}
      For Row:=Y downto 1 do
      Begin
           StopCol:=1;
           If Row<>Y then StartCol:=Length(AhelpRec.HelpLines[Row])
              else StartCol:=X;
           If AhelpRec.HelpLines[Row]<>'' then
           For Col:=StartCol downto StopCol do
               If (AHelpRec.HelpLines[Row,Col]=Null) then
               Begin
                    Find_Prev_Link:=True;
                    X:=Col; Y:=Row;
                    Exit;  {make a quick getaway!}
               end;
      end;
      {ok, search from bottom of page to the startpos}
      If X+Y>2 then
      Begin
           Row:=MaxLinesPerPage;
           Col:=Length(AHelpRec.HelpLines[Row]);
           If Find_Prev_link(Col,Row,Succ(X),Y,AHelpRec) then
           Begin
                X:=Col; Y:=Row; Find_Prev_Link:=true;
           end
      end;
 end; {find prev link}
{--------------------------------------------------------------------}
 PROCEDURE Do_Help(FileName: String; GoPage,HomePage: Word);
 Const XPos = 10;
       YPos = 5;
       Color    : Byte = Black*16+White;   {This is the hypertext engine.}
       MaxStackSize = 25;                  {This routine is used to read }
                                           {and navigate through a data  }
 Type  StackRec = Record                   {file, specfied as "FILENAME".}
            Page : Byte;                   {GoPage specifies the starting}
            Row,                           {page to display, and HomePage}
            Col  : Integer;                {is used to specify an main   }
       end;                                {index (or home) page.        }
 Var   Result  : Integer;
       Stack   : Array[0..MaxStackSize] of StackRec;
       AHelpRec: HelpRecord;
       Ch      : CHar;
       StackLvl: Byte;
       StartCol: Integer;
       Linked,
       Load    : Boolean;
       FUNCTION Pop_Stack: Byte;  {Pop the top page info (Stack) record}
       Begin
            If StackLvl>1 then
            Begin
                 Dec(StackLvl);
                 Load:=True;
            end;
            Pop_Stack:=StackLvl;
       end; {pop stack}
       FUNCTION Push_Stack(PageNum: Byte): Byte;
       Begin                               {Push a page info (stack) record.}
            Inc(StackLvl);
            Stack[StackLvl].Page:=PageNum;
            Stack[StackLvl].Col:=1;
            Stack[StackLvl].Row:=1;
            Push_Stack:=StackLvl;
       end; {push stack}
 Begin
      If GoPage=0 then GoPage:=1;      {Make sure GoPage is valid.}
      Result:=Open_HelpFile(FileName);
      If Result=0 then
      Begin
           Load:=true;
           TextAttr :=Color;
           Draw_Box(Xpos,YPos,XPos+59,YPos+MaxLinesPerPage+2,NormalColor,2);
           FillChar(Stack,SizeOf(Stack),0);
           StackLvl := 0;
           If HomePage in [1..255] then StackLvl:=Push_Stack(HomePage);
           If (GoPage in [1..255]) and (GoPage<>HomePage) then
                                              StackLvl:=Push_Stack(GoPage);
           GotoXY(XPos+29-(Length(Header) div 2),YPos);
           Writeln(Header);
           Repeat
                 With Stack[StackLvl] do
                 Begin
                      If Load then  {System needs new hyperdata file page.}
                      Begin
                           Result:=Read_HelpRec(AHelpRec,Page);
                           Show_Help_Page(XPos+1,YPos,AHelpRec);
                           Gotoxy(XPos+51,YPos+MaxLinesPerPage+2);
                           If StackLvl>1 then Write('Esc,PgUp') else
                                                         Write('Esc=Quit');
                           Linked:=Find_Next_Link(Col,Row,80,MaxLinesPerPage,
                                                                 AHelpRec);
                           Load:=False;
                      end;
                      If Linked then {We have a hot-link to show, so do it.}
                    Begin
                           StartCol := Col;
                           While Ord(AHelprec.HelpLines[Row,StartCol-1])>127
                                                           do Dec(StartCol);
                           Show_HelpLine(XPos+1,YPos+Row,StartCol,Pred(Col),
                                                   AHelpRec.HelpLines[Row]);
                    end;
                 Repeat Ch:=Read_KeyBoard until Ch<>Null;
                 Show_HelpLine(XPos+1,YPos+Row,0,0,AHelpRec.HelpLines[Row]);
                 Case Ch of   {Now handle navigation...}
                           RArrow,
                           Tab    : Begin
                                         Inc(Col);
                                         Linked:=Find_Next_Link(Col,Row,80,
                                                   MaxLinesPerPage,AHelpRec);
                                    end;
                           Return : If Linked then
                               Begin
                                 Load:=true;
                                  If (StackLvl>1) and
                                   (Stack[StackLvl-1].Page=
                                     Ord(AHelpRec.HelpLines[Row,Col+1])) then
                                       StackLvl:=Pop_Stack else
                                 StackLvl:=Push_Stack(Ord(AHelpRec.HelpLines
                                                                [Row,Col+1]));
                               end;
                           LArrow : Begin
                               Dec(Col);
                               Linked:=Find_Prev_Link(Col,Row,1,1,AHelpRec);
                               end;
                           DnArrow: Begin
                            Col:=1;
                            If Row<MaxLinesPerPage then Inc(Row) else Row:=1;
                            Linked:=Find_Next_Link(Col,Row,80,
                                                   MaxLinesPerPage,AHelprec);
                              end;
                           UpArrow: Begin
                            If Row>1 then Dec(Row) else Row:=MaxLinesPerPage;
                            If Col<Length(AHelpRec.HelpLines[Row])
                                                               then Inc(Col);
                            Linked:=Find_Prev_Link(Col,Row,1,1,Ahelprec);
                                    end;
                           PgUp   : StackLvl:=Pop_Stack;
                           PgDn   : Begin  {Let programmer set this!} end;
                      end;
                 end;
           Until Ch=ESC;
      end else
      Writeln('ERROR: ',Result,' opening ',FileName,'. Unable to continue.');
      {$I-} Close(HelpFile); result:=IOResult; {$I+}
 end; {do help}
{--------------------------------------------------------------------}
 Begin {No init code required}
 end.


[EXAMPLE 1]

   Const   ScreenWidth = 60;
      LinesPerScreen = 15;
      MaxHotLinks = 25;  {Or any other number you want}

   Type      {heres a place to store the screen text}
      ScreenTextBuffer = Array[  1..ScreenWidth,
                   1..LinesPerScreen] of Char;

      HotLinkRecord = Record
         Startpos,       {col pos where link occurs}
         LineNum  : Integer; {row/line number where link occurs}
         LinkPage : Integer;  {page number to activate for link}
      end;

           {now put it all together}
      OnePage = Record
         TheText  : ScreenTextBuffer;
         TheLinks : Array[1..MaxHotLinks] of HotLinkRecord;
      end;


[EXAMPLE 2]

   Const   MaxLinesPerPage = 15;
      MaxLineWidth   = 60;
   Type   Help_Record = Record
         HelpLines : Array[1..MaxLinesPerPage] of            String[100]; {Leave room for links}
      end;


[EXAMPLE 3]

      Helplines[8] = [` OS Shell   Main Index ']  {As a string}

         =  [ $20,$20,$20,$20,          {As Hex}
              $4F, $53, $20, $53, $68, $6C, $6C]


[EXAMPLE 4]

         =  [ $20,$20,$20,$20,
                $CF, $D3, $A0, $D3,
                $E8, $EC, $EC, $00,$05]


[EXAMPLE 5]

Helplines[8] = [` OS Shell   Main Index ']
                                ^
                              (Character 17)


[EXAMPLE 6]

HelpLines[8] = [` OS Shell<xx>   Main Index '];
            ^    ^
         (Hot link)   (Character 19)


[EXAMPLE 7]

    1 Program HelpTest;
    2 Uses Crt,HyprHelp;
    3 Var FileName : String[80];
    4 Begin
    5   ClrScr;
    6   {If you want to save the current text screen, do it here.}
    7   Write(TEnter the file name: U);
    8   Readln(FileName);
    9   Help_Editor( FileName );
   10   Do_Help ( FileName,1,1);
   11   { Restore the text screen here, if you saved it before }
   12 end.












Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.