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

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


Channels ▼
RSS

Database

A Tiny Perl Server Pages Engine


Aug01: A Tiny Perl Server Pages Engine

Andy is a solution architect and can be reached at [email protected].


Microsoft's Active Servers Pages (ASP) and Sun's Java Server Pages (JSP) are gaining popularity because they unite client-side HTML, server-side scripting, and component-based development to produce dynamic pages. ASP supports a number of host scripting languages including Perl. The Perl module Apache::ASP provides an ASP port to the Apache Web Server with Perl as the host scripting language. So why would you want to develop your own ASP-like server-side scripting facility if ASP is already available on Apache? The answer is simple — out of necessity. In my case, I recently worked on a project to develop a performance reporting add-on to enterprise network/system management platforms including HP OpenView and Tivoli Netview. These products install their own web servers during the installation process. For example, HP OpenView installs an Apache Web Server when installed on HPUX and Solaris. (Either Microsoft Internet Information Server or Personal Web Server is used on Windows 2000 and NT.) However, the Apache Web Server installed does not have the mod_perl module that is required by Apache::ASP to work. This posed a serious problem because the add-on product needed to work out-of-the-box. Customers shouldn't be expected to install a large number of other Perl modules and devote considerable time reconfiguring and extending their platform environment to make an add-on product work. The problem gets worse as the latest version of Netview no longer uses Apache as the default web server. Writing different versions of a web-based application for different web servers is not an option because of the development and maintenance effort. My solution, which I call "Perl Server Pages" (PSP), is to develop a small footprint Perl-based cross-platform JSP-like facility — with custom tag library support — for generating dynamic pages. The only requirements are that your web server supports CGI and you have Perl 5.005 or later installed on your system. The complete source code and related files for both UNIX and Windows are available electronically; see "Resource Center," page 5.

