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, '&#x\([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 .= '' + 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: