Topics
Specifying a calculation operand
Introduction to the calculator
Specifying numeric literals
Specifying large numeric literals
Expression Operators
Operation and use of calculations in mapping strings
Calculator commandline test program
Specifying a calculation operand
For the sequence items SD and SX, and the numeric conversion items DD, DX, XD and XX, you are allowed to perform arithmetic operations on the value before it is formatted as a numeric value. This is done by specifying a calculation operand, which is a string operand containing an arithmetic expression string. The expression string is processed by a calculation function, which evaluates the expression and produces a numeric result, which in turn is formatted by the SD, SX, DD, DX, XD and XX mapping items.
You can invoke an SPFLite primary command from an SPFLite macro, using the builtin macro function SPF_Cmd(). When you do this, and your SPFLite command is a CHANGE command with a mapping string (a string2 operand with an M string), the mapping string can contain one or more SD, SX, DD, DX, XD or XX mapping items, which in turn may have calculation operands.
You can also invoke the calculation function directly from an edit macro, using the builtin function SPF_Calc(). See the macro documentation for more information, by issuing the command HELP MACRO from the SPFLite primary command line.
You are likely to find the calculation function will allow you to perform any calculations you might need in connection with your use of mapping strings, and then some. The design of the calculation features was made with future enhancements and developments of SPFLite in mind.
Introduction to the calculator
The calculation function has the following features.
Because the calculation function will often be used for very simple calculations, a further expression simplification is supported. When the leftmost part of the calculation operand is a binary operator, it is assumed that the expression is preceded by an implied X variable. When the calculation operand begins with + or – these are assumed to be binary operators (not unary), with an implied X on the left. For instance, a calculation operand of +5 will be assumed to mean X+5, and then this will be treated as described in the preceding paragraph
When you use this shortcut, you must ensure that the resulting expression, including the implied X variable, is syntactically correct.
The X variable is not implied when the calculation operand has a leading operator that is only used as unary. For example, a calculation operand of !4 would not imply X!4 because ! is the unary NOT operator. Thus, !4 would calculate the value of !4 without reference to variable X.
The only case where this could be an issue is if you needed a leading + or – unary operator, since the implied X rules would be used, and a calculation adding to or subtracting from X would be performed. An actual unary + operator is never algebraically required, and can be omitted. However, if you needed an expression like ‑4*S and did not want this to mean, X4*S, you would need to change your calculation operand. For the unary – operator, if you really need to begin your expression this way, you can do any of the following:
 Precede the initial unary – operator with a ; semicolon.
example: ;4*S
 Precede the initial unary – operator with a 0, which is numerically the same.
example: 04*S
 Enclose the term having the initial unary – operator in parentheses.
example: (4)*S or (4*S)
 Rewrite the expression so it does not begin with an initial unary – operator.
example: S*4 or 4*S
 Rewrite the expression so it explicitly assigns variable R or X.
