CGI version of this site


CGI version of this site


This site is distributed as a zipfile which contains all the programs to run the Rexx Parser and its child project programs, as the Rexx Highlighter, as well as the related documentation: the file you are reading now, for example, is part of this documentation.

The documentation is distributed as a set of Markdown files. On the reference distribution site, https://rexx.epbcn.com/rexx.parser/, these Markdown files are translated on-the-fly into HTML by a Rexx Apache CGI program, CGI.markdown.rex, reproduced below.

The program is provided as a sample only, and it should be customized to fit your needs if you want to use it in your own site. In particular, parts of the Bootstrap 3 page are generated by an accompanying program, rexx.epbcn.com.optional.cls, which tries to encapsulate most of the localisms present in the http://rexx.epbcn.com/rexx.parser/ site.

Installation

Please refer to the installation page for details.

CGI.markdown.rex

Location: [installation directory]/cgi/CGI.markdown.rex.

#!/usr/bin/env rexx
/******************************************************************************/
/*                                                                            */
/* CGI.markdown.rex -- Sample Apache CGI markdown processor                   */
/* ========================================================                   */
/*                                                                            */
/* This program is part of the Rexx Parser package                            */
/* [See https://rexx.epbcn.com/rexx.parser/]                                  */
/*                                                                            */
/* Copyright (c) 2024-2025 Josep Maria Blasco <josep.maria.blasco@epbcn.com>  */
/*                                                                            */
/* License: Apache License 2.0 (https://www.apache.org/licenses/LICENSE-2.0)  */
/*                                                                            */
/* Requirements:                                                              */
/*                                                                            */
/* + Apache                                                                   */
/* + Pandoc                                                                   */
/* + Use the "Action" directive to define an action pointing to               */
/*   this CGI routine, for example                                            */
/*      Action Markdown /cgi-bin/CGI.markdown.rex                             */
/* + Tell to Apache which files you want to process using the action handler  */
/*   you have defined. For example,                                           */
/*                                                                            */
/*        <Files *.md>                                                        */
/*          SetHandler Markdown                                               */
/*        </Files>                                                            */
/*                                                                            */
/* Version history:                                                           */
/*                                                                            */
/* Date     Version Details                                                   */
/* -------- ------- --------------------------------------------------------- */
/* 20241220    0.1c First public release                                      */
/* 20241230    0.1e Switch to a local copy of Bootstrap 3 to allow for colors */
/*                  in code sections to be printed correctly.                 */
/* 20250222    0.2  Add drive to path for requires                            */
/* 20250222         Change Address PATH to Address COMMAND for pandoc         */
/*                                                                            */
/******************************************************************************/

  Signal On Syntax

--------------------------------------------------------------------------------
-- ::REQUIRES does not work well with "../" paths                             --
--------------------------------------------------------------------------------
  package   = .context~package
  local     =  package~local
  mypath    =  FileSpec( "Drive", package~name )FileSpec( "Path", package~name )
  local ~ . = .File~new( mypath"../" )~absolutePath      -- Creates ".."

  Call Requires .."/cls/FencedCode.cls"
  Call Requires mypath"rexx.epbcn.com.optional.cls"

  Signal SkipOverRequiresAndSyntaxHandler

Requires:
  package~addPackage( .Package~new( Arg(1) ) )
  Return

--------------------------------------------------------------------------------
-- Error handling in CGIs is complicated, we'd better have a decent handler   --
--------------------------------------------------------------------------------

Syntax:
 .output~destination( .stdOut )
  ConditionObject = Condition("O")
  major     = ConditionObject~rc
  code      = ConditionObject~code
  message1  = ConditionObject~errorText
  message2  = ConditionObject~message
  program   = ConditionObject~program
  line      = ConditionObject~position
  traceBack = ConditionObject~traceBack
  Say "Content-type: text/plain; charset=utf8"
  Say ""
  Say "Syntax error" major "in" program "line" line": " message1
  Say "Error" code":" message2
  Say ""
  Say "Traceback follows:"
  Do line Over traceBack
    Say line
  End
Exit

--------------------------------------------------------------------------------

SkipOverRequiresAndSyntaxHandler:

--------------------------------------------------------------------------------
-- We will collect all our output in an array. To this effect, we use         --
-- a small class that subclasses .Array and at the same time inherits from    --
-- .OutputStream. Indeed, we only need to implement the SAY method.           --
--                                                                            --
-- We change the destination of the output monitor to point to this           --
-- hybrid array.                                                              --
--------------------------------------------------------------------------------

  arrayOutput = .ArrayOutputStream~new
 .output~destination( arrayOutput )

--------------------------------------------------------------------------------
-- We now create .request and .response objects to encapsulate the            --
-- complexities of the CGI protocol.                                          --
--------------------------------------------------------------------------------

 .environment~request  = .Http.Request~new
 .environment~response = .Http.Response~new

