if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'latex') == -1
" vimtex - LaTeX plugin for Vim
"
" Maintainer: Karl Yngve Lervåg
" Email: karl.yngve@gmail.com
"
function! vimtex#parser#bib#parse(file, opts) abort " {{{1
if !filereadable(a:file) | return [] | endif
let l:backend = get(a:opts, 'backend', g:vimtex_parser_bib_backend)
if l:backend ==# 'bibtex'
if !executable('bibtex') | let l:backend = 'vim' | endif
elseif l:backend ==# 'bibparse'
if !executable('bibparse') | let l:backend = 'vim' | endif
else
let l:backend = 'vim'
endif
return s:parse_with_{l:backend}(a:file)
endfunction
" }}}1
function! s:parse_with_bibtex(file) abort " {{{1
call s:parse_with_bibtex_init()
if s:bibtex_not_executable | return [] | endif
" Define temporary files
let tmp = {
\ 'aux' : 'tmpfile.aux',
\ 'bbl' : 'tmpfile.bbl',
\ 'blg' : 'tmpfile.blg',
\ }
" Write temporary aux file
call writefile([
\ '\citation{*}',
\ '\bibstyle{' . s:bibtex_bstfile . '}',
\ '\bibdata{' . fnamemodify(a:file, ':r') . '}',
\ ], tmp.aux)
" Create the temporary bbl file
call vimtex#process#run('bibtex -terse ' . fnameescape(tmp.aux), {
\ 'background' : 0,
\ 'silent' : 1,
\})
" Parse temporary bbl file
let lines = join(readfile(tmp.bbl), "\n")
let lines = substitute(lines, '\n\n\@!\(\s\=\)\s*\|{\|}', '\1', 'g')
let lines = vimtex#util#tex2unicode(lines)
let lines = split(lines, "\n")
let l:entries = []
for line in lines
let matches = split(line, '||')
if empty(matches) || empty(matches[0]) | continue | endif
let l:entry = {
\ 'key': matches[0],
\ 'type': matches[1],
\}
if !empty(matches[2])
let l:entry.author = matches[2]
endif
if !empty(matches[3])
let l:entry.year = matches[3]
endif
if !empty(get(matches, 4, ''))
let l:entry.title = get(matches, 4, '')
endif
call add(l:entries, l:entry)
endfor
" Clean up
call delete(tmp.aux)
call delete(tmp.bbl)
call delete(tmp.blg)
return l:entries
endfunction
" }}}1
function! s:parse_with_bibtex_init() abort " {{{1
if exists('s:bibtex_init_done') | return | endif
" Check if bibtex is executable
let s:bibtex_not_executable = !executable('bibtex')
if s:bibtex_not_executable
call vimtex#log#warning(
\ 'bibtex is not executable and may not be used to parse bib files!')
endif
" Check if bstfile contains whitespace (not handled by vimtex)
if stridx(s:bibtex_bstfile, ' ') >= 0
let l:oldbst = s:bibtex_bstfile . '.bst'
let s:bibtex_bstfile = tempname()
call writefile(readfile(l:oldbst), s:bibtex_bstfile . '.bst')
endif
let s:bibtex_init_done = 1
endfunction
let s:bibtex_bstfile = expand('<sfile>:p:h') . '/vimcomplete'
" }}}1
function! s:parse_with_bibparse(file) abort " {{{1
call s:parse_with_bibparse_init()
if s:bibparse_not_executable | return [] | endif
call vimtex#process#run('bibparse ' . fnameescape(a:file)
\ . ' >_vimtex_bibparsed.log', {'background' : 0, 'silent' : 1})
let l:lines = readfile('_vimtex_bibparsed.log')
call delete('_vimtex_bibparsed.log')
let l:current = {}
let l:entries = []
for l:line in l:lines
if l:line[0] ==# '@'
if !empty(l:current)
call add(l:entries, l:current)
let l:current = {}
endif
let l:index = stridx(l:line, ' ')
if l:index > 0
let l:type = l:line[1:l:index-1]
let l:current.type = l:type
let l:current.key = l:line[l:index+1:]
endif
elseif !empty(l:current)
let l:index = stridx(l:line, '=')
if l:index < 0 | continue | endif
let l:key = l:line[:l:index-1]
let l:value = l:line[l:index+1:]
let l:current[tolower(l:key)] = l:value
endif
endfor
if !empty(l:current)
call add(l:entries, l:current)
endif
return l:entries
endfunction
" }}}1
function! s:parse_with_bibparse_init() abort " {{{1
if exists('s:bibparse_init_done') | return | endif
" Check if bibtex is executable
let s:bibparse_not_executable = !executable('bibparse')
if s:bibparse_not_executable
call vimtex#log#warning(
\ 'bibparse is not executable and may not be used to parse bib files!')
endif
let s:bibparse_init_done = 1
endfunction
" }}}1
function! s:parse_with_vim(file) abort " {{{1
" Adheres to the format description found here:
" http://www.bibtex.org/Format/
if !filereadable(a:file)
return []
endif
let l:current = {}
let l:strings = {}
let l:entries = []
for l:line in filter(readfile(a:file), 'v:val !~# ''^\s*\%(%\|$\)''')
if empty(l:current)
if s:parse_type(l:line, l:current, l:strings)
let l:current = {}
endif
continue
endif
if l:current.type ==# 'string'
if s:parse_string(l:line, l:current, l:strings)
let l:current = {}
endif
else
if s:parse_entry(l:line, l:current, l:entries)
let l:current = {}
endif
endif
endfor
return map(l:entries, 's:parse_entry_body(v:val, l:strings)')
endfunction
" }}}1
function! s:parse_type(line, current, strings) abort " {{{1
let l:matches = matchlist(a:line, '\v^\@(\w+)\s*\{\s*(.*)')
if empty(l:matches) | return 0 | endif
let l:type = tolower(l:matches[1])
if index(['preamble', 'comment'], l:type) >= 0 | return 0 | endif
let a:current.level = 1
let a:current.body = ''
if l:type ==# 'string'
return s:parse_string(l:matches[2], a:current, a:strings)
else
let a:current.type = l:type
let a:current.key = matchstr(l:matches[2], '.*\ze,\s*')
return 0
endif
endfunction
" }}}1
function! s:parse_string(line, string, strings) abort " {{{1
let a:string.level += s:count(a:line, '{') - s:count(a:line, '}')
if a:string.level > 0
let a:string.body .= a:line
return 0
endif
let a:string.body .= matchstr(a:line, '.*\ze}')
let l:matches = matchlist(a:string.body, '\v^\s*(\w+)\s*\=\s*"(.*)"\s*$')
if !empty(l:matches) && !empty(l:matches[1])
let a:strings[l:matches[1]] = l:matches[2]
endif
return 1
endfunction
" }}}1
function! s:parse_entry(line, entry, entries) abort " {{{1
let a:entry.level += s:count(a:line, '{') - s:count(a:line, '}')
if a:entry.level > 0
let a:entry.body .= a:line
return 0
endif
let a:entry.body .= matchstr(a:line, '.*\ze}')
call add(a:entries, a:entry)
return 1
endfunction
" }}}1
function! s:parse_entry_body(entry, strings) abort " {{{1
unlet a:entry.level
let l:key = ''
let l:pos = matchend(a:entry.body, '^\s*')
while l:pos >= 0
if empty(l:key)
let [l:key, l:pos] = s:get_key(a:entry.body, l:pos)
else
let [l:value, l:pos] = s:get_value(a:entry.body, l:pos, a:strings)
let a:entry[l:key] = l:value
let l:key = ''
endif
endwhile
unlet a:entry.body
return a:entry
endfunction
" }}}1
function! s:get_key(body, head) abort " {{{1
" Parse the key part of a bib entry tag.
" Assumption: a:body is left trimmed and either empty or starts with a key.
" Returns: The key and the remaining part of the entry body.
let l:matches = matchlist(a:body, '^\v(\w+)\s*\=\s*', a:head)
return empty(l:matches)
\ ? ['', -1]
\ : [tolower(l:matches[1]), a:head + strlen(l:matches[0])]
endfunction
" }}}1
function! s:get_value(body, head, strings) abort " {{{1
" Parse the value part of a bib entry tag, until separating comma or end.
" Assumption: a:body is left trimmed and either empty or starts with a value.
" Returns: The value and the remaining part of the entry body.
"
" A bib entry value is either
" 1. A number.
" 2. A concatenation (with #s) of double quoted strings, curlied strings,
" and/or bibvariables,
"
if a:body[a:head] =~# '\d'
let l:value = matchstr(a:body, '^\d\+', a:head)
let l:head = matchend(a:body, '^\s*,\s*', a:head + len(l:value))
return [l:value, l:head]
else
return s:get_value_string(a:body, a:head, a:strings)
endif
return ['s:get_value failed', -1]
endfunction
" }}}1
function! s:get_value_string(body, head, strings) abort " {{{1
if a:body[a:head] ==# '{'
let l:sum = 1
let l:i1 = a:head + 1
let l:i0 = l:i1
while l:sum > 0
let [l:match, l:_, l:i1] = matchstrpos(a:body, '[{}]', l:i1)
if l:i1 < 0 | break | endif
let l:i0 = l:i1
let l:sum += l:match ==# '{' ? 1 : -1
endwhile
let l:value = a:body[a:head+1:l:i0-2]
let l:head = matchend(a:body, '^\s*', l:i0)
elseif a:body[a:head] ==# '"'
let l:index = match(a:body, '\\\@<!"', a:head+1)
if l:index < 0
return ['s:get_value_string failed', '']
endif
let l:value = a:body[a:head+1:l:index-1]
let l:head = matchend(a:body, '^\s*', l:index+1)
return [l:value, l:head]
elseif a:body[a:head:] =~# '^\w'
let l:value = matchstr(a:body, '^\w\+', a:head)
let l:head = matchend(a:body, '^\s*', a:head + strlen(l:value))
let l:value = get(a:strings, l:value, '@(' . l:value . ')')
else
let l:head = a:head
endif
if a:body[l:head] ==# '#'
let l:head = matchend(a:body, '^\s*', l:head + 1)
let [l:vadd, l:head] = s:get_value_string(a:body, l:head, a:strings)
let l:value .= l:vadd
endif
return [l:value, matchend(a:body, '^,\s*', l:head)]
endfunction
" }}}1
function! s:count(container, item) abort " {{{1
" Necessary because in old Vim versions, count() does not work for strings
try
let l:count = count(a:container, a:item)
catch /E712/
let l:count = count(split(a:container, '\zs'), a:item)
endtry
return l:count
endfunction
" }}}1
endif