/*	usage: <source string>.parse( <name>|<specification> [ , <destination> [ , <instruction> ]] )
	syntax name:	previously defined syntax name or alias. Built-in names/aliases are:
					xml , html, htm, xht, xhtml, dtd, 'xml dtd', php (alias of html), 'php script'
					js, 'JavaScript script', JavaScript, javascript, ECMAScript,
					css, 'css stylesheet' and 'Cascading Style Sheets'
	specification:	syntax specification array (see notes below)
	destination:	optional location to append the result to. String or reference to DOM
					element(including text or attribute node). If destination is string or
					text node, escaped source code is returned.
	instruction:	string - "link" or an xml instruction (see notes at end)

	If a destination is given and no instruction, <span> elements are returned with
	the class attribute set to the name in the syntax list.

	If the instruction "link" is given, elements identified as 'url' are returned as <a>
	elements with the href and title attributes set to the string.

	If no destination is given, an array of strings is returned with a 'description'
	property set from the syntax specification on those that correspond to language elements.

	Copyright (c) James Crompton 2005 - 2006
	http://james.crompton.eu/
	License terms at http://james.crompton.eu/legalese.html

	Some regular expressions in syntax specifications adapted from work by Dean Edwards:
	(c) Dean Edwards 2004
	http://dean.edwards.name/
*/