Note that there is also an open-source project that is also called Perl Server Pages (http://psp.sourceforge.net/). However, there is no connection between that project and the one I present here.

PSP Features

PSP is modeled after JSP. It is neither an ASP nor a JSP port. PSP includes many JSP-like features and, most importantly, custom tag support. The latter gives you the ability to develop custom tag modules to encapsulate complex server-side behaviors and business rules into simple XML-like elements that content developers can use.

PSP shares the same basic elements with JSP; see Table 1. However, PSP only supports the directives in Table 2, although it does provide the built-in objects in Table 3. PSP does not provide built-in session and application objects like JSP. The limitations of PSP are described in a later section. Listing One is a simple PSP page using some basic PSP elements to display fonts with varying sizes.

Like JSP, PSP lets you create custom tags. But unlike JSP, you are not required to provide an XML-based Tag Library Descriptor (TLD) with each tag library you develop.

Listing Two uses the custom tag <test::date>, which displays the current date and time according to the format specified in the format attribute. The <test::date> custom tag does not contain a tag body (an empty element) and it is terminated with a /. In general, a custom tag may or may not have a body. But it is always terminated either with a / or an explicit end tag.

Listing Three is a PSP page that shows a more complicated example using nested tags. It uses the custom tags <test:loop>, <test:if>, <test:condition>, <test:then>, and <test:else> to generate 10 random numbers, and displays either "head" or "tail" in an unnumbered list depending on whether the random number is greater than 0.5.

Implementation

PSP uses Perl's object-oriented features exclusively. Like JSP pages, PSP pages need to be translated before use.

The first thing that you need when implementing the PSP translator is a simple parser that can handle HTML and XML-like syntax. You don't need something complicated like HTML::Parser (http://search.cpan.org/doc/GAAS/HTML-Parser-3.25/Parser.pm) that knows which tags can contain other tags, and which start tags have corresponding end tags. Nor do you need a parser that creates a hierarchical tree of HTML content. All you need is a simple parser that recognizes a small number of tags (mostly start and end tags) and parses the elements into a list. HTML::SimpleParse (http://search.cpan.org/doc/KWILLIAMS/HTML-SimpleParse-0.10/SimpleParse.pm) appears to satisfy these requirements. But on closer examination, it presents two major problems. First, it is not easily extensible to pick out elements not already recognized by it. Second, it cannot handle tags with embedded > in them (<test:something name="expr" value="x > y" />, for example). These problems have been documented by its author in the POD (Plain Old Document) for HTML::SimpleParse.

I got around the first problem by changing the module's parse method to include a couple of new tag types that I wanted it to handle. I renamed the module PSP::SimpleParse. To get around the second problem, I created a subclass named PSP::Parser to override the text method that sets the content of the text to be parsed. Before it calls its parent's text method, it replaces all single- and double-quoted strings with placeholders ~~NN~~, where NN is a unique number. The strings are saved in a hash. SimpleParse's parse method is also overridden to reverse the transformation carried out by the text method. With these transformations, there is no need for SimpleParse to handle an embedded ">." PSP expressions are another source for embedded ">" because they can appear anywhere in an HTML document. The solution here is to replace "<%=" with "&lt;%=" and "%>" with "%&gt;". The PSP translator takes care of the PSP expression code generation.

Figure 1 is the class diagram for the PSP translator program that instantiates a PSP::Translator object to handle the translation, a PSP::TransWriter object to handle the translation output, and a PSP::Writer object to handle error messages. Both PSP::TransWriter and PSP::Writer buffer output until the flush method is called.

PSP::Translator's translate method invokes PSP::Parser's file method to read in the PSP file, then its parse method to parse the text into a list. Translate then goes though each item in the list to pick out the six categories of tags that it needs to process:

  • TYPE_STARTENDTAG_S, which includes most PSP elements — declarations, directives, and scriptlets.
  • TYPE_STARTENDTAG, which includes custom tags that don't have a body as in <test:date />. The tag may contain PSP expressions that need to be processed.

  • TYPE_STARTTAG, which includes all start tags: HTML and custom. Both kinds of tags may contain PSP expressions that need to be processed. The custom tags of this type all have a body.

  • TYPE_ENDTAG, which includes all end tags: HTML and custom. HTML start tags are passed straight through. Only customer tags are handled specially.

  • All other tags include text and other more exotic elements. Since PSP expressions can appear in them, these tags are checked for the presence of PSP expressions and handled appropriately, except for HTML comments.

The translator relies on an external template file named "psp.tpl" (Listing Four) to format its output into a proper Perl script. All fields, except the keywords enclosed in ~~ (~~BODY~~, for instance), are output as is. The keywords or placeholders are substituted by the content accumulated by the PSP::TransWriter. When a fatal error is encountered, PSP::Translator's errorPage method is called to display an HTML error message before quitting. errorPage requires an external template file named "errpage.tpl." Like psp.pl, it uses placeholders (keywords enclosed in "~~") to mark where the error message should appear. These template files must be colocated in the same directory as the translators: pspt.pl and CGIpspt.pl (described later).

Translating the HTML and PSP basic elements into a Perl script is relatively straightforward compared to providing custom tag support. Listing Five shows part of the translated output of the PSP page in Listing Two. You may notice that there are a number of $out->print statements that print only blanks and new line characters in some translated pages. It is simple to eliminate these statements in the translator. The only reason why this is not done is that the output will not be intended if <PRE> and </PRE> were used in a PSP. By eliminating blanks and new lines, preformatted text won't show up properly when displayed.

The translated Perl script relies on the following classes or Perl modules to run:

  • CGI, the built-in objects $request and $response point to the one and the same CGI object. I use CGI.pm for $request and $response because it is something most Perl developers are familiar with. By using CGI, they don't have to learn yet another programming interface. For example, one uses CGI's param method to retrieve CGI parameters, cookie method to create cookies, header method to set the HTTP header, and so on.
  • PSP::Writer, a buffered output class. Since the output is buffered, you can change the HTTP header at any time using either the PSP include directive or $out->header and $response->header methods to return an HTTP header. A header containing the content type text/HTML is returned by default.

  • PSP::TagObjFactory, a PSP::TagObjFactory object is created for you automatically. It is responsible for packaging the tag body and other information into a PSP::Body object and passing it to a user-provided custom tag handler constructor to create a tag handler object (more details on this later). The name of the custom tag handler class is derived from the custom tag name by adding an extra ":" — for example, the tag handler class for handling the custom tag <test:date> is test::date.

A tag handler class or Perl module must be derived from PSP::TagHandler for each custom tag. The PSP::TagHandler class has the following methods:

  • new, a constructor.
  • doTagStart, a method you should override to handle a custom tag. If the tag does not have a body, it should return a 0. If the tag has a body, you should put an initialization code here and return a 1. In which case, the doBody method will be invoked next.

  • doBody, a method you should override to handle the manipulation of the tag body. You should return a 0 when you are done. Or you can return a 1 if you want to process the body again.

  • doTagEnd, a method you should override to do any clean up after processing the custom tag.

  • vars, a method for you to create and share variables among related classes in nested custom tags. This method is particularly useful, if you intend to create a complete markup language (like ColdFusion), in creating and sharing variables and objects among the different language elements in your markup language.

  • findAncestorWithClass, a method to find the tag in which this one is nested. This method is mandatory in writing nested tags.

  • errorPage, a method to create an HTML error page and terminate the executing Perl script.

Your custom tag may contain one or more attribute=value pairs as in <test:date format="%Y-%m-%d %a %H:%M:%S" />. In such a case, you need to provide a method called format, which is exactly the same as the attribute name, to handle the storage and retrieval of the value.

Your custom tag handler can manipulate the tag body by using the PSP::Body class. It has the following methods:

  • new, a constructor.
  • request, which returns the in-built request object.

  • response, which returns the in-built response object.

  • out, which returns the PSP::Writer being used by the current tag handler.

  • Body, which returns a string containing the entire tag body.

The PSP::TagHandler and PSP::Body classes provide a simple interface to create custom tags. Figure 2 is the PSP engine class diagram.

Listing Five shows part of the translated PSP page for the simple PSP page that uses a custom tag to display the current date and time (Listing Two). Please note that all variables with prefix PSP_ are used internally by the PSP run time. You should not change them in any way.

Each custom tag handler is contained in its own block, that is, between { and } and uses two PSP::Writers, one for its enclosing tag and the other for the current tag. These writers are named $PSP_out and $out, respectively. The tag handling code then:

1. Pushes these objects onto the stack ($PSP_stack).

2. Copies $out to $PSP_out and creates a new $out PSP::Writer object for handling the current tag's output.

3. Stores the custom tag in the variable $PSP_tag.

4. Creates a new tag object by using the built-in PSP::TagObjFactory object's CreateTagObj method. The tag handler class name is derived from the tag name. It simply inserts an extra : in the tag name. For example, the tag handler class is test::date for the custom tag test:date.

5. Calls the newly created tag object's doTagStart method.

6. Skips Step 7 if it returns a 0.

7. Keeps calling the tag object's doBody method until it returns 0.

8. Calls the tag object's doTagEnd method.

9. Calls the built-in PSP::TagObjFactory object's houseKeeping method to update the ancestry so that nested tag handlers can find their ancestors by calling the inherited findAncestorWithClass method.

10.Restores the old values of $PSP_out and $out by popping them from the PSP_stack.

A more complicated piece of code results when nested tags are involved. But it always follows the same pattern; that is, another tag handling block is inserted within the doBody loop of the enclosing tag handling code.

Examples

Listing Six contains the tag handler class for the <test:date> custom tag used in Listing Two. It overrides the doTagStart method of its superclass PSP::TagHandler. Since it supports the format attribute, it also implements the format method. When called with a value, it sets the class data member 'format'. When called without a value, it returns the stored 'format' value. This is the simplest tag handler you can build because it does not have a tag body.

Look at the tag handler classes used in the PSP page in Listing Three. Listing Seven contains the test::loop class. This time, test::loop overrides both its superclasses doTagStart and doBody methods. In doBody, it outputs its tag body and returns a 1 to repeat the operation for the number of times specified in the 'repts' data member. Also note that it implements the tag attribute handling method repts.

Listings Eight, Nine, and Ten contain the test::if, test::condition, and test::then classes, respectively. The test::if is the simplest class possible. It is an empty class that uses the PSP::TagHandler class without modification.

Test::condition overrides the doTagStart method to use the findAncestorWithClass method to check if it is enclosed inside the <test::if> tag. It calls errorPage to generate an HTML error message and terminates if an enclosing <test:if> tag is not found. DoBody retrieves the body; that is, the result in the evaluation of the specified condition, and saves it in the enclosing test::if object's hash for use by test::then and test::else.

Test::then's doTagStart is similar to that of test::condition. Its doBody method checks if a condition has been stored in the enclosing test::if's hash. If yes, it retrieves it and outputs the entire tag body, provided that the retrieved condition is 1. If there is no condition stored in the enclosing test::if object's hash, it generates an HTML error message and terminates. The test::else class is almost the same as the test::then class. Hence, it is not listed.

Deployment

There are two ways to deploy PSP pages — manual translation and automatic translation.

You can manually translate all PSP pages using pspt.pl into Perl scripts and work exclusively with the translated PSP pages. All your HTML hyperlinks (href and action properties in forms) should point to the translated .pl files. The advantage of this approach is that you are working with Perl scripts exclusively and do not have to worry about different web servers using different mechanisms to run a PSP page (discussed next).

In the automatic translation approach, you configure the web server to use the CGIpspt.pl script to run your PSP pages, that is, pages with the .psp extension. If you are using Microsoft web servers (PWS or IIS), you do this by adding a new string value to the Windows registry:

\HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Services\w3svc\ parameter\Script Map\.psp

and give it a value: "C:\Perl\bin\Perl.exe C:\cgi-bin\CGIpspt.pl %s %s". If the physical path C:\cgi-bin has been mapped to the virtual directory /cgi, after restarting the web server, you can reference your PSP page as:

http://hostname/cgi/whatever.psp

Unfortunately, the mapping mechanism is quite different on Apache (even for Apache running on Windows platforms). You have to add the following entries in Apache's srm.conf file:

AddHandler psp-script .psp

Action psp-script "C:/cgi-bin/CGIpspt.pl"

After restarting the web server, you can reference your PSP page as:

http://hostname/cgi/CGIpspt.pl/cgi/ whatever.psp

The CGIpspt.pl script uses the environment variable PATH_TRANSLATED to get the translated path name of the specified PSP page. In both cases, PATH_TRANSLATED contains C:/cgi-bin/whatever.psp. This means that HTML and PSP pages that contain links need to have the links modified when moved from Apache to IIS or vice versa. This is a bit of a pain, but the problem can be resolved by writing a simple Perl script to automate the process.

Another potential problem is related to the placement of Perl and PSP modules (.pm files). You may need to change the "#!" and "use lib" statements in CGIpspt.pl, pspt.pl, and psp.tpl for your environment to make PSP work properly. Check the web-server log to get more information if PSP does not work.

CGIpspt.pl checks for the existence of the specified .psp file and displays an error message if it is not found. It then checks if a translated version, that is, a file by the same name but with file extension .pl, is already present in the same directory. If it is not present, CGIpspt.pl invokes a PSP::Translator object to create it. If it is present, CGIpspt.pl interprets it by using the eval function. Since eval uses the current environment to interpret the translated Perl script fed to it, all CGI parameters are available to the translated script. If you should change a PSP page, you need to remove its old translated .pl file before the changes will be picked up during subsequent invocations. During the development cycle, you may want to comment out the part in CGIpspt.pl where it checks for the presence of the .pl file so that it always translates the most current version.

For better control of the PSP installation process (no flames please), I depart from the CPAN way of installation using make. I've created two packages, one for Windows and the other for UNIX. Both packages are also available electronically. The Windows package uses the usual Windows setup.exe to install. It sets up the Windows registry for you automatically. This makes deployment easy if you are using Microsoft web servers. The UNIX version uses a shell script for installation.

Limitations

PSP unites client-side HTML and server-side scripting in a small package that consists of eight small Perl modules. Most of them are around one page in size. The small package is packed with features like custom tag support. Because of its small size and independence of web-server features, PSP pages can be moved easily among different platforms. However, portability does not come without cost.

Owing to its use of the CGI interface, PSP suffers from the same problems that plagued normal Perl CGI scripts (not counting using perl_mod with Apache):

  • Each Perl script invocation spawns a new process. This may limit the scalability of the web site.
  • No built-in session or state support. You have to rely on the usual mechanisms such as hidden fields, cookies, and URL rewriting to maintain state information.

Other limitations, if you are deploying PSP using the automatic translation approach, include:

  • CGIpspt.pl uses perl's eval function. This feature is considered unsafe by some, security wise.
  • You need to give write access to your CGI directory or whatever directory you put the PSP translator and PSP pages in because the translator generates Perl scripts. This again may be considered a security risk.

  • When deploying web applications consisting of HTML or PSP pages on different web servers, hyperlinks (href and action properties in forms) pointing to these pages may need to be changed due to different web servers using different mapping mechanisms. However, this can be resolved by using another Perl script to fix up the links.

  • The translator and run-time error messages may not provide sufficient details to pinpoint the problem.

  • The translator is not a validating parser for HTML or Perl. It does not detect any syntax problems related to HTML (such as missing end tags) or Perl.

Conclusion

PSP provides a portable environment for developing dynamic web pages using features that are familiar to ASP and JSP developers such as declarations, directives, expressions, and scriptlets. It provides a simple, yet powerful custom tag support mechanism to encapsulate complex server-side behaviors and business rules into simple XML-like elements that content developers can use. The custom tag programming interface is similar to that of JSP, but does not require an XML-based tag library descriptor. These features, when paired with a wide array of Perl modules available on the Internet, make PSP a simple, yet powerful tool to use if the applications developed need to be deployed on different platforms running different web servers. The only requirements are that these web servers support the CGI interface and you must have Perl 5.005 or later installed on these systems. The PSP development is by no means complete. I will post new versions and extensions at http://www.playsport.com/psp_home/ as they are available.

DDJ

Listing One

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- Simple changing font size demo -->
<HTML><HEAD>
<TITLE>Simple Changing Font Size Demo</TITLE>
</HEAD>
<BODY>
<H3>PSP Simple Changing Font Size Demo</H3><P>
For loop incrementing font size from 1 to 5: <P>
<%-- You should not see this--%>
<% for(1..5) { %>
    <!-- iterated html text -->
    <FONT SIZE="<%= $_ %>" > Size = <%= $_ %> </FONT> <BR>
<% } %>
</BODY>
</HTML>

Back to Article

Listing Two

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  <HTML><HEAD>
  <TITLE>Simple Custom Tag</TITLE>
  </HEAD>
  <BODY>
  <H3>PSP Simple Custom Tag Demo</H3><P>
  The curent date and time: <test:date format="%Y-%m-%d %a %H:%M:%S" />
  </BODY>
  </HTML>

Back to Article

Listing Three

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Nested Custom Tag Demo</TITLE>
</HEAD>
<
BODY>
<H3>PSP Nested Custom Tag Demo</H3><P>
<UL>
<
test:loop repts=10>
<LI>
<test:if>
  <test:condition><%= (rand > .5) %></test:condition>
  <test:then>Head</test:then>
  <test:else>Tail</test:else>
</test:if>
</test:loop>
<
/UL>
<
/BODY>
</HTML>

Back to Article

Listing Four

#!perl
# ********************************************************************
# This script is generated by the PSP translator based on a PSP file.
# ********************************************************************
# import section
use PSP::Writer;
use PSP::TagObjFactory;
use CGI;

# custom tag modules import if any
~~IMPORT~~
# declaration section
~~INIT~~

# PSP initialization
my $out = new PSP::Writer;
my $request = new CGI;
my $response = $request;
my $psp = new PSP::TagObjFactory($request, $response, "errPage.tpl");
my @PSP_stack;
my $PSP_out = $out;

# header section
~~HEADER~~
# PSP body
~~BODY~~
# send buffered output to STDOUT
$out->flush(\*STDOUT);

Back to Article

Listing Five

{
push @PSP_stack, $out;
push @PSP_stack, $PSP_out;
$PSP_out = $out;
$out = new PSP::Writer;
my $PSP_tag;
$PSP_tag = qq(<test:date format="%Y-%m-%d %a %H:%M:%S" />);
my $tagobj_0 = $psp->createTagObj($PSP_out, $out, $PSP_tag);
if ($tagobj_0->doTagStart() == 1) {
    my $pred_0 = 1;
    while ($pred_0) {
        $pred_0 = $tagobj_0->doBody();
    }
}
$tagobj_0->doTagEnd();
$psp->houseKeeping($PSP_out, $out);
$PSP_out = pop @PSP_stack;
$out = pop @PSP_stack;
}

Back to Article

Listing Six

package test::date;

use POSIX;
use PSP::TagHandler;@
ISA = qw(PSP::TagHandler);

# constructor
sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'format'} = '%c';
    return $self;
}
# handler for processing the start of a custom tag
sub doTagStart {
    my $self = shift;
    my $out = $self->{'body'}->out();
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
    $out->print(strftime($self->{'format'},$sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst));
    return 0;
}
# handler for the format attribute
sub format {
    my $self = shift;
    $self->{'format'} = shift if @_;
    return $self->{'format'};
}
1;

