Contents of Article


Specifying the Search String

Simple string

Quoted String

Picture String and Format String

Delimited Search String

Regular Expression String

Mapping Expression String

Macro Controlled String Change

Hexadecimal String

Character and Text Strings

Effect of CHANGE/ALIGN Command on Column-Dependent Data

Starting Point and Direction of the Search

Scroll Alignment using TOP

Qualifying the Search String Context

Truncation following the CHANGE

Delimiters used to determine Word, Prefix and Suffix boundaries

Limiting the search to specific columns

Limiting the search to Excluded or Non-Excluded lines

Limiting the search to User or non-User lines

Limiting the search to specific highlighted strings

Repeating the FIND and CHANGE commands

Case-Conformant Change Strings

Changing the Default Search Context

Restrictions on the use of LAST and PREV with Regular Expressions


FIND,  and CHANGE, (and the negative version NFIND) allow you to find a specified search string, and /or change one search string to another based on  a specified search string. These commands provide powerful editing functions because they operate on a complete file rather than on a single line.

The characteristics of each command follow:


Searches for the specified string and moves the cursor (scrolling if necessary) to the first occurrence of the search string.


Causes the same effect as FIND, but it also has a second string operand (string-2). During a search, whenever string-1 is found, the editor replaces that string with string-2. Data to the right is shifted, if necessary.


Is like the FIND command except it searches for text lines which do not contain the specified search string. Because the search string itself is not present, NFIND only looks for lines which do not have the string, and only whole lines are found.

Specifying the Search String

The primary control for any search is the search string, because it represents the value for which you are looking. Two operands, string-1 and string-2, are required for the CHANGE command to specify the replacement value of the string once the search string is found. The rules for specifying string-1 and string-2 are mostly the same, except that if you type a single asterisk for either one, the previous value from the previous FIND or CHANGE command is used again.

SPFLite allows you to specify the following kinds of strings:  

Simple string

