')
			.append($('')
				.html($(elem).html())) },
		// XXX expperimental...
		json: function(context, elem){ return $('')
			.html($(elem).text()
				// remove JS comments...
				.replace(/\s*\/\/.*$|\s*\/\*(.|[\n\r])*?\*\/\s*/mg, '')) },
		// XXX
		nl2br: function(context, elem){ 
			return $('').html($(elem).html().replace(/\n/g, '
\n')) },
		wikiword: function(context, elem){ 
			return $('
')
				.html(setWikiWords($(elem).html(), true, this.__include_marker__)) },
		// XXX need to remove all on* event handlers...
		noscript: function(context, elem){ 
			return $(elem)
				// remove script tags...
				.find('script')
					.remove()
					.end()
				// remove js links...
				.find('[href]')
					.filter(function(i, e){ return /javascript:/i.test($(e).attr('href')) })
						.attr('href', '#')
						.end()
					.end()
				// remove event handlers...
				// XXX .off() will not work here as we need to remove on* handlers...
		},
		// XXX move this to a plugin...
		markdown: function(context, elem){
			var converter = new showdown.Converter({
				strikethrough: true,
				tables: true,
				tasklists: true,
			})
			return $('')
				.html(converter.makeHtml($(elem).html()))
				// XXX add click handling to checkboxes...
				.find('[checked]')
					.parent()
						.addClass('checked')
						.end()
					.end()
		},
	},
	// Post-filters...
	//
	// These are run on the final page.
	//
	// The main goal is to setup editors and other active stuff that the
	// user should not have direct access to, but that should be 
	// configurable per instance...
	//
	// for tech and other details see .filter
	//
	post_filter: {
		noscript: function(context, elem){
			// XXX
			return elem
		},
		// Setup the page title and .title element...
		//
		// Use the text from:
		// 	1) set it H1 if it is the first tag in .text
		// 	2) set it to .location
		//
		title: function(context, elem){
			elem = $(elem)
			var title = elem.find('.text h1').first()
			// show first H1 as title...
			if(elem.find('.text').text().trim().indexOf(title.text().trim()) == 0){
				title.detach()
				elem.find('.title').html(title.html())
				$('title').html(title.text())
			// show location...
			} else {
				$('title').text(context.location)
			}
			return elem
		},
		// XXX this needs save/reload...
		editor: function(context, elem){
			// XXX title
			// 		- on focus set it to .title
			// XXX text
			// XXX raw
			// XXX checkbox
			return elem
		},
	},
	// Parsing:
	//  1) expand macros
	//  2) apply filters
	//  3) merge and parse included pages:
	//  	1) expand macros
	//  	2) apply filters
	//  4) fill slots
	//  5) expand post-macros
	//
	// NOTE: stage 4 parsing is executed on the final merged page only 
	// 		once. i.e. it is not performed on the included pages.
	// NOTE: included pages are parsed in their own context.
	// NOTE: slots are parsed in the context of their containing page 
	// 		and not in the location they are being placed.
	//
	// XXX support quoted text...
	// XXX need to quote regexp chars of .__include_marker__...
	// XXX include recursion is detected but path recursion is not at 
	// 		this point...
	// 		e.g. the folowing paths resolve to the same page:
	// 			/SomePage
	// 			/SomePage/SomePage
	// 			or any path matching:
	// 				/\/(SomePage\/)+/
	parse: function(context, text, state, skip_post, pattern){
		var that = this
		state = state || {}
		state.filters = state.filters || []
		//state.slots = state.slots || {}
		state.include = state.include || []
		state.seen = state.seen || []
		//pattern = pattern || RegExp('@([a-zA-Z-_]+)\\(([^)]*)\\)', 'mg')
		pattern = pattern || RegExp.apply(null, this.__macro__pattern__)
		// XXX need to quote regexp chars...
		var include_marker = RegExp(this.__include_marker__, 'g')
		var parsed = typeof(text) == typeof('str') ? 
			$('').html(text) 
			: text
		var _parseText = function(context, text, macro){
			return text.replace(pattern, function(match){
				// quoted macro...
				if(match[0] == '\\' && macro['*'] == null){
					return match.slice(1)
					//return match
				}
				// XXX parse match...
				var d = match.match(/@([a-zA-Z-_:]*)\(([^)]*)\)/)
				var name = d[1]
				if(name in macro || '*' in macro){
					var elem = $('<'+name+'/>')
					name = name in macro ? name : '*'
					// format positional args....
					var a = d[2]
						.split(/((['"]).*?\2)|\s+/g)
						// cleanup...
						.filter(function(e){ return e && e != '' && !/^['"]$/.test(e)})
						// remove quotes...
						.map(function(e){ return /^(['"]).*\1$/.test(e) ? e.slice(1, -1) : e })
					// add the attrs to the element...
					name != '*' 
						&& a.forEach(function(e, i){
							var k = ((macro[name] || {}).macro_args || [])[i]
							k && elem.attr(k, e)
						})
					// call macro...
					var res = macro[name]
						.call(that, context, elem, state,
							function(elem, c){ 
								return _parse(c || context, elem, macro) },
							match)
					return res instanceof jQuery ? 
							// merge html of the returned set of elements...
							res.map(function(i, e){ return e.outerHTML })
								.toArray()
								.join('\n')
						: typeof(res) != typeof('str') ? res.outerHTML
						: res
				}
				return match
			})
		}
		// NOTE: this modifies parsed in-place...
		var _parse = function(context, parsed, macro){
			$(parsed).contents().each(function(_, e){
				// #text / comment node -> parse the @... macros...
				if(e.nodeType == e.TEXT_NODE || e.nodeType == e.COMMENT_NODE){
					// get actual element content...
					var text = $('').append($(e).clone()).html()
					// conditional comment...
					if(e.nodeType == e.COMMENT_NODE 
							&& /^$/.test(text)){
						text = text
							.replace(/^$/, '')
					}
					$(e).replaceWith(_parseText(context, text, macro))
				// node -> html-style + attrs...
				} else {
					var name = e.nodeName.toLowerCase()
					// parse attr values...
					for(var i=0; i < e.attributes.length; i++){
						var attr = e.attributes[i]
						attr.value = _parseText(context, attr.value, macro)
					}
					// macro match -> call macro...
					if(name in  macro){
						$(e).replaceWith(macro[name]
							.call(that, context, e, state, 
								function(elem, c){ 
									return _parse(c || context, elem, macro) }))
					// normal tag -> sub-tree...
					} else {
						_parse(context, e, macro)
					}
				}
			})
			return parsed
		}
		var _filter = function(lst, filters){
			lst
				// unique -- leave last occurance..
				.filter(function(k, i, lst){ 
					return k[0] != '-'
						// filter dupplicates... 
						&& lst.slice(i+1).indexOf(k) == -1 
							// filter disabled...
						&& lst.slice(0, i).indexOf('-' + k) == -1
				})
				// unique -- leave first occurance..
				//.filter(function(k, i, lst){ return lst.slice(0, i).indexOf(k) == -1 })
				// apply the filters...
				.forEach(function(f){
					var k = f
					// get filter aliases...
					var seen = []
					while(typeof(k) == typeof('str') && seen.indexOf(k) == -1){
						seen.push(k)
						k = filters[k]
					}
					// could not find the filter...
					if(!k){
						//console.warn('Unknown filter:', f)
						return
					}
					// use the filter...
					parsed = k.call(that, context, parsed) 
				})
		}
		// macro stage...
		_parse(context, parsed, this.macro)
		// filter stage...
		_filter(state.filters.concat(this.__filters__), this.filter)
		// merge includes...
		parsed
			.html(parsed.html().replace(include_marker, function(){
				var page = state.include.shift()
				var elem = $(page.shift())
				page = page.pop()
				var isolated = elem.attr('isolated') == 'true'
				var seen = state.seen.slice()
				if(seen.indexOf(page.path) >= 0){
					return elem.html()
				}
				seen.push(page.path)
				return page.map(function(page){
					return $('
')
						.append(elem
							.clone()
							.attr('src', page.path)
							.append(that
								.parse(page,
									page.raw, 
									{ 
										//slots: !isolated ? state.slots : {},
										templates: state.templates,
										seen: seen,
									}, 
									!isolated)))
									//true)))
						.html()
				}).join('\n')
			}))
		// post processing...
		if(!skip_post){
			// fill slots...
			// XXX need to prevent this from processing slots in editable
			// 		elements...
			slots = {}
			// get slots...
			parsed.find('slot')
				.each(function(i, e){
					e = $(e)
					// XXX not sure about this...
					// 		...check if it prevents correct slot parsing
					// 		within an isolated include...
					if(e.parents('[isolated="true"]').length > 0){
						return
					}
					var n = e.attr('name')
					n in slots && e.detach()
					slots[n] = e 
				})
			// place slots...
			parsed.find('slot')
				.each(function(i, e){
					e = $(e)
					// XXX not sure about this...
					// 		...check if it prevents correct slot parsing
					// 		within an isolated include...
					if(e.parents('[isolated="true"]').length > 0){
						return
					}
					var n = e.attr('name')
					e.replaceWith(slots[n])
				})
			// post-macro...
			// XXX for some odd reason this clears the backslash from 
			// 		quoted macros in raw fields...
			//this.post_macro 
			//	&& _parse(context, parsed, this.post_macro)
		}
		// post-filter stage...
		// XXX get list from context.config...
		_filter(this.__post_filters__, this.post_filter)
		// XXX shuld we get rid of the root span???
		return parsed.contents()
	},
}
/**********************************************************************
* vim:set ts=4 sw=4 :                               */ return module })