example: R=4*S
Literals may be specified in decimal or hex. You can optionally use underscores for readability with large numbers, and these will be ignored.
Specifying large numeric literals
The calculation function provides a means to concisely specify large decimal and hex values that have repeating digits. It is somewhat similar to E notation commonly used for floating point numbers, but the same syntax provided will work in both decimal and hex integers (the E notation would not work in hex, since E is a hex digit) and is more flexible.
Large numbers may be required in applications involving calculations of checksums, hash keys or encryption values on your user data.
If your work does not involve large numbers, you can skip the rest of this section.
The large number syntax relies on three suffixes, comparable to floatingpoint E notation, namely a “Low Suffix”, a “High Suffix” and a “Repeated Suffix”. These are used the same as you might specify a floating point value of 5E4 to mean 50000.0. A number may have only one suffix, which appears at the end of the number without any intervening blanks. These suffixes operate as follows:
The decimal number in a suffix is limited to 2 digits. Its value may not exceed 19 for a decimal number or 16 for a hex number; the minimum value is 1 in both cases. Additionally, the suffix cannot cause the final number of digits to exceed 19 for a decimal number or 16 for a hex number. If the decimal number in a suffix is outside these limits, a syntax error is detected and the calculation halts. For the mapping string, the returned value will be unchanged from the initial value. Readability underscores in numbers are ignored and do not affect the maximum number of digits allowed.
The following table describes the operators, their precedence and associativity. Operators with higher precedence are performed ones with lower precedence. Higherprecedence operators are listed with higher precedence numbers. Operators at level 15, if present, are performed first, while operators at level 1, if present, are performed last.
This precedence follows the traditional C language expression syntax and semantics. As is true of many beginning C programmers, some may find the extensive number of operators and precedence levels to be intimidating. However, experience has shown that this arrangement actually implements the most frequently desired choice of actions, in cases where specifying the fewest number of parentheses is desired. Of course, whenever in doubt, you are free to use parentheses as needed to clarify to the calculation function and to yourself what your intentions are.
Headings:
LTR = Left to Right
RTL = Right to Left
NON = NonAssociative. Some operators are nonassociative, because allowing two or more such operators in a row (without using parentheses) may be ambiguous or confusing.
Note: Even where operators may be LTR or RTL, expressions must still be written carefully and properly. For example, R = X < Y < Z is a “valid” expression, but it probably will not do what you want. First, X < Y is evaluated; if true, its value 1, else 0. Then, Z is compared to that result, so that you would be either be comparing 0 < Z or 1 < Z. If your intent was to determine if X, Y and Z were in numeric order, the right way to do that is with R = (X < Y) && (Y < Z).
Oper 
Prec 
Assoc 
Description 
Asgn 
+ 
15 
RTL 
Unary arithmetic positive, ignored except to clarify an expression. Not needed, but supported to conform to conventional usage. 
n/a 
 
15 
RTL 
Unary arithmetic negation 
n/a 
! 
15 
RTL 
Unary logical not If X = 0, !X = 1 If X not = 0, !X = 0 
n/a 
~ 
15 
RTL 
Unary bitwise negation (one’s complement) 
n/a 
@ 
15 
RTL 
Unary absolute value, like ABS function in Basic If X < 0, @X = –X If X >= 0, @X = X 
n/a 
$ 
15 
RTL 
Unary sign function, like SGN function in Basic If X < 0, $X = –1 If X = 0, $X = 0 If X > 0, $X = 1 
n/a 
$$ 
15 
RTL 
Unary sign index function, like 2+SGN function in Basic If X < 0, $X = 1 If X = 0, $X = 2 If X > 0, $X = 3 
n/a 
* 
14 
LTR 
Multiply 

/ 
14 
LTR 
Integer divide 

% \ 
14 14 
LTR LTR 
Modulus (remainder) % is the C language operator, \ is a common alternative notation. Both have same meaning and are interchangeable. Note that Integer Divide and Modulus (remainder) operators calculate in such a way that the quotient and remainder will have the same absolute values regardless of the signs of the dividend and divisor. This is done by doing a preliminary Divide or Modulus using the absolute values of the numeric terms, and then correcting the signs afterwards. Attempting a Divide or Modulus with a righthand term of zero is an error which will halt the calculation and the mapping string that contains it. 
Yes 
+ 
13 
LTR 
Add 
Yes 
 