String.prototype.parse = function( language , destination , outputInstruction ) {

	function getSyntax( x ) { // x is a Syntax, a string or an array
		if ( x.parseString ) // Syntax object
			return x ;
		if ( typeof x != "string" ) // not name/alias => array
			if ( x.length > 1 ) // full syntax specification
				return new Syntax( x ) ;
			else // parseFurther instruction: single item is syntax name/alias
				return getSyntax( x[0] ) ;
		x = x.toLowerCase() ; // name or alias:
		if ( typeof String.prototype.parse[ x ] == "string" ) // alias
			return getSyntax( String.prototype.parse[ x ] ) ;
		return ( String.prototype.parse[ x ] ) || // name of existing Syntax
			new Syntax( [ x , "" , /[^\s\S]/ ] ) ; // undefined name: create placeholder
		}

//	======================= Syntax constructors =============================

	function Syntax( specification ) {
		this.name = ( specification[0] != "=" ) ? specification[0].toLowerCase() : false ;
		if ( this.name ) // named syntax: remember it
			String.prototype.parse[ this.name ] = this ;

		if ( specification[1] != "=" ) {
			this.superSource = [] ;
			for ( var i = 2 , j = 0 ; specification[i] ; i += this[j].used , j++ ) {
				this[j] = new Selector( specification[i] , specification[ i - 1 ] ,
					specification[ i + 1 ] , specification[ i + 2 ] , ( specification[0] == "=" ) )
				this.superSource[j] = this[j].source ;
				}
			this.superSource = this.superSource.join( "|" ) ;
			}
		else {
			this[0] = new Selector( specification.slice( 1 ) ) ;
			//	rest of specification gets processed as a parse further syntax
			//	at first getSyntax in Selector constructor
			this.superSource = this[0].source ;
			}
		}

	function Selector( selector , name , useMatch , parseFurther , equal ) {
	//	'selector' may in fact be most of an equal-priority syntax, and
	//	parameters generally may be in the wrong place/missing. Sort them out,
	//	and keep track of how many parameters we actually use
		this.used = 4 ;
		if ( typeof useMatch != "number" ) {
			parseFurther = useMatch ;
			useMatch = 0 ;
			this.used = this.used - 1 ;
			}
		if ( /string|undefined/.test( typeof parseFurther ) ) {
		//	name of next production or end of list
			parseFurther = false ;
			this.used = this.used - 1 ;
			}
		if ( !selector.exec ) { // selector is name or missing
			if ( typeof selector != "string" ) { // no selector, only parseFurther
				parseFurther = selector = getSyntax( selector ) ;
				this.used = 2 ;
				if ( equal == false ) // but *not* if it's undefined
					var selectAll = true ; // select everything unless equal priority
				}
			selector = new RegExp( getSyntax( selector ).superSource ) ;
			}

	//	parameters now sorted out
		this.source = selector.source ;
		if ( equal ) {
			selector = new RegExp( "^(" + this.source + ")" ) ;
			useMatch = useMatch + 1 ;
			}
		if ( parseFurther ) {
		//	if it is or has a name, remember name. Otherwise remember Syntax
			parseFurther = getSyntax( parseFurther ) ;
			parseFurther = parseFurther.name || parseFurther ;
			}

		this.selector = selector ;
		this.selectAll = selectAll || false ;
		this.useMatch = useMatch ;
		this.name = name || false ;
		this.parseFurther = parseFurther ;
		}

	Syntax.prototype.parseString = function( string ) {
		//	parse a string into an array of code fragments using this Syntax
			for ( var i = 0 , result = [ string ] ; this[i] ; i++ )
				result = this[i].parseList( result ) ;
			return result ;
			}

	Selector.prototype.parseList = function( list ) {
	//	parse an array of code fragments to a new array using this Selector
	//	this function and cut() written for speed. elegant version was twice as slow
		for ( var i = 0 , out = [] ; i < list.length ; i++ ) {
			if ( !list[i].name ) // not already parsed
				while ( list[i] ) {
					var match = this.cut( list[i] ) ;
					if ( !match )
						break ;
					if ( match.before )
						out[ out.length ] = match.before ;
					if ( match.selected instanceof String || match.selected.name )
					//	single parsed string or named parsed array becomes new item in out
						out[ out.length ] = match.selected ;
					else
					//	anonymous parsed list is concatenated onto out
						out = out.concat( match.selected ) ;
					list[i] = match.after ;
					}
			if ( list[i] ) // add on leftovers/previously parsed
				out[ out.length ] = list[i] ;
			}
		return out ;
		}

	Selector.prototype.cut = function( select ) {
		if ( !this.selectAll ) {
			var match = this.selector.exec( select ) ;
			if ( !match )
				return false ;
		//	String object to be able to add properties
			select = new String( match[ this.useMatch ] ) ;
			if ( index = match.index + match[0].indexOf( select ) )
				var before = match.input.slice( 0 , index ) ;
		//	remove selected and anything before it from input
			var after = match.input.slice( index + select.length ) ;
			}
		if ( this.parseFurther )
			select = getSyntax( this.parseFurther ).parseString( select )
		select.name = this.name ;
		return { before: before || false , selected: select , after: after || false } ;
		}

	parsedList = getSyntax( language ).parseString( this ) ;

//	============================= Output processing ================================

	if ( outputInstruction && outputInstruction != "link" ) {
		stringWrap = XMLStringWrap ;
		DOMWrap = XMLDOMWrap ;
		if ( outputInstruction == "elements" ) {
			XMLStringWrap3 = XMLStringWrap2 = XMLStringWrap ;
			XMLDOMWrap2 = XMLDOMWrap ;
			}
		outputInstruction = outputInstruction.replace( /[^\w\d_:.-]/g , "" )
		}

	switch( ( destination && destination.nodeType || typeof destination ).toString() ) {
		case "1" :	//	element
		case "11" :	//	document fragment
			return DOMOutput( parsedList , destination ) ;
		case "string" :
			return stringOutput( parsedList , destination ) ;
		case "2" : //	attribute node (presumably pointless)
		case "3" : //	text node
			destination.nodeValue += stringOutput( parsedList , "" ) ;
			return destination ;
		case "undefined" :
			return parsedList ;
		default:
			throw "Invalid parse destination " + destination ;
		}

	function DOMOutput( parsedList , destination ) {
		for ( var i = 0 ; parsedList[i] ; i++ ) {
			var putHere = ( !parsedList[i].name ) ?
				destination :
				DOMWrap( parsedList[i].name.replace( /[^\w\d_:.\-\s]/g , "" ).replace( /\s+/ , " " ) ,
					destination , parsedList[i] ) ;
			if ( typeof parsedList[i].valueOf() == "string" )
				putHere.appendChild( document.createTextNode( parsedList[i] ) ) ;
			else
				DOMOutput( parsedList[i] , putHere ) ;
			}
		return destination ;
		}

	function DOMWrap( description , destination , content ) {
		if ( description != "url" || outputInstruction != "link" || content.toString().charAt(0) == "&" ) {
			//	not obfuscated urls
			var element = document.createElement( "span" ) ;
			element.className = description ;
			}
		else {
			element = document.createElement( "a" ) ;
		//	keep raw href string in title
			element.title = content.toString().replace( /,/g , "" ) ;
			element.href = element.title ;
			}
		return destination.appendChild( element ) ;
		}

	function XMLDOMWrap( description , destination ) {
		var x = description.indexOf( " " ) ;
		if ( x < 0 )
			return destination.appendChild( document.createElement( description ) ) ;
		return XMLDOMWrap2( description.slice( x + 1 ) ,
			XMLDOMWrap( description.slice( 0 , x ) , destination ) ) ;
		}

	function XMLDOMWrap2( description , element ) {
		element.setAttribute( outputInstruction , description ) ;
		return element ;
		}

	function stringOutput( parsedList , destination ) {
		for ( var i = 0 ; parsedList[i] ; i++ ){
			var string = ( typeof parsedList[i].valueOf() == "string" ) ? // simple typeof fails on String objects in IE
				parsedList[i].replace(
					/\&/g , "&amp;" ).replace( /</g , "&lt;" ).replace( />/g , "&gt;" ) :
				stringOutput( parsedList[i] , "" ) ;
			if ( !parsedList[i].name )
				destination = destination + string ;
			else
				destination = destination + stringWrap( parsedList[i].name , string ) ;
			}
		return destination ;
		}

	function stringWrap( description , content ) {
		if ( description != "url" || outputInstruction != "link" || content.replace( /<[^>]*>/ , "" ).charAt(0) == "&" )
			return '<span class="' + description + '">' + content + "</span>" ;
		else {
			//	remove markup in href
			var href = content.replace( /<[^>]*>/g , "" ) ;
			//	keep original href string in title
			return '<a href="' + href + '" title = "' + href + '">' +
				content + "</a>" ;
			}
		}

	function XMLStringWrap( description , content ) {
		var x = description.indexOf( " " ) ;
		if ( x < 0 )
			return "<" + description + ">" + content + "</" + description + ">" ;
		return XMLStringWrap2( description.slice( 0 , x ) , 
			XMLStringWrap3( description.slice( x + 1 ) , content ) ) ;
		}

	function XMLStringWrap2( tagName , stuff ) {
		return "<" + tagName + " " + stuff + "</" + tagName + ">" ;
		}

	function XMLStringWrap3( attribute , content ) {
		return outputInstruction + '="' + attribute + '">' + content ;
		}
	}

