diff --git a/autoload/vimtex/parser/bib.vim b/autoload/vimtex/parser/bib.vim index 86a54f13cf..17905f145e 100644 --- a/autoload/vimtex/parser/bib.vim +++ b/autoload/vimtex/parser/bib.vim @@ -270,117 +270,111 @@ function! s:parse_with_vim(file) abort " {{{1 return [] endif - let l:current = {} + let l:items = [] let l:strings = {} - let l:entries = [] + + let l:item = {} let l:lnum = 0 for l:line in readfile(a:file) let l:lnum += 1 - if empty(l:current) - if s:parse_type(a:file, l:lnum, l:line, l:current, l:strings, l:entries) - let l:current = {} - endif - continue - endif + let l:item = empty(l:item) + \ ? s:parse_head(a:file, l:lnum, l:line) + \ : s:parse_tail(l:item, l:line) - 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 = {} + if has_key(l:item, 'parsed') + if l:item.type == "string" + let [l:key, l:value] = s:parse_string(l:item.body) + if !empty(l:key) + let l:strings[l:key] = l:value + endif + else + call add(l:items, l:item) endif + let l:item = {} endif endfor - return map(l:entries, 's:parse_entry_body(v:val, l:strings)') + return map(l:items, 's:parse_item(v:val, l:strings)') endfunction " }}}1 -function! s:parse_type(file, lnum, line, current, strings, entries) abort " {{{1 +function! s:parse_head(file, lnum, line) abort " {{{1 let l:matches = matchlist(a:line, '\v^\@(\w+)\s*\{\s*(.*)') - if empty(l:matches) | return 0 | endif + if empty(l:matches) | return {} | 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 = '' - let a:current.source_file = a:file - let a:current.source_lnum = a:lnum + if l:type == 'preamble' || l:type == 'comment' | return {} | endif + + return s:parse_tail({ + \ 'level': 1, + \ 'body': '', + \ 'source_file': a:file, + \ 'source_lnum': a:lnum, + \ 'type': l:type, + \}, l:matches[2]) +endfunction - if l:type ==# 'string' - return s:parse_string(l:matches[2], a:current, a:strings) +" }}}1 +function! s:parse_tail(item, line) abort " {{{1 + let a:item.level += s:count(a:line, '{') - s:count(a:line, '}') + if a:item.level > 0 + let a:item.body .= a:line else - let l:matches = matchlist(l:matches[2], '\v^([^, ]*)\s*,\s*(.*)') - let a:current.type = l:type - let a:current.key = l:matches[1] - - return empty(l:matches[2]) - \ ? 0 - \ : s:parse_entry(l:matches[2], a:current, a:entries) + let a:item.body .= matchstr(a:line, '.*\ze}') + let a:item.parsed = v:true endif + + return a:item 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 +function! s:parse_string(raw_string) abort " {{{1 + let l:matches = matchlist(a:raw_string, '\v^\s*(\S+)\s*\=\s*"(.*)"\s*$') + if !empty(l:matches) && !empty(l:matches[1]) + return [l:matches[1], l:matches[2]] endif - let a:string.body .= matchstr(a:line, '.*\ze}') - - let l:matches = matchlist(a:string.body, '\v^\s*(\w+)\s*\=\s*"(.*)"\s*$') + let l:matches = matchlist(a:raw_string, '\v^\s*(\S+)\s*\=\s*\{(.*)\}\s*$') if !empty(l:matches) && !empty(l:matches[1]) - let a:strings[l:matches[1]] = l:matches[2] + return [l:matches[1], l:matches[2]] endif - return 1 + return ['', ''] 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}') +function! s:parse_item(item, strings) abort " {{{1 + let l:parts = matchlist(a:item.body, '\v^([^, ]*)\s*,\s*(.*)') - call add(a:entries, a:entry) - return 1 -endfunction - -" }}}1 + let a:item.key = l:parts[1] + if empty(a:item.key) | return {} | endif -function! s:parse_entry_body(entry, strings) abort " {{{1 - unlet a:entry.level + unlet a:item.level + unlet a:item.body + unlet a:item.parsed - 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) + let l:body = l:parts[2] + let l:tag = '' + let l:head = 0 + while l:head >= 0 + if empty(l:tag) + let [l:tag, l:head] = s:get_tag_name(l:body, l:head) 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 = '' + let [l:value, l:head] = s:get_tag_value(l:body, l:head, a:strings) + let a:item[l:tag] = l:value + let l:tag = '' endif endwhile - unlet a:entry.body - return a:entry + return a:item endfunction " }}}1 -function! s:get_key(body, head) abort " {{{1 - " Parse the key part of a bib entry tag. +function! s:get_tag_name(body, head) abort " {{{1 + " Parse the name 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. @@ -391,7 +385,7 @@ function! s:get_key(body, head) abort " {{{1 endfunction " }}}1 -function! s:get_value(body, head, strings) abort " {{{1 +function! s:get_tag_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. @@ -405,15 +399,16 @@ function! s:get_value(body, head, strings) abort " {{{1 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] + return s:get_tag_value_concat(a:body, a:head, a:strings, "") endfunction " }}}1 -function! s:get_value_string(body, head, strings) abort " {{{1 +function! s:get_tag_value_concat(body, head, strings, pre_value) abort " {{{1 + let l:value = "" + let l:head = a:head + if a:body[a:head] ==# '{' let l:sum = 1 let l:i1 = a:head + 1 @@ -432,27 +427,24 @@ function! s:get_value_string(body, head, strings) abort " {{{1 elseif a:body[a:head] ==# '"' let l:index = match(a:body, '\\\@