13 
LTR 
Subtract 
Yes 
<< 
12 
LTR 
Bitwise (unsigned) shift left. Zero bits are filled on the right. If you shift with a negative shift amount, it will reverse the direction of the shift. An expression of X << 4 will shift left 4 bits, while an expression of X << –4 will operate the same as X >> 4. The determination of a negative shift amount is not just a matter of an explicit – minus sign operator, but is based on the evaluation of the expression. 
Yes 
>> 
12 
LTR 
Bitwise (unsigned) shift right. Zero bits are propagated on the left. If you shift with a negative shift amount, it will reverse the direction of the shift. An expression of X >> 4 will shift right 4 bits, while an expression of X >> –4 will operate the same as X << 4. The determination of a negative shift amount is not just a matter of an explicit – minus sign operator, but is based on the evaluation of the expression. 
Yes 
+> 
12 
LTR 
Numeric (signed) shift right. The sign bit is propagated on the left. If you shift with a negative shift amount, it will reverse the direction of the shift. An expression of X +> 4 will shift right 4 bits, while an expression of X +> –4 will operate the same as X << 4. The determination of a negative shift amount is not just a matter of an explicit – minus sign operator, but is based on the evaluation of the expression. The operator +> is equivalent to the >>> operator in Java. 
Yes 
# 
12 
LTR 
Numeric scale In the expression X # S, the value X is divided by S. Each division operation is counted, with the first one treated as division number 1. After division, a test is made to see if the result is 0; if so, the # operator halts; otherwise, the quotient is divided again and process repeats. The result of the # operator is the count of the number of divisions performed. If S < 2, X # S produces 0. When S is 10, X # S is the number of decimal digits required to represent X. For example, 123456 # 10 will produce 6. When X is zero and S >= 2, X # S will produce 1. 
Yes 
## 
12 
LTR 
Bitwise scale In the expression X ## S, the value X is unsignedshifted right by S, the same as done by X >> S. Each shift operation is counted, with the first one treated as shift number 1. After a shift, a test is made to see if the result is 0; if so, the ## operator halts; otherwise, the result is shifted again and process repeats. The result of the ## operator is the count of the number of shifts performed. If S < 1 or S > 63, X ## S produces 0. When S is 4, X ## S will report the number of hex digits required to represent X. For example, .FF123 ## 4 will produce 5. When S is 1, X ## S is the number of binary digits (bits) required to represent X. When X is zero, and 1<=S<=63 , X ## S will produce 1. 
Yes 
> 
11 
LTR 
Greater Than. If X is > Y, the value of X > Y is 1. If X is not > Y, the value of X > Y is 0. 
No 
< 
11 
LTR 
Less Than. If X is < Y, the value of X < Y is 1. If X is not < Y, the value of X < Y is 0. 
No 
>= 
11 
LTR 
Greater Than or Equal To. If X is >= Y, the value of X >= Y is 1. If X is not >= Y, the value of X >= Y is 0. 
No 
<= 
11 
LTR 
Less Than or Equal To. If X is <= Y, the value of X <= Y is 1. If X is not <= Y, the value of X <= Y is 0. 
No 
!> 
11 
LTR 
Not Greater Than. This operator is equivalent to a MIN function in some programming languages. In the expression X !> Y, the intent is to limit the value of X such that it is not greater than Y. If X is > Y, the value of X !> Y is Y. If X is not > Y, the value of X !> Y is X. 
No 
!< 
11 
LTR 
Not Less Than. This operator is equivalent to a MAX function in some programming languages. In the expression X !< Y, the intent is to limit the value of X such that it is not less than Y. If X is < Y, the value of X !< Y is Y. If X is not < Y, the value of X !< Y is X. 
No 
!>= 
11 
LTR 
Not Greater Than or Equal To In the expression X !>= Y, the intent is to limit the value of X such that it is not greater than or equal to Y. If X is >= Y, the value of X !>= Y is Y–1 . If X is not >= Y, the value of X !>= Y is X. 
No 
!<= 
11 
LTR 
Not Less Than or Equal To In the expression X !<= Y, the intent is to limit the value of X such that it is not less than or equal to Y. If X is <= Y, the value of X !<= Y is Y+1. If X is not <= Y, the value of X !=< Y is X. 
No 
!= <> 
10 10 
LTR LTR 
Not Equal To != is the C language operator, <> is a common alternative notation. Both have same meaning and are interchangeable. If X is equal to Y, the value of X <> Y is 0. If X is not equal Y, the value of X <> Y is 1. 
No 
== 
10 
LTR 
Equal To. == is the C language operator. If X is not equal to Y, the value of X == Y is 0. If X is equal Y, the value of X == Y is 1. 
No 
= 
10 
LTR 
Equal To. The calculation function defines the = operator by context. When it appears enclosed within () parentheses , the = operator is treated as an alias for the == comparison operator, meaning it has the same priority and associativity. When = appears with [] set enclosures or is not enclosed, it is treated the same as the := explicit assignment operator. Only one unenclosed = may appear in a given statement or set clause. This means that expressions like X = Y = Z are illegal. The calculation function performs this context determination in order to prevent common misuse of = and == inside of comparison subexpressions, to help prevent errors. Because a plain = outside of parentheses is assumed to be an assignment, if you wanted to perform a comparison instead, you must either use an explicit == comparison operator, or put the expression with = in parentheses. 
No 
& 
9 
LTR 
Bitwise AND 
Yes 
^ 
8 
LTR 
Bitwise XOR 
Yes 
 