/*=============================== SYNTAX SPECIFICATIONS =============================

	Syntax specification is an array:
	First item: syntax name. If not empty, the syntax is stored as a property of
			String.prototype.parse and may be referred to by name in other syntax specifications.
			May be anything except "=".
	Second item (optional): if  "=", then this is an equal priority syntax: all productions are looked
			for at once. This option is needed because things that look like one element may be
			contained inside another and be misidentified if they are searched for one after the other.
			For example: strings, regular expressions and comments in JavaScript.
	Remaining items: groups of one to four items defining syntax productions:
		1.	production name: string, may be empty. Used to identify productions
			when found. Leave empty to avoid identifying them before further parsing with (4).
		2.	production selector: (optional if (4) is given) regular expression or name of a previously
			defined syntax. Used to find productions. Omit to find everything and parse further with (4).
			If a syntax name is given, then anything that the syntax's selectors find
			will be found, regardless of whether the syntax identifies it as a production.
			If the selector is omitted in an equal priority list, the syntax given at (4)'s
			selectors will be used as a selector; this probably only makes sense if (4) is itself
			an equal priority syntax.
		3.	(optional, only if (2) is given) index of regular expression capture corresponding to
			production. Unlikely to be useful unless (2) is a regular expression.
		4.	(optional unless (2) was omitted) (sub)syntax to use to parse the production further. May be given
			as a one item list [ syntax name ] or as a syntax specification. Outside an equal priority
			syntax, names need not already have been defined. If the element name (1) is not empty, the results
			of parsing with the (sub)syntax are stored as an array in a single item of the result array or
			contained in a single element of the output. If the name is empty, they are concatenated
			onto the end of the array or added on to the end of output at the same level of the
			DOM tree/markup.

	Syntax names are case-insensitive.

	All syntaxes referred to by name in an equal priority syntax must already have been defined
	for the syntax to function properly. If the definitions are changed later, the syntax will no
	longer function correctly.

	Where multiple names are given at (1), start with the general and move to the specific,
	or move down the hierarchy of productions. XML output (see below) will then be meaningful
	if the syntax is sufficiently rigorously defined (not the case in any of my definitions).

	To define a syntax name for later use, parse a string using a syntax definition with that name.
*/

