diff --git a/autoload/SpaceVim/api/web/html.vim b/autoload/SpaceVim/api/web/html.vim
new file mode 100644
index 000000000..4b4d152de
--- /dev/null
+++ b/autoload/SpaceVim/api/web/html.vim
@@ -0,0 +1,53 @@
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:self = {}
+let s:XML = SpaceVim#api#import('web#xml')
+let s:HTTP = SpaceVim#api#import('web#http')
+
+function! s:self.decodeEntityReference(str) abort
+ let str = a:str
+ let str = substitute(str, '>', '>', 'g')
+ let str = substitute(str, '<', '<', 'g')
+ let str = substitute(str, '"', '"', 'g')
+ let str = substitute(str, ''', "'", 'g')
+ let str = substitute(str, ' ', ' ', 'g')
+ let str = substitute(str, '¥', '\¥', 'g')
+ let str = substitute(str, '\(\d\+\);', '\=s:nr2enc_char(submatch(1))', 'g')
+ let str = substitute(str, '&', '\&', 'g')
+ let str = substitute(str, '»', '>', 'g')
+ let str = substitute(str, '«', '<', 'g')
+ return str
+endfunction
+
+function! s:self.encodeEntityReference(str) abort
+ let str = a:str
+ let str = substitute(str, '&', '\&', 'g')
+ let str = substitute(str, '>', '\>', 'g')
+ let str = substitute(str, '<', '\<', 'g')
+ let str = substitute(str, "\n", '\
', 'g')
+ let str = substitute(str, '"', '\"', 'g')
+ let str = substitute(str, "'", '\'', 'g')
+ let str = substitute(str, ' ', '\ ', 'g')
+ return str
+endfunction
+
+function! s:self.parse(html) abort
+ let html = substitute(a:html, '<\(area\|base\|basefont\|br\|nobr\|col\|frame\|hr\|img\|input\|isindex\|link\|meta\|param\|embed\|keygen\|command\)\([^>]*[^/]\|\)>', '<\1\2/>', 'g')
+ return s:XML.parse(html)
+endfunction
+
+function! s:self.parseFile(file) abort
+ return self.parse(join(readfile(a:file), "\n"))
+endfunction
+
+function! s:self.parseURL(url) abort
+ return self.parse(s:HTTP.get(a:url).content)
+endfunction
+
+function! SpaceVim#api#web#html#get() abort
+ return deepcopy(s:self)
+endfunction
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
diff --git a/autoload/SpaceVim/api/web/http.vim b/autoload/SpaceVim/api/web/http.vim
new file mode 100644
index 000000000..9b548e498
--- /dev/null
+++ b/autoload/SpaceVim/api/web/http.vim
@@ -0,0 +1,478 @@
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:self = {}
+
+let s:system = function(get(g:, 'webapi#system_function', 'system'))
+
+function! s:nr2byte(nr) abort
+ if a:nr < 0x80
+ return nr2char(a:nr)
+ elseif a:nr < 0x800
+ return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
+ elseif a:nr < 0x10000
+ return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
+ elseif a:nr < 0x200000
+ return nr2char(a:nr/262144%16+240).nr2char(a:nr/4096/16+128).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
+ elseif a:nr < 0x4000000
+ return nr2char(a:nr/16777216%16+248).nr2char(a:nr/262144%16+128).nr2char(a:nr/4096/16+128).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
+ else
+ return nr2char(a:nr/1073741824%16+252).nr2char(a:nr/16777216%16+128).nr2char(a:nr/262144%16+128).nr2char(a:nr/4096/16+128).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
+ endif
+endfunction
+
+function! s:nr2enc_char(charcode) abort
+ if &encoding ==# 'utf-8'
+ return nr2char(a:charcode)
+ endif
+ let char = s:nr2byte(a:charcode)
+ if strlen(char) > 1
+ let char = strtrans(iconv(char, 'utf-8', &encoding))
+ endif
+ return char
+endfunction
+
+function! s:nr2hex(nr) abort
+ let n = a:nr
+ let r = ''
+ while n
+ let r = '0123456789ABCDEF'[n % 16] . r
+ let n = n / 16
+ endwhile
+ return r
+endfunction
+
+function! s:urlencode_char(c, ...) abort
+ let is_binary = get(a:000, 1)
+ let c = a:c
+ if !is_binary
+ let c = iconv(a:c, &encoding, 'utf-8')
+ if c ==# ''
+ let c = a:c
+ endif
+ endif
+ let s = ''
+ for i in range(strlen(c))
+ let s .= printf('%%%02X', char2nr(c[i]))
+ endfor
+ return s
+endfunction
+
+function! s:self.decodeURI(str) abort
+ let ret = a:str
+ let ret = substitute(ret, '+', ' ', 'g')
+ let ret = substitute(ret, '%\(\x\x\)', '\=printf("%c", str2nr(submatch(1), 16))', 'g')
+ return ret
+endfunction
+
+function! s:self.escape(str) abort
+ return substitute(a:str, '[^a-zA-Z0-9_.~/-]', '\=s:urlencode_char(submatch(0))', 'g')
+endfunction
+
+function! s:self.encodeURI(items, ...) abort
+ let is_binary = get(a:000, 1)
+ let ret = ''
+ if type(a:items) == 4
+ for key in sort(keys(a:items))
+ if strlen(ret) | let ret .= '&' | endif
+ let ret .= key . '=' . s:self.encodeURI(a:items[key])
+ endfor
+ elseif type(a:items) == 3
+ for item in sort(a:items)
+ if strlen(ret) | let ret .= '&' | endif
+ let ret .= item
+ endfor
+ else
+ let ret = substitute(a:items, '[^a-zA-Z0-9_.~-]', '\=s:urlencode_char(submatch(0), is_binary)', 'g')
+ endif
+ return ret
+endfunction
+
+function! s:self.encodeURIComponent(items) abort
+ let ret = ''
+ if type(a:items) == 4
+ for key in sort(keys(a:items))
+ if strlen(ret) | let ret .= '&' | endif
+ let ret .= key . '=' . s:self.encodeURIComponent(a:items[key])
+ endfor
+ elseif type(a:items) == 3
+ for item in sort(a:items)
+ if strlen(ret) | let ret .= '&' | endif
+ let ret .= item
+ endfor
+ else
+ let items = iconv(a:items, &enc, 'utf-8')
+ let len = strlen(items)
+ let i = 0
+ while i < len
+ let ch = items[i]
+ if ch =~# '[0-9A-Za-z-._~!''()*]'
+ let ret .= ch
+ elseif ch ==# ' '
+ let ret .= '+'
+ else
+ let ret .= '%' . substitute('0' . s:nr2hex(char2nr(ch)), '^.*\(..\)$', '\1', '')
+ endif
+ let i = i + 1
+ endwhile
+ endif
+ return ret
+endfunction
+
+function! s:self.get(url, ...) abort
+ let getdata = a:0 > 0 ? a:000[0] : {}
+ let headdata = a:0 > 1 ? a:000[1] : {}
+ let follow = a:0 > 2 ? a:000[2] : 1
+ let url = a:url
+ let getdatastr = self.encodeURI(getdata)
+ if strlen(getdatastr)
+ let url .= '?' . getdatastr
+ endif
+ if executable('curl')
+ let command = printf('curl -q %s -s -k -i', follow ? '-L' : '')
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' -H ' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' -H ' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' ' . quote . url . quote
+ let res = s:system(command)
+ elseif executable('wget')
+ let command = printf('wget -O- --save-headers --server-response -q %s', follow ? '-L' : '')
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' --header=' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' --header=' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' ' . quote . url . quote
+ let res = s:system(command)
+ else
+ throw 'require `curl` or `wget` command'
+ endif
+ if follow != 0
+ let mx = 'HTTP/\%(1\.[01]\|2\%(\.0\)\?\)'
+ while res =~# '^' . mx . ' 3' || res =~# '^' . mx . ' [0-9]\{3} .\+\n\r\?\n' . mx . ' .\+'
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let res = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let res = strpart(res, pos+2)
+ endif
+ endwhile
+ endif
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let content = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let content = strpart(res, pos+2)
+ endif
+ let header = split(res[:pos-1], '\r\?\n')
+ let matched = matchlist(get(header, 0), '^HTTP/\%(1\.[01]\|2\%(\.0\)\?\)\s\+\(\d\+\)\s*\(.*\)')
+ if !empty(matched)
+ let [status, message] = matched[1 : 2]
+ call remove(header, 0)
+ else
+ if v:shell_error || len(matched)
+ let [status, message] = ['500', "Couldn't connect to host"]
+ else
+ let [status, message] = ['200', 'OK']
+ endif
+ endif
+ return {
+ \ 'status' : status,
+ \ 'message' : message,
+ \ 'header' : header,
+ \ 'content' : content
+ \}
+endfunction
+
+function! s:self.post(url, ...) abort
+ let postdata = a:0 > 0 ? a:000[0] : {}
+ let headdata = a:0 > 1 ? a:000[1] : {}
+ let method = a:0 > 2 ? a:000[2] : 'POST'
+ let follow = a:0 > 3 ? a:000[3] : 1
+ let url = a:url
+ if type(postdata) == 4
+ let postdatastr = self.encodeURI(postdata)
+ else
+ let postdatastr = postdata
+ endif
+ let file = tempname()
+ if executable('curl')
+ let command = printf('curl -q %s -s -k -i -X %s', (follow ? '-L' : ''), len(method) ? method : 'POST')
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' -H ' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' -H ' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' ' . quote . url . quote
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let res = s:system(command . ' --data-binary @' . quote.file.quote)
+ elseif executable('wget')
+ let command = printf('wget -O- --save-headers --server-response -q %s', follow ? '-L' : '')
+ let headdata['X-HTTP-Method-Override'] = method
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' --header=' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' --header=' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' '.quote.url.quote
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let res = s:system(command . ' --post-data @' . quote.file.quote)
+ else
+ throw 'require `curl` or `wget` command'
+ endif
+ call delete(file)
+ if follow != 0
+ let mx = 'HTTP/\%(1\.[01]\|2\%(\.0\)\?\)'
+ while res =~# '^' . mx . ' 3' || res =~# '^' . mx . ' [0-9]\{3} .\+\n\r\?\n' . mx . ' .\+'
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let res = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let res = strpart(res, pos+2)
+ endif
+ endwhile
+ endif
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let content = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let content = strpart(res, pos+2)
+ endif
+ let header = split(res[:pos-1], '\r\?\n')
+ let matched = matchlist(get(header, 0), '^HTTP/\%(1\.[01]\|2\%(\.0\)\?\)\s\+\(\d\+\)\s*\(.*\)')
+ if !empty(matched)
+ let [status, message] = matched[1 : 2]
+ call remove(header, 0)
+ else
+ if v:shell_error || len(matched)
+ let [status, message] = ['500', "Couldn't connect to host"]
+ else
+ let [status, message] = ['200', 'OK']
+ endif
+ endif
+ return {
+ \ 'status' : status,
+ \ 'message' : message,
+ \ 'header' : header,
+ \ 'content' : content
+ \}
+endfunction
+
+function! s:self.send(req) abort
+ let postdata = get(a:req, 'data', '')
+ let method = get(a:req, 'method', postdata ==# '' ? 'GET': 'POST')
+ let headdata = get(a:req, 'header', {})
+ let follow = get(a:req, 'follow', 1)
+ let url = get(a:req, 'url', '')
+ if type(postdata) == 4
+ let postdatastr = self.encodeURI(postdata)
+ else
+ let postdatastr = postdata
+ endif
+ if empty(postdatastr)
+ let file = ''
+ else
+ let file = tempname()
+ endif
+ if executable('curl')
+ let command = printf('curl -q %s -s -k -i -X %s', (follow ? '-L' : ''), len(method) ? method : 'POST')
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' -H ' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' -H ' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' ' . quote . url . quote
+ if file ==# ''
+ let res = s:system(command)
+ else
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let res = s:system(command . ' --data-binary @' . quote.file.quote)
+ call delete(file)
+ endif
+ elseif executable('wget')
+ let command = printf('wget -O- --save-headers --server-response -q %s', follow ? '-L' : '')
+ let headdata['X-HTTP-Method-Override'] = method
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' --header=' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' --header=' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' '.quote.url.quote
+ if file ==# ''
+ let res = s:system(command)
+ else
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let res = s:system(command . ' --post-data @' . quote.file.quote)
+ call delete(file)
+ endif
+ else
+ throw 'require `curl` or `wget` command'
+ endif
+ if follow != 0
+ let mx = 'HTTP/\%(1\.[01]\|2\%(\.0\)\?\)'
+ while res =~# '^' . mx . ' 3' || res =~# '^' . mx . ' [0-9]\{3} .\+\n\r\?\n' . mx . ' .\+'
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let res = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let res = strpart(res, pos+2)
+ endif
+ endwhile
+ endif
+ let pos = stridx(res, "\r\n\r\n")
+ if pos != -1
+ let content = strpart(res, pos+4)
+ else
+ let pos = stridx(res, "\n\n")
+ let content = strpart(res, pos+2)
+ endif
+ let header = split(res[:pos-1], '\r\?\n')
+ let matched = matchlist(get(header, 0), '^HTTP/\%(1\.[01]\|2\%(\.0\)\?\)\s\+\(\d\+\)\s*\(.*\)')
+ if !empty(matched)
+ let [status, message] = matched[1 : 2]
+ call remove(header, 0)
+ else
+ if v:shell_error || len(matched)
+ let [status, message] = ['500', "Couldn't connect to host"]
+ else
+ let [status, message] = ['200', 'OK']
+ endif
+ endif
+ return {
+ \ 'status' : status,
+ \ 'message' : message,
+ \ 'header' : header,
+ \ 'content' : content
+ \}
+endfunction
+
+function! s:self.stream(req) abort
+ let postdata = get(a:req, 'data', '')
+ let method = get(a:req, 'method', postdata ==# '' ? 'GET': 'POST')
+ let headdata = get(a:req, 'header', {})
+ let follow = get(a:req, 'follow', 1)
+ let url = get(a:req, 'url', '')
+ let mode = get(a:req, 'mode', 'nl')
+ if type(postdata) == 4
+ let postdatastr = self.encodeURI(postdata)
+ else
+ let postdatastr = postdata
+ endif
+ if empty(postdatastr)
+ let file = ''
+ else
+ let file = tempname()
+ endif
+ if executable('curl')
+ let command = printf('curl -q %s -s -k -X %s', (follow ? '-L' : ''), len(method) ? method : 'POST')
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' -H ' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' -H ' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' '.quote . url . quote
+ if file ==# ''
+ let job = job_start(command)
+ else
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let job = job_start(command . ' --data-binary @' . quote.file.quote)
+ call delete(file)
+ endif
+ elseif executable('wget')
+ let command = printf('wget -O- -q %s', follow ? '-L' : '')
+ let headdata['X-HTTP-Method-Override'] = method
+ let quote = &shellxquote ==# '"' ? "'" : '"'
+ for key in keys(headdata)
+ if has('win32')
+ let command .= ' --header=' . quote . key . ': ' . substitute(headdata[key], '"', '"""', 'g') . quote
+ else
+ let command .= ' --header=' . quote . key . ': ' . headdata[key] . quote
+ endif
+ endfor
+ let command .= ' '.quote.url.quote
+ if file ==# ''
+ let job = job_start(command)
+ else
+ call writefile(split(postdatastr, "\n"), file, 'b')
+ let job = job_start(command . ' --post-data @' . quote.file.quote)
+ call delete(file)
+ endif
+ else
+ throw 'require `curl` or `wget` command'
+ endif
+ call job_setoptions(job,
+ \{
+ \ 'exit_cb': function('webapi#http#exit_cb', [a:req]),
+ \ 'stoponexit': 'kill',
+ \})
+ let a:req['job'] = job
+
+ let channel = job_getchannel(job)
+ call ch_setoptions(channel,
+ \{
+ \ 'out_cb': function('webapi#http#out_cb', [a:req]),
+ \ 'mode': mode,
+ \})
+ let a:req['channel'] = channel
+ let a:req['file'] = file
+endfunction
+
+" @vimlint(EVL103, 1, a:job)
+function! s:self.exit_cb(req, job, code) abort
+ let file = get(a:req, 'file')
+ if file !=# ''
+ call delete(file)
+ endif
+ let fexit_cb = get(a:req, 'exit_cb', v:none)
+ if fexit_cb != v:none
+ call call(fexit_cb, [a:code])
+ endif
+endfunction
+" @vimlint(EVL103, 0, a:job)
+
+" @vimlint(EVL103, 1, a:ch)
+function! s:self.out_cb(req, ch, data) abort
+ let fout_cb = get(a:req, 'out_cb', v:none)
+ if fout_cb != v:none
+ call Fout_cb(a:data)
+ call call(fout_cb, [a:data])
+ endif
+endfunction
+" @vimlint(EVL103, 0, a:ch)
+
+function! SpaceVim#api#web#http#get() abort
+ return deepcopy(s:self)
+endfunction
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
+
+" vim:set et:
diff --git a/autoload/SpaceVim/api/web/xml.vim b/autoload/SpaceVim/api/web/xml.vim
new file mode 100644
index 000000000..b6317ffdf
--- /dev/null
+++ b/autoload/SpaceVim/api/web/xml.vim
@@ -0,0 +1,327 @@
+let s:save_cpo = &cpo
+set cpo&vim
+
+let s:self = {}
+let s:HTTP = SpaceVim#api#import('web#http')
+
+let s:template = { 'name': '', 'attr': {}, 'child': [] }
+
+function! s:nr2byte(nr) abort
+ if a:nr < 0x80
+ return nr2char(a:nr)
+ elseif a:nr < 0x800
+ return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
+ else
+ return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
+ endif
+endfunction
+
+function! s:nr2enc_char(charcode) abort
+ if &encoding == 'utf-8'
+ return nr2char(a:charcode)
+ endif
+ let char = s:nr2byte(a:charcode)
+ if strlen(char) > 1
+ let char = strtrans(iconv(char, 'utf-8', &encoding))
+ endif
+ return char
+endfunction
+
+function! s:nr2hex(nr) abort
+ let n = a:nr
+ let r = ""
+ while n
+ let r = '0123456789ABCDEF'[n % 16] . r
+ let n = n / 16
+ endwhile
+ return r
+endfunction
+
+function! s:decodeEntityReference(str, ...) abort
+ let str = a:str
+ let str = substitute(str, '>', '>', 'g')
+ let str = substitute(str, '<', '<', 'g')
+ if get(g:, 'webapi#xml#decodeAsHTML', 0)
+ let str = substitute(str, '"', '"', 'g')
+ let str = substitute(str, ''', "'", 'g')
+ let str = substitute(str, ' ', ' ', 'g')
+ let str = substitute(str, '¥', '\¥', 'g')
+ endif
+ let str = substitute(str, '\([0-9a-fA-F]\+\);', '\=s:nr2enc_char("0x".submatch(1))', 'g')
+ let str = substitute(str, '\(\d\+\);', '\=s:nr2enc_char(submatch(1))', 'g')
+ let str = substitute(str, '&', '\&', 'g')
+ return str
+endfunction
+
+function! s:encodeEntityReference(str) abort
+ let str = a:str
+ let str = substitute(str, '&', '\&', 'g')
+ let str = substitute(str, '>', '\>', 'g')
+ let str = substitute(str, '<', '\<', 'g')
+ let str = substitute(str, '"', '\"', 'g')
+ "let str = substitute(str, "\n", '\
', 'g')
+ "let str = substitute(str, '"', '"', 'g')
+ "let str = substitute(str, "'", ''', 'g')
+ "let str = substitute(str, ' ', ' ', 'g')
+ return str
+endfunction
+
+function! s:matchNode(node, cond) abort
+ if type(a:cond) == 1 && a:node.name == a:cond
+ return 1
+ endif
+ if type(a:cond) == 2
+ return a:cond(a:node)
+ endif
+ if type(a:cond) == 3
+ let ret = 1
+ for l:R in a:cond
+ if !s:matchNode(a:node, l:R) | let ret = 0 | endif
+ unlet l:R
+ endfor
+ return ret
+ endif
+ if type(a:cond) == 4
+ for k in keys(a:cond)
+ if has_key(a:node.attr, k) && a:node.attr[k] == a:cond[k] | return 1 | endif
+ endfor
+ endif
+ return 0
+endfunction
+
+function! s:template.childNode(...) dict abort
+ for c in self.child
+ if type(c) == 4 && s:matchNode(c, a:000)
+ return c
+ endif
+ unlet c
+ endfor
+ return {}
+endfunction
+
+function! s:template.childNodes(...) dict abort
+ let ret = []
+ for c in self.child
+ if type(c) == 4 && s:matchNode(c, a:000)
+ let ret += [c]
+ endif
+ unlet c
+ endfor
+ return ret
+endfunction
+
+function! s:template.value(...) dict abort
+ if a:0
+ let self.child = a:000
+ return
+ endif
+ let ret = ''
+ for c in self.child
+ if type(c) <= 1 || type(c) == 5
+ let ret .= c
+ elseif type(c) == 4
+ let ret .= c.value()
+ endif
+ unlet c
+ endfor
+ return ret
+endfunction
+
+function! s:template.find(...) dict abort
+ for c in self.child
+ if type(c) == 4
+ if s:matchNode(c, a:000)
+ return c
+ endif
+ unlet! ret
+ let ret = c.find(a:000)
+ if !empty(ret)
+ return ret
+ endif
+ endif
+ unlet c
+ endfor
+ return {}
+endfunction
+
+function! s:template.findAll(...) dict abort
+ let ret = []
+ for c in self.child
+ if type(c) == 4
+ if s:matchNode(c, a:000)
+ call add(ret, c)
+ endif
+ let ret += c.findAll(a:000)
+ endif
+ unlet c
+ endfor
+ return ret
+endfunction
+
+function! s:template.toString() dict abort
+ let xml = '<' . self.name
+ for attr in keys(self.attr)
+ let xml .= ' ' . attr . '="' . s:encodeEntityReference(self.attr[attr]) . '"'
+ endfor
+ if len(self.child)
+ let xml .= '>'
+ for c in self.child
+ if type(c) == 4
+ let xml .= c.toString()
+ elseif type(c) > 1
+ let xml .= s:encodeEntityReference(string(c))
+ else
+ let xml .= s:encodeEntityReference(c)
+ endif
+ unlet c
+ endfor
+ let xml .= '' . self.name . '>'
+ else
+ let xml .= ' />'
+ endif
+ return xml
+endfunction
+
+function! webapi#xml#createElement(name) abort
+ let node = deepcopy(s:template)
+ let node.name = a:name
+ return node
+endfunction
+
+function! s:parse_tree(ctx, top) abort
+ let node = a:top
+ let stack = [a:top]
+ let pos = 0
+ " content accumulates the text only tags
+ let content = ""
+ let append_content_to_parent = 'if len(stack) && content != "" | call add(stack[-1].child, content) | let content ="" | endif'
+
+ let mx = '^\s*\(]\+>\)'
+ if a:ctx['xml'] =~ mx
+ let match = matchstr(a:ctx['xml'], mx)
+ let a:ctx['xml'] = a:ctx['xml'][stridx(a:ctx['xml'], match) + len(match):]
+ let mx = 'encoding\s*=\s*["'']\{0,1}\([^"'' \t]\+\|[^"'']\+\)["'']\{0,1}'
+ let matches = matchlist(match, mx)
+ if len(matches)
+ let encoding = matches[1]
+ if len(encoding) && len(a:ctx['encoding']) == 0
+ let a:ctx['encoding'] = encoding
+ let a:ctx['xml'] = iconv(a:ctx['xml'], encoding, &encoding)
+ endif
+ endif
+ endif
+
+ " this regex matches
+ " 1) the remaining until the next tag begins
+ " 2) maybe closing "/" of tag name
+ " 3) tagname
+ " 4) the attributes of the text (optional)
+ " 5) maybe closing "/" (end of tag name)
+ " or
+ " 6) CDATA or ''
+ " 7) text content of CDATA
+ " 8) the remaining text after the tag (rest)
+ " (These numbers correspond to the indexes in matched list m)
+ let tag_mx = '^\(\_.\{-}\)\%(\%(<\(/\?\)\([^!/>[:space:]]\+\)\(\%([[:space:]]*[^/>=[:space:]]\+[[:space:]]*=[[:space:]]*\%([^"'' >\t]\+\|"[^"]*"\|''[^'']*''\)\|[[:space:]]\+[^/>=[:space:]]\+[[:space:]]*\)*\)[[:space:]]*\(/\?\)>\)\|\%(\)\|\(\)\)'
+
+ while len(a:ctx['xml']) > 0
+ let m = matchlist(a:ctx.xml, tag_mx)
+ if empty(m) | break | endif
+ let a:ctx.xml = a:ctx.xml[len(m[0]) :]
+ let is_end_tag = m[2] == '/' && m[5] == ''
+ let is_start_and_end_tag = m[2] == '' && m[5] == '/'
+ let tag_name = m[3]
+ let attrs = m[4]
+
+ if len(m[1])
+ let content .= s:decodeEntityReference(m[1])
+ endif
+
+ if is_end_tag
+ " closing tag: pop from stack and continue at upper level
+ exec append_content_to_parent
+
+ if len(stack) " TODO: checking whether opened tag is exist.
+ call remove(stack, -1)
+ endif
+ continue
+ endif
+
+ " comment tag
+ if m[8] != ''
+ continue
+ endif
+
+ " if element is a CDATA
+ if m[6] != ''
+ let content .= m[7]
+ continue
+ endif
+
+ let node = deepcopy(s:template)
+ let node.name = tag_name
+ let attr_mx = '\([^=[:space:]]\+\)\s*\%(=\s*''\([^'']*\)''\|=\s*"\([^"]*\)"\|=\s*\(\w\+\)\|\)'
+ while len(attrs) > 0
+ let attr_match = matchlist(attrs, attr_mx)
+ if len(attr_match) == 0
+ break
+ endif
+ let name = attr_match[1]
+ let value = len(attr_match[2]) ? attr_match[2] : len(attr_match[3]) ? attr_match[3] : len(attr_match[4]) ? attr_match[4] : ""
+ if value == ""
+ let value = name
+ endif
+ let node.attr[name] = s:decodeEntityReference(value)
+ let attrs = attrs[stridx(attrs, attr_match[0]) + len(attr_match[0]):]
+ endwhile
+
+ exec append_content_to_parent
+
+ if len(stack)
+ call add(stack[-1].child, node)
+ endif
+ if !is_start_and_end_tag
+ " opening tag, continue parsing its contents
+ call add(stack, node)
+ endif
+ endwhile
+endfunction
+
+
+function! s:self.parse(xml) abort
+ let top = deepcopy(s:template)
+ let oldmaxmempattern=&maxmempattern
+ let oldmaxfuncdepth=&maxfuncdepth
+ let &maxmempattern=2000000
+ let &maxfuncdepth=2000
+ "try
+ call s:parse_tree({'xml': a:xml, 'encoding': ''}, top)
+ for node in top.child
+ if type(node) == 4
+ return node
+ endif
+ unlet node
+ endfor
+ "catch /.*/
+ "endtry
+ let &maxmempattern=oldmaxmempattern
+ let &maxfuncdepth=oldmaxfuncdepth
+ throw "Parse Error"
+endfunction
+
+function! s:self.parseFile(file) abort
+ return self.parse(join(readfile(a:file), "\n"))
+endfunction
+
+function! s:self.parseURL(url) abort
+ return self.parse(s:HTTP.get(a:url).content)
+endfunction
+
+function! SpaceVim#api#web#xml#get()
+ return deepcopy(s:self)
+endfunction
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
+
+" vim:set et sw=2 cc=80: