1" Vim completion script
2" Language:	XHTML 1.0 Strict
3" Maintainer:	Mikolaj Machowski ( mikmach AT wp DOT pl )
4" Last Change:	2006 Feb 6
5
6function! htmlcomplete#CompleteTags(findstart, base)
7  if a:findstart
8    " locate the start of the word
9    let line = getline('.')
10    let start = col('.') - 1
11	let curline = line('.')
12	let compl_begin = col('.') - 2
13    while start >= 0 && line[start - 1] =~ '\(\k\|[:.-]\)'
14		let start -= 1
15    endwhile
16	" Handling of entities {{{
17	if start >= 0 && line[start - 1] =~ '&'
18		let b:entitiescompl = 1
19		let b:compl_context = ''
20		return start
21	endif
22	" }}}
23	" Handling of <style> tag {{{
24	let stylestart = searchpair('<style\>', '', '<\/style\>', "bnW")
25	let styleend   = searchpair('<style\>', '', '<\/style\>', "nW")
26	if stylestart != 0 && styleend != 0
27		if stylestart <= curline && styleend >= curline
28			let start = col('.') - 1
29			let b:csscompl = 1
30			while start >= 0 && line[start - 1] =~ '\(\k\|-\)'
31				let start -= 1
32			endwhile
33		endif
34	endif
35	" }}}
36	" Handling of <script> tag {{{
37	let scriptstart = searchpair('<script\>', '', '<\/script\>', "bnW")
38	let scriptend   = searchpair('<script\>', '', '<\/script\>', "nW")
39	if scriptstart != 0 && scriptend != 0
40		if scriptstart <= curline && scriptend >= curline
41			let start = col('.') - 1
42			let b:jscompl = 1
43			let b:jsrange = [scriptstart, scriptend]
44			while start >= 0 && line[start - 1] =~ '\w'
45				let start -= 1
46			endwhile
47			" We are inside of <script> tag. But we should also get contents
48			" of all linked external files and (secondary, less probably) other <script> tags
49			" This logic could possible be done in separate function - may be
50			" reused in events scripting (also with option could be reused for
51			" CSS
52			let b:js_extfiles = []
53			let l = line('.')
54			let c = col('.')
55			call cursor(1,1)
56			while search('<\@<=script\>', 'W') && line('.') <= l
57				if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
58					let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
59					if filereadable(sname)
60						let b:js_extfiles += readfile(sname)
61					endif
62				endif
63			endwhile
64			call cursor(1,1)
65			let js_scripttags = []
66			while search('<script\>', 'W') && line('.') < l
67				if matchstr(getline('.'), '<script[^>]*src') == ''
68					let js_scripttag = getline(line('.'), search('</script>', 'W'))
69					let js_scripttags += js_scripttag
70				endif
71			endwhile
72			let b:js_extfiles += js_scripttags
73			call cursor(l,c)
74			unlet! l c
75		endif
76	endif
77	" }}}
78	if !exists("b:csscompl") && !exists("b:jscompl")
79		let b:compl_context = getline('.')[0:(compl_begin)]
80		if b:compl_context !~ '<[^>]*$'
81			" Look like we may have broken tag. Check previous lines.
82			let i = 1
83			while 1
84				let context_line = getline(curline-i)
85				if context_line =~ '<[^>]*$'
86					" Yep, this is this line
87					let context_lines = getline(curline-i, curline)
88					let b:compl_context = join(context_lines, ' ')
89					break
90				elseif context_line =~ '>[^<]*$'
91					" Normal tag line, no need for completion at all
92					let b:compl_context = ''
93					break
94				endif
95				let i += 1
96			endwhile
97			" Make sure we don't have counter
98			unlet! i
99		endif
100		let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
101		" Return proper start for on-events. Without that beginning of
102		" completion will be badly reported
103		if b:compl_context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
104			let start = col('.') - 1
105			while start >= 0 && line[start - 1] =~ '\w'
106				let start -= 1
107			endwhile
108		endif
109	else
110		let b:compl_context = getline('.')[0:compl_begin]
111	endif
112    return start
113  else
114	" Initialize base return lists
115    let res = []
116    let res2 = []
117	" a:base is very short - we need context
118	let context = b:compl_context
119	" Check if we should do CSS completion inside of <style> tag
120	" or JS completion inside of <script> tag
121	if exists("b:csscompl")
122		unlet! b:csscompl
123		let context = b:compl_context
124		unlet! b:compl_context
125		return csscomplete#CompleteCSS(0, context)
126	elseif exists("b:jscompl")
127		unlet! b:jscompl
128		return javascriptcomplete#CompleteJS(0, a:base)
129	else
130		if len(b:compl_context) == 0 && !exists("b:entitiescompl")
131			return []
132		endif
133		let context = matchstr(b:compl_context, '.\zs.*')
134	endif
135	unlet! b:compl_context
136	" Entities completion {{{
137	if exists("b:entitiescompl")
138		unlet! b:entitiescompl
139
140		if !exists("g:xmldata_xhtml10s")
141			runtime! autoload/xml/xhtml10s.vim
142		endif
143
144	    let entities =  g:xmldata_xhtml10s['vimxmlentities']
145
146		if len(a:base) == 1
147			for m in entities
148				if m =~ '^'.a:base
149					call add(res, m.';')
150				endif
151			endfor
152			return res
153		else
154			for m in entities
155				if m =~? '^'.a:base
156					call add(res, m.';')
157				elseif m =~? a:base
158					call add(res2, m.';')
159				endif
160			endfor
161
162			return res + res2
163		endif
164
165
166	endif
167	" }}}
168	if context =~ '>'
169		" Generally if context contains > it means we are outside of tag and
170		" should abandon action - with one exception: <style> span { bo
171		if context =~ 'style[^>]\{-}>[^<]\{-}$'
172			return csscomplete#CompleteCSS(0, context)
173		elseif context =~ 'script[^>]\{-}>[^<]\{-}$'
174			let b:jsrange = [line('.'), search('<\/script\>', 'nW')]
175			return javascriptcomplete#CompleteJS(0, context)
176		else
177			return []
178		endif
179	endif
180
181	" Set attribute groups
182    let coreattrs = ["id", "class", "style", "title"]
183    let i18n = ["lang", "xml:lang", "dir=\"ltr\" ", "dir=\"rtl\" "]
184    let events = ["onclick", "ondblclick", "onmousedown", "onmouseup", "onmousemove",
185    			\ "onmouseover", "onmouseout", "onkeypress", "onkeydown", "onkeyup"]
186    let focus = ["accesskey", "tabindex", "onfocus", "onblur"]
187    let coregroup = coreattrs + i18n + events
188	" If context contains > it means we are already outside of tag and we
189	" should abandon action
190	" If context contains white space it is attribute.
191	" It can be also value of attribute.
192	" We have to get first word to offer proper completions
193	if context == ''
194		let tag = ''
195	else
196		let tag = split(context)[0]
197	endif
198	" Get last word, it should be attr name
199	let attr = matchstr(context, '.*\s\zs.*')
200	" Possible situations where any prediction would be difficult:
201	" 1. Events attributes
202	if context =~ '\s'
203		" Sort out style, class, and on* cases
204		if context =~? "\\(on[a-z]*\\|id\\|style\\|class\\)\\s*=\\s*[\"']"
205			" Id, class completion {{{
206			if context =~? "\\(id\\|class\\)\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
207				if context =~? "class\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
208					let search_for = "class"
209				elseif context =~? "id\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
210					let search_for = "id"
211				endif
212				" Handle class name completion
213				" 1. Find lines of <link stylesheet>
214				" 1a. Check file for @import
215				" 2. Extract filename(s?) of stylesheet,
216				call cursor(1,1)
217				let head = getline(search('<head\>'), search('<\/head>'))
218				let headjoined = join(copy(head), ' ')
219				if headjoined =~ '<style'
220					" Remove possibly confusing CSS operators
221					let stylehead = substitute(headjoined, '+>\*[,', ' ', 'g')
222					if search_for == 'class'
223						let styleheadlines = split(stylehead)
224						let headclasslines = filter(copy(styleheadlines), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
225					else
226						let stylesheet = split(headjoined, '[{}]')
227						" Get all lines which fit id syntax
228						let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
229						" Filter out possible color definitions
230						call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
231						" Filter out complex border definitions
232						call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
233						let templines = join(classlines, ' ')
234						let headclasslines = split(templines)
235						call filter(headclasslines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
236					endif
237					let internal = 1
238				else
239					let internal = 0
240				endif
241				let styletable = []
242				let secimportfiles = []
243				let filestable = filter(copy(head), "v:val =~ '\\(@import\\|link.*stylesheet\\)'")
244				for line in filestable
245					if line =~ "@import"
246						let styletable += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
247					elseif line =~ "<link"
248						let styletable += [matchstr(line, "href\\s*=\\s*[\"']\\zs\\f\\+\\ze")]
249					endif
250				endfor
251				for file in styletable
252					if filereadable(file)
253						let stylesheet = readfile(file)
254						let secimport = filter(copy(stylesheet), "v:val =~ '@import'")
255						if len(secimport) > 0
256							for line in secimport
257								let secfile = matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")
258								let secfile = fnamemodify(file, ":p:h").'/'.secfile
259								let secimportfiles += [secfile]
260							endfor
261						endif
262					endif
263				endfor
264				let cssfiles = styletable + secimportfiles
265				let classes = []
266				for file in cssfiles
267					if filereadable(file)
268						let stylesheet = readfile(file)
269						let stylefile = join(stylesheet, ' ')
270						let stylefile = substitute(stylefile, '+>\*[,', ' ', 'g')
271						if search_for == 'class'
272							let stylesheet = split(stylefile)
273							let classlines = filter(copy(stylesheet), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
274						else
275							let stylesheet = split(stylefile, '[{}]')
276							" Get all lines which fit id syntax
277							let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
278							" Filter out possible color definitions
279							call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
280							" Filter out complex border definitions
281							call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
282							let templines = join(classlines, ' ')
283							let stylelines = split(templines)
284							let classlines = filter(stylelines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
285
286						endif
287					endif
288					" We gathered classes definitions from all external files
289					let classes += classlines
290				endfor
291				if internal == 1
292					let classes += headclasslines
293				endif
294
295				if search_for == 'class'
296					let elements = {}
297					for element in classes
298						if element =~ '^\.'
299							let class = matchstr(element, '^\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
300							let class = substitute(class, ':.*', '', '')
301							if has_key(elements, 'common')
302								let elements['common'] .= ' '.class
303							else
304								let elements['common'] = class
305							endif
306						else
307							let class = matchstr(element, '[a-zA-Z1-6]*\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
308							let tagname = tolower(matchstr(element, '[a-zA-Z1-6]*\ze.'))
309							if tagname != ''
310								if has_key(elements, tagname)
311									let elements[tagname] .= ' '.class
312								else
313									let elements[tagname] = class
314								endif
315							endif
316						endif
317					endfor
318
319					if has_key(elements, tag) && has_key(elements, 'common')
320						let values = split(elements[tag]." ".elements['common'])
321					elseif has_key(elements, tag) && !has_key(elements, 'common')
322						let values = split(elements[tag])
323					elseif !has_key(elements, tag) && has_key(elements, 'common')
324						let values = split(elements['common'])
325					else
326						return []
327					endif
328
329				elseif search_for == 'id'
330					" Find used IDs
331					" 1. Catch whole file
332					let filelines = getline(1, line('$'))
333					" 2. Find lines with possible id
334					let used_id_lines = filter(filelines, 'v:val =~ "id\\s*=\\s*[\"''][a-zA-Z0-9_-]\\+"')
335					" 3a. Join all filtered lines
336					let id_string = join(used_id_lines, ' ')
337					" 3b. And split them to be sure each id is in separate item
338					let id_list = split(id_string, 'id\s*=\s*')
339					" 4. Extract id values
340					let used_id = map(id_list, 'matchstr(v:val, "[\"'']\\zs[a-zA-Z0-9_-]\\+\\ze")')
341					let joined_used_id = ','.join(used_id, ',').','
342
343					let allvalues = map(classes, 'matchstr(v:val, ".*#\\zs[a-zA-Z0-9_-]\\+")')
344
345					let values = []
346
347					for element in classes
348						if joined_used_id !~ ','.element.','
349							let values += [element]
350						endif
351
352					endfor
353
354				endif
355
356				" We need special version of sbase
357				let classbase = matchstr(context, ".*[\"']")
358				let classquote = matchstr(classbase, '.$')
359
360				let entered_class = matchstr(attr, ".*=\\s*[\"']\\zs.*")
361
362				for m in sort(values)
363					if m =~? '^'.entered_class
364						call add(res, m . classquote)
365					elseif m =~? entered_class
366						call add(res2, m . classquote)
367					endif
368				endfor
369
370				return res + res2
371
372			elseif context =~? "style\\s*=\\s*[\"'][^\"']*$"
373				return csscomplete#CompleteCSS(0, context)
374
375			endif
376			" }}}
377			" Complete on-events {{{
378			if context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
379				" We have to:
380				" 1. Find external files
381				let b:js_extfiles = []
382				let l = line('.')
383				let c = col('.')
384				call cursor(1,1)
385				while search('<\@<=script\>', 'W') && line('.') <= l
386					if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
387						let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
388						if filereadable(sname)
389							let b:js_extfiles += readfile(sname)
390						endif
391					endif
392				endwhile
393				" 2. Find at least one <script> tag
394				call cursor(1,1)
395				let js_scripttags = []
396				while search('<script\>', 'W') && line('.') < l
397					if matchstr(getline('.'), '<script[^>]*src') == ''
398						let js_scripttag = getline(line('.'), search('</script>', 'W'))
399						let js_scripttags += js_scripttag
400					endif
401				endwhile
402				let b:js_extfiles += js_scripttags
403
404				" 3. Proper call for javascriptcomplete#CompleteJS
405				call cursor(l,c)
406				let js_context = matchstr(a:base, '\w\+$')
407				let js_shortcontext = substitute(a:base, js_context.'$', '', '')
408				let b:compl_context = context
409				let b:jsrange = [l, l]
410				unlet! l c
411				"return map(javascriptcomplete#CompleteJS(0, js_context), 'js_shortcontext.v:val')
412				return javascriptcomplete#CompleteJS(0, js_context)
413
414			endif
415
416			" }}}
417			let stripbase = matchstr(context, ".*\\(on[a-zA-Z]*\\|style\\|class\\)\\s*=\\s*[\"']\\zs.*")
418			" Now we have context stripped from all chars up to style/class.
419			" It may fail with some strange style value combinations.
420			if stripbase !~ "[\"']"
421				return []
422			endif
423		endif
424		" Value of attribute completion {{{
425		" If attr contains =\s*[\"'] we catched value of attribute
426		if attr =~ "=\s*[\"']"
427			" Let do attribute specific completion
428			let attrname = matchstr(attr, '.*\ze\s*=')
429			let entered_value = matchstr(attr, ".*=\\s*[\"']\\zs.*")
430			let values = []
431			if attrname == 'media'
432				let values = ["screen", "tty", "tv", "projection", "handheld", "print", "braille", "aural", "all"]
433			elseif attrname == 'xml:space'
434				let values = ["preserve"]
435			elseif attrname == 'shape'
436				let values = ["rect", "circle", "poly", "default"]
437			elseif attrname == 'valuetype'
438				let values = ["data", "ref", "object"]
439			elseif attrname == 'method'
440				let values = ["get", "post"]
441			elseif attrname == 'dir'
442				let values = ["ltr", "rtl"]
443			elseif attrname == 'frame'
444				let values = ["void", "above", "below", "hsides", "lhs", "rhs", "vsides", "box", "border"]
445			elseif attrname == 'rules'
446				let values = ["none", "groups", "rows", "all"]
447			elseif attrname == 'align'
448				let values = ["left", "center", "right", "justify", "char"]
449			elseif attrname == 'valign'
450				let values = ["top", "middle", "bottom", "baseline"]
451			elseif attrname == 'scope'
452				let values = ["row", "col", "rowgroup", "colgroup"]
453			elseif attrname == 'href'
454				" Now we are looking for local anchors defined by name or id
455				if entered_value =~ '^#'
456					let file = join(getline(1, line('$')), ' ')
457					" Split it be sure there will be one id/name element in
458					" item, it will be also first word [a-zA-Z0-9_-] in element
459					let oneelement = split(file, "\\(meta \\)\\@<!\\(name\\|id\\)\\s*=\\s*[\"']")
460					for i in oneelement
461						let values += ['#'.matchstr(i, "^[a-zA-Z][a-zA-Z0-9%_-]*")]
462					endfor
463				endif
464			elseif attrname == 'type'
465				if context =~ '^input'
466					let values = ["text", "password", "checkbox", "radio", "submit", "reset", "file", "hidden", "image", "button"]
467				elseif context =~ '^button'
468					let values = ["button", "submit", "reset"]
469				elseif context =~ '^style'
470					let values = ["text/css"]
471				elseif context =~ '^script'
472					let values = ["text/javascript"]
473				endif
474			else
475				return []
476			endif
477
478			if len(values) == 0
479				return []
480			endif
481
482			" We need special version of sbase
483			let attrbase = matchstr(context, ".*[\"']")
484			let attrquote = matchstr(attrbase, '.$')
485
486			for m in values
487				" This if is needed to not offer all completions as-is
488				" alphabetically but sort them. Those beginning with entered
489				" part will be as first choices
490				if m =~ '^'.entered_value
491					call add(res, m . attrquote.' ')
492				elseif m =~ entered_value
493					call add(res2, m . attrquote.' ')
494				endif
495			endfor
496
497			return res + res2
498
499		endif
500		" }}}
501		" Attribute completion {{{
502		" Shorten context to not include last word
503		let sbase = matchstr(context, '.*\ze\s.*')
504		if tag =~ '^\(abbr\|acronym\|address\|b\|bdo\|big\|caption\|cite\|code\|dd\|dfn\|div\|dl\|dt\|em\|fieldset\|h\d\|hr\|i\|kbd\|li\|noscript\|ol\|p\|samp\|small\|span\|strong\|sub\|sup\|tt\|ul\|var\)$'
505			let attrs = coregroup
506		elseif tag == 'a'
507			let attrs = coregroup + focus + ["charset", "type", "name", "href", "hreflang", "rel", "rev", "shape", "coords"]
508		elseif tag == 'area'
509			let attrs = coregroup + focus + ["shape", "coords", "href", "nohref", "alt"]
510		elseif tag == 'base'
511			let attrs = ["href", "id"]
512		elseif tag == 'blockquote'
513			let attrs = coregroup + ["cite"]
514		elseif tag == 'body'
515			let attrs = coregroup + ["onload", "onunload"]
516		elseif tag == 'br'
517			let attrs = coreattrs
518		elseif tag == 'button'
519			let attrs = coregroup + focus + ["name", "value", "type"]
520		elseif tag == '^\(col\|colgroup\)$'
521			let attrs = coregroup + ["span", "width", "align", "char", "charoff", "valign"]
522		elseif tag =~ '^\(del\|ins\)$'
523			let attrs = coregroup + ["cite", "datetime"]
524		elseif tag == 'form'
525			let attrs = coregroup + ["action", "method=\"get\" ", "method=\"post\" ", "enctype", "onsubmit", "onreset", "accept", "accept-charset"]
526		elseif tag == 'head'
527			let attrs = i18n + ["id", "profile"]
528		elseif tag == 'html'
529			let attrs = i18n + ["id", "xmlns"]
530		elseif tag == 'img'
531			let attrs = coregroup + ["src", "alt", "longdesc", "height", "width", "usemap", "ismap"]
532		elseif tag == 'input'
533			let attrs = coregroup + ["type", "name", "value", "checked", "disabled", "readonly", "size", "maxlength", "src", "alt", "usemap", "onselect", "onchange", "accept"]
534		elseif tag == 'label'
535			let attrs = coregroup + ["for", "accesskey", "onfocus", "onblur"]
536		elseif tag == 'legend'
537			let attrs = coregroup + ["accesskey"]
538		elseif tag == 'link'
539			let attrs = coregroup + ["charset", "href", "hreflang", "type", "rel", "rev", "media"]
540		elseif tag == 'map'
541			let attrs = i18n + events + ["id", "class", "style", "title", "name"]
542		elseif tag == 'meta'
543			let attrs = i18n + ["id", "http-equiv", "content", "scheme", "name"]
544		elseif tag == 'title'
545			let attrs = i18n + ["id"]
546		elseif tag == 'object'
547			let attrs = coregroup + ["declare", "classid", "codebase", "data", "type", "codetype", "archive", "standby", "height", "width", "usemap", "name", "tabindex"]
548		elseif tag == 'optgroup'
549			let attrs = coregroup + ["disbled", "label"]
550		elseif tag == 'option'
551			let attrs = coregroup + ["disbled", "selected", "value", "label"]
552		elseif tag == 'param'
553			let attrs = ["id", "name", "value", "valuetype", "type"]
554		elseif tag == 'pre'
555			let attrs = coregroup + ["xml:space"]
556		elseif tag == 'q'
557			let attrs = coregroup + ["cite"]
558		elseif tag == 'script'
559			let attrs = ["id", "charset", "type=\"text/javascript\"", "type", "src", "defer", "xml:space"]
560		elseif tag == 'select'
561			let attrs = coregroup + ["name", "size", "multiple", "disabled", "tabindex", "onfocus", "onblur", "onchange"]
562		elseif tag == 'style'
563			let attrs = coreattrs + ["id", "type=\"text/css\"", "type", "media", "title", "xml:space"]
564		elseif tag == 'table'
565			let attrs = coregroup + ["summary", "width", "border", "frame", "rules", "cellspacing", "cellpadding"]
566		elseif tag =~ '^\(thead\|tfoot\|tbody\|tr\)$'
567			let attrs = coregroup + ["align", "char", "charoff", "valign"]
568		elseif tag == 'textarea'
569			let attrs = coregroup + ["name", "rows", "cols", "disabled", "readonly", "onselect", "onchange"]
570		elseif tag =~ '^\(th\|td\)$'
571			let attrs = coregroup + ["abbr", "headers", "scope", "rowspan", "colspan", "align", "char", "charoff", "valign"]
572		else
573			return []
574		endif
575
576		for m in sort(attrs)
577			if m =~ '^'.attr
578				if m =~ '^\(ismap\|defer\|declare\|nohref\|checked\|disabled\|selected\|readonly\)$' || m =~ '='
579					call add(res, m)
580				else
581					call add(res, m.'="')
582				endif
583			elseif m =~ attr
584				if m =~ '^\(ismap\|defer\|declare\|nohref\|checked\|disabled\|selected\|readonly\)$' || m =~ '='
585					call add(res2, m)
586				else
587					call add(res2, m.'="')
588				endif
589			endif
590		endfor
591
592		return res + res2
593
594	endif
595	" }}}
596	" Close tag {{{
597	let b:unaryTagsStack = "base meta link hr br param img area input col"
598	if context =~ '^\/'
599		let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
600		return [opentag.">"]
601	endif
602	" Deal with tag completion.
603	let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
604	if opentag == ''
605		" Hack for sometimes failing GetLastOpenTag.
606		" As far as I tested fail isn't GLOT fault but problem
607		" of invalid document - not properly closed tags and other mish-mash.
608		" If returns empty string assume <body>. Safe bet.
609		let opentag = 'body'
610	endif
611	" }}}
612	" Load data {{{
613	if !exists("g:xmldata_xhtml10s")
614		runtime! autoload/xml/xhtml10s.vim
615	endif
616	" }}}
617	" Tag completion {{{
618
619	let tags = g:xmldata_xhtml10s[opentag][0]
620
621	for m in sort(tags)
622		if m =~ '^'.context
623			call add(res, m)
624		elseif m =~ context
625			call add(res2, m)
626		endif
627	endfor
628
629	return res + res2
630
631	" }}}
632  endif
633endfunction
634" vim:set foldmethod=marker:
635