void "".parse( [ "ECMAScript" , // named syntax
	"" , [ "" , "=" , // anonymous equal priority sub-syntax
	//	most common productions first to improve performance
		"literal string" , /"([^\n"\\]|\\[\s\S])*"|'([^\n'\\]|\\[\s\S])*'/ ,
		"literal regexp" , /\/[^*\/\n\s]([^\n\/\\]|\\[^\n])*\/[gim]{0,3}/ ,
		"comment multiline" , /\/\*([^*]|\*[^\/])*\*\// , [ "urls" ] , // parse urls inside the comment
		"comment single-line" , /(^|[^\\])(\/\/[^\n]*)/ , 2 , [ "urls" ] , // target is in 2nd capture parentheses
		] ,
	//	not equal priority: *less* common first improves performance
	"literal number decimal" , /(^|[^\w])([+-]?(\d*\.?\d+|\d+\.?\d*)([eE][+-]?\d+)?)\b/ , 2 ,
	"literal number hex" , /\b(0[xX][\da-fA-F]*)\b/ , 1 ,
	"literal number keyword" , /\b(infinity|NaN)\b/ , 1 ,
	"literal boolean keyword" , /\b(true|false)\b/ , 1 ,
	"literal primitive keyword" , /\b(null|undefined)\b/ , 1 ,
	"operator keyword" , /\b(delete|in|instanceof|new|this|typeof|void)\b/ , 1 ,
	"operator keyword" , /\b(function)[^\w_$\(]*\(/ , 1 ,
	"local builtin" , /\b(arguments)\b/ , 1 ,
	"property builtin" , /\.(prototype|constructor|callee)\b/ , 1 ,
	"function builtin" , /\b(decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|isFinite|isNaN|Number|parseFloat|parseInt|String)\s*\(/ , 1 ,
	"global builtin" , /\b(Object|Function|Array|String|Boolean|Number|Math|Date|RegExp)\b/ , 1 ,
	"statement keyword declaration" , /\b(const|function|var)\b/ , 1 ,
	"statement keyword" , /\b(break|continue|do|while|export|for|in|if|else|import|label|return|switch|case|default|throw|try|catch|finally|with)\b/ , 1 ,
	] ) ;

void "".parse( [ "JavaScript" ,
	"" , [ "ECMAScript" ] ,
	"global builtin host" , /\b(history|window|document|navigator)\b/ , 1 ,
	] ) ;

void "".parse( [ "php script" ,
	"keyword" , /\b(as|break|case|continue|declare|default|do|echo|else|elseif|endif|endfor|endswitch|endwhile|exit|for|foreach|function|global|if|include|include_once|next|return|require|require_once|switch|while)\b/ , 1 ,
	"global" , /([^$]|^)\b(\w+)/ , 2 ,
	"variable" , /$\w+/ , ] );

void "".parse( [ "Cascading Style Sheets" ,
	"" , [ "" , "=" ,
		"" , [ "css comments and urls" ,
				"comment" , /\/\*([^*]|\*[^\/])*\*\// , [ "urls" ] ,
				"" , /\b(url\([^\)]*\))/ , 1 , [ "" ,
					"function" , /^url/ ,
					"url" , /\(\s*["']?(([^\)\s'"]|\s+[^\s\)"'])*)/ , 1 ,
					] ,
				] ,
		"" , /[\S][^;{}]*\{(([^{}]|(\/\*([^*]|\*[^\/])*\*\/)|'[^']*'|"[^"]*")*)\}/ , 1 , [ "css style rules" ,
			"" , [ "" , "=" ,
				"" , [ "css comments and urls" ] ,
				"string" , /'[^']*'|"[^"]*"/ ,
				] ,
			"property proprietary" , /[^\w]((-|mso-)[\w-\\]+)\s*:/ , 1 ,
			"property" , /([\w-\\]+)\s*:/ , 1 ,
			"color" , /(^|[^\w])(#[\da-fA-F]{3,6})\b/ , 2 ,
			"unit" , /\b(\d+|\d*\.\d+)+(cm|em|ex|pt|px|%)/ , 2 ,
			"number" , /\b(\d+\.?\d*|\d*\.\d+)\b/ , 1 ,
			"important" , /!\s*important/ ,
			"function" , /\b(attr|rect|rgb|auto|inherit)\b/ , 1 ,
			"value" , /\w[^\s;]*/ ,
			] ,
		"atrule" , /@\w([\S]|\s[^\{;])*/ , [ "css comments and urls" ] ,
		] ,
//	before braces: should be nothing left but comments and selectors:
	"" , /(\S[\s\S]+)\{/ , 1 , [ "" ,
		"" , [ "css comments and urls" ] ,
		"selector" , /[^,;{}\s]([^,\s]|\s+[^\s,])*/ ,
		] ,
// after braces:
	"" , /\}\s*(\S[\s\S]+)/ , 1 , [ "css comments and urls" ] ,
	] ) ;

void "".parse( [ "xml" ,
	"" , [ "xml declarations" , "=" ,
		"" , [ "xml comments" ,
			"comment" , /<!--([^-]|-[^-])*-->/ , [ "" ,
				"" , [ "xml delimiters" ,
					"delimiter" , /^<(\/|\?|!(--|\[%?\w+\[)?)?|(\/|\?|--|\]\])?>$/ ,
					] ,
				"" , [ "urls" ,
					"url" , /http:[\\\/][\\\/]+[\w\\\/\-%&#=.,?+$]+/ ,
					] ,
				] ,
			] ,
		"" , [ "xml cdata sections" ,
			"cdata" , /<!\[CDATA\[(([^\]]|\][^\]]|]][^>])*)\]\]>/ , [ "xml delimiters" ] ,
			] ,
		"declaration doctype" , /<!DOCTYPE\s[^>[]*(\[([^<\]]|<[^>]*>)*\])?\s*>/ , [ "" ,
			"dtd subset internal" , /<!DOCTYPE\s[^>[]*(\[(([^<\]]|<[^>]*>)*)\])\s*>/ , 1 , [ "xml dtd" ] ,
			"" , [ "xml declaration innards" ] ,
			] ,
		] ,
	"" , [ "xml prolog" ,
		"declaration xml" , /^<(\?xml\s+([^?]|\?[^>])*\?)>/ ,
		] ,
	"" , [ "xml processing instructions" ,
		"pi" , /<\?([\w_][\w\d_:.-]+([^?]|\?[^>])*)\?>/ , [ "xml pi innards" ,
			"target" , /^<\?([\w\d_:.-]+)/ , 1 ,
			] ,
		] ,
	"" , [ "xml tags" ,
		"tag" , /<([^>'"]|'[^']*'|"[^"]*")*>/ , [ "xml tag innards" ,
			"tagname" , /^<\/?([\w_][\w\d_:.-]*)(\s|\/?>)/ , 1 ,
			"" , [ "xml strings" ,
				"value" , /('[^']*'|"[^"]*")/ , 1 , [ "xml entity references" ] ,
			] ,
			"attribute" , /\s([\w_][\w\d_:.-]*)\s*=/ , 1 ,
			"" , [ "xml delimiters" ] ,
		] ,
	] ,
	"" , [ "xml entity references" ,
		"entity" , /&#?\w+;/ ,
		] ,
	] ) ;