Back to Article

Listing Seven

package test::loop;

use PSP::TagHandler;@
ISA = qw(PSP::TagHandler);

# constructor
sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    $self->{'repts'} = undef;
    return $self;
}
# handler for processing the body of a custom tag
sub doBody {
    my $self = shift;
    my $body = $self->{'body'};
    my $out = $body->out;
    # output the tag body only for the specified number
    # of times
    if ($self->{'repts'}--) {
        $out->print($body->body);
        return 3;
    }
    return 0;
}
# handler for the repts attribute
sub repts {
    my $self = shift;
    $self->{'repts'} = shift if @_;
    return $self->{'repts'};
}
1;

Back to Article

Listing Eight

package test::if;

use PSP::TagHandler;@
ISA = qw(PSP::TagHandler);
1;

Back to Article

Listing Nine

package test::condition;

use PSP::TagHandler;@
ISA = qw(PSP::TagHandler);

# constructor
sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
   return $self;
}
# handler for processing the start of a custom tag
sub doTagStart {
    my $self = shift;
    #check if enclosed in test::if
    my $parent = $self->findAncestorWithClass("test::if");
    if (defined($parent)) {
        # save the parent object
        $self->{'parent'} = $parent;
        return 1;
    }
    $self->errorPage("errPage.tpl", 
        "<test:condition> 
                    not enclosed in <test:if><test:if>");
    return 0;
}
# handler for processing the body of a custom tag
sub doBody {
    my $self = shift;
    my $body = $self->{'body'};
    my $out = $body->out;
    my $text = $body->body;
    my $parent = $self->{'parent'};

    # create condition variable in parent object
    $parent->vars('condition', sprintf("%d", $text));
    return 0;
}
1;

Back to Article

Listing Ten

package test::then;

use PSP::TagHandler;@
ISA = qw(PSP::TagHandler);

# constructor
sub new {
    my $class = shift;
    my $self = $class->SUPER::new(@_);
    return $self;
}
# handler for processing the start of a custom tag
sub doTagStart {
    my $self = shift;

    #check if enclosed in test::if
    my $parent = $self->findAncestorWithClass("test::if");
    if (defined($parent)) {
        # save the parent object
        $self->{'parent'} = $parent;
        return 1;
    }
    $self->errorPage("errPage.tpl", 
        "<then:condition> 
                   not enclosed in <test:if><test:if>");
    return 0;
}
# handler for processing the body of a custom tag
sub doBody {
    my $self = shift;
    my $body = $self->{'body'};
    my $out = $body->out;
    my $text = $body->body;
    my $parent = $self->{'parent'};

    # check if a condition has been set in the parent object
    my $cond = $parent->vars('condition');
    if (!defined($cond)) {
        $self->errorPage("errPage.tpl", 
            "<test:then> without <test:condition>");
    }
    # output the tag body only if the condition is true
    if ( $cond == 1) {
        $out->print($text);
    }
    return 0;
}
1;




Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

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

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

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

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

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

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