1" Vim completion script
2" Language:	HTML and XHTML
3" Maintainer:	Mikolaj Machowski ( mikmach AT wp DOT pl )
4" Last Change:	2006 Apr 24
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] =~ '\k'
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 =~ '>[^<]*$' || i == curline
91					" We are in normal tag line, no need for completion at all
92					" OR reached first line without tag at all
93					let b:compl_context = ''
94					break
95				endif
96				let i += 1
97			endwhile
98			" Make sure we don't have counter
99			unlet! i
100		endif
101		let b:compl_context = matchstr(b:compl_context, '.*\zs<.*')
102
103		" Return proper start for on-events. Without that beginning of
104		" completion will be badly reported
105		if b:compl_context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
106			let start = col('.') - 1
107			while start >= 0 && line[start - 1] =~ '\k'
108				let start -= 1
109			endwhile
110		endif
111		" If b:compl_context begins with <? we are inside of PHP code. It
112		" wasn't closed so PHP completion passed it to HTML
113		if &filetype =~? 'php' && b:compl_context =~ '^<?'
114			let b:phpcompl = 1
115			let start = col('.') - 1
116			while start >= 0 && line[start - 1] =~ '[a-zA-Z_0-9\x7f-\xff$]'
117				let start -= 1
118			endwhile
119		endif
120	else
121		let b:compl_context = getline('.')[0:compl_begin]
122	endif
123    return start
124  else
125	" Initialize base return lists
126    let res = []
127    let res2 = []
128	" a:base is very short - we need context
129	let context = b:compl_context
130	" Check if we should do CSS completion inside of <style> tag
131	" or JS completion inside of <script> tag or PHP completion in case of <?
132	" tag AND &ft==php
133	if exists("b:csscompl")
134		unlet! b:csscompl
135		let context = b:compl_context
136		unlet! b:compl_context
137		return csscomplete#CompleteCSS(0, context)
138	elseif exists("b:jscompl")
139		unlet! b:jscompl
140		return javascriptcomplete#CompleteJS(0, a:base)
141	elseif exists("b:phpcompl")
142		unlet! b:phpcompl
143		let context = b:compl_context
144		return phpcomplete#CompletePHP(0, a:base)
145	else
146		if len(b:compl_context) == 0 && !exists("b:entitiescompl")
147			return []
148		endif
149		let context = matchstr(b:compl_context, '.\zs.*')
150	endif
151	unlet! b:compl_context
152	" Entities completion {{{
153	if exists("b:entitiescompl")
154		unlet! b:entitiescompl
155
156		if !exists("b:html_doctype")
157			call htmlcomplete#CheckDoctype()
158		endif
159		if !exists("b:html_omni")
160			"runtime! autoload/xml/xhtml10s.vim
161			call htmlcomplete#LoadData()
162		endif
163
164	    let entities =  b:html_omni['vimxmlentities']
165
166		if len(a:base) == 1
167			for m in entities
168				if m =~ '^'.a:base
169					call add(res, m.';')
170				endif
171			endfor
172			return res
173		else
174			for m in entities
175				if m =~? '^'.a:base
176					call add(res, m.';')
177				elseif m =~? a:base
178					call add(res2, m.';')
179				endif
180			endfor
181
182			return res + res2
183		endif
184
185
186	endif
187	" }}}
188	if context =~ '>'
189		" Generally if context contains > it means we are outside of tag and
190		" should abandon action - with one exception: <style> span { bo
191		if context =~ 'style[^>]\{-}>[^<]\{-}$'
192			return csscomplete#CompleteCSS(0, context)
193		elseif context =~ 'script[^>]\{-}>[^<]\{-}$'
194			let b:jsrange = [line('.'), search('<\/script\>', 'nW')]
195			return javascriptcomplete#CompleteJS(0, context)
196		else
197			return []
198		endif
199	endif
200
201	" If context contains > it means we are already outside of tag and we
202	" should abandon action
203	" If context contains white space it is attribute.
204	" It can be also value of attribute.
205	" We have to get first word to offer proper completions
206	if context == ''
207		let tag = ''
208	else
209		let tag = split(context)[0]
210		" Detect if tag is uppercase to return in proper case,
211		" we need to make it lowercase for processing
212		if tag =~ '^[A-Z]*$'
213			let uppercase_tag = 1
214			let tag = tolower(tag)
215		else
216			let uppercase_tag = 0
217		endif
218	endif
219	" Get last word, it should be attr name
220	let attr = matchstr(context, '.*\s\zs.*')
221	" Possible situations where any prediction would be difficult:
222	" 1. Events attributes
223	if context =~ '\s'
224		" Sort out style, class, and on* cases
225		if context =~? "\\(on[a-z]*\\|id\\|style\\|class\\)\\s*=\\s*[\"']"
226			" Id, class completion {{{
227			if context =~? "\\(id\\|class\\)\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
228				if context =~? "class\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
229					let search_for = "class"
230				elseif context =~? "id\\s*=\\s*[\"'][a-zA-Z0-9_ -]*$"
231					let search_for = "id"
232				endif
233				" Handle class name completion
234				" 1. Find lines of <link stylesheet>
235				" 1a. Check file for @import
236				" 2. Extract filename(s?) of stylesheet,
237				call cursor(1,1)
238				let head = getline(search('<head\>'), search('<\/head>'))
239				let headjoined = join(copy(head), ' ')
240				if headjoined =~ '<style'
241					" Remove possibly confusing CSS operators
242					let stylehead = substitute(headjoined, '+>\*[,', ' ', 'g')
243					if search_for == 'class'
244						let styleheadlines = split(stylehead)
245						let headclasslines = filter(copy(styleheadlines), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
246					else
247						let stylesheet = split(headjoined, '[{}]')
248						" Get all lines which fit id syntax
249						let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
250						" Filter out possible color definitions
251						call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
252						" Filter out complex border definitions
253						call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
254						let templines = join(classlines, ' ')
255						let headclasslines = split(templines)
256						call filter(headclasslines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
257					endif
258					let internal = 1
259				else
260					let internal = 0
261				endif
262				let styletable = []
263				let secimportfiles = []
264				let filestable = filter(copy(head), "v:val =~ '\\(@import\\|link.*stylesheet\\)'")
265				for line in filestable
266					if line =~ "@import"
267						let styletable += [matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")]
268					elseif line =~ "<link"
269						let styletable += [matchstr(line, "href\\s*=\\s*[\"']\\zs\\f\\+\\ze")]
270					endif
271				endfor
272				for file in styletable
273					if filereadable(file)
274						let stylesheet = readfile(file)
275						let secimport = filter(copy(stylesheet), "v:val =~ '@import'")
276						if len(secimport) > 0
277							for line in secimport
278								let secfile = matchstr(line, "import\\s\\+\\(url(\\)\\?[\"']\\?\\zs\\f\\+\\ze")
279								let secfile = fnamemodify(file, ":p:h").'/'.secfile
280								let secimportfiles += [secfile]
281							endfor
282						endif
283					endif
284				endfor
285				let cssfiles = styletable + secimportfiles
286				let classes = []
287				for file in cssfiles
288					if filereadable(file)
289						let stylesheet = readfile(file)
290						let stylefile = join(stylesheet, ' ')
291						let stylefile = substitute(stylefile, '+>\*[,', ' ', 'g')
292						if search_for == 'class'
293							let stylesheet = split(stylefile)
294							let classlines = filter(copy(stylesheet), "v:val =~ '\\([a-zA-Z0-9:]\\+\\)\\?\\.[a-zA-Z0-9_-]\\+'")
295						else
296							let stylesheet = split(stylefile, '[{}]')
297							" Get all lines which fit id syntax
298							let classlines = filter(copy(stylesheet), "v:val =~ '#[a-zA-Z0-9_-]\\+'")
299							" Filter out possible color definitions
300							call filter(classlines, "v:val !~ ':\\s*#[a-zA-Z0-9_-]\\+'")
301							" Filter out complex border definitions
302							call filter(classlines, "v:val !~ '\\(none\\|hidden\\|dotted\\|dashed\\|solid\\|double\\|groove\\|ridge\\|inset\\|outset\\)\\s*#[a-zA-Z0-9_-]\\+'")
303							let templines = join(classlines, ' ')
304							let stylelines = split(templines)
305							let classlines = filter(stylelines, "v:val =~ '#[a-zA-Z0-9_-]\\+'")
306
307						endif
308					endif
309					" We gathered classes definitions from all external files
310					let classes += classlines
311				endfor
312				if internal == 1
313					let classes += headclasslines
314				endif
315
316				if search_for == 'class'
317					let elements = {}
318					for element in classes
319						if element =~ '^\.'
320							let class = matchstr(element, '^\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
321							let class = substitute(class, ':.*', '', '')
322							if has_key(elements, 'common')
323								let elements['common'] .= ' '.class
324							else
325								let elements['common'] = class
326							endif
327						else
328							let class = matchstr(element, '[a-zA-Z1-6]*\.\zs[a-zA-Z][a-zA-Z0-9_-]*\ze')
329							let tagname = tolower(matchstr(element, '[a-zA-Z1-6]*\ze.'))
330							if tagname != ''
331								if has_key(elements, tagname)
332									let elements[tagname] .= ' '.class
333								else
334									let elements[tagname] = class
335								endif
336							endif
337						endif
338					endfor
339
340					if has_key(elements, tag) && has_key(elements, 'common')
341						let values = split(elements[tag]." ".elements['common'])
342					elseif has_key(elements, tag) && !has_key(elements, 'common')
343						let values = split(elements[tag])
344					elseif !has_key(elements, tag) && has_key(elements, 'common')
345						let values = split(elements['common'])
346					else
347						return []
348					endif
349
350				elseif search_for == 'id'
351					" Find used IDs
352					" 1. Catch whole file
353					let filelines = getline(1, line('$'))
354					" 2. Find lines with possible id
355					let used_id_lines = filter(filelines, 'v:val =~ "id\\s*=\\s*[\"''][a-zA-Z0-9_-]\\+"')
356					" 3a. Join all filtered lines
357					let id_string = join(used_id_lines, ' ')
358					" 3b. And split them to be sure each id is in separate item
359					let id_list = split(id_string, 'id\s*=\s*')
360					" 4. Extract id values
361					let used_id = map(id_list, 'matchstr(v:val, "[\"'']\\zs[a-zA-Z0-9_-]\\+\\ze")')
362					let joined_used_id = ','.join(used_id, ',').','
363
364					let allvalues = map(classes, 'matchstr(v:val, ".*#\\zs[a-zA-Z0-9_-]\\+")')
365
366					let values = []
367
368					for element in classes
369						if joined_used_id !~ ','.element.','
370							let values += [element]
371						endif
372
373					endfor
374
375				endif
376
377				" We need special version of sbase
378				let classbase = matchstr(context, ".*[\"']")
379				let classquote = matchstr(classbase, '.$')
380
381				let entered_class = matchstr(attr, ".*=\\s*[\"']\\zs.*")
382
383				for m in sort(values)
384					if m =~? '^'.entered_class
385						call add(res, m . classquote)
386					elseif m =~? entered_class
387						call add(res2, m . classquote)
388					endif
389				endfor
390
391				return res + res2
392
393			elseif context =~? "style\\s*=\\s*[\"'][^\"']*$"
394				return csscomplete#CompleteCSS(0, context)
395
396			endif
397			" }}}
398			" Complete on-events {{{
399			if context =~? 'on[a-z]*\s*=\s*\(''[^'']*\|"[^"]*\)$'
400				" We have to:
401				" 1. Find external files
402				let b:js_extfiles = []
403				let l = line('.')
404				let c = col('.')
405				call cursor(1,1)
406				while search('<\@<=script\>', 'W') && line('.') <= l
407					if synIDattr(synID(line('.'),col('.')-1,0),"name") !~? 'comment'
408						let sname = matchstr(getline('.'), '<script[^>]*src\s*=\s*\([''"]\)\zs.\{-}\ze\1')
409						if filereadable(sname)
410							let b:js_extfiles += readfile(sname)
411						endif
412					endif
413				endwhile
414				" 2. Find at least one <script> tag
415				call cursor(1,1)
416				let js_scripttags = []
417				while search('<script\>', 'W') && line('.') < l
418					if matchstr(getline('.'), '<script[^>]*src') == ''
419						let js_scripttag = getline(line('.'), search('</script>', 'W'))
420						let js_scripttags += js_scripttag
421					endif
422				endwhile
423				let b:js_extfiles += js_scripttags
424
425				" 3. Proper call for javascriptcomplete#CompleteJS
426				call cursor(l,c)
427				let js_context = matchstr(a:base, '\k\+$')
428				let js_shortcontext = substitute(a:base, js_context.'$', '', '')
429				let b:compl_context = context
430				let b:jsrange = [l, l]
431				unlet! l c
432				return javascriptcomplete#CompleteJS(0, js_context)
433
434			endif
435
436			" }}}
437			let stripbase = matchstr(context, ".*\\(on[a-zA-Z]*\\|style\\|class\\)\\s*=\\s*[\"']\\zs.*")
438			" Now we have context stripped from all chars up to style/class.
439			" It may fail with some strange style value combinations.
440			if stripbase !~ "[\"']"
441				return []
442			endif
443		endif
444		" Value of attribute completion {{{
445		" If attr contains =\s*[\"'] we catched value of attribute
446		if attr =~ "=\s*[\"']" || attr =~ "=\s*$"
447			" Let do attribute specific completion
448			let attrname = matchstr(attr, '.*\ze\s*=')
449			let entered_value = matchstr(attr, ".*=\\s*[\"']\\?\\zs.*")
450			let values = []
451			if attrname == 'href'
452				" Now we are looking for local anchors defined by name or id
453				if entered_value =~ '^#'
454					let file = join(getline(1, line('$')), ' ')
455					" Split it be sure there will be one id/name element in
456					" item, it will be also first word [a-zA-Z0-9_-] in element
457					let oneelement = split(file, "\\(meta \\)\\@<!\\(name\\|id\\)\\s*=\\s*[\"']")
458					for i in oneelement
459						let values += ['#'.matchstr(i, "^[a-zA-Z][a-zA-Z0-9%_-]*")]
460					endfor
461				endif
462			else
463				if has_key(b:html_omni, tag) && has_key(b:html_omni[tag][1], attrname)
464					let values = b:html_omni[tag][1][attrname]
465				else
466					return []
467				endif
468			endif
469
470			if len(values) == 0
471				return []
472			endif
473
474			" We need special version of sbase
475			let attrbase = matchstr(context, ".*[\"']")
476			let attrquote = matchstr(attrbase, '.$')
477			if attrquote !~ "['\"]"
478				let attrquoteopen = '"'
479				let attrquote = '"'
480			else
481				let attrquoteopen = ''
482			endif
483
484			for m in values
485				" This if is needed to not offer all completions as-is
486				" alphabetically but sort them. Those beginning with entered
487				" part will be as first choices
488				if m =~ '^'.entered_value
489					call add(res, attrquoteopen . m . attrquote)
490				elseif m =~ entered_value
491					call add(res2, attrquoteopen . m . attrquote)
492				endif
493			endfor
494
495			return res + res2
496
497		endif
498		" }}}
499		" Attribute completion {{{
500		" Shorten context to not include last word
501		let sbase = matchstr(context, '.*\ze\s.*')
502
503		" Load data {{{
504		if !exists("b:html_doctype")
505			call htmlcomplete#CheckDoctype()
506		endif
507		if !exists("b:html_omni")
508			call htmlcomplete#LoadData()
509		endif
510		" }}}
511
512		if has_key(b:html_omni, tag)
513			let attrs = keys(b:html_omni[tag][1])
514		else
515			return []
516		endif
517
518		for m in sort(attrs)
519			if m =~ '^'.attr
520				call add(res, m)
521			elseif m =~ attr
522				call add(res2, m)
523			endif
524		endfor
525		let menu = res + res2
526		if has_key(b:html_omni, 'vimxmlattrinfo')
527			let final_menu = []
528			for i in range(len(menu))
529				let item = menu[i]
530				if has_key(b:html_omni['vimxmlattrinfo'], item)
531					let m_menu = b:html_omni['vimxmlattrinfo'][item][0]
532					let m_info = b:html_omni['vimxmlattrinfo'][item][1]
533				else
534					let m_menu = ''
535					let m_info = ''
536				endif
537				if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
538					let item = item
539					let m_menu = 'Bool'
540				else
541					let item .= '="'
542				endif
543				let final_menu += [{'word':item, 'menu':m_menu, 'info':m_info}]
544			endfor
545		else
546			let final_menu = []
547			for i in range(len(menu))
548				let item = menu[i]
549				if len(b:html_omni[tag][1][item]) > 0 && b:html_omni[tag][1][item][0] =~ '^\(BOOL\|'.item.'\)$'
550					let item = item
551				else
552					let item .= '="'
553				endif
554				let final_menu += [item]
555			endfor
556			return final_menu
557
558		endif
559		return final_menu
560
561	endif
562	" }}}
563	" Close tag {{{
564	let b:unaryTagsStack = "base meta link hr br param img area input col"
565	if context =~ '^\/'
566		if context =~ '^\/.'
567			return []
568		else
569			let opentag = xmlcomplete#GetLastOpenTag("b:unaryTagsStack")
570			return [opentag.">"]
571		endif
572	endif
573	" }}}
574	" Load data {{{
575	if !exists("b:html_doctype")
576		call htmlcomplete#CheckDoctype()
577	endif
578	if !exists("b:html_omni")
579		"runtime! autoload/xml/xhtml10s.vim
580		call htmlcomplete#LoadData()
581	endif
582	" }}}
583	" Tag completion {{{
584	" Deal with tag completion.
585	let opentag = tolower(xmlcomplete#GetLastOpenTag("b:unaryTagsStack"))
586	" MM: TODO: GLOT works always the same but with some weird situation it
587	" behaves as intended in HTML but screws in PHP
588	if opentag == '' || &filetype == 'php' && !has_key(b:html_omni, opentag)
589		" Hack for sometimes failing GetLastOpenTag.
590		" As far as I tested fail isn't GLOT fault but problem
591		" of invalid document - not properly closed tags and other mish-mash.
592		" Also when document is empty. Return list of *all* tags.
593	    let tags = keys(b:html_omni)
594		call filter(tags, 'v:val !~ "^vimxml"')
595	else
596		if has_key(b:html_omni, opentag)
597			let tags = b:html_omni[opentag][0]
598		else
599			return []
600		endif
601	endif
602	" }}}
603
604	if exists("uppercase_tag") && uppercase_tag == 1
605		let context = tolower(context)
606	endif
607	" Handle XML keywords: DOCTYPE and CDATA.
608	if opentag == ''
609		let tags += [
610				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">',
611				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">',
612				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">',
613				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" "http://www.w3.org/TR/REC-html40/frameset.dtd">',
614				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">',
615				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
616				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">',
617				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
618				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
619				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
620				\ '!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/1999/xhtml">',
621				\ '!CDATA'
622				\ ]
623	endif
624
625	for m in sort(tags)
626		if m =~ '^'.context
627			call add(res, m)
628		elseif m =~ context
629			call add(res2, m)
630		endif
631	endfor
632	let menu = res + res2
633	if has_key(b:html_omni, 'vimxmltaginfo')
634		let final_menu = []
635		for i in range(len(menu))
636			let item = menu[i]
637			if has_key(b:html_omni['vimxmltaginfo'], item)
638				let m_menu = b:html_omni['vimxmltaginfo'][item][0]
639				let m_info = b:html_omni['vimxmltaginfo'][item][1]
640			else
641				let m_menu = ''
642				let m_info = ''
643			endif
644			if &filetype == 'html' && exists("uppercase_tag") && uppercase_tag == 1 && item !~ 'DOCTYPE'
645				let item = toupper(item)
646			endif
647			if item =~ 'DOCTYPE'
648				let abbr = 'DOCTYPE '.matchstr(item, 'DTD \zsX\?HTML .\{-}\ze\/\/')
649			else
650				let abbr = item
651			endif
652			let final_menu += [{'abbr':abbr, 'word':item, 'menu':m_menu, 'info':m_info}]
653		endfor
654	else
655		let final_menu = menu
656	endif
657	return final_menu
658
659	" }}}
660  endif
661endfunction
662
663function! htmlcomplete#LoadData() " {{{
664	if !exists("b:html_omni_flavor")
665		if &filetype == 'html'
666			let b:html_omni_flavor = 'html401t'
667		else
668			let b:html_omni_flavor = 'xhtml10s'
669		endif
670	endif
671	" With that if we still have bloated memory but create new buffer
672	" variables only by linking to existing g:variable, not sourcing whole
673	" file.
674	if exists('g:xmldata_'.b:html_omni_flavor)
675		exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
676	else
677		exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
678		exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
679	endif
680endfunction
681" }}}
682function! htmlcomplete#CheckDoctype() " {{{
683	if exists('b:html_omni_flavor')
684		let old_flavor = b:html_omni_flavor
685	else
686		let old_flavor = ''
687	endif
688	let i = 1
689	while i < 10 && i < line("$")
690		let line = getline(i)
691		if line =~ '<!DOCTYPE.*\<DTD HTML 3\.2'
692			let b:html_omni_flavor = 'html32'
693			let b:html_doctype = 1
694			break
695		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Transitional'
696			let b:html_omni_flavor = 'html40t'
697			let b:html_doctype = 1
698			break
699		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0 Frameset'
700			let b:html_omni_flavor = 'html40f'
701			let b:html_doctype = 1
702			break
703		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.0'
704			let b:html_omni_flavor = 'html40s'
705			let b:html_doctype = 1
706			break
707		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Transitional'
708			let b:html_omni_flavor = 'html401t'
709			let b:html_doctype = 1
710			break
711		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01 Frameset'
712			let b:html_omni_flavor = 'html401f'
713			let b:html_doctype = 1
714			break
715		elseif line =~ '<!DOCTYPE.*\<DTD HTML 4\.01'
716			let b:html_omni_flavor = 'html401s'
717			let b:html_doctype = 1
718			break
719		elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Transitional'
720			let b:html_omni_flavor = 'xhtml10t'
721			let b:html_doctype = 1
722			break
723		elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Frameset'
724			let b:html_omni_flavor = 'xhtml10f'
725			let b:html_doctype = 1
726			break
727		elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.0 Strict'
728			let b:html_omni_flavor = 'xhtml10s'
729			let b:html_doctype = 1
730			break
731		elseif line =~ '<!DOCTYPE.*\<DTD XHTML 1\.1'
732			let b:html_omni_flavor = 'xhtml11'
733			let b:html_doctype = 1
734			break
735		endif
736		let i += 1
737	endwhile
738	if !exists("b:html_doctype")
739		return
740	else
741		" Tie g:xmldata with b:html_omni this way we need to sourca data file only
742		" once, not every time per buffer.
743		if old_flavor == b:html_omni_flavor
744			return
745		else
746			if exists('g:xmldata_'.b:html_omni_flavor)
747				exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
748			else
749				exe 'runtime! autoload/xml/'.b:html_omni_flavor.'.vim'
750				exe 'let b:html_omni = g:xmldata_'.b:html_omni_flavor
751			endif
752			return
753		endif
754	endif
755endfunction
756" }}}
757" vim:set foldmethod=marker:
758