void "".parse( [ "xml dtd" ,
	"declaration" , /<!(-?[^>-]|--([^-]|-[^-])*--)*>/ , [ "xml declaration innards" ,
		"" , /^ATTLIST\s[\s\S]*/ , [ "" ,
			"" , /\(([^\)]*)\)/ , 1 , [ "" ,
				"value enumerated" , /[^|]+/ ,
				] ,
			] ,
		"" , [ "" , "=" ,
			"comment" , /--([^-]|-[^-])*--/ , [ "urls" ] ,
			"" , [ "xml strings" ] ,
		] ,
		"" , [ "xml delimiters" ] ,
		"keyword" , /^\w+|SYSTEM|PUBLIC|NOTATION/ ,
		"keyword contentmodel" , /#PCDATA|EMPTY/ ,
		"keyword attributetype" , /CDATA|NMTOKENS?|ID(REFS?)?|ENTIT(Y|IES)/ ,
		"keyword attributedefault" , /#\w+/ ,
		"entity parameter" , /%[^;]+;/ ,
		"name" , /[^%\s(),\|?&+]+/ ,
		] ,
	"entity parameter" , /%[^;]+;/ ,
	] )

void "".parse( [ "xml html" ,
	"" , [ "" , "=" ,
		"" , [ "xml declarations" ] ,
		"" , /<(style|STYLE)([^>]*[^\/])?>(([^<]|<[^\/!]|<![^\[]|<!\[CDATA\[(([^\]]|\][^\]]|]][^>])*)\]\]>)+)<\/(style|STYLE)>/ , 3 , [ "" ,
		//	'i' flag would get thrown away when building the superSource
			"comment" , "xml comments" , [ "" ,
				"delimiter" , "xml delimiters" ,
				"stylesheet css" , /.*\n([\s\S]*)/ , 1 , [ "Cascading Style Sheets" ] ,
				] ,
			"cdata" , "xml cdata sections" , [ "" ,
				"delimiter" , "xml delimiters" ,
				"stylesheet css" , [ "Cascading Style Sheets" ] ,
				] ,
			"stylesheet css" , [ "Cascading Style Sheets" ] ,
			] ,
		"" , /<(script|SCRIPT)([^>]*[^\/])?>(([^<]|<[^\/!]|<![^\[]|<!\[CDATA\[(([^\]]|\][^\]]|]][^>])*)\]\]>)+)<\/(script|SCRIPT)>/ , 3 , [ "" ,
			"comment" , "xml comments" , [ "" ,
				"delimiter" , "xml delimiters" ,
				"script JavaScript" , /.*\n([\s\S]*)/ , 1 , [ "JavaScript" ] ,
				] ,
			"cdata" , "xml cdata sections" , [ "" ,
				"delimiter" , "xml delimiters" ,
				"script JavaScript" , [ "JavaScrIpt" ] ,
				] ,
			"script JavaScript" , [ "JavaScript" ] , ,
			] ,
		] ,
	"" , [ "xml prolog" ] ,
	"pi" , "xml processing instructions" , [ "" ,
		"" , /^<\?php([\s\S]+)\?>$/ , 1 , [ "" ,
			"script php" , [ "php script" ] ,
			] ,
		"" , [ "xml pi innards" ] ,
		] ,
	"tag" , "xml tags" , [ "" ,
		"value" , /href\s*=\s*('javascript:[^']*'|"javascript:[^"]*")/i , 1 , [ "" ,
			"JavaScript script" , /["']javascript:([\s\S]*)['"]/ , 1 , [ "JavaScript" ] ,
			] ,
		"value" , /style\s*=\s*('[^']*'|"[^"]*")/i , 1 , [ "" ,
			"css" , /["']([\s\S]*)['"]/ , 1 , [ "css style rules" ] ,
			] ,
		"value" , /(src|href)\s*=\s*('[^']*'|"[^"]*")/i , 2 , [ "" ,
			"url" , /["']([\s\S]*)['"]/ , 1 , [ "xml entity references" ] ,
			] ,
		"" , [ "xml tag innards" ] ,
		] ,
	"" , [ "xml entity references" ] ,
	] ) ;

// ============================= aliases =============================
// property names must be lower case

String.prototype.parse.php =
String.prototype.parse.htm =
String.prototype.parse.html =
String.prototype.parse.xht =
String.prototype.parse.xhtml = "xml html" ;

String.prototype.parse.dtd = "xml dtd" ;

String.prototype.parse.js = "JavaScript script" ; // class name for source listings
String.prototype.parse[ "javascript script" ] = "JavaScript" ;

String.prototype.parse.css = "css stylesheet" ; // class name for source listings
String.prototype.parse[ "css stylesheet" ] = "Cascading Style Sheets" ;

/*================================== XML OUTPUT ==================================

If an instruction other than 'link' is given then xml is returned. Given the current state of
my syntax definitions, this option is pretty pointless, but it was easy to program, so
what the hell.

If the instruction is "elements", the names of language productions are treated as
space-separated lists of production names to be nested around the content, starting with
the outermost. Otherwise the name up to the first space is used for the element name and
anything after a first space is used as an attribute whose name is given by the instruction.
In either case, any illegal characters are removed from the element and attribute names.
(So it is even possible to name the attribute "elements" or "link": "elements*" or "$link"
would do the trick, for example. But why should you want to?)
*/

