Introduction
Wednesday, March 28, 2001
LibDocu is an ActiveX-EXE that helps you document your VB6 projects. You can use it in several ways (as I'll discuss). It's source code-oriented (it does not depend on TlbInf32.dll), and there's an object model built arround Visual Basic's programming elements such as classes and enums.
You can download the source code and the binary. Please be sure to read the VB6Core topic for a discussion about builds, dependencies, and compatibility. What's more, LibDocu depends on the VB6Core component (you can download that one from the VB6Core topic). However, LibDocu has no other direct dependencies.
In the zip file, there are same ".ldt" files (LibDocu templates). Copy them to an extra folder. Also, there are some GIFs and a StyleSheet; keep them in an extra folder as well, and copy them to the output folder after the program has finished documenting a project.
Basic concepts
LibDocu reads the .vbp file (that's the project file), and follows the included code files. However, I have restricted it to classes because documentation is most crucial here. For example, enums and structures have to be defined in (public) classes anyway in order to be public. Mostly, you'll want to use LibDocu for ActiveX-DLLs or ActiveX-EXEs. However, it shouldn't be that hard to add the parsing code for bastard modules if you really want to.
As LibDocu parses the source code, there's no need for TlbInf32.dll. The latter allows you to access the code model of a type library. The perfect example is the VB Object Browser, which uses this component to display the interfaces of referenced referenced libraries. The downside of not using TlbInf32.dll is the obvious bug potential and the loss of some features. The good news is that source code analysis lets you read code comments (other than member descriptions), making documentation a coding-time activity. LibDocu does not implement this feature yet, but I might work on it in a moment of classic VB nostalgia.
If you use LibDocu as an executable, it will generate files, mostly in HTML format, but that's really up to you. You can control both the structure as well as the formatting by providing custom templates. I'll discuss this below.
You can use LibDocu as a component, too. For example, you can hook it up in Access and write the results to a database. You'll find some hints towards the end of this page.
Coding requirements
Some of the parsing code is plain crazy. Just try to follow the parts of the CClass class that deal with properties. I use the NList class from VB6Core a lot (it's a real helper if you read from files). But the code is readable, and you can add custom parsing code if you require it. In any case, it's not Perl.
Now, there are some restrictions. If you're one of those fellows that indent procedure signatures, LibDocu won't recognize them. The same goes for the opening statement of a structure or an enum (but indenting in between the block is OK).
LibDocu does recognize, however, line continuation. That's because I use it sometimes, so I allowed for it. My tests show it's reliable.
Using the program
From a usage standpoint, there isn't much to LibDocu. Select the project file, say what kinds of types you want to include in the documentation, pick a template, specify an output folder and the file extension, and hit "Create":
In the statusbar, you can see the progress. LibDocu creates one or more files with your project's documentation; you can control how the documentation mappes to physical files in the template file. The latter also controls the formatting (binding in stylesheets or image references). But there's a lot more to the templates, as you shall soon find out.
If you use a template from the zip file, you'll probably need the resources it includes as well. Copy them to the output folder when the program has finished.
Structuring a library
LibDocu thinks that documentation should be structured into five levels. These correspond to programming elements, although there are more classes in LibDocu's object model than that. For example, a class (CClass class) is at the same level as a structure (CStructure class). Here's how your code will be organized:
- The project level. There is only one project, of course.
- Type overview level. There can three of them (one for classes, structures, and enums, respectively), giving an overview of all types of that kind.
- Type level. For example, a class, a structure, or an enum is a type.
- Member level. For example, methods, properties, or events if it's a class; elements if it's a structure; constants if it's an enum.
- Parameter level. For class members only.
These levels are important, because they interact with template files, controlling how many HTML files are created. You can have anything from one just file for the whole library to one file for every method parameter.
Enter the object model
The classes that represent code elements are organized as follows:
CProject | ||
NClasses | NStructures | NEnums |
CClass/NMembers | CStructure/NElements | CEnum/NConstants |
CMember/NParameters | CElement | CConstant |
CParameter |
I think the object model is easy to follow from just reading the names. You can use the object browser to find out more. Note that the logical levels here do not necessarily correspond to object model hierarchies. Just for the sake of nitpicking, I hereby state that an object model is not the same thing as a class hierarchy, but of course you know that.
Enter the template
You use a template to tell LibDocu how to distribute all the text it generates among files. The LibDocu template language, which I hereby introduce, has special keywords recognized by the program, which define a "section" of the documentation. A section mostly denotes a programming element, such as a class. But there can be more than one section for a given programming element (such as opening and closing sections), while some sections do not strictly correspond to any programming element.
Basicly, you put such a keyword into the template, and LibDocu will include the text following it in the output. These keywords are preceded by special tokens (called section modifiers), which tell the program whether a section goes into its own file or into the parent file.
Sections, keywords and levels
Here is a list of those keywords, ordered by those five levels mentioned above. The third column tells you which kinds of objects are actually used for the respective level:
Level | Keywords | Instance of |
---|---|---|
Project | StartProject, EndProject | CProject |
Type overview | StartClasses, EndClasses StartStructures, EndStructures StartEnums, EndEnums | CProject CProject CProject |
Type | StartClass, EndClass StartStructure, EndStructure StartEnum, EndEnum | CClass CStructure CEnum |
Member | StartMember, EndMember Element Constant | CMember CElement CConstant |
Parameter | Parameter | CParameter |
Note that you can access the CProject instance at the type overview level instead of the collection classes. I deemed it not necessary to access the collection classes, since the CProject instance has properties that return them. However, I have not yet implemented a syntax that lets you access properties that return objects. It might be interesting to get at the Count property of the collection, but not this time.
Here is an example of how to use the keywords:
#StartProject <html> <head> <title>Project: $Name%</title> </head> <body> # @StartClass <p> Class $Name%: $Description% </p> @ #EndProject </body> </html> #
This example tells a lot about how the LibDocu template language works:
- The the "#" and the "@" tokens are section modifiers. A keyword must be preceded by one of these. They also denote the end of a section; but this is optional. If you leave the closing operators out, LibDocu will assume that the section ends at the beginning of the next section. Closing section modifiers allow you to insert blank lines into the output, while you can still leave blank lines in the template file that are not used for the output for better overview. The closing modifiers must be the same as the opening modifiers, although LibDocu does not currently enforce this rule.
- Section modifiers must start at the first column. There must not be blank spaces between the opening modifiers and the keywords. After the keyword, there must be a line break.
- The "#" modifier directs LibDocu to generate a new file for every entity at the level that corresponds to the keyword. See above for a table mapping section keywords to levels.
- The "@" modifier directs LibDocu to include the output for every entity corresponding to this section in the output file of the parent section. If the section at the parent level is introduced with a "@" modifier as well, the grandparent section will be used, and so on. Note that this applies only to individual sections. For example, one can write constants into one file along with the enum, but use different files for class members (constants and class members are at the same level).
- For most keywords, there is an opening and a closing keyword. For example, "StartProject" is an opening keyword, and "EndProject" is the corresponding closing keyword. Closing keywords must be introduced by the same section modifier (either "#" or "@") as the opening keyword, although LibDocu does not enforce this rule as of now. Closing sections will be mapped to right file.
- It is not necessary to use closing keywords. If you leave out any keyword for a certain section, LibDocu will not generate output for this section. In the above example, LibDocu will print the name and description of classes, but in this case it is not necessary to close any opened HTML tags. This might be different if class members were also documented.
- It is not necessary to use every level. In the example, the type overview, member and parameter levels are ignored altogether.
Inside sections, there can be literals, variables, and conditional expressions.
Literals
Let's consider this section, which is written for every class member parameter:
@Parameter <p> This paragraph is written "as is" (including HTML tags). </p> @
The LibDocu template language is line oriented, meaning that a template is translated line by line. Line breaks are inserted into the output files as they stand in the template. It also treats everything as a literal, except keywords introduced by section modifiers (see above), variable accessors, the variable names in between variable accessors, and conditional operators.
Variables
You can access properties of the object that is available for the current section. See above for a table that maps section keywords to objects. For example, in the "StartMember" or "EndMember" section, you can access a CMember object. An object whose properties you can read in a section is called an accessible object with respect to that section.
You can only access properties with no parameters. The data type must be a primitive type (not a complex type or a reference type). The return value will converted into a String; beware of VB's output formatting.
Use the "$" sign at the beginning of a property name (opening variable accessor); use the "%" sign at the end of the name (closing variable accessor). You need to end the variable on the same line where it begins. You can use several variables in one line, however. In every line, the number of "$" signs must equal the number of "%" signs, otherwise it is erroneous. A line with a syntax error will be replaced by a blank line in the output. Here is an example:
#StartClass Name of the current class: $Name% #
Conditional expressions
You can add conditional expressions to a template, modifying the output according to the value of a property of an accessible object. Like variables, you use the "$" sign to open a conditional expression, and a "%" sign to close it. Both tokens must occur the same number of times in a line; a conditional expression cannot exceed one line. Syntax errors are handled by outputting a blank line.
In a conditional expression, there is the Test part, the True part, and an optional False part. The test part ends with a question mark ("?"), and includes exactly one of several comparison operators. The following example will add the text "GlobalMultiUse" if the Instancing property of the accessible CClass object is six:
$Instancing=6?GlobalMultiUse%
Note that the literal "GlobalMultiUse" is not enclosed in quotation marks. Likewise, if you test for a certain string, you do not enclose it in quotes; the following example uses the pattern-matching operator "~":
$Name~I*?Interface class%
Here is a list of comparison operators recognized by LibDocu:
- Equality: Use the "=" sign. Strings are compared without regard to case.
- Inequality: Use the "!" sign. Again, strings are still considered equal if they differ in case only.
- Pattern-matching: Use the "~" sign. It works like Visual Basic's "Like" operator, recognizing "*" as a wildcard, expressions like "[AB]" or "[A-C]"; but it does not recognize "?" or "!", since these are used for other purposes. The "~" operator compares case-insensitively.
As with variables, one line may contain several conditional expressions:
$Instancing=2?PublicNotCreatable%$Instancing=4?GlobabSingleUse%
You can specify a False part. In this case, use the ":" token to seperate it from the True part. This example assumes it's in the "Parameter" section, accessing an instance of CParameter:
$Mode=1?ByVal:ByRef%
Both True and False parts may contain literals, variables, or a combination of both:
$MemberType=1?Function $Name%:$Name%%
You can also nest conditional expressions, opening a new conditional block inside the True part, the False part, or both. Think of the "$" and "%" sign as parens:
$Name~I*?Interface:%Name~N*?Collection class%:$Name~G*?Global class%%%
The next example shows how you can use parens (the "$" and the "%" signs) to arrive at a different behaviour:
$Name=GUtilities?$Instancing=4?GlobalSingleUse:GlobalMultiUse%% $Name=GUtilities?$Instancing=4?GlobalSingleUse%:GlobalMultiUse%
In the first line, "GlobalMultiUse" is put out only if the Name is "GUtilites" and the Instancing property is *not* equal to 4. In the second line that's the case if the Name is *not* "GUtilities", regardless of the Instancing property.
There is a little more to the Test part of the expression. You cannot use a logical "And" there; you can only compare one property at a time. However, using nested conditional expressions, you can emulate a logical "And" (or a "Between").
A logical "Or", on the other hand, is supported. Use the "|" operator. But note that the operator precedence is different from the usual, in order to allow for shorter notation and faster interpretation: The "|" (Or) operator has higher precedence that all comparison operators:
$Instancing=4|6?Global class%
In the Test part, on the right side of the comparision operator you can use literals (including blanks), but no variables or nested conditional expression. On the left side of the comparison operator, you can use a variable only.
Other ways to use LibDocu
Using ILibUser and CLibWalker
This approach gives you more control over the output, while the overall structure is the same as when using the program, relying on CLibWalker's path through the library. Implement the ILibUser interface, create an instance of CLibWalker, pass a reference of the implementing object to the ConstructObject method, and handle the callbacks yourself.
This way, you can generate custom XML output, or write to a database. You could set up a network help system, too, and generate output on demand.
Using the object model
You can also walk through LibDocu's object model yourself, for maximum flexiblity. Just create the objects as needed, and iterate over the collections.