mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-24 02:10:05 +08:00
Add web api
This commit is contained in:
parent
7401904c51
commit
1538b4a41d
53
autoload/SpaceVim/api/web/html.vim
Normal file
53
autoload/SpaceVim/api/web/html.vim
Normal file
@ -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
|
478
autoload/SpaceVim/api/web/http.vim
Normal file
478
autoload/SpaceVim/api/web/http.vim
Normal file
@ -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:
|
327
autoload/SpaceVim/api/web/xml.vim
Normal file
327
autoload/SpaceVim/api/web/xml.vim
Normal file
@ -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 .= '</' . 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*\(<?xml[^>]\+>\)'
|
||||
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:]]*\(/\?\)>\)\|\%(<!\[\(CDATA\)\[\(.\{-}\)\]\]>\)\|\(<!--.\{-}-->\)\)'
|
||||
|
||||
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:
|
Loading…
Reference in New Issue
Block a user