The best way to produce structured data is often with a structured program. You don't have to leave your comfortable C++ world to produce code for a foreign environment.
Copyright © 1995, All Rights Reserved
Introduction
Publishing on the Internet using the World Wide Web has become very popular. The ease of authoring in HTML (Hypertext Markup Language) and the sizzle of the results have attracted thousands to this new publication medium. However, the millions of us who have been surfing the web have learned some of its limitations. Two of these limitations result from the inability of most authors to compose HTML correctly and the static nature of the data.
This paper presents an object-oriented programming approach to web authoring. This approach has several advantages over the manual technique. Composing a web page dynamically, using a program, provides the opportunity to keep the information on the page up to date. Using an object-oriented programming methodology can make programmatic authoring easier and more reliable than manually marking up a text file.
HTML is relatively easy to produce. For example, to deliver the ever popular "Hello, world!" message to someone surfing the web, an author need only create a file, say, hello.htm, containing:
<html><body>Hello, world!</body></html>A web surfer has only to click on a link to this file in some web page, or at worst enter:
http://www.inet.com/hello.htmin the Universal Resource Locator (URL) field in a web browser such as Mosaic or Netscape. (www.inet.com is the supposed Internet address of the computer providing the HTTP, Hypertext Transmission Protocol, server.)
Programmatic authoring can be even easier than manually marking up text. The "Hello, world!" message is delivered by the following C++ program:
#include "html.h" void main(){ cout << (Html() << "Hello, world!"); }To execute this program, the web surfer has only to enter the URL:
http://www.inet.com/hello.exe?The ? at the end of the URL signals the HTTP process to use the Common Gateway Interface (CGI) standard method of executing the hello.exe program. The program returns its output to the HTTP processor through the standard output. The output of hello.exe is:
Content-type: text/html <html><body>Hello, world!</body> </html>HTML Formatting
HTML emphasizes functional formatting. That is, the author specifies the desired effect rather than the way in which that effect should be achieved. Headings are good example of functional formatting in HTML. The HTML standard specifies seven levels of headings. A heading is marked in the text by preceding the heading with a tag such as <H3> and marking the end of the heading with a closing tag, in this case, </H3>. The number following the H must be in the range of 1 to 7.
The way a web browser chooses to display a heading is up to the browser. For example, a first-level heading may be in a very large, bold font, centered in the display window. Other levels may use smaller or less bold or non-centered text. In fact, most browsers support only five distinct heading levels. Levels five through seven are all displayed the same way.
Adding a heading to the "Hello, world!" page and putting the text below in bold we get:
<html> <body> <H3>OOP Output</H3> <B>Hello, world!</B> </body> </html>A program to accomplish the same function is:
#include "html.h" void main(){ Html html(); Block heading("H3"), text("B"); heading << "OOP Output"; text << "Hello, world!"; html << heading << text; cout << html; }This example introduces the Block class. An object in this class is a string of text that knows it must be enclosed between an opening and closing tag. The string in the Block declaration is the text to include in these tags. The text injected into the Block is output between the tags. A more compact style for writing the same program is:
#include "html.h" void main(){ cout << (Html() << ( Block("H3") << "OOP Output" ) << ( Block("B")<< "Hello, world!") ); }The same result could be achieved without using the Block class:
#include "html.h" void main(){ Html html(); html << "<H3>OOP Output</H3>\n" << "<B>Hello, world! </B>\n" ; cout << html; }However, this second approach relies on the programmer to remember to enclose the heading and bold text with the correct, corresponding pairs of opening and closing tags.
Class Hierarchy
The class hierarchy used in these programs is illustrated in the diagram shown in Figure 1. The up arrows in the this diagram indicate that the class named below is derived from the class named above. The horizontal arrow from Block to BlockOption is used to indicate that there are BlockOption objects included as members of the Block class. The double-headed arrow is used to indicate that there are multiple BlockOption members in each Block class.
The ostrstream class is provided by the IOStreams library. Good tutorial information on the IOStreams library is difficult to find. As a result, many C++ programmmers do not take advanage of all the IOStreams library has to offer. The best reference that I have been able to locate is Steve Teale's C++ IOStreams Handbook, (Addison-Wesley, 1993).
The ostrstream class is conceptually an infinite character buffer. All the built-in scalar types in C++ and arrays of characters (char*) can be injected into an object in this class. The injection results in a character representation of the object injected in the ostrstring object.
The String class inherits all of the features of the ostrstring class, and adds a few important new features:
- a (char*) cast operator that returns a pointer to a buffer containing a null-terminated string consisting of all the characters injected. The empty string is treated in the usual way a pointer to a single null character is returned. (The ostrstream class provides the member function char *str() that returns a pointer to its character buffer, but this buffer is not null terminated automatically. The function ends can be invoked to manually inject a null character into the ostrstream.)
- the ability to automatically continue injecting characters into the String object even after a string buffer pointer is returned. (The ostrstream class freezes the buffer. The String class member operator char* unfreezes the ostrstream buffer and backs up the buffer pointer so that the next injection overwrites the terminting null character.)
- automatic memory deallocation. (The ostrstream class requires the user to deallocate the buffer returned by the str member function.)
There is a potential problem with using the buffer pointer returned by the String class, which results from its liberal definition. After a pointer to a String buffer is returned by the String class cast operator (char*), additional characters can be injected into the String. The subsequent insertion of additional characters may overflow the String's buffer, and consequently the String may be forced to reallocate its buffer. As a result, the previously returned pointer may no longer be valid.
It is interesting to note that the ostrstream class treats user access to its buffer in the opposite way. After a pointer to an ostrstream buffer is returned by the str function, the buffer is frozen, the user cannot put any more characters into it, and the user must free the memory allocated to the buffer. The String class inherits this behavior from the ostrstream class. But the String class is defined to provide a more liberal approach.
The memory management approach of the ostrstream class is incompatible with the contemporary philosophy of object memory management. Part of this philosophy is that a class should assume the full responsiblity for the allocation and deallocation of the memory required for its members. The ostrstream class compromises this objective in favor of the reliability of the buffer pointer returned by the str function. The String class makes the opposite compromise.
A corollary of this contemporary philosophy of object memory management is that an object should make a copy, called a deep copy, of its member data, rather than store a pointer to an object whose memory allocation is outside of its control, called a shallow copy. This behavior is a characteric of the String class. It is even more clearly a characteristic of the Block class in its treatment of its BlockOption member data described below.
The Pause class illustrates a justifiable exception to this deep copy requirement in its treatment of the static member object char* programName. The Pause class expects programName to be assigned as a pointer to the first command-line argument, argv[0], in its init function. As a result, the Pause class feels justified in making a shallow copy, since this pointer will not change. That is, the buffer will not move, and its contents will not change.
Class Blockoption
The BlockOption class encapsulates the requirement to add options to the opening block tag. For example, a hypertext reference is added as options in an anchor tag. More concretely, if the file named sayHello.htm is in the same directory as hello.exe and contains:
<html> <body> <a HREF="hello.exe?"> Say, "Hello, world!" </a> </body> </html>a web browser will display it something like:
Say, "Hello, world!"An underline, or some similar effect, is added by the web browser to indicate that this text is a hypertext link. The user can click on this text to jump to the associated hypertext reference which in this case is the program hello.exe.
The expression, <a HREF="hello.exe?">, is an anchor tag with the block option named HREF and the value hello.exe?.
The C++ code to produce the equivalent HTML is:
#include "html.h" void main(){ out << (Html() <<( (Block("a") << (BlockOption("HREF")<< "hello.exe?") << "Hello, world!" ) ); }Each Block object can contain up to ten BlockOptions. This limit is hard coded in the Block class definition. If a user attempts to put more than ten options in a Block object, all attempted injections after the tenth are rejected without warning.
The BlockOption member data in the Block class would be better implemented as a more general type of collection which removes the ten option limit. In fact, that is the case in the production version of this code. An implementation approach to collections that is general enough to deal with the BlockOption member data in the Block class added too much complexity to this presentation.
Class Html
The Html class is a String containing the body of an HTML document. When an Html object is injected into an ostream such as cout, the Html object adds the appropriate response header and body header information and includes the String text as the body text within the body and html tags. The net result of injecting an Html object into an ostream is that information required by the HTTP server is output with the characters previously injected into the Html object output as the body of the HTML.
It is sometimes desirable to output an HTML file, rather than output directly to the HTTP server. The difference is that an HTML file must omit the response header. An Html class member function noResponseHeader(void) is provided to meet this requirement. If the noResponseHeader message is sent to the Html object any time before it is injected into an ostream, the response header will be omitted, and properly formatted HTML will be constructed.
An optional HTML title can be included in the Html output by including the title text in the Html constructor. For example, the following program will produce HTML with the title, OOP Output:
#include "html.h" void main(){ cout << (Html("OOP Output") << "Hello, world!"); }The Html class also makes provision for including the <isIndex> tag in the HTML heading. This is accomplished by including a non-zero integer as the second parameter in the Html class constructor, as in Html("OOP Output", 1).
Filtering Text
Some special characters in HTML text can produce unintended results. For example, the HTML author who wrote:
<html><body> John makes A where $15 < A and A > 45 shekels. </body></html>expected the Netscape browser to display:
John makes $15 < salary and salary > 45 shekels.But instead, it displayed:
John makes $1545 shekels.The browser displayed none of the characters in the HTML source between the first < in the body and the subsequent >. It interpreted the sequence of characters, < A and A >, as a tag. In order to prevent this type of problem, HTML provides alternate representations of characters so that the browser will not be confused by the < and other special mark-up characters. So, for example:
<html><body> John makes $15 < salary and salary > 45 shekels. </body></html>replaces the < with <. This tells the browser to display the < character rather than interpret it as the beginning of a tag.
The Block class and its sibling classes, Anchor and Html, have a filter message which translates potentially confusing characters as they are injected into the Block object. For example:
const char *text = "John makes $15 < salary and \ salary > 45 shekels."; #include "html.h" void main() { cout << Html().filter(text); }will produce the output,
John makes < salary and salary > 45 shekelsThis filter also transforms the ampersand character, & to &.
Filtering text is important when the program is not in control of the text that it is inserting into the HTML. For example, text could be retrieved from an HTML form into which the user typed something. Also, the text could be read from a dynamic database in which data is being constantly updated. In such situations, it is not safe to assume the < and & characters are not included in the text. It is better to replace them with an escape sequence that results in the character being displayed.
Pausing
When developing a console application in Visual C++, executing the program under development in the GUI environment results in the output being displayed in a temporary window that is closed when the program exits. One consequence of this behavior is that the text displayed by the program is not displayed long enough to be read. It is necessary to add a "press Enter to continue" message before exiting the program and wait for user input before continuing. A similar situation occurs using the Borland C++ integrated development environment to develop DOS or win32/console applications.
Adding an unconditional pause at exit has a very undesirable consequence. When such a program is executed by the HTTP server, the server waits for the user input, but there is no console attached to the server process. As a result, the server process hangs! Under NT this is even more of a problem than under UNIX. Under NT the server must be stopped and restarted to remove the hung process.
The obvious solution to this problem is to remember to remove the pause before testing the code under the HTTP server. Unfortunately, this simple solution does not work very well in practice. Every programmer who uses this system to develop HTML programs sooner or later forgets to remove the pause before testing under the HTTP server. The result of this failure is a hung HTTP process which is difficult recognize since it is not visible.
A more complex solution, implemented in the code listing at the end of this article, has proved effective. This solution consists of creating a Pause class that tests the command-line parameter list for -pause. If the Pause class finds this pause flag in the argument list, it plants an unconditional pause at exit with in the code, atexit(pause).
The Pause class is implemented with only private static data, and it declares the pause functions as friends. In order to get the pause to execute it is necessary to:
- add -pause to the command line arguments
- include argc and argv in the formal parameter list of main
- include Pause::init(argc, argv) near the beginning of main()
This rather complex technique assures that the pause will be executed in the GUI environment but will not be executed when the program is executed by the HTTP Server. If the programmer skips any one of these steps, the pause will not be executed. This situation is immediately observed when the program is tested locally in the GUI development tool, and it is easily corrected.
Compilers and Configuration
There are a few lines of compiler specific code in strClass.h that deal with casting I/O manipulators. This requirement results from different implementation of I/O manipulators in different IOStreams implementations. Two different compilers are handled by a preprocessor switch. These are the Borland and Microsoft compiler implementations. If your compiler is not one of these, the Borland implementation will probably work if your compiler is based on a relatively recent version of IOStreams.
Two different development environments were used to help assure cross compiler compatibility. These development environments were Microsoft NT version 3.51, and Microsoft Windows for Workgroups version 3.11 running under MS-DOS version 6.22. In both development environments, the final production code was executed under NT using the HTTP Server version 0.96 from the European Microsoft Windows Academic Center.
In the NT development environment, it is possible to compile and test on the same computer. Two type of tests were conducted, local and client/server. The local tests consisted of executing the programs as console applications in the Visual C++ GUI development system. The HTML source code output of a program is displayed in a console window by the system when the program is executed.
The C++ source code presented above was compiled and linked with both Microsoft Visual C++ version 2.2 under Microsoft NT version 3.51 and by Borland C++ version 4.02 under Microsoft Windows for Workgroups version 3.11. The production code was developed as a console application under the Microsoft compiler. The production code was developed as an Executable/Win32/Console application in the Borland compiler. The Borland compiler was used primarily to assure a minimal level of cross-compiler compatibility.
On the server side, the executables were tested on the Internet on an Intel 486 processor with 32 Mbytes of RAM running HTTP Server version 0.96 from the European Microsoft Windows Academic Center.
On the client side, two web browsers were used: NCSA Mosaic for Microsoft Windows version 2.0 Final Beta, and Netscape Navigator 2.0 Beta 2.
Don Colner is a software engineer for I-net, Inc. He has an advanced degree in mathematics from Georgetown University, and has over 25 years experience in computer systems and software development. He has been an operations research analyst, has managed software development projects, and has developed many software products used internationally. He may be reached at colner@ccmail.inet.com.