7 
LTR 
Bitwise OR 
Yes 
&& 
6 
LTR 
Logical AND. X && Y is equivalent to !!X & !!Y.
If X=0 or Y=0, X && Y = 0 If X not = 0 and Y not = 0, X && Y = 1 
Yes 
^^ 
5 
LTR 
Logical XOR. X ^^ Y is equivalent to !!X ^ !!Y. The operator returns 1 if X and Y are logically different. If X=0 and Y=0, X ^^ Y = 0 If X = 0 and Y not = 0, X ^^ Y = 1 If X not = 0 and Y = 0, X ^^ Y = 1 If X not = 0 and Y not = 0, X ^^ Y = 0 
Yes 
 
4 
LTR 
Logical OR. X  Y is equivalent to !!X  !!Y. If X=0 and Y=0, X  Y = 0 If X not = 0 or Y not = 0, X  Y = 1 
Yes 
:: 
3 
NON 
Swap variables. Both the lefthand operand and the righthand operand must be variables. The contents of the two variables are swapped. It is not an error if the two variables are the same, but this would serve no purpose. Once the operator is executed, the value of the expression as a whole is the value of the variable on the left side after the swap occurs. That is, after X :: Y is performed, the value of the expression will be the new value of X which will be the former value of Y. 
No 
<: 
3 
NON 
Conditional swap variables for <= Both the lefthand operand and the righthand operand must be variables. The operator implements a “sorting” or “ordering” function. In the expression X <: Y, the intent is to conditionally swap the contents of the two variables, if necessary, so that the relationship X <= Y holds true. If X <= Y is already true, no swap occurs. If X <= Y is false, a swap occurs. It is not an error if the two variables are the same, but this would serve no purpose. Once the operator is executed, the value of the expression as a whole is the value of the variable on the left side, whether swapped or not. That is, after X <: Y is performed, the value of the expression will be the new value of X which will be the former value of Y if a swap occurred, otherwise it will be the former value of X. 
No 
>: 
3 
NON 
Conditional swap variables for >= Both the lefthand operand and the righthand operand must be variables. The operator implements a “sorting” or “ordering” function. In the expression X >: Y, the intent is to conditionally swap the contents of the two variables, if necessary, so that the relationship X >= Y holds true. If X >= Y is already true, no swap occurs. If X >= Y is false, a swap occurs. It is not an error if the two variables are the same, but this would serve no purpose. Once the operator is executed, the value of the expression as a whole is the value of the variable on the left side, whether swapped or not. That is, after X >: Y is performed, the value of the expression will be the new value of X which will be the former value of Y if a swap occurred, otherwise it will be the former value of X. 
No 
[ ] 
2 
RTL 
Set expression A set expression provides a means both to evaluate expressions conditionally, and to do a simple tablelookup operation. The general form of a set expression is: expr select [ expr 1 , expr 2 , … expr n ] Each expression inside the brackets is called a set clause. The selector expression is evaluated.
If your selector expression is a conditional operator, in which true expressions evaluate to 1 and false expressions evaluate to 0, you would write an expression that used such a condition like this: conditionalExpression [ truePart , falsePart ] For example, if you wanted a value of 123 if X is greater than 5, otherwise a value of 456, you would use: X > 5 [123, 456] Here, if X>5 is true, the condition evaluates to 1, and the first set clause (123) is selected. If X>5 is false, the condition evaluates to 0, which is an outofbounds set selector, resulting in the last set clause of the set expression (456) being selected by default. Suppose you wanted to set the return variable R (or other variable) to one of these two values. In a programming language, you might do this with a statement something like, if x > 5 then r = 123 else r = 456 endif In a calculation operand, you could do the same thing with: R = X > 5 [123, 456] You are allowed to use assignment operators within a set expression. For example, if you wanted a value of 123 assigned to variable R if X is greater than 5, otherwise a value of 456, you can rewrite the expression above as: X > 5 [R=123, R=456] When you use such expressions, only the selected set clause is executed, including any variable assignments within it; the remainder is skipped. Variable assignment operators in set clauses that are not selected are not performed. If you want to use a set expression to look up values, you write the set clauses in the order you want the selector expression to choose them. Suppose variable X was supposed to be from 1 to 3, and you wanted this converted to some special values, but set to 0 if it was out of range. You could do that with a set expression like this: X[123,456,789,0] 
n/a 
= 
1 
NON 
Assignment. The calculation function defines the = operator by context. When it appears within parentheses, the = operator is treated as an alias for the == comparison operator. When = appears with [] set enclosures or not enclosed, it is treated the same as the := operator. Only one unenclosed = may appear in a given statement or set clause. This means that expressions like X = Y = Z are illegal, because when the = operator is considered an assignment operator, it is nonassociative. That means the second equal sign has neither higher nor lower precedence than the first one, but is simply illegal. The first = equal sign between X and Y is valid, but the one between Y and Z is invalid. The reason this is done is to prevent confusing the intent of the expression. If the intent is to compare Y and Z for equality, and assign the comparison result to X, use X = (Y = Z) or X = Y == Z. If the intent is to first assign Z to Y, then assign Y to X, use X = Y := Z or X := Y := Z. The calculation function performs this context determination in order to prevent common misuse of = and == inside of comparison subexpressions. The lefthand operand must be a variable name. The value of an assignment expression is the value assigned to the variable. 
n/a 
:= 
1 
RTL 
Assignment. The lefthand operand must be a variable name. The value of an assignment expression is the value assigned to the variable. The := operator may be used in chained assignments (as shown above) and inside of parentheses and set expressions. 
n/a 
Operation and use of calculations in mapping strings
As noted in the beginning of this article, calculation operands can be used with the sequence mapping items SD and SX, and the numeric conversion mapping items DD, DX, XD and XX in mapping strings.
For a complete description of mapping strings, see Working with Mapping Strings. For concise information on specific mapping commands, see Mapping Strings Quick Reference Syntax and Examples.
Here are some brief examples.
1. Suppose you want to find some “words” and append a dash and a decimal numeric suffix to them. The first “word” gets a sequence number 1, the second get 2, etc. Let’s say your data looks like this:
000151 ***** one +++
000152 ***** two +++
000153 ***** three +++
000154 ***** four +++
You can find “word” strings of varying length with a Regular Expression of R'[az]+'. In order for the sequence number mechanism of mapping strings to function, you will have to use ALL in your CHANGE command.
For the M string portion, you will want to do the following:
If you want the shortest possible number used as the numeric suffix, you would use the Varying length format, with SDV or just SD alone. If you wanted a fixed number of digits (say, 4) you would specify a command like SD4F.
Put that all together, and your CHANGE command would look like this:
CHANGE ALL R'[az]+' M"1+ '' SD4F"
That would change your data as follows:
000151 ***** one0001 +++
000152 ***** two0002 +++
000153 ***** three0003 +++
000154 ***** four0004 +++
You will note that you can do all the above without the need of a calculation operand. But, suppose you didn’t want the suffix to be a value from 1 by 1, but a value from 0 by 20 plus a constant of 5. The SD command alone can’t do that, but used with a calculation operand, it can.
Recall from the description of the preinitialized values for calculation variables, variable S will contain the value of the original sequence number. In this example, that original sequence number ranges from 1 to 4. To get the numbers the way you want, you would have to subtract one from the sequence number, multiply it by 20, and then add 5. To make that the returned value, you would then assign the result to variable R. You would specify this in a calculation operand as R=(S1)*20+5. You incorporate the calculation operand into the mapping string and issue your command like this:
CHANGE ALL R'[az]+' M"1+ '' SD4F'R=(S1)*20+5'"
That would change your data as follows:
000151 ***** one0005 +++
000152 ***** two0025 +++
000153 ***** three0045 +++
000154 ***** four0065 +++
2. Continuing with the first example, suppose what you wanted was, not to append a sequential number, but the actual line number where the data lines are found. Let’s say our data is similar to the first example, but there are excluded lines between them:
****** ****************** Top of Data *******************
  < 000150 > 