Any series of characters not starting or ending with a single quote ( ' ), accent quote ( ` )  or double quote ( " ) and not containing any embedded blanks. A string which is also a command keyword, like ALL, cannot be specified as a simple string but must be quoted.

Note: Refer to the syntax description of each command for the complete list of reserved keywords for each command. SPFLite defines a number of extended keywords not used in ISPF, so if you are coming from a mainframe background, this could be important to you.

Quoted String

Any string enclosed by either single quotes ( ' ), accent quotes ( ` )  or double quotes ( " ). The beginning and ending delimiters must be the same character.

Note: SPFLite does not use quote-doubling to represent quotes as data, as some programming languages do.

To use quotes as data, use one type of quote as the delimiters, and another type as the "quoted quote". Example:

       "It's correct" 

to quote a quote this way; this is standard IBM ISPF usage

       `It's also correct` 

to use accent quotes in SPFLite

       'It isn''t correct'

to use quote-doubling like this is wrong

Picture String and Format String

Any quoted string of characters, preceded by the character P or F, such as P'>>>###'. The picture / format strings provide a powerful pattern matching ability to enable searching for data of certain types, rather than by specific actual characters. A Picture string can be the Find string or the Change string, but a Format can only be a Change string. A Format string is a Picture-like string that operates under slightly different rules than a Picture string does. The creation of Picture / Format strings is fully covered in Specifying A Picture or Format String.

When a FIND or CHANGE search picture uses the special characters = or . and a character that cannot be displayed is found, that character's hexadecimal representation is used in the confirmation message that appears in the upper-right corner of the Edit or Browse screen. For example, the command FIND P'..' could result in the message CHARS X'0415' found.

Delimited Search String

Like Picture Strings,  Delimiter search strings  provide another alternative to perform pattern-matching which can be simpler to use than Regular expressions. This literal type allows you to search for strings of varying length which are delimited by some identifiable characters.  For example strings within brackets or parenthesis. Or quoted strings. See Specifying a Delimited Literal for more details

Regular Expression String

Any quoted string of characters, preceded by the character R, such as R'abc$'. The regular expression string provides an industry standard syntax for specifying search strings. The creation of Regular Expressions is fully covered in Specifying A Regular Expression.

Macro Controlled String Change

There are times when the format of the 'To' string is not a constant, nor a simple re-arrangement of the 'From' string data. e.g. it requires some form of logical reasoning to determine what the 'To' string should be. A Macro Controlled String Change is one which passes control to an SPFLite MACRO to determine what exactly the CHANGE should perform.

This is done by coding an E type literal, where the contents of the literal are the name (and possible operands) of an SPFLite MACRO. e.g. E"MyMacroName aaa bbb" which will cause the macro MyMacroName to be invoked with operands of aaa bbb. The macro is able to examine the found string and perform any needed logic of it's own before returning a value to CHANGE to use as the 'To' string.

This is more completely described in "Writing a MACRO for a macro controlled string CHANGE"

Hexadecimal String

Any quoted string of Hex characters (0123456789ABCDEF), preceded by the character X, such as X'41CF'. The string must be an even number of valid hex characters. Note that when you look for Hex values, it is dependent on the encoding of the file. In ANSI, the digit 1 is X'31' while in EBCDIC it is X'F1'. Hex digits great than 9 can be in upper or lower case.

If you are looking for a known hex value, then hex strings are what you need. If you are looking for some unusual hex value in your data - but you don't know exactly what it is - there are a number of things you can do:

      • You can put the editor in HEX mode, and view the data that way. If you can find the data somewhere, you will see the hex value for it.
      • If you can see the character (assuming it's displayable), you can bring up the ANSI popup window, and find it there,  Then, just look at the 'edges' of the ANSI window that show the encoding, and add-up the row and column values to determine the hex value for it.
      • If the character is considered "undisplayable" in "Picture" terms, you can try finding it with the P'.' notation. Be aware that the roster of characters considered to be undisplayable might need to be configured or adjusted to your needs. See Options - General for more information. Be aware that the P'.' notation finds characters that are not in the "Normal" characters list in the General Options dialog, which may be opposite of what you expected - so be careful not to get confused by that.

For example, an ANSI encoded file, FIND X'3132' would be equivalent to FIND '12'

Character and Text Strings

Any quoted string of characters, preceded by the character C, such as C'conditions for' or T, such as T'some text'.  

When using the normal simple or delimited strings (see above) , SPFLite uses your choice for the CASE setting to determine whether simple literals are to be treated as case-sensitive or case-insensitive when performing comparisons. Explicitly specifying the C or T literal type will override the CASE default for this individual command.

The Character string literal is used to direct that a proper case sensitive comparison is to be made.

For example, this command:

find ALL 'Condition No. 1'

would (assuming CASE = T) find all of the following:


Condition No. 1

condition no. 1

coNDitION nO. 1

however, the command:

find ALL C'Condition No. 1'

would find only:

Condition No. 1  

Note: You must use quotes if a string contains embedded blanks or commas, or if a string is the same as a command or keyword. You delimit strings with quotes, either ' singe-quotes, ` accent-quotes, or " double-quotes.

For example, if you want to change the next occurrence of every one to all, type:

CHANGE 'every one' 'all'

If you left off the quotes and did this:

CHANGE every one all

the all would be taken as the command keyword ALL, and SPFLite would try to change all occurrences of every into one, which is not what you had in mind.

Effect of CHANGE/ALIGN Command on Column-Dependent Data

Historically, SPFLite has mimicked the processing of ISPF as to how the CHANGE command handles data which is column oriented. This method of processing is data dependent as it varies depending of the presence or absence of columns within the data.

This original processing is referred to as Data Shift mode and is still the default mode in SPFLite for both the CHANGE and ALIGN commands..

Data Shift Mode (DS)

Column-dependent data is groups of non-blank source data separated by two or more blanks, such as a table, or source code with comments starting half-way across the line. When you use CHANGE or ALIGN to change column-dependent data, the Editor attempts to maintain positional relationships. For instance, if you change a long word to a short word, the editor pads the short word with blanks. This padding maintains the column position of any data to the right of the change by preventing it from shifting left. Similarly when shifting data with the ALIGN command.

When only one blank separates words, as in most text data, padding does not occur. Changing a long word to a short word causes data to the right of the change to shift left.

Because Data Shifting is the default behavior, and is the way in which ISPF has always operated, you can think of DS as meaning either Data Shift or Default Shift, whichever you find easier to remember.

Column Shift Mode (CS)

SPFLite has introduced the option of Column Shift mode to the CHANGE  / ALIGN command. In this mode, when the length of the from/to strings in a CHANGE command are different, the presence or absence of columns in the data has no effect; the strings are changed and the data to the right of the change shifts left or right accordingly.

The current mode in which SPFLite is operating is associated and retained as another file Profile property, and will be displayed in the Status Bar at the bottom of the screen. The default shift mode for a Profile can be changed at any time with a CHANGE DS or CHANGE CS command.

As well, the shift mode for an individual CHANGE or ALIGN command can be explicitly specified by adding a CS or DS operand to the command.

Starting Point and Direction of the Search

To control the starting point and direction of the search, use one of the following operands. SPFLite sometimes describes these keywords as placement operands, since they describe the place where the search starts and the place toward which it is going.


Starts at the first position after the current cursor location and searches ahead to find the next occurrence of string-1. NEXT is the default and is generally not specified.


Starts at the top of the data and searches ahead to find all occurrences of string-1. When complete, a message is issued stating the number of occurrences found. If you use this operand with CHANGE, the lines changed are marked with ==CHG> flag. The status of these lines can be changed to normal by RESET. When used with FIND then all occurrences of the string will be hi-lighted in the text.



Starts at the top of the data and searches ahead to find the first occurrence of string-1.


Starts at the bottom of the data and searches backward to find the last occurrence of string-1.


Starts at the current cursor location and searches backward to find the previous occurrence of string-1.


A LEFT operand causes the search-string to be found at most once in any given line. Where the search-string occurs more than once in the same line, only the left-most occurrence of search-string is found/changed, and any other instances on that same line are ignored.


A RIGHT operand causes the search-string to be found at most once in any given line. Where the search-string occurs more than once in the same line, only the right-most occurrence of search-string is found/changed, and any other instances on that same line are ignored.

If you specify ALL or FIRST, the direction of the search is forward. When you press the assigned function keys, the RFIND or RCHANGE commands find or change the next occurrence of the designated string. If you specify LAST or PREV, the direction of the search is backward. When you specify those operands, the editor finds or changes the previous occurrence of the string.  The search proceeds until the editor finds one or all occurrences of string-1, or the end of data.

When ALL is specified, the FIND and CHANGE commands will report the number of lines in which a string is found or changed, in addition to the number of times the string itself is found.

If you omit the ALL operand on the CHANGE command, the editor searches only for the first occurrence of string-1 after the current cursor location. If the cursor is not in the data area of the panel, the search starts at the beginning of the first line currently displayed. Scrolling is performed, if necessary, to bring the string into view.

Note: The SPLIT command allows an extended syntax of ALL FIRST and ALL LAST. See SPLIT - Split Lines Using Find/Change Strings for more information.

Scroll Alignment using TOP

When a command such as FIND or CHANGE finds successive lines on the same screen, SPFLite will "walk" down the screen by just repositioning the cursor, and will only scroll the screen when there are no more lines to be found on that same screen. If you include the word TOP in your command, each time a successive line is found, the screen will be repositioned so that the found line appears on the top of the screen. This can be useful when scrolling through large files of repetitive fields, so that scrolling doesn't make the screen "jump around".


Qualifying the Search String Context

You can specify the "search context" of string-1 by using the operands PREFIX, SUFFIX, WORD or CHARS. The search context defines "where" or "under what circumstances" a given search string is considered to be "found".


Locates string-1 at the beginning of a word. String-1 must be at the beginning of the line, or must be preceded by a non-WORD character. PREFIX may be abbreviated as PRE or PFX.


Locates string-1 at the end of a word. String-1 must be at the end of the line, or must be followed by a non-WORD character SUFFIX may be abbreviated as SUF or SFX.


A WORD string must follow the rules for PREFIX and SUFFIX at the same time: It must be at the beginning of the line, or must be preceded by a non-WORD character, and it must be at the end of the line, or must be followed by a non-WORD character.


String-1 is searched for as-is without regard to what precedes or follows it.

CHARS is the default, and so the keyword CHARS is not normally used. CHARS is allowed primarily for ISPF compatibility purposes, and when the default has been changed. This default can be configured to either CHARS or WORD in the Global Options dialog. See Options - General for more information.

In the following examples, the editor would find the noted strings only:

FIND 'DO'          - DO DONT ADO ADOPT 'DO' (DONT)    Finds all of them

FIND 'DO' CHARS    - DO DONT ADO ADOPT 'DO' (DONT)    SAME THING - Finds all of them



FIND WORD 'DO'     - DO DONT ADO ADOPT 'DO' (DONT)    Finds DO and 'DO'

Truncation following the CHANGE

By specifying the keyword TRUNC in the CHANGE command, you can request that all line data following the new change string be deleted from the line. Because TRUNC is a new reserved word, you would have to quote this word if you ever wanted to use it as a normal string value in CHANGE command.

The following example changes the characters "END)" to "END." and deletes all remaining characters on the line.


which would alter the line




You can use truncation to truncation all characters including the search string by making the change string of length zero:


which would alter the line




Delimiters used to determine Word, Prefix and Suffix boundaries

In order to determine what a 'word', 'prefix' or 'suffix' actually is, SPFLite uses a set of characters to determine what a valid WORD is. The characters that make up a WORD are specified in the Profile's WORD control string. These default characters are:

A-Z a-z 0-9

However, some computer languages allow other characters to be used in variable names (such as the _ underscore character in many languages, the - dash character in COBOL programs, or the $, # and @ in PL/1). This can cause word searches to find strings which you don't consider to be a "word", or it might fail to find words you do want to be words. SPFLite allows you to modify the list of valid WORD characters. The modified list you create will be associated with the file type being edited, and will be saved and used in future edit sessions of this file type automatically. See the WORD line command, and  Working with Word and Delimiter Characters for details on how to change these characters.

Note that the space character is always assumed to a delimiter. This assumption cannot be changed.

If you are the one looking for a string, and you consider it to be a "word", how could SPFLite not treat it as a word and find it?  For example, if you were looking for a word ABCD, and you said FIND ABCD WORD, it seems pretty straight-forward that if ABCD exists as a word, it would get found. However, what if you were looking for any four-character word?  If the word only had English letters, you could be pretty certain you'd find it with FIND P'@@@@' WORD.

But suppose it were a four-character string that might have an underscore or dash in it; what then?  Just saying FIND P'====' WORD won't work, because you would find any four characters, as long as there were delimiters next to it, and that's not what you wanted. The data you found could be well-delimited junk. How do you solve this problem, and only find what you really want - and nothing else?

First, you modify the WORD lines so that your set of valid WORD characters is correct. That "expands" the definition of a "word character" and removes those characters as delimiters. For example, if you add the underscore to the WORD characters, then it will no longer be considered as a character that delimits a word.

Then, how can you tell SPFLite to only look for the characters that you say are part of a special kind of "word" that you want?  We could have changed how the @ picture works, but it's best not to tamper with that, so @ stays as-is, and only matches letters. Instead, SPFLite defines two extended picture code types:

       &        defines any character in in the set of WORD characters

       %        defines any character not in the set of WORD characters

So, if you want a four-character string of your kind of words, as defined by your settings of the WORD string, you would do this:


       FIND P'&&&&' WORD

This works because WORD means a string delimited by non-WORD characters or the edges of a line, and the & picture means all characters which are in in the WORD character list.

What is nice about this is that the WORD characters are in the PROFILE, so whether you have ordinary text, C programs, COBOL programs, or some special-purpose data, you can customize each file type to exactly the definition of what a "word" means to best suits your needs.

Note:  While P'&' can be used to find "your kind of words", SPFLite does not look more closely than that. So, there is no provision to search for things like "your kind of words in lower case only". That restriction only makes sense, because if you were to add special characters like $, # or @ to the list of "word" characters, are they upper case or lower case?  Ordinarily, the answer would be "neither", and since SPFLite doesn't know what your intentions are if it's other than that, it can't guess. If you really apply such fine distinctions to your data, you may need to write a programmable macro to inspect your data as needed.

Limiting the search to specific columns

The col-1 and col-2 operands allow you to search only a portion of each line, rather than the entire line. These operands, which are numbers separated by at least one blank, show the starting and ending columns for the search. The following rules apply:

    • If you specify neither col-1 nor col-2, the search continues across all columns within the current boundary columns.

    • If you specify col-1, the editor finds the string only if the string starts in the specified column.

    • If you specify both col-1 and col-2, the editor finds the string only if it is entirely within the specified columns.

Limiting the search to Excluded or Non-Excluded lines

You can limit the lines to be searched by using the X or NX operands:

X        means search only excluded lines

NX        means search only unexcluded lines

A number of commands also allow you to control the exclusion status of a line after it is found or changed, using the MX (make excluded) or DX (do not change exclusion status) keywords.

Limiting the search to User or non-User lines

You can limit the lines to be searched by using the U or NU operands:

U        means search only user lines

NU        means search only non-user lines

All data lines are either User lines (U lines) or non-User lines (also called V lines). A user line is marked by a vertical bar in the "gap column" to the right of the sequence number field.

A line can be made a User line by the primary commands ULINE and NULINE and the line command U/UU.

A line can be made a non-User line by the primary commands REVERT and NREVERT and the line command V/VV.

Limiting the search to specific highlighted strings

You can request searches only locate strings that have previously been highlighted in specific colors. This is done via use of the color-selection-criteria keywords. These can be any of the names shown in "Options - Hi-Lites" like BLUE, GREEN, BLACK etc... More details can be found in  "Color-Selection-Criteria-Specification".

Repeating the FIND and CHANGE commands

The easiest way to repeat FIND, and CHANGE commands, without retyping them, is to assign those commands to function keys. There are already ISPF-compatible key-mapping defaults made at installation time to do this, using the RFIND and RCHANGE commands:

F5        RFIND

F6        RCHANGE

The search begins at the cursor. If the cursor has not moved since the last FIND, or CHANGE command, the search continues from the string that was just found. Instead of retyping string-1, you can type an * asterisk to specify that you want to use the last search string.

If you decide to type RCHANGE or RFIND on the Command line instead of using a function key, position the cursor at the desired starting location before pressing Enter. If you are searching for every string in a file, one at a time, from beginning to end, and you are using the mapped functions F5 and F6, the cursor will already be in the location you need. You would only have to reposition it if you have moved it through some type of editing action. (That's why most people use F5 and F6, for that very reason - it's easy and convenient.)

All these commands share the same string-1. Therefore:


followed by:


first shows you where ABC is, and then replaces it with XYZ. However, you can do this more easily by typing:


Then press F5 to repeat FIND. The editor finds the next occurrence of ABC. You can either press F5 to find the next ABC, or F6 to change it. Continue to press F5 to find remaining occurrences of the string.

The previous value of a search string, specified by an asterisk or by use of RFIND or RCHANGE, is retained until you end your editing session.

SPFLite adds a new command RLOCFIND, which repeats the last LOCATE or FIND command, whichever has been done most recently. For many users, it will be more productive to assign RLOCFIND to F5, which can now serve both as a repeat-find (RFIND) and as a repeat-locate (RLOC) command.

RFIND, RLOCFIND and RCHANGE also apply to the SPLIT and JOIN primary commands, and as well applies to the DELETE primary command, when PREV or NEXT is specified, or when NEXT is implied by the absence of other keywords.

Case-Conformant Change Strings

When you do a change with search string having a type code of T, it matches letters in a case-insensitive way. So, if you say,

CHANGE WORD T'four' 'nine'

it will match on the word “four” no matter how it is capitalized. However, regardless of the original string, the result of this CHANGE will always be capitalized as “nine”:

       four        becomes  nine

       Four        becomes  nine

       FOUR        becomes  nine        

and so, the result fails to conform to the character-casing of the original string.

With case-conformant changes, you can make the result string match the pattern of upper and lower casing that existed in the original string. That is, now you can make this happen:

       four        becomes  nine

       Four        becomes  Nine

       FOUR        becomes  NINE        

How?  Just put a type code of T on the change string, like this:

CHANGE WORD T'four' T'nine'

That's great if the two strings are the same size, but what if they're not?

In the easy case, when the change string is shorter, the pattern of upper and lower casing applies for as long as the change string is. If you change a 4-letter word into a 3-letter word, the first 3 positions of the search string are used as the “capitalization pattern”, like this:

CHANGE WORD T'four' T'two'

       four        becomes  two

       Four        becomes  Two

       FOUR        becomes  TWO        

When the change string is longer, it's a little more complicated. The pattern of upper and lower casing applies for as long as the search string is. Beyond that point, the case of the last character in the search string is used as a guide to propagate the casing of the result string from that point forward. (This "remaining result string" could be called the 'tail' of the result string.)

What happens if the last character of the search string isn't a letter?  SPFLite uses the following policy to handle this:

    • Characters of the search string are scanned from right to left, starting with the last character, until a letter is found or the beginning of the string is reached.

    • If a letter is found by this scan, the case of that letter is used as a guide to propagate the casing of the tail of the result string.

    • If a letter is not found by this scan, the casing of the tail of the result string is copied in lower case.

Is that a good choice?  It's a toss-up. In designing this, five or six different possible approaches were considered, and many were hard to explain and harder to implement. For most users, the propagation rules are about as good as any. For changing one English word to another, it works well. For changing strings like alphanumeric-coded values (such as part numbers) it may or may not be the answer for everyone. See the discussion below in case these rules are not what you need.

If you change a 4-letter word into a 5-letter word, the first 3 positions of the search string are used as the capitalization pattern of the first 3 positions of the change string, and position 4 of the search string is used as the pattern for everything else:

CHANGE WORD T'four' T'seven'

       four        becomes  seven

       Four        becomes  Seven

       FOUR        becomes  SEVEN        

Here, the R of FOUR is used as the pattern for positions 4 and 5 of the string SEVEN. For the first two, the R is lower case, so E and N become lower case. In the last one, the R is upper case, so the E and N become upper case.

A few final notes:

    • It is not a requirement that the search string have a type code of T, if CASE T is in effect. But, if you have a type code of C or CASE C is in effect, you will always find the same string, cased in the same way. You could do that, but there wouldn't be much point to it.

    • As you might expect, the case of the actual CHANGE command operands for case-conformant changes is not significant. That means,

CHANGE WORD T'four' T'seven'



will work exactly the same way. And, of course, the T itself is case-insensitive.

    • When CASE T is in effect, it implies type code T on the search string, unless you explicitly say otherwise. For the change string, type code T (and thus, a case-conformant change) is never assumed, even when CASE T is in effect. You have to put the T code on the change string yourself to get a case-conformant change.

You might ask, what if SPFLite's rules for Case-Conformant strings aren't good enough for my needs?  You basically have these choices:

    • Use SPFLite's Case Conformant strings for what they do, and if you need to, 'correct' any improper casing after the fact. That might be a good choice if the CHANGE did what you wanted most of the time, and you just had to "touch up" a few exceptions.

    • If Case Conformant strings do something you really dislike, you can UNDO the change or CANCEL the edit session and try something else

Changing the Default Search Context

When a FIND, CHANGE or similar command is used, and none of the operands CHARS, WORD, PREFIX or SUFFIX are specified, the FIND or CHANGE command will normally assume that the string being searched for is a CHAR string; that is, any delimiters next to the search string are ignored. This is the standard way SPFLite and ISPF look for strings.

If you wish to look for WORD strings, that is, strings with a delimiter on each side, you normally would specify the WORD operand, as in FIND ABC WORD.

You are now able to set the default search context to either WORDS or CHARS. When set to WORDS mode, every FIND, CHANGE or similar command that can take the WORD operand will assume it has already been set.

When the default search context is set to WORDS, a W will appear the C/T indicator on the status line that shows the current CASE C/T mode. Thus, depending on the CASE mode, the indicator will show either C  W or T  W on the status line when you are in WORD mode. When it is set back to CHARS mode, the indicator will show either C or T on the status line, without the W.

If you find you frequently need to search for string in WORD mode, you can configure SPFLite to always use WORD mode as a default. This is done by checking the entry Use WORD as the default for FIND/CHANGE commands. When this checkbox is unchecked, SPFLite will look for strings as CHAR values as usual. See "Options - General".

If you want to quickly change the search context from the edit command line without using the Global Options window, you can issue a FIND WORDS or FIND CHARS command. See FIND - Find a Character String for more information.

If you issue a RESET command with no operands, the default search context will revert to the setting you have for this checkbox, either WORD mode (checked) or CHAR mode (unchecked).

Restrictions on the use of LAST and PREV with Regular Expressions

When a FIND or CHANGE search operand is a Regular Expression (a string with an R type code) and reverse-order searching is done with PREV or LAST, only the left-most occurrence on any given line is found, as if the LEFT operand had been used. That is, the command


is treated as if it were specified as




is treated as if it were specified as


This limitation stems from the regular-expression engine used by SPFLite.

Created with the Personal Edition of HelpNDoc: Free HTML Help documentation generator