Monday, November 08, 2010 at 12:05 AM.

system.verbs.builtins.radio.webServer.buildPage

on buildPage (f, pta) {
	<<Changes
		<<8/5/03; 5:21:48 PM by JES
			<<Added support for Mozilla's rich text editor. If the editor has been added to the page being rendered, add the required onload event handler to the body tag before returning the rendered HTML.
		<<11/5/02; 1:56:53 AM by JES
			<<Prevent multiple paragraph tags.
		<<1/26/02; 2:42:54 AM by JES
			<<Call radio.file.editInBrowser instead of radio.file.openWithEditor, when handling the Edit This Page button.
		<<12/14/01; 7:55:06 PM by JES
			<<Fixed a pair of bugs with making the local copy of the website. Now we copy the whole site table from radio.data.website, instead of trying to be smart by creating address entries in a local table.
		<<12/13/01; 7:31:27 PM by JES
			<<To edit files via the Edit This Page button, or icons in the folderView, call through radio.file.openWithEditor.
		<<12/1/01; 12:00:04 AM by JES
			<<Make a local website table, which contains addresses that point at thread-safe objects. Objects which aren't handled correctly as addresses by the website framework (#filters and #prefs) are copied directly into the table from radio.data.website.
		<<11/30/01; 12:25:12 PM by JES
			<<Convert line endings in filetext to \r, since the website framework expects \r line-endings.
		<<11/28/01; 1:08:36 AM by JES
			<<Don't lock a semaphore on "this" when rendering. This was causing errors in both the upstreaming, and in local rendering. Rendering should now be thread-safe, and if not, then any non-thread-safe code is a bug.
		<<11/24/01; 9:05:57 PM by JES
			<<When setting adrpage, pop off the file extension. This fixes the extension on the path, in glossary entries at radio.data.website.["#glossary"].
		<<11/24/01; 6:10:40 PM by JES
			<<Don't run directives in #prefs files, for folderView pages. This fixes the title problem on the folderView pages.
			<<For folderView pages, set pta^.flUseHeadTitle to true. radio.userInterface.folderToHtml sets pta^.headTitle to the folder name. and sets pta^.title to the breadcrumb path for the folder.
		<<11/16/01; 6:02:56 PM by JES
			<<After running directives, check to see if the file being rendered, or a #prefs.txt file defined a #flArchivePage directive. If so, then set pagetable.radioResponder.flHomePage to not pta^.flArchivePage.
		<<11/12/01; 8:57:32 PM by JES
			<<Fixed a bug rendering ompl files: "Assignment over existing outline object "filetext" is not allowed..."
		<<11/12/01; 2:26:04 PM by JES
			<<For pages which have a #flHomePage directive, set pta^.radioResponder.flHomePage to its value.
		<<11/11/01; 10:52:54 PM by JES
			<<When setting filetext, run the directives in the file, before calling radio.webserver.gatherAttributes, since directives in the #prefs file may rely on having objects in the page table which are defined by directives in the file itself.
			<<Pass pta to radio.webserver.gatherAttributes, so that directives in #prefs files can be added to the top-level of the page table, instead of to pta^.radioResponder.atts.
		<<10/23/01; 1:33:14 PM by JES
			<<Use thread.callScript to publish story pages in their own thread, instead of thread.easyCall. This fixes the bug dave reported with errors like this:
				<<The file "C:Program FilesRadio UserLandwwwstories20011023karlinlillingtononirishterrorism.opml" wasn't found.
			<<The problem is that thread.easyCall passes parameters as a string, so backslash characters don't pass through to the script being called.
		<<10/18/01; 12:24:58 PM by JES
			<<Clear the news page HTML caches as a side effect of a POST request.
		<<10/17/01; 3:03:57 PM by PBS
			<<Set the page table address, since radio.webserver.setTemplate needs it.
			<<Delete the page table address, since dangling ptas can cause crashes.
		<<10/13/01; 11:36:52 PM by JES
			<<Fixed a bug where outline files would not ignore renderOutlineWith directives and rules.
		<<10/12/01; 2:57:45 PM by JES
			<<Fixed a bug where sizestring (wordcount) would be 3, for all opml files.
		<<10/11/01; 7:25:14 PM by JES
			<<Added publish side effect.
		<<10/11/01; 6:18:51 PM by PBS
			<<The Edit this Page button now works when the editor is Radio.
		<<10/11/01; 2:31:02 PM by PBS
			<<Don't bump the hit count here since it's bumped in the responder.
		<<8/15/01; 4:50:06 PM by JES
			<<If the app version is less than 7.1b1, reverse-patch macro delimiters in the filetext, so that macros will expand properly. (The template is also patched, but that's done in radio.webServer.setTemplate.)
		<<8/14/01; 5:06:17 PM by JES
			<<Template file display fixes. (A template file is any file whose name starts with a # character.)
				<<Convert < and & characters to entities after converting from OPML to an outline. Prevents 500 Server Errors when the OPML contains these characters.
				<<To neuter macros, call radio.string.untaint, with macroStartCharacters.
				<<Convert \ characters to numerical HTML entities, so they will always display in the browser.
		<<8/9/01; 1:21:17 AM by JES
			<<If the file's line endings are LF-only, convert to native line-endings. Prevents server errors when processing directives in files with LF-only line-endings.
		<<8/2/01; 3:23:25 AM by JES
			<<Bring the Finder to the front, when editing a folder, on a Mac.
		<<6/15/01; 12:30:16 AM by PBS
			<<When viewing a template, also translate & to &.
		<<5/4/01; 10:45:48 PM by DW
			<<Now it works if f is a folder. Nice.
		<<4/14/01; 9:26:55 PM by DW
			<<Created.
			<<http://radio.userland.com/stories/storyReader$5894
	local (ws = radio.data.website);
	local (adrsite = @ws);
	local (flCleanUpObjects = true); //if true we delete all objects created
	local (pc = file.getpathchar ());
	radio.init ();
	
	on editSideEffect () {
		local (filetoedit);
		if defined (pta^.radioResponder.postArgs.f) { //(args.f)
			filetoedit = string.urlDecode (pta^.radioResponder.postArgs.f)}
		else { //edit this page
			filetoedit = f};
		if file.isfolder (filetoedit) {
			file.openfolder (filetoedit);
			if system.environment.isMac {
				finder.bringToFront ()}}
		else { //edit the file with the appropriate editing app
			try { //don't prevent the page from building
				<<radio.file.openWithEditor (filetoedit)
				radio.file.editInBrowser (filetoedit, pta^.path, pta)}
			else { //put the error in the pagetable, so pages can display it
				pta^.editSideEffectError = tryError}}};
			<<bundle //new code
				<<if defined (user.radio.prefs.editors.[radio.webserver.getfilemimetype (filetoedit)])
					<<local (editor = user.radio.prefs.editors.[radio.webserver.getfilemimetype (filetoedit)])
					<<if Frontier.getProgramPath () ==  editor //PBS 
						<<Frontier.bringToFront ()
						<<Frontier.clickers.typeOPML (filetoedit) //make sure we open in this instance of Radio (MacOS)
					<<else
						<<launch.appWithDocument (editor, filetoedit)
				<<else
					<<if string.lower (filetoedit) endsWith (".opml")
						<<Frontier.bringToFront ()
						<<Frontier.clickers.typeOPML (filetoedit) //make sure we open in this instance of Radio (MacOS)
					<<else
						<<launch.anything (filetoedit)
			<<bundle //old code
				<<local (editor)
				<<if defined (user.radio.prefs.editors.[radio.webserver.getfilemimetype (filetoedit)])
					<<editor = user.radio.prefs.editors.[radio.webserver.getfilemimetype (filetoedit)]
				<<else //try to get the OS to tell us where the editor is
					<<if system.environment.isWindows //on windows, file.findApplication takes the file's extension
						<<editor = file.findApplication (string.nthField (filetoedit, '.', string.countFields (filetoedit, '.')))
					<<else //on mac, file.findApplication takes the file's creator code
						<<editor = file.findApplication (file.creator (filetoedit))
						<<if not file.exists (editor)
							<<editor = false
				<<if editor != false //file.findApplication returns false if the app that owns the file can't be found
					<<launch.appWithDocument (editor, filetoedit)
	on publishSideEffect () {
		thread.callScript (@radio.html.publishStaticPage, {f})};
	bundle { //side-effects of POST
		if pta^.method == "POST" {
			<<scratchpad.params = pta^; edit (@scratchpad.params)
			bundle { //clear news page HTML caches
				local (adrAggregatorData = @aggregatorData); //don't use xml.aggregator.init, because that would open/create a gdb that we might not need.
				if defined (adrAggregatorData^) {
					local (adrcache = @adrAggregatorData^.cache);
					if defined (adrcache^) {
						if sizeOf (adrcache^) > 0 {
							new (tableType, adrcache)}}}};
			if defined (pta^.radioResponder.postArgs.command) {
				case pta^.radioResponder.postArgs.command {
					"edit" {
						editSideEffect ()};
					"publish" {
						publishSideEffect ()}}}}};
	bundle { //side-effects of GET
		if pta^.method == "GET" {
			<<scratchpad.params = pta^; edit (@scratchpad.params)
			if defined (pta^.radioResponder.getArgs.command) {
				case pta^.radioResponder.getArgs.command {
					"edit" {
						editSideEffect ()};
					"publish" {
						publishSideEffect ()}}}}};
	
	pta^.radioResponder.fileBeingRendered = f;
	pta^.radioResponder.adrblog = radio.weblog.init ();
	local (defaulttitle = pta^.radioResponder.adrblog^.prefs.title);
	pta^.title = defaulttitle;
	local (flFolderView = false);
	bundle { //set flFolderView
		if defined (pta^.radioResponder.postArgs.folderView) {
			if pta^.radioResponder.postArgs.folderView != "0" {
				flFolderView = true}};
		if file.isFolder (f) {
			flFolderView = true}};
	
	local (adrpage, adrnewtable = nil);
	bundle { //set adrpage, creating tables as necessary
		local (nomad = adrsite);
		local (path = string.delete (f, 1, sizeof (user.radio.prefs.wwwfolder)));
		local (ctparts = string.countfields (path, pc), foldernomad = user.radio.prefs.wwwfolder);
		for i = 1 to ctparts - 1 {
			local (subfoldername = string.nthfield (path, pc, i));
			nomad = @nomad^.[subfoldername];
			if not defined (nomad^) {
				new (tabletype, nomad);
				if adrnewtable == nil {
					adrnewtable = nomad}};
			foldernomad = foldernomad + subfoldername + pc};
		if file.isfolder (f) {
			adrpage = @nomad^.viewFolder}
		else {
			local (pagename = string.nthfield (path, pc, ctparts));
			if pagename contains "." { //pop off the file extension: this fixes glossary entries stored at radio.data.website.["#glossary"]
				local (sizeExtension = sizeOf (string.nthField (pagename, ".", string.countFields (pagename, "."))) + 1);
				pagename = string.mid (pagename, 1, sizeOf (pagename) - sizeExtension)};
			adrpage = @nomad^.[pagename]}};
	
	local (filetext, sizestring = "");
	bundle { //set filetext, and run directives in the file
		if file.isfolder (f) {
			filetext = radio.userInterface.folderToHtml (f, pta);
			if defined (pta^.radioResponder.ctFilesInFolder) { //check because radio.prefs.upstream doesn't set ctFilesInFolder
				sizestring = pta^.radioResponder.ctFilesInFolder + " files/folders"}}
		else {
			local (fname = file.filefrompath (f));
			pta^.title = fname;
			filetext = string (file.readwholefile (f));
			bundle { //patch line-endings; the rendering code expects \r line endings
				filetext = string.replaceAll (filetext, "\r\n", "\r");
				filetext = string.replaceAll (filetext, "\n", "\r")};
			<<bundle //if line ending is LF, replace LFs with CR/LFs
				<<local (ixCR = string.patternMatch ("\r", filetext))
				<<if ixCR == 0
					<<local (ixLF = string.patternMatch ("\n", filetext))
					<<if ixLF > 0 //LF-only line ending
						<<if system.environment.isMac
							<<filetext = string.replaceAll (filetext, "\n", "\r")
						<<else //Windows line-endings
							<<filetext = string.replaceAll (filetext, "\n", "\r\n")
			if pta^.radioResponder.fileMimeType == "text/x-opml" {
				op.xmlToOutline (filetext, @filetext)};
			bundle { //make templates viewable in HTML
				if fname beginswith "#" {
					if typeOf (filetext) == outlineType { //convert to a string for calls to string.replaceAll
						table.assign (@filetext, string (filetext))};
					filetext = string.replaceall (filetext, "&", "&"); //PBS 06/15/01
					filetext = string.replaceall (filetext, "<", "<");
					filetext = string.replaceall (filetext, "\\", "\");
					local (startchars = "{");
					if defined (pta^.macroStartCharacters) {
						startchars = pta^.macroStartCharacters};
					filetext = radio.string.untaint (filetext, false, startchars);
					filetext = "<pre>" + filetext + "</pre>"}};
			bundle { //run directives here instead of relying on html.buildObject
				<<We do this here because radio.webserver.gatherAttributes may run directives in #prefs files which depend on directives which are defined in the file itself.
				html.runDirectives (string (filetext), pta)};
			sizestring = string.countwords (string (filetext)) + " words"}};
	
	bundle { //gather attributes, and run directives in #prefs files
		if file.isFolder (f) { //don't run directives in #prefs files -- this fixes the title problem on the folderView pages
			radio.webServer.gatherAttributes (f, @pta^.radioResponder.atts);
			pta^.flUseHeadTitle = true} //radio.userInterface.folderToHtml sets pta^.headTitle to the folder name
		else {
			radio.webServer.gatherAttributes (f, @pta^.radioResponder.atts, pta)}};
	
	bundle { //set pta^.radioResponder.flHomePage, if defined by a directive in a #prefs file or in the file being rendered
		if defined (pta^.flHomePage) { //determines if we use #homeTemplate.txt, which has a <%calendar%> macro
			pta^.radioResponder.flHomePage = true}};
	
	html.setPageTableAddress (pta); //PBS 10/17/01: radio.webserver.setTemplate needs this
	radio.webserver.settemplate (@pta^.radioResponder.atts, pta^.radioResponder.flHomePage, adrsite);
	
	local (htmltext);
	bundle { //read the file into adrpage, render it into htmltext
		if date.versionLessThan (Frontier.version (), "7.1b1") { //patch macro delimiters
			if defined (adrsite^.["#prefs"].macroStartCharacters) {
				filetext = string.replaceAll (filetext, adrsite^.["#prefs"].macroStartCharacters, "{", false)};
			if defined (adrsite^.["#prefs"].macroEndCharacters) {
				filetext = string.replaceAll (filetext, adrsite^.["#prefs"].macroEndCharacters, "}", false)}};
		table.assign (adrpage, filetext); //JES: use table.assign to override type protection
		htmltext = html.buildObject (adrpage, pta);
		while htmltext contains "<p><p>" {
			htmltext = string.replaceAll (htmltext, "<p><p>", "<p>")};
		if flCleanUpObjects {
			delete (adrpage);
			if adrnewtable != nil {
				delete (adrnewtable)}}};
	bundle { //update the file attribute cache for this file
		<<scratchpad.paramtable = pta^
		local (adrcache = pta^.radioResponder.adrFileTable);
		bundle { //title
			if not file.isfolder (f) {
				local (title = pta^.title);
				if title == defaulttitle {
					title = file.filefrompath (f)};
				adrcache^.title = title}};
		<<adrcache^.hits++ //PBS 10/11/01: don't bump here; it's done in the responder
		adrcache^.sizestring = sizestring};
	
	bundle { //patch in the onload event handler for the Mozilla rich text editor if necessary
		<<workspace.pt = pta^ //debugging
		if not pta^.radioResponder.flStaticRendering {
			if defined (pta^.flEditingFormOnPage) {
				if defined (pta^.adrEditingTool) {
					if pta^.adrEditingTool == @html.editor.wysiwygEditorMozilla {
						local (ix = string.patternMatch ("<body", string.lower (htmltext)));
						if ix > 0 {
							ix = ix + 5;
							htmltext = string.insert (" onload=\"try{StartEditor()}catch(e){}\" ", htmltext, ix)}}}}}};
	
	html.deletePageTableAddress (); //PBS 10/17/01: dangling ptas can cause crashes
	
	return (htmltext)}
<<bundle //test code
	<<local (pagetable)
	<<bundle //set up page table
		<<new (tabletype, @pagetable)
		<<pagetable.method = "GET"
		<<pagetable.searchargs = ""
		<<new (tabletype, @pagetable.radioresponder)
		<<pagetable.radioresponder.flsamemachine = true
		<<pagetable.radioresponder.fileMimeType = "text/x-opml"
		<<pagetable.radioresponder.flHomePage = false
	<<webbrowser.displaytext (buildpage ("C:\\Program Files\\Radio UserLand\\www\\index.html", @pagetable))
	<<webbrowser.displaytext (buildpage ("C:\\Program Files\\Radio UserLand\\www\\stories\\", @pagetable))
	<<webbrowser.displaytext (buildpage ("Backup:Radio UserLand:www:sh6:Stories:2001:10:11:usmanFarman.opml", @pagetable))
<<bundle //more test code
	<<buildPage (user.radio.prefs.wwwfolder + "index.txt", @scratchpad.params)



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.