--------------------------------------------------------------------------------
-- PATH_TRANSLATED should point to the markdown file we have to process,      --
-- and REQUEST_URI should contain the request URI.                            --
--------------------------------------------------------------------------------

  file = .request~PATH_TRANSLATED
  url  = .request~REQUEST_URI

--------------------------------------------------------------------------------
-- We need to ensure that the CGI processor has not been called directly;     --
-- if this happens, the environment strings will be empty. In that case,      --
-- we produce a soft 404.                                                     --
--------------------------------------------------------------------------------

  If file == ""         Then Exit .Response~404
  If url  == ""         Then Exit .Response~404

--------------------------------------------------------------------------------
-- We don't want queries                                                      --
--------------------------------------------------------------------------------

  If url~contains("?")  Then Exit .Response~404

--------------------------------------------------------------------------------
-- We don't want strangely formatted URIs                                     --
--------------------------------------------------------------------------------

  If url~contains("//") Then Exit .Response~404
  If url~contains("/.") Then Exit .Response~404
  If url~contains("..") Then Exit .Response~404

--------------------------------------------------------------------------------
-- We are using "readme.md" as one of the index pages, using the Apache       --
-- DirectoryIndex directive.                                                  --
--------------------------------------------------------------------------------

  -- In case we need to form canonical URLs
  If url~endsWith("readme.md") Then url = Left(url, Length(url) - 9)

--------------------------------------------------------------------------------
-- The file should exist in the filesystem; if not, that's a 404 too.         --
--------------------------------------------------------------------------------

  resolved = Stream(file,"C","Query exists")
  If resolved == ""     Then Exit .Response~404

--------------------------------------------------------------------------------
-- There is a bug in the Linux version of ooRexx by which trailing slashes    --
-- are wrongly accepted at the end of a filename. We don't want that.         --
-- See https://sourceforge.net/p/oorexx/bugs/1940/                            --
--------------------------------------------------------------------------------

  If file~endsWith("/") Then Do
    file2 = Strip(file,"T","/")
    resolved2 = Stream(file2,"C","Query exists")
    If resolved2 == resolved Then Exit .Response~404
  End

--------------------------------------------------------------------------------
-- See if an accompanying extra style .css file exists                        --
--   This is is a file with the same name as the cgi, with ".css" added at    --
--   the end. It is useful for specifying variables that are file-dependent,  --
--   like the running header and footer (this should be done with the         --
--   string-set property, but it is not properly implemented in the major     --
--   browsers).
--------------------------------------------------------------------------------

  Select Case FileSpec("Name",file)
    When "slides.md"  Then ownStyle = "slides"
    When "article.md" Then ownStyle = "article"
    Otherwise              ownStyle = ""
  End
  extraStyle = Stream(resolved".css","c","Q exists")
  If extraStyle \== "" Then Do
    p = LastPos(.File~separator,extraStyle)
    extraStyle = SubStr(extraStyle,p+1)
  End

--------------------------------------------------------------------------------
-- Ok, now we have a file to process. Read it into an array                   --
--------------------------------------------------------------------------------

  file = resolved
  source = CharIn( file, 1, Chars(file) )~makeArray
  Call Stream file, "c", "close"

--------------------------------------------------------------------------------
-- We process Rexx fenced code blocks first                                   --
--------------------------------------------------------------------------------

  source = FencedCode( file, source )

--------------------------------------------------------------------------------
-- Now call pandoc. It will transform markdown into html by default           --
--------------------------------------------------------------------------------

  contents = .Array~new
  Address COMMAND 'pandoc --from markdown-smart+footnotes' -
    '--reference-location=section' -
    With Input Using (source) Output Using (contents)

--------------------------------------------------------------------------------
-- As the document title, pick the contents of the first h1 header            --
--------------------------------------------------------------------------------

  title = "Missing title"
  chunk = contents~makeString("L"," ")
  If chunk~contains("<h1") Then
    Parse Var chunk "<h1" ">"title"</h1>"

--------------------------------------------------------------------------------
-- Our output will be html                                                    --
--------------------------------------------------------------------------------

 .response~"Content-Type" = "text/html"

--------------------------------------------------------------------------------
-- Copy the HTML resource, with some substitutions                            --
--------------------------------------------------------------------------------

  template = .Resources~HTML

  Do line Over template
    Select
      When line = "%title%"         Then Say  title
      When line = "%header%"        Then Call OptionalCall PageHeader, title
      When line = "%contents%"      Then Say  contents
      When line = "%footer%"        Then Call OptionalCall PageFooter
      When line = "%sidebar%"       Then Call OptionalCall Sidebar, url
      When line = "%contentheader%" Then Call OptionalCall ContentHeader, url
      When line = "%extrastyle%"    Then
        If extraStyle \== "" Then
          Say "    <link rel='stylesheet' media='print' href='"extraStyle"'>"
      When line = "%ownstyle%"    Then
        If ownStyle \== "" Then
          Say "    <link rel='stylesheet'" -
              "href='/rexx.parser/css/print/"ownstyle".css'>"
      Otherwise Say line
    End
  End

