undefinedStrings, is now returned that holds field values that are judged to be undefined strings. i.e. they are a non-numeric value that is not defined in a @string{...} entry and not enclosed by braces or double-quotes. This array will be empty unless the following condition is met: ($this->removeDelimit || $this->expandMacro && $this->fieldExtract) 24/04/2006 Esteban Zimanyi - When an undefined string is found in function removeDelimiters return the empty string - Return $this->undefinedStrings in the last position to allow compatibility with previous versions - Fix management of preamble in function returnArrays */ // For a quick command-line test (php -f PARSEENTRIES.php) after installation, uncomment these lines: require_once(drupal_get_path('module', 'biblio') . '/biblio.contributors.inc'); /************************* // Parse a file $parse = NEW PARSEENTRIES(); $parse->expandMacro = TRUE; // $array = array("RMP" =>"Rev., Mod. Phys."); // $parse->loadStringMacro($array); // $parse->removeDelimit = FALSE; // $parse->fieldExtract = FALSE; $parse->openBib("bib.bib"); $parse->extractEntries(); $parse->closeBib(); list($preamble, $strings, $entries, $undefinedStrings) = $parse->returnArrays(); print_r($preamble); print "\n"; print_r($strings); print "\n"; print_r($entries); print "\n\n"; *************************/ /************************ // Parse a bibtex PHP string $bibtex_data = <<< END @STRING{three = "THREE"} @STRING{two = "TWO"} @string{JRNL23 = {NatLA 23 } # " " # two # " " # three} @article{klitzing.1, author = "v. Klitzing and Dorda and Pepper", title = "New method for high mark@sirfragalot.com accuracy determination of fine structure constant based on quantized hall resistance", volume = "45", journal = {Journal of } # JRNL23, pages = "494", citeulike-article-id = {12222 } , ignoreMe = {blah}, } @article { klitzing.2, author = "Klaus von Klitzing", title = "The Quantized Hall Effect", volume = "58", journal = two, pages = "519", } END; $parse = NEW PARSEENTRIES(); $parse->expandMacro = TRUE; // $parse->removeDelimit = FALSE; // $parse->fieldExtract = FALSE; $array = array("RMP" =>"Rev., Mod. Phys."); $parse->loadStringMacro($array); $parse->loadBibtexString($bibtex_data); $parse->extractEntries(); list($preamble, $strings, $entries, $undefinedStrings) = $parse->returnArrays(); print_r($preamble); print "\n"; print_r($strings); print "\n"; print_r($entries); print "\n\n"; **********************/ class PARSEENTRIES { /** * @return unknown_type */ function PARSEENTRIES() { require_once(drupal_get_path('module', 'biblio') . '/bibtexParse/transtab_latex_unicode.inc.php'); $this->preamble = $this->strings = $this->undefinedStrings = $this->entries = array(); $this->count = 0; $this->fieldExtract = TRUE; $this->removeDelimit = TRUE; $this->expandMacro = FALSE; $this->parseFile = TRUE; $this->outsideEntry = TRUE; $this->translate_latex = TRUE; } // Open bib file /** * @param $file * @return none */ function openBib($file) { if(!is_file($file)) die; $this->fid = fopen ($file,'r'); $this->parseFile = TRUE; } // Load a bibtex string to parse it function loadBibtexString($bibtex_string) { if(is_string($bibtex_string)) { //$bibtex_string = $this->searchReplaceText($this->transtab_latex_unicode, $bibtex_string, false); $this->bibtexString = explode("\n",$bibtex_string); } else { $this->bibtexString = $bibtex_string; } $this->parseFile = FALSE; $this->currentLine = 0; } function searchReplaceText($searchReplaceActionsArray, $sourceString, $includesSearchPatternDelimiters=FALSE) { // apply the search & replace actions defined in '$searchReplaceActionsArray' to the text passed in '$sourceString': foreach ($searchReplaceActionsArray as $searchString => $replaceString) { if (!$includesSearchPatternDelimiters) $searchString = "/" . $searchString . "/"; // add search pattern delimiters if (preg_match($searchString, $sourceString)) $sourceString = preg_replace($searchString, $replaceString, $sourceString); } return $sourceString; } // Set strings macro function loadStringMacro($macro_array) { $this->userStrings = $macro_array; } // Close bib file function closeBib() { fclose($this->fid); } // Get a non-empty line from the bib file or from the bibtexString function getLine() { if($this->parseFile) { if(!feof($this->fid)) { do { $line = trim(fgets($this->fid)); } while(!feof($this->fid) && !$line); return $line; } return FALSE; } else { do { $line = trim($this->bibtexString[$this->currentLine]); $this->currentLine++; } while($this->currentLine < count($this->bibtexString) && !$line); return $line; } } // Extract value part of @string field enclosed by double-quotes or braces. // The string may be expanded with previously-defined strings function extractStringValue($string) { // $string contains a end delimiter, remove it $string = trim(substr($string,0,strlen($string)-1)); // remove delimiters and expand $string = $this->removeDelimitersAndExpand($string); return $string; } // Extract a field function fieldSplit($seg) { // echo "**** ";print_r($seg);echo "
"; // handle fields like another-field = {} $array = preg_split("/,\s*([-_.:,a-zA-Z0-9]+)\s*={1}\s*/U", $seg, PREG_SPLIT_DELIM_CAPTURE); // echo "**** ";print_r($array);echo "
"; //$array = preg_split("/,\s*(\w+)\s*={1}\s*/U", $seg, PREG_SPLIT_DELIM_CAPTURE); if(!array_key_exists(1, $array)) return array($array[0], FALSE); return array($array[0], $array[1]); } // Extract and format fields function reduceFields($oldString) { // 03/05/2005 G. Gardey. Do not remove all occurences, juste one // * correctly parse an entry ended by: somefield = {aValue}} $lg = strlen($oldString); if($oldString[$lg-1] == "}" || $oldString[$lg-1] == ")" || $oldString[$lg-1] == ",") $oldString = substr($oldString,0,$lg-1); // $oldString = rtrim($oldString, "}),"); $split = preg_split("/=/", $oldString, 2); $string = $split[1]; while($string) { list($entry, $string) = $this->fieldSplit($string); $values[] = $entry; } foreach($values as $value) { $pos = strpos($oldString, $value); $oldString = substr_replace($oldString, '', $pos, strlen($value)); } $rev = strrev(trim($oldString)); if($rev{0} != ',') $oldString .= ','; $keys = preg_split("/=,/", $oldString); // 22/08/2004 - Mark Grimshaw // I have absolutely no idea why this array_pop is required but it is. Seems to always be // an empty key at the end after the split which causes problems if not removed. array_pop($keys); foreach($keys as $key) { $value = trim(array_shift($values)); $rev = strrev($value); // remove any dangling ',' left on final field of entry if($rev{0} == ',') $value = rtrim($value, ","); if(!$value) continue; // 21/08/2004 G.Gardey -> expand macro // Don't remove delimiters now needs to know if the value is a string macro // $this->entries[$this->count][strtolower(trim($key))] = trim($this->removeDelimiters(trim($value))); $key = strtolower(trim($key)); $value = trim($value); $this->entries[$this->count][$key] = $value; } // echo "**** ";print_r($this->entries[$this->count]);echo "
"; } // Start splitting a bibtex entry into component fields. // Store the entry type and citation. function fullSplit($entry) { $matches = preg_split("/@(.*)[{(](.*),/U", $entry, 2, PREG_SPLIT_DELIM_CAPTURE); $this->entries[$this->count]['bibtexEntryType'] = strtolower(trim($matches[1])); // sometimes a bibtex entry will have no citation key if(preg_match("/=/", $matches[2])) // this is a field $matches = preg_split("/@(.*)\s*[{(](.*)/U", $entry, 2, PREG_SPLIT_DELIM_CAPTURE); // print_r($matches); print "

"; $this->entries[$this->count]['bibtexCitation'] = $matches[2]; $this->reduceFields($matches[3]); } // Grab a complete bibtex entry function parseEntry($entry) { set_time_limit(30); // reset the script timer to avoid timeouts $entry = $this->translate_latex ? $this->searchReplaceText($this->transtab_latex_unicode, $entry, false) : $entry; $count = 0; $lastLine = FALSE; if(preg_match("/@(.*)([{(])/U", preg_quote($entry), $matches)) { if(!array_key_exists(1, $matches)) return $lastLine; if(preg_match("/string/i", trim($matches[1]))) $this->strings[] = $entry; else if(preg_match("/preamble/i", trim($matches[1]))) $this->preamble[] = $entry; else if(preg_match("/comment/i", $matches[1])); // MG (31/Jan/2006) -- ignore @comment else { if($this->fieldExtract) $this->fullSplit($entry); else $this->entries[$this->count] = $entry; $this->count++; } return $lastLine; } } // Remove delimiters from a string function removeDelimiters($string) { if($string && ($string{0} == "\"")) { $string = substr($string, 1); $string = substr($string, 0, -1); } else if($string && ($string{0} == "{")) { if(strlen($string) > 0 && $string[strlen($string)-1] == "}") { $string = substr($string, 1); $string = substr($string, 0, -1); } } else if(!is_numeric($string) && !array_key_exists($string, $this->strings) && (array_search($string, $this->undefinedStrings) === FALSE)) { $this->undefinedStrings[] = $string; // Undefined string that is not a year etc. return ''; } return $string; } // This function works like explode('#',$val) but has to take into account whether // the character # is part of a string (i.e., is enclosed into "..." or {...} ) // or defines a string concatenation as in @string{ "x # x" # ss # {xx{x}x} } function explodeString($val) { $openquote = $bracelevel = $i = $j = 0; while ($i < strlen($val)) { if ($val[$i] == '"') $openquote = !$openquote; elseif ($val[$i] == '{') $bracelevel++; elseif ($val[$i] == '}') $bracelevel--; elseif ( $val[$i] == '#' && !$openquote && !$bracelevel ) { $strings[] = substr($val,$j,$i-$j); $j=$i+1; } $i++; } $strings[] = substr($val,$j); return $strings; } // This function receives a string and a closing delimiter '}' or ')' // and looks for the position of the closing delimiter taking into // account the following Bibtex rules: // * Inside the braces, there can arbitrarily nested pairs of braces, // but braces must also be balanced inside quotes! // * Inside quotes, to place the " character it is not sufficient // to simply escape with \": Quotes must be placed inside braces. function closingDelimiter($val,$delimitEnd) { // echo "####>$delimitEnd $val
"; $openquote = $bracelevel = $i = $j = 0; while ($i < strlen($val)) { // a '"' found at brace level 0 defines a value such as "ss{\"o}ss" if ($val[$i] == '"' && !$bracelevel) $openquote = !$openquote; elseif ($val[$i] == '{') $bracelevel++; elseif ($val[$i] == '}') $bracelevel--; if ( $val[$i] == $delimitEnd && !$openquote && !$bracelevel ) return $i; $i++; } // echo "--> $bracelevel, $openquote"; return 0; } // Remove enclosures around entry field values. Additionally, expand macros if flag set. function removeDelimitersAndExpand($string, $inpreamble = FALSE) { // only expand the macro if flag set, if strings defined and not in preamble if(!$this->expandMacro || empty($this->strings) || $inpreamble) $string = $this->removeDelimiters($string); else { $stringlist = $this->explodeString($string); $string = ""; foreach ($stringlist as $str) { // trim the string since usually # is enclosed by spaces $str = trim($str); // replace the string if macro is already defined // strtolower is used since macros are case insensitive if (isset($this->strings[strtolower($str)])) $string .= $this->strings[strtolower($str)]; else $string .= $this->removeDelimiters(trim($str)); } } return $string; } // This function extract entries taking into account how comments are defined in BibTeX. // BibTeX splits the file in two areas: inside an entry and outside an entry, the delimitation // being indicated by the presence of a @ sign. When this character is met, BibTex expects to // find an entry. Before that sign, and after an entry, everything is considered a comment! function extractEntries() { $inside = $possibleEntryStart = FALSE; $entry=""; while($line=$this->getLine()) { if($possibleEntryStart) $line = $possibleEntryStart . $line; if (!$inside && strchr($line,"@")) { // throw all characters before the '@' $line=strstr($line,'@'); if(!strchr($line, "{") && !strchr($line, "(")) $possibleEntryStart = $line; elseif(preg_match("/@.*([{(])/U", preg_quote($line), $matches)) { $inside = TRUE; if ($matches[1] == '{') $delimitEnd = '}'; else $delimitEnd = ')'; $possibleEntryStart = FALSE; } } if ($inside) { $entry .= " ".$line; if ($j=$this->closingDelimiter($entry,$delimitEnd)) { // all characters after the delimiter are thrown but the remaining // characters must be kept since they may start the next entry !!! $lastLine = substr($entry,$j+1); $entry = substr($entry,0,$j+1); // Strip excess whitespaces from the entry $entry = preg_replace('/\s\s+/', ' ', $entry); $this->parseEntry($entry); $entry = strchr($lastLine,"@"); if ($entry) $inside = TRUE; else $inside = FALSE; } } } } // Return arrays of entries etc. to the calling process. function returnArrays() { global $transtab_latex_unicode; // defined in 'transtab_latex_unicode.inc.php' foreach($this->preamble as $value) { preg_match("/.*?[{(](.*)/", $value, $matches); $preamble = substr($matches[1], 0, -1); $preambles['bibtexPreamble'] = trim($this->removeDelimitersAndExpand(trim($preamble), TRUE)); } if(isset($preambles)) $this->preamble = $preambles; if($this->fieldExtract) { // Next lines must take into account strings defined by previously-defined strings $strings = $this->strings; // $this->strings is initialized with strings provided by user if they exists // it is supposed that there are no substitutions to be made in the user strings, i.e., no # $this->strings = isset($this->userStrings) ? $this->userStrings : array() ; foreach($strings as $value) { // changed 21/08/2004 G. Gardey // 23/08/2004 Mark G. account for comments on same line as @string - count delimiters in string value $value = trim($value); $matches = preg_split("/@\s*string\s*([{(])/i", $value, 2, PREG_SPLIT_DELIM_CAPTURE); $delimit = $matches[1]; $matches = preg_split("/=/", $matches[2], 2, PREG_SPLIT_DELIM_CAPTURE); // macros are case insensitive $this->strings[strtolower(trim($matches[0]))] = $this->extractStringValue($matches[1]); } } // changed 21/08/2004 G. Gardey // 22/08/2004 Mark Grimshaw - stopped useless looping. // removeDelimit and expandMacro have NO effect if !$this->fieldExtract if($this->removeDelimit || $this->expandMacro && $this->fieldExtract) { for($i = 0; $i < count($this->entries); $i++) { foreach($this->entries[$i] as $key => $value) // 02/05/2005 G. Gardey don't expand macro for bibtexCitation // and bibtexEntryType if($key != 'bibtexCitation' && $key != 'bibtexEntryType') $this->entries[$i][$key] = trim($this->removeDelimitersAndExpand($this->entries[$i][$key])); } } // EZ: Remove this to be able to use the same instance for parsing several files, // e.g., parsing a entry file with its associated abbreviation file // if(empty($this->preamble)) // $this->preamble = FALSE; // if(empty($this->strings)) // $this->strings = FALSE; // if(empty($this->entries)) // $this->entries = FALSE; return array($this->preamble, $this->strings, $this->entries, $this->undefinedStrings); } function bib2node($terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $start = 0, $limit = 0){ //list($preamble, $strings, $entries, $undefinedStrings) = $this->returnArrays(); $limit = ($limit) ? $limit : count($this->entries); if (($start + $limit) > count($this->entries)) $limit = count($this->entries) - $start; for ($i = $start; $i < $limit; $i++) { $node = array(); $entry = $this->entries[$i]; if($this->removeDelimit || $this->expandMacro && $this->fieldExtract) { foreach($entry as $key => $value) { if($key != 'bibtexCitation' && $key != 'bibtexEntryType') { $entry[$key] = trim($this->removeDelimitersAndExpand($entry[$key])); } } } //foreach($entries as $entry){ $node['biblio_contributors'] = array(); $node['biblio_type'] = $this->bibtex_type_map($entry['bibtexEntryType']); switch ($entry['bibtexEntryType']){ case mastersthesis: $node['biblio_type_of_work'] = 'masters'; break; case phdthesis: $node['biblio_type_of_work'] = 'phd'; break; } if (!empty($entry['author'])){ // split on ' and ' $authorArray = preg_split("/\s(and|&)\s/i", trim($entry['author'])); foreach ($authorArray as $key => $author) { $node['biblio_contributors'][1][]= array('name' => $author, 'auth_type' => _biblio_get_auth_type(1, $node['biblio_type'])); } } $node['biblio_citekey'] = (!empty($entry['bibtexCitation'])) ? $entry['bibtexCitation'] : NULL; if (!empty($entry['editor'])) { $authorArray = preg_split("/\s(and|&)\s/i", trim($entry['editor'])); foreach ($authorArray as $key => $author) { $node['biblio_contributors'][2][]= array('name' => $author, 'auth_type' => _biblio_get_auth_type(2, $node['biblio_type'])); } } $node['biblio_secondary_title'] = (!empty($entry['journal'])) ? $entry['journal'] : NULL; if (!empty($entry['booktitle'])) $node['biblio_secondary_title'] = $entry['booktitle']; if (!empty($entry['series'])) $node['biblio_tertiary_title'] = $entry['series']; $node['biblio_volume'] = (!empty($entry['volume'])) ? $entry['volume'] : NULL; $node['biblio_number'] = (!empty($entry['number'])) ? $entry['number'] : NULL; $node['biblio_year'] = (!empty($entry['year'])) ? $entry['year'] : NULL; $node['biblio_notes'] = (!empty($entry['note'])) ? $entry['note'] : NULL; $node['biblio_date'] = (!empty($entry['month'])) ? $entry['month'] : NULL; $node['biblio_pages'] = (!empty($entry['pages'])) ? $entry['pages'] : NULL; $node['biblio_publisher'] = (!empty($entry['publisher'])) ? $entry['publisher'] : NULL; if (!empty($entry['organization'])) $node['biblio_publisher'] = $entry['organization']; if (!empty($entry['school'])) $node['biblio_publisher'] = $entry['school']; if (!empty($entry['institution'])) $node['biblio_publisher'] = $entry['institution']; $node['title'] = (!empty($entry['title'])) ? $entry['title'] : NULL; $node['biblio_type_of_work'] .= (!empty($entry['type'])) ? $entry['type'] : NULL; $node['biblio_edition'] = (!empty($entry['edition'])) ? $entry['edition'] : NULL; $node['biblio_section'] = (!empty($entry['chapter'])) ? $entry['chapter'] : NULL; $node['biblio_place_published'] = (!empty($entry['address'])) ? $entry['address'] : NULL; $node['biblio_abst_e'] = (!empty($entry['abstract'])) ? $entry['abstract'] : NULL; if (!empty($entry['keywords'])){ if (strpos($entry['keywords'],';')) $entry['keywords'] = str_replace(';',',',$entry['keywords']); $node['biblio_keywords'] = explode(',', $entry['keywords']); } $node['biblio_isbn'] = (!empty($entry['isbn'])) ? $entry['isbn'] : NULL; $node['biblio_issn'] = (!empty($entry['issn'])) ? $entry['issn'] : NULL; $node['biblio_url'] = (!empty($entry['url'])) ? $entry['url'] : NULL; $node['biblio_doi'] = (!empty($entry['doi'])) ? $entry['doi'] : NULL; if (!empty($terms)) { if (!isset($node['taxonomy'])) $node['taxonomy'] = array(); $node['taxonomy'] = array_merge($terms,$node['taxonomy']); } $nids[] = biblio_save_node($node, $batch, $session_id, $save); } return (!empty($nids)) ? $nids : NULL; } function bibtex_type_map($type) { static $map = array(); if (empty($map)) { module_load_include('inc', 'biblio', 'biblio.type.mapper'); $map = biblio_get_type_map('bibtex'); } return (isset($map[$type])) ? $map[$type] : 129; //return the biblio type or 129 (Misc) if type not found } }