/* * Document generator - HTML output * * Copyright 2009 April White * Copyright 1999-2003 Luiz Rafael Culik (Portions of this project are based on hbdoc) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file LICENSE.txt. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA (or visit https://www.gnu.org/licenses/). * * As a special exception, the Harbour Project gives permission for * additional uses of the text contained in its release of Harbour. * * The exception is that, if you link the Harbour libraries with other * files to produce an executable, this does not by itself cause the * resulting executable to be covered by the GNU General Public License. * Your use of that executable is in no way restricted on account of * linking the Harbour library code into it. * * This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU General Public License. * * This exception applies only to the code released by the Harbour * Project under the name Harbour. If you copy code from other * Harbour Project or Free Software Foundation releases into a copy of * Harbour, as the General Public License permits, the exception does * not apply to the code that you add in this way. To avoid misleading * anyone as to the status of such modified files, you must delete * this exception notice from them. * * If you write modifications of your own for Harbour, it is your choice * whether to permit this exception to apply to your modifications. * If you do not wish that, delete this exception notice. * */ #include "hbclass.ch" #include "hbver.ch" #define EXTENSION ".html" #define STYLEFILE "hbdoc.css" CREATE CLASS GenerateHTML INHERIT TPLGenerate HIDDEN: METHOD RecreateStyleDocument( cStyleFile ) METHOD OpenTagInline( cText, ... ) METHOD OpenTag( cText, ... ) METHOD Tagged( cText, cTag, ... ) METHOD CloseTagInline( cText ) METHOD CloseTag( cText ) METHOD AppendInline( cText, cFormat, lCode ) METHOD Append( cText, cFormat, lCode ) METHOD Space() INLINE ::cFile += ", ", Self METHOD Spacer() INLINE ::cFile += hb_eol(), Self METHOD Newline() INLINE ::cFile += "
" + hb_eol(), Self METHOD NewFile() CLASS VAR lCreateStyleDocument AS LOGICAL INIT .T. VAR TargetFilename AS STRING INIT "" EXPORTED: METHOD NewIndex( cDir, cFilename, cTitle, cLang ) METHOD NewDocument( cDir, cFilename, cTitle, cLang ) METHOD AddEntry( oEntry ) METHOD AddReference( oEntry, cReference, cSubReference ) METHOD BeginSection( cSection, cFilename ) METHOD EndSection( cSection, cFilename ) METHOD Generate() METHOD WriteEntry( cField, cContent, lPreformatted ) HIDDEN VAR nStart INIT hb_MilliSeconds() VAR nIndent INIT 0 ENDCLASS METHOD NewFile() CLASS GenerateHTML ::cFile += "" + hb_eol() ::OpenTag( "html", "lang", StrTran( ::cLang, "_", "-" ) ) ::Spacer() ::OpenTag( "meta", "charset", "utf-8" ) ::OpenTag( "meta", "name", "referrer", "content", "origin" ) ::OpenTag( "meta", "name", "viewport", "content", "initial-scale=1" ) ::Spacer() ::OpenTag( "meta", "name", "generator", "content", "hbdoc" ) ::OpenTag( "meta", "name", "keywords", "content", ; "Harbour, Clipper, xBase, database, Free Software, GPL, compiler, cross-platform, 32-bit, 64-bit" ) ::Spacer() IF ::lCreateStyleDocument ::lCreateStyleDocument := .F. ::RecreateStyleDocument( STYLEFILE ) ENDIF ::Append( ::cTitle, "title" ) ::Spacer() ::OpenTag( "link", "rel", "stylesheet", "href", STYLEFILE ) ::Spacer() ::OpenTag( "body" ) ::Spacer() ::OpenTag( "header" ) ::Append( ::cTitle, "h1" ) ::CloseTag( "header" ) ::Spacer() ::OpenTag( "main" ) RETURN Self STATIC FUNCTION GitRev() LOCAL cStdOut := "" hb_processRun( "git rev-parse --short HEAD",, @cStdOut ) RETURN hb_StrReplace( cStdOut, Chr( 13 ) + Chr( 10 ) ) METHOD Generate() CLASS GenerateHTML LOCAL cRevision := GitRev() ::Spacer() ::CloseTag( "main" ) ::Spacer() ::OpenTag( "footer" ) ::Append( "Generated by hbdoc on " + hb_TToC( hb_DateTime() - ( hb_UTCOffset() / 86400 ), "yyyy-mm-dd", "hh:mm" ) + " UTC", "div" ) ::OpenTagInline( "div" ) ::AppendInline( "Based on commit " ) #if defined( HB_VERSION_URL_BASE ) ::OpenTagInline( "a", "href", hb_Version( HB_VERSION_URL_BASE ) + "tree/" + cRevision ) #endif ::AppendInline( cRevision ) #if defined( HB_VERSION_URL_BASE ) ::CloseTagInline( "a" ) #endif ::CloseTag( "div" ) ::CloseTag( "footer" ) ::super:Generate() #if 0 ? Round( ( hb_MilliSeconds() - ::nStart ) / 1000, 3 ) #endif RETURN Self METHOD NewDocument( cDir, cFilename, cTitle, cLang ) CLASS GenerateHTML ::super:NewDocument( cDir, cFilename, cTitle, EXTENSION, cLang ) ::NewFile() RETURN Self METHOD NewIndex( cDir, cFilename, cTitle, cLang ) CLASS GenerateHTML ::super:NewIndex( cDir, cFilename, cTitle, EXTENSION, cLang ) ::NewFile() RETURN Self METHOD BeginSection( cSection, cFilename ) CLASS GenerateHTML cSection := SymbolToHTMLID( cSection ) IF ::IsIndex() IF cFilename == ::cFilename ::OpenTagInline( "div", "id", cSection ):AppendInline( cSection, "h" + hb_ntos( ::Depth + 2 ) ):CloseTag( "div" ) ELSE ::OpenTag( "a", "href", cFilename + ::cExtension + "#" + cSection ):Append( cSection, "h" + hb_ntos( ::Depth + 2 ) ):CloseTag( "a" ) ENDIF ELSE ::OpenTagInline( "div", "id", cSection ):AppendInline( cSection, "h" + hb_ntos( ::Depth + 2 ) ):CloseTag( "div" ) ENDIF ::TargetFilename := cFilename ::Depth++ RETURN Self METHOD EndSection( cSection, cFilename ) CLASS GenerateHTML HB_SYMBOL_UNUSED( cSection ) HB_SYMBOL_UNUSED( cFilename ) ::Depth-- RETURN Self METHOD AddReference( oEntry, cReference, cSubReference ) CLASS GenerateHTML IF HB_ISOBJECT( oEntry ) .AND. oEntry:ClassName() == "ENTRY" ::OpenTag( "a", "href", ::TargetFilename + ::cExtension + "#" + oEntry:_filename ):Append( oEntry:fld[ "NAME" ] ):CloseTag( "a" ):Append( oEntry:fld[ "ONELINER" ] ):Newline() ELSE IF HB_ISSTRING( cSubReference ) ::OpenTag( "a", "href", cReference + ::cExtension + "#" + cSubReference ):Append( oEntry ):CloseTag( "a" ):Newline() ELSE ::OpenTag( "a", "href", cReference + ::cExtension /* + "#" + oEntry:_filename */ ):Append( oEntry ):CloseTag( "a" ):Newline() ENDIF ENDIF RETURN Self METHOD AddEntry( oEntry ) CLASS GenerateHTML LOCAL item LOCAL cEntry ::Spacer() ::OpenTag( "section", "id", SymbolToHTMLID( oEntry:_filename ) ) FOR EACH item IN FieldIDList() IF item == "NAME" cEntry := oEntry:fld[ "NAME" ] IF "(" $ cEntry .OR. Upper( cEntry ) == cEntry // guess if it's code ::OpenTagInline( "h4" ):OpenTagInline( "code" ):AppendInline( cEntry ):CloseTagInline( "code" ):CloseTag( "h4" ) ELSE ::OpenTagInline( "h4" ):AppendInline( cEntry ):CloseTag( "h4" ) ENDIF ELSEIF oEntry:IsField( item ) .AND. oEntry:IsOutput( item ) .AND. Len( oEntry:fld[ item ] ) > 0 ::WriteEntry( item, oEntry:fld[ item ], oEntry:IsPreformatted( item ) ) ENDIF NEXT ::CloseTag( "section" ) RETURN Self METHOD PROCEDURE WriteEntry( cField, cContent, lPreformatted ) CLASS GenerateHTML STATIC s_class := { ; "NAME" => "d-na", ; "ONELINER" => "d-ol", ; "EXAMPLES" => "d-ex", ; "TESTS" => "d-te" } LOCAL cTagClass LOCAL cCaption LOCAL lFirst LOCAL tmp, tmp1 LOCAL cLine LOCAL lCode, lTable, lTablePrev, cHeaderClass IF ! Empty( cContent ) cTagClass := hb_HGetDef( s_class, cField, "d-it" ) IF ! HB_ISNULL( cCaption := FieldCaption( cField ) ) ::Tagged( cCaption, "div", "class", "d-d" ) ENDIF DO CASE CASE lPreformatted /* EXAMPLES, TESTS */ ::OpenTag( "pre", "class", cTagClass ) ::Append( cContent,, .T. ) ::CloseTag( "pre" ) CASE cField == "SEEALSO" ::OpenTagInline( "div", "class", cTagClass ) lFirst := .T. FOR EACH tmp IN hb_ATokens( cContent, "," ) tmp := AllTrim( tmp ) IF ! HB_ISNULL( tmp ) // TOFIX: for multi-file output tmp1 := Parse( tmp, "(" ) IF lFirst lFirst := .F. ELSE ::Space() ENDIF ::OpenTagInline( "code" ):OpenTagInline( "a", "href", "#" + SymbolToHTMLID( tmp1 ) ):AppendInline( tmp ):CloseTagInline( "a" ):CloseTagInline( "code" ) ENDIF NEXT ::CloseTag( "div" ) CASE cField == "SYNTAX" ::OpenTagInline( "div", "class", cTagClass ) DO WHILE ! HB_ISNULL( cContent ) ::OpenTagInline( "code" ) ::AppendInline( Indent( Parse( @cContent, hb_eol() ), 0, -1,, .T. ),, .F. ) ::CloseTagInline( "code" ) ENDDO ::CloseTag( "div" ) OTHERWISE ::OpenTag( "div", "class", cTagClass ) ::nIndent++ lTable := .F. DO WHILE ! HB_ISNULL( cContent ) lCode := .F. lTablePrev := lTable tmp1 := "" DO WHILE ! HB_ISNULL( cContent ) cLine := Parse( @cContent, hb_eol() ) DO CASE CASE hb_LeftEq( LTrim( cLine ), "```" ) IF lCode EXIT ELSE lCode := .T. ENDIF CASE cLine == "" lCode := .T. CASE cLine == "" IF lCode EXIT ENDIF CASE hb_LeftEq( cLine, "" ; cHeaderClass := "" CASE cLine == "" ; cHeaderClass := "d-t1 d-t2" OTHERWISE ; cHeaderClass := "d-t1" ENDCASE CASE cLine == "" lTable := .F. OTHERWISE tmp1 += cLine + hb_eol() IF ! lCode EXIT ENDIF ENDCASE ENDDO IF lTable != lTablePrev IF lTable ::OpenTag( "div", "class", "d-t" + iif( HB_ISNULL( cHeaderClass ), "", " " + cHeaderClass ) ) ELSE ::CloseTag( "div" ) ENDIF ENDIF DO CASE CASE lCode ::OpenTag( "pre" ) ::Append( tmp1,, .T. ) CASE lTable ::OpenTagInline( "div" ) ::AppendInline( iif( lTable, StrTran( tmp1, " ", hb_UChar( 160 ) ), tmp1 ),, .T. ) OTHERWISE ::OpenTagInline( "div" ) IF cField $ "DESCRIPTION|" ::OpenTagInline( "p" ) ENDIF ::AppendInline( iif( lTable, StrTran( tmp1, " ", hb_UChar( 160 ) ), tmp1 ),, .F. ) ENDCASE IF lCode ::CloseTag( "pre" ) ELSE ::CloseTag( "div" ) ENDIF ENDDO ::nIndent-- ::CloseTag( "div" ) ENDCASE ENDIF RETURN METHOD OpenTagInline( cText, ... ) CLASS GenerateHTML LOCAL aArgs := hb_AParams() LOCAL idx FOR idx := 2 TO Len( aArgs ) STEP 2 cText += " " + aArgs[ idx ] + "=" + '"' + aArgs[ idx + 1 ] + '"' NEXT IF ! cText $ "pre" ::cFile += Replicate( " ", ::nIndent ) ENDIF ::cFile += "<" + cText + ">" RETURN Self METHOD OpenTag( cText, ... ) CLASS GenerateHTML ::OpenTagInline( cText, ... ) ::cFile += hb_eol() RETURN Self METHOD Tagged( cText, cTag, ... ) CLASS GenerateHTML LOCAL aArgs := hb_AParams() LOCAL cResult := "" LOCAL idx FOR idx := 3 TO Len( aArgs ) STEP 2 cResult += " " + aArgs[ idx ] + "=" + '"' + aArgs[ idx + 1 ] + '"' NEXT ::cFile += "<" + cTag + cResult + ">" + cText + "" + hb_eol() RETURN Self METHOD CloseTagInline( cText ) CLASS GenerateHTML ::cFile += "" RETURN Self METHOD CloseTag( cText ) CLASS GenerateHTML ::cFile += "" + hb_eol() RETURN Self STATIC FUNCTION StrEsc( cString ) STATIC s_html := { ; "&" => "&", ; '"' => """, ; "<" => "<", ; ">" => ">" } RETURN hb_StrReplace( cString, s_html ) METHOD AppendInline( cText, cFormat, lCode ) CLASS GenerateHTML LOCAL idx LOCAL cChar, cPrev, cNext, cOut, tmp, tmp1, nLen LOCAL lEM, lIT, lPR LOCAL nEM, nIT, nPR LOCAL cdp IF ! HB_ISNULL( cText ) hb_default( @lCode, .F. ) IF lCode cText := StrEsc( cText ) ELSE cdp := hb_cdpSelect( "EN" ) /* make processing loop much faster */ lEM := lIT := lPR := .F. cOut := "" nLen := Len( cText ) FOR tmp := 1 TO nLen cPrev := iif( tmp > 1, SubStr( cText, tmp - 1, 1 ), "" ) cChar := SubStr( cText, tmp, 1 ) cNext := SubStr( cText, tmp + 1, 1 ) DO CASE CASE ! lPR .AND. cChar == "\" .AND. tmp < Len( cText ) tmp++ cChar := cNext CASE ! lPR .AND. cChar == "*" .AND. ! lIT .AND. ; iif( lEM, ! Empty( cPrev ) .AND. Empty( cNext ), Empty( cPrev ) .AND. ! Empty( cNext ) ) lEM := ! lEM IF lEM nEM := Len( cOut ) + 1 ENDIF cChar := iif( lEM, "", "" ) CASE ! lPR .AND. cChar == "_" .AND. ! lEM .AND. ; ( ( ! lIT .AND. Empty( cPrev ) .AND. ! Empty( cNext ) ) .OR. ; ( lIT .AND. ! Empty( cPrev ) .AND. Empty( cNext ) ) ) lIT := ! lIT IF lIT nIT := Len( cOut ) + 1 ENDIF cChar := iif( lIT, "", "" ) CASE cChar == "`" .AND. ; ( ( ! lPR .AND. Empty( cPrev ) .AND. ! Empty( cNext ) ) .OR. ; ( lPR .AND. ! Empty( cPrev ) .AND. Empty( cNext ) ) ) lPR := ! lPR IF lPR nPR := Len( cOut ) + 1 ENDIF cChar := iif( lPR, "", "" ) CASE ! lPR .AND. SubStr( cText, tmp, 3 ) == "" tmp += 2 cChar := "" CASE ! lPR .AND. SubStr( cText, tmp, 4 ) == "" tmp += 3 cChar := "" CASE ! lPR .AND. ; ( SubStr( cText, tmp, 3 ) == "===" .OR. SubStr( cText, tmp, 3 ) == "---" ) DO WHILE tmp < nLen .AND. SubStr( cText, tmp, 1 ) == cChar tmp++ ENDDO cChar := "
" CASE ! lPR .AND. ; ( SubStr( cText, tmp, 5 ) == "", cText, tmp + 6 ) ) > 0 ) tmp1 := SubStr( cText, tmp + 5, tmp1 - tmp - 5 ) tmp += Len( tmp1 ) + 5 cChar := "" + tmp1 + "" CASE ! lPR .AND. ; ( SubStr( cText, tmp, 3 ) == "==>" .OR. SubStr( cText, tmp, 3 ) == "-->" ) tmp += 2 cChar := "→" CASE ! lPR .AND. ; ( SubStr( cText, tmp, 2 ) == "->" ) tmp += 1 cChar := "→" CASE cChar == "&" cChar := "&" CASE cChar == '"' cChar := """ CASE cChar == "<" cChar := "<" CASE cChar == ">" cChar := ">" ENDCASE cOut += cChar NEXT /* Remove these tags if they weren't closed */ IF lPR cOut := Stuff( cOut, nPR, Len( "" ), "`" ) ENDIF IF lEM cOut := Stuff( cOut, nEM, Len( "" ), "*" ) ENDIF IF lIT cOut := Stuff( cOut, nIT, Len( "" ), "_" ) ENDIF cText := cOut hb_cdpSelect( cdp ) ENDIF FOR EACH idx IN hb_ATokens( hb_defaultValue( cFormat, "" ), "," ) DESCEND IF ! Empty( idx ) cText := "<" + idx + ">" + cText + "" ENDIF NEXT DO WHILE Right( cText, Len( hb_eol() ) ) == hb_eol() cText := hb_StrShrink( cText, Len( hb_eol() ) ) ENDDO ::cFile += cText ENDIF RETURN Self METHOD Append( cText, cFormat, lCode ) CLASS GenerateHTML ::AppendInline( cText, cFormat, lCode ) ::cFile += hb_eol() RETURN Self METHOD RecreateStyleDocument( cStyleFile ) CLASS GenerateHTML LOCAL cString #pragma __streaminclude "hbdoc.css" | cString := %s IF ! hb_MemoWrit( ::cDir + hb_ps() + cStyleFile, cString ) /* TODO: raise an error, could not create style file */ ENDIF RETURN Self STATIC FUNCTION SymbolToHTMLID( cID ) RETURN Lower( hb_StrReplace( cID, { ; "%" => "pct", ; "_" => "-", ; " " => "-" } ) )