--------------------------------------------------------------------------------
-- We are done. We only have to revert to normal .stdout, ...                 --
--------------------------------------------------------------------------------

 .output~destination

--------------------------------------------------------------------------------
-- ... emit the stored HTTP headers ...                                       --
--------------------------------------------------------------------------------

 .response~printHeaders

--------------------------------------------------------------------------------
-- ... and an empty line to separate http headers and html body ...           --
--------------------------------------------------------------------------------

  Say ""

--------------------------------------------------------------------------------
-- ...and we can finally emit the body, by dumping the whole array at once    --
--------------------------------------------------------------------------------

  Say arrayOutput

Exit 0

--------------------------------------------------------------------------------
-- This allows us to optionally implement headers, footers, breadcrumbs and   --
-- sidebars.                                                                  --
--------------------------------------------------------------------------------

OptionalCall: Procedure
  Signal On Syntax Name OptionalRoutineMissing
  routineName = Markdown"."Arg(1)
  Call (routineName) Arg(2)
  Return
OptionalRoutineMissing:
  code = Condition("O")~code
  If code == 43.1, Condition("A")[1] = routineName Then Return
Raise Propagate

--------------------------------------------------------------------------------

::Class Http.Request
::Method unknown
  Return Value(Arg(1),,"ENVIRONMENT")

--------------------------------------------------------------------------------

::Class Http.Response
::Method 404
 .output~destination( .stdOut )
  Say 'Content-Type: text/html; charset=iso-8859-1'
  Say 'Status: 404 Not Found'
  Say ""
  Say '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">'
  Say '<html><head>'
  Say '<title>404 Not Found</title>'
  Say '</head><body>'
  Say '<h1>Not Found</h1>'
  Say '<p>The requested URL was not found on this server.</p>'
  Say '<hr>'
  Say Value('SERVER_SIGNATURE',,"ENVIRONMENT")'</body></html>'
  Return 0 -- Necessary to allow EXIT syntax
::Method init
  Expose headers order
  order   = .Array~new
  headers = .Directory~new
::Method printHeaders
  Expose headers order
  Do header Over order
    Say header":" headers[header]
  End
::Method unknown
  Expose headers order
  messageName = Arg(1)
  If messageName~endsWith("=") Then Do
    messageName = messageName[1, Length(messageName) - 1]
    messageName = Process(messageName)
    order~append( messageName )
    headers[ messageName ] = Arg(2)
    Return
  End
  messageName = Process(messageName)
  If \headers~hasIndex(messageName) Then Return ""
  Return headers[messageName]

Process:
  array = messageName~translate("  ","-_")~space~makeArray(" ")
  Do i = 1 To array~items
    array[i] = Upper( array[i][1] )Lower( SubStr(array[i],2) )
  End
  Return array~makeString("Line","-")

--------------------------------------------------------------------------------

::Class ArrayOutputStream SubClass Array Inherit OutputStream
::Method say
  Use Strict Arg string -- We make string not optional
  self~append(string)
  Return 0

--------------------------------------------------------------------------------
--  Structure of a page:
--
--  +------------------------------------------+
--  |         page header, inc. title          |
--  +------------------------------------------+
--  |       content header      |   side bar   |
--  +---------------------------+              |
--  |                           |              |
--  |          contents         |              |
--  |                           |              |
--  |        [9 columns]        |  [3 columns] |
--  +------------------------------------------+
--  |              page footer                 |
--  +------------------------------------------+
--
--------------------------------------------------------------------------------

-- We are loading a local copy of Bootstrap 3, customized to eliminate
-- print media styles, and then we add our own media styles css.

::Resource HTML
<!doctype html>
<html lang='en'>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>
      %title%
    </title>
    <link rel="stylesheet" href="/css/bootstrap.min.css">
    <link rel='stylesheet' href='/rexx.parser/css/rexx-light.css'>
    <link rel='stylesheet' href='/rexx.parser/css/rexx-dark.css'>
    <link rel='stylesheet' href='/rexx.parser/css/markdown.css'>
    %ownstyle%
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Questrial&display=swap" rel="stylesheet">
    %extrastyle%
    <!--[if lt IE 9]>
      <script src="https://cdn.jsdelivr.net/npm/html5shiv@3.7.3/dist/html5shiv.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/respond.js@1.4.2/dest/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <div class='container bg-white' lang='en'>
      %header%
      <div class='row'>
        <div class='col-md-9'>
          %contentheader%
          <div class='content'>
            %contents%
          </div>
        </div>
        <div class="col-md-3 text-center text-larger">
          %sidebar%
        </div>
      </div>
      %footer%
    </div>
    <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
    <script src="/js/bootstrap.min.js"></script>
  </body>
</html>
::END