Rebol [
	Title: "make-doc-pro"
	Version: 1.0.8
	Date: 13-Jan-2004
	File: %make-doc-pro.r
	Author: "Robert M. Münch"
	Email: robert.muench@robertmuench.de
	Home: http://www.robertmuench.de/projects/mdp/
	Copyright: {This parser can be freely used for non-commercial purposes.
		For commercial use, you have to contact the author.
	}
	Purpose: {Parses the make-doc-pro markup language into a
		datastructure that can be into other
		document formats (such as HTML) with good titles, table
		of contents, section headers, indented fixed-spaced
		examples, bullets and definitons.
	}
	Category: [file markup text util 4]
	Library: [
		level: 'advanced
		platform: 'all
		type: [dialect tool]
		domain: [dialects files html markup parse text text-processing web xml]
		tested-under: [view 1.2.8 [W2K XP]]
                   support: "See Rebol header"
                   license: "See Rebol header"
	]
	Note: {Based on make-doc.r from Carl Sassenrath, Rebol Technologies Inc.}
]

; do %../rm_library.r

;{
;-- Library paste BEGIN
split: func ["Splits value" v [series!] rest [series!] /last] [
    rest: either last [find/last v: copy v rest][find v: copy v rest]
    if rest [clear rest] v
]

; Stack Datastructure Object
stack!: make object! [
	stack: make block! []

	push: func['value][
		either (type? value) == block!
			[insert/only stack value]
			[insert stack value]
	]

	pop: does [
		either (length? stack) > 0
		[
			value: first stack
			remove stack
			return value
		]
		[return none]
	]

	top: does [
		if not empty? [return first stack]
	]

	empty?: does [
		either (length? stack) == 0 [return true][return false]
	]

	ontop?: func ['value][
		either value == top [return true][return false]
	]

	instack?: func ['value][
		either result: find stack value [return index? result][return none]
	]

	reset: does [
		clear stack
	]

	size: does [
		return length? stack
	]

	debug: does [
		foreach entry stack [probe entry]
	]

; 	insert: func ['value][
; 		either (type? value) == block!
; 			[insert/only tail stack value]
; 			[insert tail stack value]
; 	]
]


;--- Additional Functions

assert: func[ test [block!] text [string!]][
	if not reduce test [
		print reduce ["Asssert:" text "failed!"]
	]

	exit
]

pif: func [[throw] {polymorphic if, minimum checking, no default, compatible
	with:
	  computed blocks,
	  Return
	  Exit
	  Break
	  non-logic conditions
  }
	args [block!]] [
    if not unset? first args: do/next args [
        either first args
        	[either block? first args: do/next second args
        		[do first args]
        		[first args]
        	]
        	[pif second do/next second args]
    ]
]

;-- Library paste END
;}


;-- global data
; debug_mode: true
debug_mode: false
; light_mode: true
; light_mode: false