000151 ***** one +++
  < 000010 > 
000162 ***** two +++
  < 000012 > 
000175 ***** three +++
  < 000014 > 
000190 ***** four +++
****** ***************** Bottom of Data *****************
We can accomplish this basically the same way as in example 1. We will again use the SD command.
You might wonder how we can use SD if we are not going to use the sequence number in the value. The answer is that when SD has a calculation operand, the calculation function is called and variables get preinitialized as described above. However, there is no requirement that SD or any of the other mapping commands that can use a calculation operand must reference variable S or any other variable. For our second example here, we will simply ignore variable S. The reason SD is the right command to use is that we want to “generate” a number that is not based on our input data – which here consists of words, rather than numbers.
The calculation operand here will be very simple. Variable L contains the line number where the CHANGE command finds the words in question. We just have to make sure than only nonexcluded lines are affected, and change the calculation operand to copy the value of variable L to variable R.
You would issue your command like this:
CHANGE ALL NX R'[az]+' M"1+ '' SD4F'R=L'"
That would change your data as follows:
****** ****************** Top of Data *******************
  < 000150 > 
000151 ***** one0151 +++
  < 000010 > 
000162 ***** two0162 +++
  < 000012 > 
000175 ***** three0175 +++
  < 000014 > 
000190 ***** four0190 +++
****** ***************** Bottom of Data *****************
3. Converting data to a hash key. This is just a madeup example, but it may give you some idea of what you can do with calculation operands, when you have an elaborate computation to perform.
Suppose you had a series of 5digit numbers, and you want to calculate a hash value. You have various issues to contend with:
Since we want to process original numeric data, this requires a DD command instead of SD. To keep the example simple, the data will be in fixed columns:
****** ****************** Top of Data *******************
000001 19453
000002 48470
000003 03085
000004 66247
****** ***************** Bottom of Data *****************
The numbers themselves are found with a picture of P'#####' 1 5, and variable X is set to this value. The result string will be 4 digits, so the basic DD command will appear as DD4F.
This gives us the following calculation:
A=X\1L5;B=100003\(A+(A=0));((R:=B\9973)=0)[R=9973,0]
The complete CHANGE command would be:
C P'#####' 1 5 M"DD4F'A=X\1L5;B=100003\(A+(A=0));((R:=B\9973)=0)[R=9973,0]'"
Of course, a command this long is something you would prepare ahead of time. You can place the mapping string into an SPFLite SET variable, like this:
SET MYMAP = `M"DD4F'A=X\1L5;B=100003\(A+(A=0));((R:=B\9973)=0)[R=9973,0]']`
Then, issue the command as:
C P'#####' 1 5 =MYMAP
You can also compose long mapping strings in an SPFLite edit macro, and issue them using the SPF_Cmd() builtin function. The results of the CHANGE command would appear as follows. Note that these results were tested using the actual calculation function, and verified with an Excel spreadsheet:
****** ****************** Top of Data *******************
000001 2738
000002 3063
000003 1283
000004 3837
****** ***************** Bottom of Data *****************
Calculator commandline test program
Because the use of a calculator in a text editor is a somewhat novel feature, you may need a little practice working with it to be sure you understand how it works. One way to do that is by creating experimental mapping strings with calculation operands, and observing how they operate on test data. However, that process could be somewhat timeconsuming.
An easier way to explore this is with a small commandline test program called mapping_calc.exe.
This program will first prompt you for an initial sequence number (seq ‑>). If you use expressions that reference variable S, this will provide an incrementing value for each calc expression you supply, starting with the number you enter. If you just press Enter, the starting number will be 1. If you provide a starting number of 0, variable S will be set to 0 each time the calculation function is called, and the value will not be incremented.
You cannot directly set the values of the Linenumber variable L or the Columnnumber variable C. Instead, mapping_calc.exe will assign random numbers to these values for testing purposes, with variable L receiving random numbers between 1 and 1000, and variable C receiving random numbers between 1 and 80.
The program will then prompt you for an initial value (init ‑>) and a calculation operand (calc ‑>). The initial value is used to initialize variables R and X.
You type these in as you would in a mapping string (just the calculation part). It will then return the calculated result in decimal (dec ‑>) and hex (hex ‑>), and report any errors it may have found.
If a specific column is associated with an error, it will appear at the beginning of the error message in parentheses. For example, if you specified a calculation operand of R=X/\2 this would be an error, since both / and \ are binary operators, and there is no such operator as /\. Since the error would be detected when the \ was reached in position 5 of the expression, you would see the error message (5): Calc operand has syntax error at \.
To terminate the program, simply press Enter twice at the (init ‑>) prompt, or click on the [x] Windows button. In this program, hex values are entered and displayed with a leading . dot notation. You can use this hex format for both the initial value and in the calculation expression itself, as described above in the section Specifying numeric literals.
The mapping_calc.exe program is distributed with the SPFLite installation.
Created with the Personal Edition of HelpNDoc: Easily create HTML Help documents