This answer has two proposed solutions one with a dedicated parser the other using conversion to JSON. The programming of the JSON conversion happened three times faster. (But it was also done earlier in the day when I was also properly caffeinated...)
Making a dedicated parser
We can make a dedicated parser in a manner similar to the approach explained in this answer of "How to parse a clojure expression?".
Load the FunctionalParsers.m package:
Import["https://raw.githubusercontent.com/antononcube/MathematicaForPrediction/master/FunctionalParsers.m"]
Write BibTeX grammar in EBNF :
ebnfBibTeXCode = "
<bt-database> = { <bt-record> } <@ BTDatabase ;
<bt-record> = ( '@' &> <bt-record-type> ) , ( '{' &> <bt-id> <& ',' ) , <bt-record-entry-list> <& '}' <@ BTRecord ;
<bt-record-entry-list> = <bt-record-entry> , [ { ',' &> <bt-record-entry> } ] ;
<bt-record-entry> = ( <bt-key> <& '=' ) , ( <bt-num-val> | '{' &> { <bt-val> } <& '}' ) <@ BTEntry ;
<bt-key> = '_WordString' ;
<bt-num-val> = '_?NumberQ' ;
<bt-val> = <bt-num-val> | '_WordString' ;
<bt-record-type> = 'article' | 'Article' | 'book' | 'Book' | 'incollection' | 'InCollection' <@ BTType ;
<bt-id> = '_WordString' <@ BTID ;
";
res = GenerateParsersFromEBNF[ParseToEBNFTokens[ebnfBibTeXCode]];
LeafCount /@ res[[1]]
(* {411} *)
After generating parsers we can (have to) modify them in order to handle more general objects than the specified EBNF rules:
pBTVAL = (ToExpression\[CircleDot]ParsePredicate[
StringMatchQ[#, NumberString] &])\[CirclePlus]ParsePredicate[
StringMatchQ[#, (Except[{"{", "}"}] ..)] &];
pBTKEY = ParsePredicate[StringMatchQ[#, (Except[{"{", "}"}] ..)] &];
pBTID = ParsePredicate[
StringMatchQ[#, (WordCharacter | PunctuationCharacter) ..] &];
pBTRECORDENTRYLIST =
ParseShortest[ParseListOf[pBTRECORDENTRY, ParseSymbol[","]]];
Here is a dedicated tokenizer:
ToBibTeXTokens[s_String] :=
ParseToTokens[s, {"=", "{", "}", "@", ","}];
We get this output over the example citations in the question:
pBTDATABASE[ToBibTeXTokens[textCitations]]
(* {{{}, BTDatabase[{BTRecord[{BTType[
"article"], {"feynman1948space", {BTEntry[{"title", \
{"Space-time", "approach", "to", "non-relativistic", "quantum",
"mechanics"}}],
BTEntry[{"author", {"Feynman", ",", "Richard", "Phillips"}}],
BTEntry[{"journal", {"Reviews", "of", "Modern", "Physics"}}],
BTEntry[{"volume", {20}}], BTEntry[{"number", {2}}],
BTEntry[{"pages", {367}}], BTEntry[{"year", {1948}}],
BTEntry[{"publisher", {"APS"}}]}}}],
BTRecord[{BTType[
"article"], {"einstein1905theory", {BTEntry[{"title", {"The",
"theory", "of", "the", "brownian", "movement"}}],
BTEntry[{"author", {"Einstein", ",", "Albert"}}],
BTEntry[{"journal", {"Ann.", "der", "Physik"}}],
BTEntry[{"volume", {17}}], BTEntry[{"pages", {549}}],
BTEntry[{"year", {1905}}]}}}]}]}} *)
It is probably better though to use Map[pBTRecord, ___]
than pBTDATABASE
. This is how it is done below with the JSON conversion.
Conversion to JSON
Here is an implementation of my first suggestion in the comments -- we can easily convert the BiBTeX records to JSON and then use ImportString
. As discussed, YMMV with different sets of records.
t1 = StringReplace[textCitations,
StartOfLine ~~ "@" ~~
x : (LetterCharacter ..) ~~ (Whitespace | "") ~~
"{" ~~ (Whitespace | "") ~~ y : (WordCharacter ..) :>
"@" <> "\"" <> x <> "\"" <> ":{\"label\":" <> "\"" <> y <> "\""];
t2 = StringReplace[t1,
StartOfLine ~~ (Whitespace | "") ~~
x : (LetterCharacter ..) ~~ (Whitespace | "") ~~
"=" ~~ (Whitespace | "") ~~ "{" ~~ y : (Except["}"] ..) ~~ "}" :>
"\"" <> x <> "\"" <> ":" <>
"[" <> (StringJoin @@
Riffle["\"" <> # <> "\"" & /@
StringSplit[y, {Whitespace, ","}], ","]) <> "]"];
t3 = StringSplit[t2, "@"];
t4 = Select[t3,
StringLength[StringReplace[#, Whitespace -> ""]] > 0 &];
ImportString["{" <> # <> "}", "JSON"] & /@ t4
(* {{"article" -> {"author" -> {"Feynman", "", "Richard",
"Phillips"}, "label" -> "feynman1948space",
"journal" -> {"Reviews", "of", "Modern", "Physics"},
"title" -> {"Space-time", "approach", "to", "non-relativistic",
"quantum", "mechanics"}, "volume" -> {"20"}, "number" -> {"2"},
"pages" -> {"367"}, "year" -> {"1948"},
"publisher" -> {"APS"}}},
{"article" -> {"author" -> {"Einstein",
"", "Albert"}, "label" -> "einstein1905theory",
"journal" -> {"Ann.", "der", "Physik"},
"title" -> {"The", "theory", "of", "the", "brownian", "movement"},
"volume" -> {"17"}, "pages" -> {"549"}, "year" -> {"1905"}}}} *)
Assignment of textCitations
textCitations = "
@article{feynman1948space,
title={Space-time approach to non-relativistic quantum \
mechanics},
author={Feynman, Richard Phillips},
journal={Reviews of Modern Physics},
volume={20},
number={2},
pages={367},
year={1948},
publisher={APS}
}
@article{einstein1905theory,
title={The theory of the brownian movement},
author={Einstein, Albert},
journal={Ann. der Physik},
volume={17},
pages={549},
year={1905}
}
";
tutorial/CitationManagement
that works in version 9. But the weblink tutorial/CitationManagement doesn't work anymore. $\endgroup$ImportString
and (2) relatively quickly actually write a parser that works most of the time. $\endgroup$