;--- make-doc-pro parser
mdp-parser: context [
	mdp-stack: 		make stack! []	; storage to hold the mdp datastructure that will be the result of the parsing
	inline-stack: make stack! []	; storage to hold mdp inline markup block
	active-stack: mdp-stack				; reference to active stack

	skip-counter: 0								; counter how many chars of the input stream have been skipped (should be 0)
	rule-names: 	make stack! [] 	; used to store rule-names for debugging
	lastemitted: none							; stores the last emitted name
	lastcode: none								; last parsed code

	;--Flags
	debugparse: true				; if true the mdp-parser will print debug messages
	debugparse: false				; if true the mdp-parser will print debug messages
	flags: make stack! []		; stack of flags that are used to control the parser

	;--MDP-Stack handling
	emit: func ['name value /local tmp] [
		; trim all obsolete spaces
	  if string? value [
	  	if (back tail value) == " " [
	  		trim/tail value
	  		if value = "" [exit]
	  		append value " "
	  	]
	  ]

		; pack name value into a block
		tmp: reduce to-block [name value]

		; and push this as block onto the stack
		active-stack/push 	:tmp

		lastemitted: name
	]

	emit-section: func [num /local tmp] [tmp: to-word join "sect" num emit :tmp text]

	;--Helper functions
	init: does [
		mdp-stack/reset
		inline-stack/reset
		active-stack/reset
		rule-names/reset
		flags/reset

		lastcode: lastemitted: none
		skip-counter: 0

		; reset parse rule as this rule is altered after parsing the header
;		titlerule: either light_mode [copy ["~~~"]][copy [opt "~~~"]]
		titlerule: copy [opt "~~~"]
	]

	inputstream: func [width [integer!]][print ["###" mold copy/part mark width]]

	; debug just pushes the rule-name onto the stack
	; this function might be called many times more than debugo that pops a value from the stack
	; therefore we first make a pop and then a push, the first pop will be on an empty stack but that's ok per definition: nothing will happen

	debug: func ['rule-name][]
;	debug: func ['rule-name][rule-names/pop rule-names/push rule-name]

	; debug-out prints the rule-name; this indicates that the rule was called
	debugo: func [value][]
;	debugo: func [value][print reduce ["-->" rule-names/pop "--" mold value]]

	insert-file: func [str file /local text] [
      if file/1 = "%" [remove file]

			; try to read the include file
      pif [
      	exists? file 								[text: read file]
      	exists? join mdp-path file	[text: read join mdp-path file]
      	true												[alert reform ["Missing include file:" file] exit]
			]


			; insert the text from the include file up the end specifier or to the end
      insert/part str text any [find text "^/###" tail text]
  ]

	inline-parsing: func [text][
		if none? text [exit]

		lastemitted_tmp: lastemitted
		active-stack: inline-stack

		either debugparse
			[
				print ["Inline-Parsing:" text]
				print ["Inline-Parsing correct:" parse/all text inlinemarkup]
			]
			[parse/all text inlinemarkup]

		active-stack: mdp-stack
		lastemitted: lastemitted_tmp

		either debugparse
			[reverse inline-stack/stack print ["Inline-Stack:" mold inline-stack/stack]]
			[reverse inline-stack/stack]
	]

  ;
	;--make-doc-pro parsing rules
	;

	pdebug: [here: (prin "pdebug:" probe copy/part here 35)]

	;Parsing storage variables
	text: none		; stores parsed text sequences
	para: none		; stores paragraph parts

	;Charactersets
	space: 	charset " ^-"
	spaces: [any space]

	nochar: charset " ^-^/"
	chars: 	complement nochar

	;Helper rules
	line: 		[copy text to newline]		; copy the text from the actual stream position up to | or 'newline' (not including these chars) into 'text. The | is need because of table handling
	paragraph:[copy para some [chars [to newline | to end]]]
	word: 		[some space copy text some chars] 	; skip spaces and copy all characters until the next whitespace

	example:   	[copy code some [indented | some newline indented] (lastcode: copy code)]
	indented:  	[some space chars to newline ]

	; this rule is used to parse the first line of a document which is the title. The title can either
	; be marked with ~~~ or nothing. A title starting with no markup is only allowed once in a document.
	; This rule is changed to ["~~~"] after the title has been parsed by removing 'opt
	titlerule: [opt "~~~"]

;--- Main rules
	mdp: [
		some [
			;--Debug point
			mark:

			;--Title and End of document
				titlerule (debug title) line (debugo text emit title text if (first titlerule) == 'opt [remove titlerule])
			| "###" to end

			;--Section Headers
			| ["===" | "-1-"] line (emit-section 1)
			| ["---" | "-2-"] line (emit-section 2)
			| ["+++" | "-3-"] line (emit-section 3)
			| ["..." | "-4-"] line (emit-section 4)

			;--Special common notations:
			| (debug define) define	(
					debugo text

					inline-parsing text

					; really a define or only the : character es first char in a line
					either none? defword
						[emit paragraph copy inline-stack/stack]
						[	; if there are several defines in a row, join them all in one table
							if lastemitted == 'define [emit define-join none]
							emit define reduce [defword copy inline-stack/stack]
						]

					inline-stack/reset
				)
			| "#" (debug numberitem) 	numberitem 	(debugo text
							; parse inline markup chars
							inline-parsing text

							; and emit the parsed stack
							emit number copy inline-stack/stack

							; clear inline stack
							inline-stack/reset
						)
			| (debug bulletitem) 	bulletitem 	(
					debugo text

					; remember numbered-bullets
					if lastemitted == 'number [flags/push number-bullets]

					; parse inline markup chars, this will handle tables as well, solution see below
					inline-parsing text

					; it could be that we entered this rule because the first character was a * but didn't introduced
					; a bullet sequence but a bold sequence, this is the case if the length of bulles is 0
					either (length? bullets) == 0
					[emit paragraph copy inline-stack/stack]
					[
						; inline-stack could now contain a newcell or newrow command, which would be emitted as a bullet item
						; resulting in a wrong output because the closing bullet markup  would be emitted after the newcell/newrow
						; markup. The following code handles this situation be spliting out the tablehandling code

						; split stack newcell or newrow as this ends our bulletitem
						newcell_split: split inline-stack/stack [[newcell #[none]]]
						newrow_split:  split inline-stack/stack [[newrow #[none]]]

						; the shorter of both will be emitted as bullet
						either (length? newcell_split) < (length? newrow_split)
							[bullet_emit: newcell_split]
							[bullet_emit: newrow_split]

						either flags/top == 'number-bullets
							[emit bullet reduce [(length? bullets) - 1 bullet_emit]]
							[emit bullet reduce [length? bullets bullet_emit]]

						; the rest will be emitted as paragraph
						rest: exclude inline-stack/stack bullet_emit
						if not empty? rest [emit paragraph rest]
					]

					; clear inline stack
					inline-stack/reset
				)
			| ";" to newline	; comment

			;--Translator options
			| "=include" 	word here: (insert-file here to-file text)
			| "=meta" word (emit meta text)
			| (debug file) "=file" 		word (debugo text emit file text)
			| "=toc" 			(debug TOC) to newline (debugo "" emit toc none)
			| "=outline"	(debug TOC) to newline (debugo "outline" emit toc 'outline)
			| "=language" word
			| "=options" 	some space some [
					"faq"
					| "debug" (debug_mode: true) ; (debug: debug_d debugo: debugo_d)
				] to newline

			;--Special output
			| "=" copy bars some "-" (emit bar length? bars)
			| "=image" image to newline
			| "=url" 	 some space [{"} copy url to {"} 1 skip | copy url some chars] copy text to newline (either text == none [emit url reduce [url form url]][emit url reduce [url trim text]])
			| "=view" (
					; we use first as the stack isn't reversed yet. So the newest emitted stuff comes first.
					replace first mdp-stack/stack 'example 'view
				)

			;--Special sections:
			| "\in" 		to newline 	(emit indent-in none)
			| "/in" 		to newline	(emit indent-out none)
			| "\note" 	line 				(emit note-in text)
			| "/note" 	to newline 	(emit note-out none)
			| "\table" 	[some space "header" (emit table-in 'tableheader) | (emit table-in none)] to newline (
					flags/push intable		; keep track of tablemode on stack
					table: tablehandling)	; change table rule to handle table characters
			| "/table" (
					emit table-out none
					if flags/pop <> 'intable [print "Flags-Stack not correct!"]
					table: notablehandling)	; change table rule to emit normal table characters

			;--Example Text
			| (debug example) example (debugo code
					; remove starting newlines
					while [(first code) == newline] [remove code]

					pif [
						; header flag is pushed in newline rule below
						flags/instack? header	[emit example code]
						true									[emit header  code]
					]
				)

			;--Text
			| (debug paragraph) paragraph (debugo para

					; parse inline markup chars
					inline-parsing para

					pif [

						lastemitted == 'bullet 		[emit bullet-join reduce [length? bullets copy inline-stack/stack] lastemitted: 'bullet]
						lastemitted == 'number 		[emit number-join para lastemitted: 'number]
						lastemitted == 'paragraph	[emit paragraph-join none emit paragraph copy inline-stack/stack]
						true											[emit paragraph copy inline-stack/stack]
					]

					; clear inline stack
					inline-stack/reset
				)

			;--Newline and join handling
			| newline [some newline
					; This is the section handling 'newline 'newline
					; If nothing special is needed, we reset lastemitted to none, so the rest of the parser behaves
					; in default mode (for example bullet emitting in rule 'TEXT will be reset to normal text output.
					(
						; remember that we did / should have emited a header already because the header text
						; has to follow the newline character of the titleline
						flags/push header

						; if we reach this point do some clean-up work as 'newline 'newline is the termination sequence
						; for bullet lists, numbered lists etc.
						lastemitted: lastemitted_tmp: none

						if flags/top == 'number-bullets [flags/pop]
					)
				|
					; This is the section handling 'newline
				 	(
				 		pif [lastemitted 					 == 'header	 		[emit header-join none]]
					)
				]

			; This rule will skip everything from the input stream that we couldn't handle yet with any other rule
			| skiped: skip (print ["SKIP:"  mold copy/part skiped 1] skip-counter: skip-counter + 1)
		]
		( ; cleanup stack
			if find to-string mdp-stack/top "join" [
				mdp-stack/pop
			]
		)
	]

	; Tricky rules: These rules have to handle all kind of special cases for the defineword because a defineword can contain
	; the seperator character '-' as well. The trick is to use a break-rule to exit the any rule part in defineword and reset the
	; input stream after the any rule. Than the defineseparator will be parsed again splitting the text into the two pieces
	; defword and line that we need. (Thanks to Gabriele Santilli for this trick).
	define:    				[definestart: ":" copy defword defineword defineseparator copy text definition]

	definechars: 			complement charset " ^/"
	defineseparator: 	[spaces "-" spaces | #"^/" (defword: none) :definestart]
	defineword: 			[any
		[ ; consume as much chars as possible
			some definechars (break-rule: none)
			[ ; if we have a defineseperator break-out -> defineseperator will be consumed in rule 'define
					tmp: defineseparator (break-rule: [end skip])
				| #" "
			]
			; execute break-rule that will exit this rule
			break-rule
		]
		; reposition input stream to 'defineseperator position so this will be parsed
		:tmp
		]

	definitionchars: 			complement charset "^/"
	definitionseparator: 	["^/:" | "^/^/"]
	definition: 					[any [
				; read as much chars as possible
				some definitionchars (break-rule: none)
				[
					; if there is a definitionseperator found exit rule
					tmp: definitionseparator (break-rule: [end skip])
					| "^/"
				]
				break-rule
			]
			; reposition to 'definitionseperator for furthe parsing
			:tmp
		]

	numberitem: [line]

	bulletitem: [
		boldstart: copy bullets some "*" opt [some boldchars] opt [ "*" ["^/" | "^-" | "|" | " " | "," | ";" | "." | "!" | "?"] (remove bullets)]
		(boldstart: skip boldstart length? bullets)
		:boldstart
		line
	]

	;-- Inline markup character handling
	parachars:  		complement charset "|~_-*=" ; ^/" ; |="
	markupdelimiters:	[[" " | "." | "," | ";" | "|" | "||" | newline | none]]
	boldchars: 			complement charset "*|^/"
	underlinechars: complement charset "_^/"
	italicchars:		complement charset "~^/"
	strikechars:		complement charset "-^/"

	parapart:	[copy inline_para some parachars]

	tablehandling: [
		(debug newrow) "||" (debugo none emit newrow none lastemitted_tmp: none) ; emit paragraph [[newrow ""]])

		; This will handle empty cells at the begin of a line
		| (debug newcell) "|" (debugo none emit newcell none lastemitted_tmp: none) ; emit paragraph [[newcell ""]])
	]

	notablehandling: ["|" (emit parapart "|")]

	table: notablehandling

	; mark: (mark: skip mark -1) :mark -> Move input cursor one char to front to check special chars
	inlineprolog: [mark: (mark: skip mark -1) :mark markupdelimiters]
	inlineepilog: [mark: (mark: skip mark -1) :mark chars]
	inlineend:		[mark:	(if (length? mark) == 0 [insert markupdelimiters 'opt]) markupdelimiters (if (length? mark) == 0 [remove markupdelimiters]) :mark]

	inlinemarkup:	[
		some [
			(debug parapart) parapart (debugo inline_para emit parapart inline_para)
			; Tricky rules:
			; 	1. we parse 'markupdelimiter AND inline markup character
			;		2. Next we check for a char, a whitespace is not allowed as this would indicate that the markup char should be emitted
			;		3. and reposition the input stream to get this char in the following copy sequence as well
			;		4. we catch all characters that are not the inline markup character
			;		5. we check for a char AND the closing inline markup character
			;		6. we check if this closing inline markup character is followed by delimiter so that we can be sure it's not the inline character we should emit
			| (debug bold) inlineprolog "*" mark: chars :mark copy boldtext some boldchars
				[
						; the if part is needed as the string could directly end with an inlinemerkup character
						inlineepilog "*" inlineend (debugo none emit bold boldtext)
					| newline	(debugo none emit parapart rejoin ["*" boldtext])
				]
			| (debug italic) inlineprolog "~" mark: chars :mark copy italictext some italicchars
				[
						inlineepilog "~" inlineend (debugo none emit italic italictext)
					| newline	(debugo none emit parapart rejoin ["~" italictext])
				]
			| (debug strike) inlineprolog "-" mark: chars :mark copy striketext some strikechars
				[
						; if the inlinemarkup char is the last in the line we have to make the check for the markupdelimiters optional
						; and reposition the input-sequence pointer
						inlineepilog "-" inlineend (debugo none emit strike striketext)
					| newline (debugo none emit parapart rejoin ["-" striketext])
				]
			| (debug underline) inlineprolog "_" mark: chars :mark copy underlinetext some underlinechars
				[
						inlineepilog "_" inlineend (debugo none emit underline underlinetext)
					| newline (debugo none emit parapart rejoin ["_" underlinetext])
				]
			| (debug star) "*" (debugo none emit parapart "*")
			| (debug snail) "~" (debugo none emit parapart "~")
			| (debug minus) "-" (debugo none emit parapart "-")
			| (debug under) "_" (debugo none emit parapart "_")

			; --Special handling
			| (debug image) "=image" image
			| (debug url) "=url" some space [{"} copy url to {"} 1 skip | copy url some chars] copy text to "=" (either text == none [emit url reduce [url form url]][emit url reduce [url trim text]])

			;--Table handling
			| table

			; This rule will skip everything from the input stream that we couldn't handle yet with any other rule
			| skiped: skip (print ["Inline SKIP:"  mold copy/part skiped 1] skip-counter: skip-counter + 1)
		]
	]

	; check alignment
	alignement: [
		some space [
				"left"		(emit align 'left)
			| "right"		(emit align 'right)
			| "center"	(emit align 'center)
			| "float"		(emit paragraph-join none emit align 'float)
		]
	]

	; handles images
  image: [opt alignement some space copy text some chars (emit image to-file text)]
]

;--- HTML Emitter
; Character-Level Formatting
; -------------------------------------------------------------------##
html-format: context [
    pos: marked: href: ""
    ascii-charset: make bitset! #{
    000000003B9EFFAFFEFFFFF7FFFFFF7F00000000000000000000000000000000
    }
    html-charset: make bitset! #{
    FFFFFFFFC46100500100000800000080FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    }
    special-charset: make bitset! #{
    0004000000610000000000080000000000000000000000000000000000000000
    }
    space: make bitset! #{
    0002000001000000000000000000000000000000010000000000000000000000
    }
    html-map: [
        34 #quot 38 #amp 60 #lt 62 #gt
        64 ##064 128 #euro ##8364 130 ##8218 131 ##402 132 ##8222 133 ##8230 134 ##8224 135 ##8225 136 ##710
        137 ##8240 138 ##352 139 ##8249 140 ##338 145 ##8216 146 ##8217 147 ##8220
        148 ##8221 149 ##8226 150 ##8211 151 ##8212 152 ##732 153 ##8482 154 ##353
        155 ##8250 156 ##339 159 ##376 160 #nbsp 161 #iexcl 162 #cent 163 #pound
        164 #curren 165 #yen 166 #brvbar 167 #sect 168 #uml 169 #copy 170 #ordf
        171 #laquo 172 #not 173 #shy 174 #reg 175 #macr 176 #deg 177 #plusmn 178 #sup2
        179 #sup3 180 #acute 181 #micro 182 #para 183 #middot 184 #cedil 185 #sup1
        186 #ordm 187 #raquo 188 #frac14 189 #frac12 190 #frac34 191 #iquest
        192 #Agrave 193 #Aacute 194 #Acirc 195 #Atilde 196 #Auml 197 #Aring
        198 #AElig 199 #Ccedil 200 #Egrave 201 #Eacute 202 #Ecirc 203 #Euml
        204 #Igrave 205 #Iacute 206 #Icirc 207 #Iuml 208 #ETH 209 #Ntilde
        210 #Ograve 211 #Oacute 212 #Ocirc 213 #Otilde 214 #Ouml 215 #times
        216 #Oslash 217 #Ugrave 218 #Uacute 219 #Ucirc 220 #Uuml 221 #Yacute
        222 #THORN 223 #szlig 224 #agrave 225 #aacute 226 #acirc 227 #atilde
        228 #auml 229 #aring 230 #aelig 231 #ccedil 232 #egrave 233 #eacute
        234 #ecirc 235 #euml 236 #igrave 237 #iacute 238 #icirc 239 #iuml 240 #eth
        241 #ntilde 242 #ograve 243 #oacute 244 #ocirc 245 #otilde 246 #ouml
        247 #divide 248 #oslash 249 #ugrave 250 #uacute 251 #ucirc 252 #uuml
        253 #yacute 254 #thorn 255 #yuml
    ]

    to-entity: func [ent [string! issue!]][return rejoin ["&" ent ";"]]
    to-encode: func [doc /local old new ent][
        old: doc/1
        new: switch/default to-integer old [
            34 [to-entity either any [head? doc doc/-1 = #" " doc/-1 = #"^(A0)" doc/-1 = #"^/"][##8220][##8221]]
            39 [to-entity either any [head? doc doc/-1 = #" " doc/-1 = #"^(A0)" doc/-1 = #"^/"][##8216][##8217]]
        ][
            either ent: select html-map to-integer old [to-entity ent]["?"]
        ]
        change/part doc new length? to-string old
    ]

    to-pre: func [doc /local old new ent][
        old: doc/1
        new: either ent: select html-map to-integer old [to-entity ent]["?"]
        change/part doc new 1
    ]

    regular-rule: [
        any [
            some ascii-charset |
            #"&" ["amp" | "copy" | "nbsp" | "quot" | "gt" | "lt"] #";" |
            #"<" opt "/" pos: [
                "em" | "strong" | "code" | "br /" |
                "br/"  (change/part pos "br /" 3) :pos "br /" |
                "br"  (change/part pos "br /" 2) :pos "br /" |
                "b" (change/part pos "strong" 1) :pos "strong" |
                "i" (change/part pos "em" 1) :pos "em"
            ] #">" |
            #"." pos: [
                2 space (change/part pos "  " 2) skip |
                #"." #"." (change/part back pos "… " 3)
            ] |
            #"(" pos: [
                "c)" (change/part back pos "©" 3) |
                "r)" (change/part back pos "®" 3) |
                "o)" (change/part back pos "°" 3) |
                "tm)" (change/part back pos "™" 4) |
                "br)" (change/part back pos 
4) :pos 5 skip | "e)" (change/part back pos "€" 3) ] | #"-" pos: #"-" (change/part back pos "—" 2) | #"[" pos: [ "TM]" (change/part back pos "™" 4) | "break]" (change/part back pos "
" 7) :pos 5 skip ] | special-charset | #"^/" | html-charset pos: (to-encode back pos) :pos ] ] pre-rule: [ any [ some ascii-charset | #"^/" pos: (change/part back pos
1) 5 skip | #"'" | special-charset | html-charset pos: (to-pre back pos) :pos ] ] url-rule: [ "[url " copy href to #"]" #"]" copy marked to "[/url]" ( replace pos rejoin ["[url " href "]" marked "[/url]"] rejoin [ {} marked ] ) ] bold-rule: [ "[b]" copy marked to "[/b]" (replace pos rejoin ["[b]" marked "[/b]"] rejoin ["" marked ]) ] italic-rule: [ "[i]" copy marked to "[/i]" (replace pos rejoin ["[i]" marked "[/i]"] rejoin ["" marked ]) ] markup-rule: [ some [pos: to #"[" [url-rule | bold-rule | italic-rule | #"[" pos:]] to end ] set 'escape-html func [text /tags][ ; pos: doc ; parse/all pos either tags [pre-rule][regular-rule] if any [word? text none? text empty? text] [return text] if not tags [parse/all text markup-rule] ; trim/lines text] text ] ] html-emitter: context [ html: [] flags: make stack! [] alignment: none ; used to temporarly store an alignment hint name: none ; these two hold the current item (name/value) of the parsed mdp-stack value: none path: none ; used for site_mode sects: [0 0 0 0] ; this is the counter for our 4 level sections toc-title: "Contents" ; text to use for TOC img-num: 0 ; counter for generated images ;--Helper functions init: does [ clear html flags/reset alignment: name: value: none img-num: 0 clear-sects ; sects: [0 0 0 0] ] nsp: "^/ " ; nsp = newline-space html-codes: [ "&" "&" "<" "<" ">" ">" {"} """ "Á" "Á" "á" "á" "À" "À" "à" "à" "Â" "Â" "â" "â" "Ä" "Ä" "ä" "ä" "Ã" "Ã" "ã" "ã" "Å" "Å" "å" "å" "Æ" "Æ" "æ" "æ" "Ç" "Ç" "ç" "ç" "Ð" "Ð" "ð" "ð" "É" "É" "é" "é" "È" "È" "è" "è" "Ê" "Ê" "ê" "ê" "Ë" "Ë" "ë" "ë" "Í" "Í" "í" "í" "Ì" "Ì" "ì" "ì" "Î" "Î" "î" "î" "Ï" "Ï" "ï" "ï" "Ñ" "Ñ" "ñ" "ñ" "Ó" "Ó" "ó" "ó" "Ò" "Ò" "ò" "ò" "Ô" "Ô" "ô" "ô" "Ö" "Ö" "ö" "ö" "Õ" "Õ" "õ" "õ" "Ø" "Ø" "ø" "ø" "ß" "ß" "Þ" "Þ" "þ" "þ" "Ú" "Ú" "ú" "ú" "Ù" "Ù" "ù" "ù" "Û" "Û" "û" "û" "Ü" "Ü" "ü" "ü" "Ý" "Ý" "ý" "ý" "ÿ" "ÿ" ] comment { escape-html: func [text][ if any [word? text none? text empty? text] [return text] foreach [from to] html-codes [replace/all/case text from to] return text ] } emit: func [data] [append html reduce data] ; Reset all section counter to 0 clear-sects: does [change/dup sects 0 4] ; Increase section counters, create section counter string and return this string sect-num?: func [num /local n sn] [ ; increase section counter at num place by 1 change at sects num n: sects/:num + 1 ; reset all section counters behind 'num to 0 change/dup at sects num + 1 0 4 - num ; initialize local variable sn: copy "" ; append num times the section counter to form a w.x.y.z number repeat n num [append sn join sects/:n "."] ; remove trailing point remove back tail sn ; return the created number copy sn ] ;--- Predefined HTML emitter objects html-copyright: [

; -- add your footer information stuff below -- ; ---add your footer information stuff above -- "Document formatter copyright " "Robert M. Münch" ". All Rights Reserved."
"XHTML 1.0 Transitional formatted with Make-Doc-Pro Version:" system/script/header/version " on " now/date " at " now/time

] stylesheets: [ "@media screen {" "h1,h2,h3,h4,h5,p,a,br,li,td, .underline {font-family:Arial, Helvetica, sans-serif;text-align:justify}" "hr {text-align:center}" "p,table {margin-left: 10px;margin-right:10px}" ".defword {white-space:nowrap}" ".deftable {border-style:none;vertical-align:top}" ".deftablefaq {border-style:solid;border-width:thin}" ".end {font-size:8pt}" ".example {margin-left:50px;margin-right:50px;border:2px solid;padding: 10px;background-color:#EEEEEE}" ".header {margin-left:50px;margin-right:50px;border:2px solid;padding: 10px;background-color:yellow}" ".indented {margin-left: 50px}" ".litable {text-align:left}" ".new {border-right: 10px solid; padding-right: 10px; font-family:Arial}" ".note {margin-left:50px;margin-right:50px;border:2px solid;padding: 10px;background-color:#F0F0D0}" ".tocindent {margin-left: 20px}" ".top {font-size:8pt;text-align:right}" ".underline {text-decoration:underline}" "}" "@media print {" "h1,h2,h3,h4,h5,p,a,br,li, #underline {font-family:Arial;text-align:justify;orphans:5;widows:5}" "p,li,td {font-size:10pt}" "ul,ol {page-break-after:avoid;orphans:5;widows:5}" "}" ] ;--HTML code generation functions (sorted alphaticaly) align: does [alignment: value] bar: does [emit [{
}]] bullet: has [counter][ counter: 0 ; is this a numbered list? if (back tail html) == [] [remove back tail html flags/push number-list-end] ; how far do we need to go back in the hierarchy? until [ counter: counter - 1 (pick tail html counter) <> ] ; add one and make positiv counter: (counter + 1) * -1 ; first remove as few as possible loop min counter value/1 [remove back tail html] ; deletes counter times ; if necessary add a new (the first remove might be tag) remove back tail html ; deletes emit " " ; now use the normal paragraph emitter without a paragraph-start and paragraph-end ; and emit the text flags/push no-paragraph-end flags/push no-paragraph-start value: value/2 paragraph emit ; emit counter (a.k.a value/1) times loop counter [emit ] ; replace closing tag with for numbered lists if num-list [ remove back tail html emit ] ] define: does [ either site_mode [ either flags/top == 'no-define-start [flags/pop] [emit
] ;now emit the definition word emit [
any [escape-html value/1 " "]
] ;emit the definition text emit
; now use the normal paragraph emitter without a paragraph-start and paragraph-end ; and emit the text flags/push no-paragraph-end flags/push no-paragraph-start ; emit inline markup value: value/2 paragraph ; and close definition emit [
] ] [ either flags/top == 'no-define-start [flags/pop] [emit [{

}]] ;now emit the definition word emit [] ;emit the definition text emit
any [escape-html value/1 " "] ; emit inline markup value: value/2 paragraph ; and close definition emit [

] ] ] define-join: does [ "In normal mode, we use tables and need to reopen the table to add more defines" if not site_mode [ remove back tail html ; deletes remove back tail html ; deletes

] ] epilog: does [ emit [
"[ " "back to top" " ]"
] emit
emit html-copyright emit [] ] example: does [ either flags/top == 'no-example-start [emit [newline escape-html value
] flags/pop] [emit [
 escape-html value 
]] ] example-join: does [ assert [(back tail html) == []] "Indented end expected" remove back tail html ; deletes ] file: does [ ; emit epilog and write output file epilog write destinationfile html ; set new output filename with HTML extension destinationfile: either (pick parse value "." 2) == "html" [value][to-file join value ".html"] ; clear old HTML output html: clear head html ; write prolog prolog ; reset section counters. Either a new TOC will be emitted and section counter will be reset there too ; but if not we are save with this call ; clear-sects ] footer: func [path [path!] /local node short-path][ emit

repeat node length? path [ short-path: copy/part path node if node > 1 [emit " / "] emit [] emit [ build-tag compose [ a href (rejoin [anchor-root next short-path either node > 1 ["/"][""]]) title (either node = 1 ["home"][mold short-path/:node]) ] short-path/:node ] ] emit [

"last update: " now

] return "" ] header: does [ either flags/top == 'no-header-start [emit [newline escape-html value ] flags/pop] [emit [
  escape-html value 
]] ] header-join: does [ assert [(back tail html) == []] "Header end expected" remove back tail html ; deletes ] image: func [value][ ; check if image file exists if all [not light_mode not exists? value] [print ["Image file:" value "not found."]] switch/default alignment [ left [emit [

{}

]] right [emit [

{}

]] center [emit [

{}

]] float [emit [" " {} " "]] ][emit [

{}

]] ] indent-in: does [ emit
] indent-out: does [ emit
] menu: func [path [path!] /local short-path menu href marked tag][ menu: make block! 20 short-path: copy/part path 1 foreach [menu-path menu-content] menus [ if short-path = menu-path [append menu menu-content] ] remove-each [style content] menu [style <> 'url] if empty? menu [return none] emit [ newline newline newline newline ] ] note-in: does [ emit [
] if found? value [emit []] emit [
value
] ] note-out: does [ emit [
] ] number: does [ either all [flags/top <> 'sequence-ended (back tail html) == []] [remove back tail html] [ emit
    if flags/top == 'sequence-ended [flags/pop] ] ; emit start of list item emit
  1. ; now use the normal paragraph emitter without a paragraph-start and paragraph-end flags/push no-paragraph-end flags/push no-paragraph-start paragraph ; emit list item and list end emit [
] ] number-join: does [ remove back tail html ; deletes remove back tail html ; deletes emit [" " escape-html value ] ] paragraph: has [name pvalue] [ ; no paragraph start if inside a table or a tableheader if any [flags/top == 'intable flags/top == 'tableheader] [flags/push no-paragraph-start] ; emit paragraph start? either flags/top <> 'no-paragraph-start [emit

] [flags/pop] ; value now has a name/value structure itself foreach tmp value [ name: tmp/1 pvalue: escape-html tmp/2 switch/default name [ paragraph-join [] url [url/plain pvalue] image [image pvalue] align [alignment: pvalue] parapart [either any [none? pvalue empty? parse pvalue ""][emit " "][emit pvalue]] ; handle explicit spaces and none values bold [emit [ pvalue ]] italic [emit [ pvalue ]] strike [emit [ pvalue ]] underline [emit [ pvalue ]] newcell [either flags/top == 'tableheader ; This is the code for the second cell an on. First cell is handled in table-in [ emit [] ; keep track of number of cells number_of_table_cells: number_of_table_cells + 1 number_of_emitted_table_cells: number_of_emitted_table_cells + 1 ] [ ; handle empty cells if (back tail html) == [] [emit " "] emit [] number_of_emitted_table_cells: number_of_emitted_table_cells + 1 ] ] newrow [ either flags/top == 'tableheader [flags/pop] ; did we handled a tableheader directive? [ ; fill in missing table cells loop (number_of_table_cells - number_of_emitted_table_cells) [emit [ " "]] ] emit [] ; reset counter to 0, this will keep counting consistens because function paragraph will be called ; more than one time for one cell if the cell text was typed with linebreak. In this case one cell ; consists of serveral [paragraph [parapart...]] blocks number_of_emitted_table_cells: 0 ] ][print ["1: Unknown INLINE-TAG found:" name]] ] ; no paragraph end if inside a table or a tableheader if any [flags/top == 'intable flags/top == 'tableheader] [flags/push no-paragraph-end] ; if no paragraph-start was emitted than normally no paragraph-end is required either flags/top <> 'no-paragraph-end [emit

] [flags/pop] ] paragraph-join: does [ ; prin "--->" probe flags/stack ; no tag removing if inside a table if (back tail html) == [

] [ remove back tail html ; deletes

and keeps no-paragaph-start on the stack emit " " ; add space around text ] ; prin "<---" probe flags/stack ] prolog: does [ emit emit ; emit ; Start HTML document emit [] ; Emit general stylesheets emit [] ; closing head is emitted in title rule ; title rule is only called if light_mode = false ] section: func [num /local sn] [ either site_mode [ if num = 1 [emit newline] sn: sect-num? num emit [nsp "}] ; if toc? [emit [sn " "]] emit value emit [ ""] ] [ ; Include a horizontal line before a new section starts if all [num = 1 sects <> [0 0 0 0]][ emit [
"[ " "back to top" " ]"
] emit
] ; create correct section number string sn: sect-num? num ; emit section tags, section number, section string and closing tag emit ["" {} either (length? sn) == 1 [join sn "."][sn] " " escape-html value ""] ] ] sitemode-epilog: func [mdp-stack][ ; Begin Extras Column emit [ newline newline newline
nsp

sitename siteext " Web Site"

] sitetoc mdp-stack sub-menu path emit [ nsp nsp

"Notes"

] ; emit-tag 'ul {^/
  • Site design by Christopher Ross-Gill
  • ^/ } emit [ nsp newline
    newline ] ; Page Footer emit [ newline newline newline newline newline newline newline newline newline newline
    build-tag compose [a href (anchor-root) title "Home"] build-tag compose [ img id (join sitename siteext2) src (join anchor-root [%style/ sitename '- siteext2 '.png]) width 310 height 55 alt (uppercase join sitename siteext) / ]
    newline newline ] ] sitemode-prolog: does [ ; Page Header ; -------------------------------------------------------------------## emit [ newline newline newline newline newline build-tag compose [link rel "shortcut icon" href (join anchor-root %style/favicon.ico) /] newline build-tag compose [link rel "stylesheet" type "text/css" href (join anchor-root %style/basic.css) /] newline build-tag compose [link rel "stylesheet" type "text/css" href (join anchor-root [%style/ sitename '.css]) media "screen" /] newline build-tag compose [link rel "stylesheet" type "text/css" href (join anchor-root %style/print.css) media "print" /] newline build-tag compose [script type "text/javascript" src (join anchor-root [%style/ sitename '-logo.js])] ] ] sitetoc: func [doc /local sects hdrs ts ls ns sn][ clear-sects ls: 1 hdrs: copy [] sects: [sect1 sect2] foreach entry head doc [ if find sects entry/1 [repend hdrs [entry/1 entry/2]] ] if empty? hdrs [return none] emit [ nsp ; nsp

    "Table of Contents"

    nsp nsp ] ] sub-menu: func [path [path!] /local menu short-path][ menu: make block! 20 ; iterate over each path part beginning with first path repeat node length? path [ short-path: copy/part path node if node > 1 [append menu compose/deep [split [(node) (short-path)]]] foreach [menu-path menu-content] menus [ if all [node <> 1 short-path = menu-path][append menu menu-content] ] ] remove-each [style content] menu [all [style <> 'split style <> 'url]] if empty? menu [return none] emit [ newline nsp nsp

    "Related"

    nsp nsp ] ] table-in: does [ ; Table start flags/push intable number_of_table_cells: number_of_emitted_table_cells: 0 emit

    either value == 'tableheader [ ; handle code for first cell here. Follwoing cells code is handled in paragraph: emit [
    ] flags/push tableheader ] [emit [
    ]] ] table-out: does [ ; handle missing table cells for last line of a table. This is needed because there is no 'newrow emitted after the ; last line and therefore the special handling for missing table cells didn't get called. loop (number_of_table_cells - number_of_emitted_table_cells) [emit [ " "]] emit [

    ] ; depending off the number of newlines until /table there might be no-paragraph-start flag on the stack if flags/top == 'no-paragraph-start [flags/pop] either flags/top <> 'intable [print "Table-Out: Stack not correct" probe flags/stack] [flags/pop] ] title: func [mdp-stack /local meta file][ either site_mode [ emit [ sitename siteext ": " value newline newline newline newline newline newline newline newline
    newline newline newline
    ] emit [

    value

    ] emit [nsp
    ] ] [ ; should we include meta data? foreach entry mdp-stack [ if entry/1 == 'meta [ file: to-file entry/2 ; try to read the meta file pif [ exists? file [meta: load file] exists? join mdp-path file [meta: load join mdp-path file] true [alert reform ["Missing META include file:" file] exit] ] ; emit meta data foreach [name entry] meta [ emit [{}] ] emit [{}] emit break ] ] ; Emit Title of HTML document -> Shown in Browser Title line emit [ value: escape-html value ] emit ; Start body and emit Title into HTML document emit emit [] emit [

    value

    ] ] ] toc: func [mdp-stack /local sn level old_level filename seperator old_sects] [ ; TOC or OUTLINE mode? either none? value [emit [

    toc-title

    ]] [emit [

    "outline: "]] filename: make file! none old_level: 0 old_sects: copy sects clear-sects foreach entry mdp-stack [ ; check to see if there is an other file name used? if entry/1 == 'file [ filename: either (pick parse entry/2 "." 2) == "html" [entry/2][to-file join entry/2 ".html"] ; clear-sects ; reset section counter to start over by 1 emit [
    "references into file: " filename
    ] ] ; check each word to find a section if level: find [sect1 sect2 sect3 sect4] entry/1 [ sn: sect-num? level: index? level ; get index of position we found and calculate section number ; handle TOC indention or OUTLINE either none? value [ if old_level < level [emit

    ] if old_level > level [loop (old_level - level) [emit
    ]] emit [ {} pick [ ""] level <= 2 either (length? sn) == 1 [join sn "."][sn] " " entry/2 pick [ ""] level <= 2
    ] ; keep level old_level: level ] [ emit [{} entry/2 " ,"] ] ] ] either none? value [if old_level > 0 [loop old_level [emit
    ]]] [ remove back tail html ; removes " ," emit

    ] ; reset section counters so that the counters for the sections ; will be emitted corretly for the rest of the text because normal section emitting follows ; clear-sects sects: copy old_sects ] url: func [value /plain][ if not plain [emit

    ] emit [{} escape-html value/2 ] if not plain [emit

    ] ] view-image: has [last-code code file] [ if error? last-code: try [load/all value] [ request/ok reform ["ERROR in VIEW CODE:^/" mold disarm :last-code] exit ] ; is a layout command present, else add one code: find last-code 'layout if none? code [code: compose/deep [layout [(last-code)]]] ; now exectue the code to get a graphic code: do code ; create filename file: join %graphics/ ["image" img-num ".png"] ; view code and save as graphics file if object? code [ view/new code img: to-image code unview/only code if not exists? %graphics [make-dir %graphics] save/png file img ] ; increase counter img-num: img-num + 1 ; emit HTML code emit [{

    }

    ] ] generate: func [mdp-stack][ ; emit HTML prolog if not light_mode [prolog] if site_mode [sitemode-prolog] ; iterate through the MDP stack and emit the HTML code. The stack uses a name/value pair in a block. ; this block is assigned to entry, that is used within the HTML emiter functions foreach tmp mdp-stack [ name: tmp/1 value: tmp/2 switch/default name [ align [align] bar [bar] ; bold [bold] bullet [bullet] bullet-join [bullet-join] define [define] define-join [define-join flags/push no-define-start] example [example] example-join [example-join flags/push no-example-start] file [file] header [header] header-join [header-join flags/push no-header-start] image [image value] indent-in [indent-in] indent-out [indent-out] meta [] ; is handled in title emitter function note-in [note-in] note-out [note-out] number [number] number-join [number-join] paragraph [paragraph] paragraph-join [flags/push no-paragraph-start paragraph-join] sect1 [section 1] sect2 [section 2] sect3 [section 3] sect4 [section 4] sequence-end [flags/push sequence-ended] table-in [table-in] table-out [table-out] title [title mdp-stack] toc [toc mdp-stack] url [url value] view [view-image] ][print ["Unknown TAG found:" name]] ] if not light_mode [epilog] if site_mode [ emit [nsp
    newline
    newline ] sitemode-epilog mdp-stack ] ] ] ;--- PDF emitter ----------------------------------------------------------------------------------- pdf-emitter: context [ pdf: [] sects: [0 0 0 0] ; this is the counter for our 4 level sections y-textpos: 280 emit: func [data /local tmp] [ foreach [dialectword item] reduce data [ pif [ dialectword == 'textbox [ if (type? item) = string! [item: reduce [item]] tmp: precalc-textbox 200 item append pdf reduce [dialectword 10 y-textpos 200 10 item] y-textpos: y-textpos - tmp ] true [print ["Unknown tag found:" dialectword item]] ] ] ] block: func [data] [rejoin [{["} data {"]}]] ; Reset all section counter to 0 clear-sects: does [change/dup sects 0 4] ; Increase section counters, create section counter string and return this string sect-num?: func [num /local n sn] [ ; increase section counter at num place by 1 change at sects num n: sects/:num + 1 ; reset all section counters behind 'num to 0 change/dup at sects num + 1 0 4 - num ; initialize local variable sn: copy "" ; append num times the section counter to form a w.x.y.z number repeat n num [append sn join sects/:n "."] ; remove trailing point remove back tail sn ; return the created number copy sn ] pt2mm: func [pt][return pt * 0.49] ;--- Emitting parts header: does [ emit ['textbox compose [font Courier 4.23 (value) font Helvetica 4.23]] ] paragraph: has [name pvalue toemit][ toemit: copy/deep ['textbox []] ; value now has a name/value structure itself foreach tmp value [ name: tmp/1 pvalue: tmp/2 switch/default name [ parapart [append last toemit pvalue] bold [append last toemit compose [font Helvetica-Bold 4.23 (pvalue) font Helvetica 4.23]] ][print ["2: Unknown INLINE-TAG found:" name]] ] ; end this paragraph append last toemit 'p emit toemit ] title: does [ ; space after is in cm (?) emit ['textbox compose [font Helvetica-Bold (pt2mm 15) (value) font Helvetica 4.23 p]] ] section: func[num /local sn][ ; Include a horizontal line before a new section starts if num = 1 [ ] ; create correct section number string sn: sect-num? num ; emit section number, section string and pif [ num = 1 [emit ['textbox compose [font Helvetica-Bold (pt2mm 12) (sn) " " (value) p]]] num = 2 [emit ['textbox compose [font Helvetica-Bold (pt2mm 10) (sn) " " (value) p]]] num = 3 [emit ['textbox compose [font Helvetica-Bold (pt2mm 8) (sn) " " (value) p]]] num = 4 [emit ['textbox compose [font Helvetica-Bold (pt2mm 6) (sn) " " (value) p]]] ] ] generate: func [mdp-stack][ ; iterate through the MDP stack and emit the PDF code. The stack uses a name/value pair in a block. ; this block is assigned to entry, that is used within the PDF emiter functions foreach tmp mdp-stack [ name: tmp/1 value: tmp/2 switch/default name [ align [align] bar [bar] ; bold [bold] bullet [bullet] bullet-join [bullet-join] define [define] define-join [define-join flags/push no-define-start] example [example] example-join [example-join flags/push no-example-start] file [file] header [header] header-join [header-join flags/push no-header-start] image [image] indent-in [indent-in] indent-out [indent-out] meta [] ; is handled in title emitter function note-in [note-in] note-out [note-out] number [number] number-join [number-join] paragraph [paragraph] paragraph-join [flags/push no-paragraph-start paragraph-join] sect1 [section 1] sect2 [section 2] sect3 [section 3] sect4 [section 4] sequence-end [flags/push sequence-ended] table-in [table-in] table-out [table-out] title [title mdp-stack] toc [toc mdp-stack] url [url] view [view-image] ][print ["Unknown TAG found:" name]] ] ] ] ; PDF-Emitter ;--- Main Program if unset? system/words/site_mode [site_mode: false] if unset? system/words/light_mode [light_mode: false] mdp-path: copy system/script/path mdp-startup-dir: join mdp-path %mdp-startup-dir.txt files: any [ system/options/args system/script/args either light_mode [false] [ light_mode: false if exists? mdp-startup-dir [ change-dir to-file read mdp-startup-dir ] request-file/keep/filter "*.txt" ] ] mdp-parser/init generate-files: func [files /path _path /formats out-formats [block!]][ foreach file compose [(files)] [ ; do we have source-text or files? either (type? file) == file! [ either exists? file [ ; only generate HTML code? either light_mode [parse/all detab read file mdp-parser/mdp] [ ; remember file selection for next run file: to-file file change-dir first split-path file write join mdp-path %mdp-startup-dir.txt first split-path file print ["Parsing done correct:" parse/all detab read file mdp-parser/mdp] print ["Input chars skipped:" mdp-parser/skip-counter] ] ] [print ["File:" file "doesn't exist."] halt] ] [parse/all detab file mdp-parser/mdp] ; reverse the stack, so that the emitter can iterator front to back, ALWAYS REQUIRED reverse mdp-parser/mdp-stack/stack ; debug_mode: true if debug_mode [ if (type? file) == file! [ destinationfile: join first split-path file append first parse/all second split-path file "." ".mdp" save destinationfile mdp-parser/mdp-stack/stack ] print ["Reversed MDP-Stack:" mdp-parser/mdp-stack/debug] ] ; emit HTML code if nothing special is given if any [not formats found? find out-formats 'html] [ if path [html-emitter/path: _path] html-emitter/generate mdp-parser/mdp-stack/stack ; write output to file either all [not light_mode (type? file) == file!] [ destinationfile: join first split-path file append first parse/all second split-path file "." ".html" write destinationfile html-emitter/html html-emitter/init ] [ mdp-parser/init return html-emitter/html ] ] ; emit PDF code if all [formats found? find out-formats 'pdf] [ do %pdf-maker.r destinationfile: join first split-path file append first parse/all second split-path file "." ".pdf" pdf-emitter/generate mdp-parser/mdp-stack/stack pdf-input: [] append/only pdf-input pdf-emitter/pdf probe pdf-input write/binary destinationfile layout-pdf to-block pdf-input ] ; reset parser mdp-parser/init ] ; foreach if all [debug_mode not light_mode any [user-prefs/name == "Robert" user-prefs/name == "Robby"]][change-dir mdp-path halt] ] if not light_mode [generate-files files]