1+ /* start bibtexParse 0.0.24 */
2+
3+ //Original work by Henrik Muehe (c) 2010
4+ //
5+ //CommonJS port by Mikola Lysenko 2013
6+ //
7+ //Choice of compact (default) or pretty output from toBibtex:
8+ // Nick Bailey, 2017.
9+ //
10+ //Port to Browser lib by ORCID / RCPETERS
11+ //
12+ //Issues:
13+ //no comment handling within strings
14+ //no string concatenation
15+ //no variable values yet
16+ //Grammar implemented here:
17+ //bibtex -> (string | preamble | comment | entry)*;
18+ //string -> '@STRING' '{' key_equals_value '}';
19+ //preamble -> '@PREAMBLE' '{' value '}';
20+ //comment -> '@COMMENT' '{' value '}';
21+ //entry -> '@' key '{' key ',' key_value_list '}';
22+ //key_value_list -> key_equals_value (',' key_equals_value)*;
23+ //key_equals_value -> key '=' value;
24+ //value -> value_quotes | value_braces | key;
25+ //value_quotes -> '"' .*? '"'; // not quite
26+ //value_braces -> '{' .*? '"'; // not quite
27+ ( function ( exports ) {
28+
29+ function BibtexParser ( ) {
30+
31+ this . months = [ "jan" , "feb" , "mar" , "apr" , "may" , "jun" , "jul" , "aug" , "sep" , "oct" , "nov" , "dec" ] ;
32+ this . notKey = [ ',' , '{' , '}' , ' ' , '=' ] ;
33+ this . pos = 0 ;
34+ this . input = "" ;
35+ this . entries = new Array ( ) ;
36+
37+ this . currentEntry = "" ;
38+
39+ this . setInput = function ( t ) {
40+ this . input = t ;
41+ } ;
42+
43+ this . getEntries = function ( ) {
44+ return this . entries ;
45+ } ;
46+
47+ this . isWhitespace = function ( s ) {
48+ return ( s == ' ' || s == '\r' || s == '\t' || s == '\n' ) ;
49+ } ;
50+
51+ this . match = function ( s , canCommentOut ) {
52+ if ( canCommentOut == undefined || canCommentOut == null )
53+ canCommentOut = true ;
54+ this . skipWhitespace ( canCommentOut ) ;
55+ if ( this . input . substring ( this . pos , this . pos + s . length ) == s ) {
56+ this . pos += s . length ;
57+ } else {
58+ throw TypeError ( "Token mismatch: match" , "expected " + s + ", found "
59+ + this . input . substring ( this . pos ) ) ;
60+ } ;
61+ this . skipWhitespace ( canCommentOut ) ;
62+ } ;
63+
64+ this . tryMatch = function ( s , canCommentOut ) {
65+ if ( canCommentOut == undefined || canCommentOut == null )
66+ canCommentOut = true ;
67+ this . skipWhitespace ( canCommentOut ) ;
68+ if ( this . input . substring ( this . pos , this . pos + s . length ) == s ) {
69+ return true ;
70+ } else {
71+ return false ;
72+ } ;
73+ this . skipWhitespace ( canCommentOut ) ;
74+ } ;
75+
76+ /* when search for a match all text can be ignored, not just white space */
77+ this . matchAt = function ( ) {
78+ while ( this . input . length > this . pos && this . input [ this . pos ] != '@' ) {
79+ this . pos ++ ;
80+ } ;
81+
82+ if ( this . input [ this . pos ] == '@' ) {
83+ return true ;
84+ } ;
85+ return false ;
86+ } ;
87+
88+ this . skipWhitespace = function ( canCommentOut ) {
89+ while ( this . isWhitespace ( this . input [ this . pos ] ) ) {
90+ this . pos ++ ;
91+ } ;
92+ if ( this . input [ this . pos ] == "%" && canCommentOut == true ) {
93+ while ( this . input [ this . pos ] != "\n" ) {
94+ this . pos ++ ;
95+ } ;
96+ this . skipWhitespace ( canCommentOut ) ;
97+ } ;
98+ } ;
99+
100+ this . value_braces = function ( ) {
101+ var bracecount = 0 ;
102+ this . match ( "{" , false ) ;
103+ var start = this . pos ;
104+ var escaped = false ;
105+ while ( true ) {
106+ if ( ! escaped ) {
107+ if ( this . input [ this . pos ] == '}' ) {
108+ if ( bracecount > 0 ) {
109+ bracecount -- ;
110+ } else {
111+ var end = this . pos ;
112+ this . match ( "}" , false ) ;
113+ return this . input . substring ( start , end ) ;
114+ } ;
115+ } else if ( this . input [ this . pos ] == '{' ) {
116+ bracecount ++ ;
117+ } else if ( this . pos >= this . input . length - 1 ) {
118+ throw TypeError ( "Unterminated value: value_braces" ) ;
119+ } ;
120+ } ;
121+ if ( this . input [ this . pos ] == '\\' && escaped == false )
122+ escaped = true ;
123+ else
124+ escaped = false ;
125+ this . pos ++ ;
126+ } ;
127+ } ;
128+
129+ this . value_comment = function ( ) {
130+ var str = '' ;
131+ var brcktCnt = 0 ;
132+ while ( ! ( this . tryMatch ( "}" , false ) && brcktCnt == 0 ) ) {
133+ str = str + this . input [ this . pos ] ;
134+ if ( this . input [ this . pos ] == '{' )
135+ brcktCnt ++ ;
136+ if ( this . input [ this . pos ] == '}' )
137+ brcktCnt -- ;
138+ if ( this . pos >= this . input . length - 1 ) {
139+ throw TypeError ( "Unterminated value: value_comment" , + this . input . substring ( start ) ) ;
140+ } ;
141+ this . pos ++ ;
142+ } ;
143+ return str ;
144+ } ;
145+
146+ this . value_quotes = function ( ) {
147+ this . match ( '"' , false ) ;
148+ var start = this . pos ;
149+ var escaped = false ;
150+ while ( true ) {
151+ if ( ! escaped ) {
152+ if ( this . input [ this . pos ] == '"' ) {
153+ var end = this . pos ;
154+ this . match ( '"' , false ) ;
155+ return this . input . substring ( start , end ) ;
156+ } else if ( this . pos >= this . input . length - 1 ) {
157+ throw TypeError ( "Unterminated value: value_quotes" , this . input . substring ( start ) ) ;
158+ } ;
159+ }
160+ if ( this . input [ this . pos ] == '\\' && escaped == false )
161+ escaped = true ;
162+ else
163+ escaped = false ;
164+ this . pos ++ ;
165+ } ;
166+ } ;
167+
168+ this . single_value = function ( ) {
169+ var start = this . pos ;
170+ if ( this . tryMatch ( "{" ) ) {
171+ return this . value_braces ( ) ;
172+ } else if ( this . tryMatch ( '"' ) ) {
173+ return this . value_quotes ( ) ;
174+ } else {
175+ var k = this . key ( ) ;
176+ if ( k . match ( "^[0-9]+$" ) )
177+ return k ;
178+ else if ( this . months . indexOf ( k . toLowerCase ( ) ) >= 0 )
179+ return k . toLowerCase ( ) ;
180+ else
181+ throw "Value expected: single_value" + this . input . substring ( start ) + ' for key: ' + k ;
182+
183+ } ;
184+ } ;
185+
186+ this . value = function ( ) {
187+ var values = [ ] ;
188+ values . push ( this . single_value ( ) ) ;
189+ while ( this . tryMatch ( "#" ) ) {
190+ this . match ( "#" ) ;
191+ values . push ( this . single_value ( ) ) ;
192+ } ;
193+ return values . join ( "" ) ;
194+ } ;
195+
196+ this . key = function ( optional ) {
197+ var start = this . pos ;
198+ while ( true ) {
199+ if ( this . pos >= this . input . length ) {
200+ throw TypeError ( "Runaway key: key" ) ;
201+ } ;
202+ // а-яА-Я is Cyrillic
203+ //console.log(this.input[this.pos]);
204+ if ( this . notKey . indexOf ( this . input [ this . pos ] ) >= 0 ) {
205+ if ( optional && this . input [ this . pos ] != ',' ) {
206+ this . pos = start ;
207+ return null ;
208+ } ;
209+ return this . input . substring ( start , this . pos ) ;
210+ } else {
211+ this . pos ++ ;
212+
213+ } ;
214+ } ;
215+ } ;
216+
217+ this . key_equals_value = function ( ) {
218+ var key = this . key ( ) ;
219+ if ( this . tryMatch ( "=" ) ) {
220+ this . match ( "=" ) ;
221+ var val = this . value ( ) ;
222+ key = key . trim ( )
223+ return [ key , val ] ;
224+ } else {
225+ throw TypeError ( "Value expected, equals sign missing: key_equals_value" ,
226+ this . input . substring ( this . pos ) ) ;
227+ } ;
228+ } ;
229+
230+ this . key_value_list = function ( ) {
231+ var kv = this . key_equals_value ( ) ;
232+ this . currentEntry [ 'entryTags' ] = { } ;
233+ this . currentEntry [ 'entryTags' ] [ kv [ 0 ] ] = kv [ 1 ] ;
234+ while ( this . tryMatch ( "," ) ) {
235+ this . match ( "," ) ;
236+ // fixes problems with commas at the end of a list
237+ if ( this . tryMatch ( "}" ) ) {
238+ break ;
239+ }
240+ ;
241+ kv = this . key_equals_value ( ) ;
242+ this . currentEntry [ 'entryTags' ] [ kv [ 0 ] ] = kv [ 1 ] ;
243+ } ;
244+ } ;
245+
246+ this . entry_body = function ( d ) {
247+ this . currentEntry = { } ;
248+ this . currentEntry [ 'citationKey' ] = this . key ( true ) ;
249+ this . currentEntry [ 'entryType' ] = d . substring ( 1 ) ;
250+ if ( this . currentEntry [ 'citationKey' ] != null ) {
251+ this . match ( "," ) ;
252+ }
253+ this . key_value_list ( ) ;
254+ this . entries . push ( this . currentEntry ) ;
255+ } ;
256+
257+ this . directive = function ( ) {
258+ this . match ( "@" ) ;
259+ return "@" + this . key ( ) ;
260+ } ;
261+
262+ this . preamble = function ( ) {
263+ this . currentEntry = { } ;
264+ this . currentEntry [ 'entryType' ] = 'PREAMBLE' ;
265+ this . currentEntry [ 'entry' ] = this . value_comment ( ) ;
266+ this . entries . push ( this . currentEntry ) ;
267+ } ;
268+
269+ this . comment = function ( ) {
270+ this . currentEntry = { } ;
271+ this . currentEntry [ 'entryType' ] = 'COMMENT' ;
272+ this . currentEntry [ 'entry' ] = this . value_comment ( ) ;
273+ this . entries . push ( this . currentEntry ) ;
274+ } ;
275+
276+ this . entry = function ( d ) {
277+ this . entry_body ( d ) ;
278+ } ;
279+
280+ this . alernativeCitationKey = function ( ) {
281+ this . entries . forEach ( function ( entry ) {
282+ if ( ! entry . citationKey && entry . entryTags ) {
283+ entry . citationKey = '' ;
284+ if ( entry . entryTags . author ) {
285+ entry . citationKey += entry . entryTags . author . split ( ',' ) [ 0 ] += ', ' ;
286+ }
287+ entry . citationKey += entry . entryTags . year ;
288+ }
289+ } ) ;
290+ }
291+
292+ this . bibtex = function ( ) {
293+ while ( this . matchAt ( ) ) {
294+ var d = this . directive ( ) ;
295+ this . match ( "{" ) ;
296+ if ( d . toUpperCase ( ) == "@STRING" ) {
297+ this . string ( ) ;
298+ } else if ( d . toUpperCase ( ) == "@PREAMBLE" ) {
299+ this . preamble ( ) ;
300+ } else if ( d . toUpperCase ( ) == "@COMMENT" ) {
301+ this . comment ( ) ;
302+ } else {
303+ this . entry ( d ) ;
304+ }
305+ this . match ( "}" ) ;
306+ } ;
307+
308+ this . alernativeCitationKey ( ) ;
309+ } ;
310+ } ;
311+
312+ exports . toJSON = function ( bibtex ) {
313+ var b = new BibtexParser ( ) ;
314+ b . setInput ( bibtex ) ;
315+ b . bibtex ( ) ;
316+ return b . entries ;
317+ } ;
318+
319+ /* added during hackathon don't hate on me */
320+ /* Increased the amount of white-space to make entries
321+ * more attractive to humans. Pass compact as false
322+ * to enable */
323+ exports . toBibtex = function ( json , compact ) {
324+ if ( compact === undefined ) compact = true ;
325+ var out = '' ;
326+
327+ var entrysep = ',' ;
328+ var indent = '' ;
329+ if ( ! compact ) {
330+ entrysep = ',\n' ;
331+ indent = ' ' ;
332+ }
333+ for ( var i in json ) {
334+ out += "@" + json [ i ] . entryType ;
335+ out += '{' ;
336+ if ( json [ i ] . citationKey )
337+ out += json [ i ] . citationKey + entrysep ;
338+ if ( json [ i ] . entry )
339+ out += json [ i ] . entry ;
340+ if ( json [ i ] . entryTags ) {
341+ var tags = indent ;
342+ for ( var jdx in json [ i ] . entryTags ) {
343+ if ( tags . trim ( ) . length != 0 )
344+ tags += entrysep + indent ;
345+ tags += jdx + ( compact ? '={' : ' = {' ) +
346+ json [ i ] . entryTags [ jdx ] + '}' ;
347+ }
348+ out += tags ;
349+ }
350+ out += compact ? '}\n' : '\n}\n\n' ;
351+ }
352+ return out ;
353+
354+ } ;
355+
356+ } ) ( typeof exports === 'undefined' ? this [ 'bibtexParse' ] = { } : exports ) ;
357+
358+ /* end bibtexParse */
0 commit comments