Adam Frieberg » » Computer Programming http://blog.adamfrieberg.com Minister, Photographer, Computer Programmer Thu, 27 Feb 2014 01:29:36 +0000 en-US hourly 1 http://wordpress.org/?v=4.3.2 On Software Developer Limits http://blog.adamfrieberg.com/2012/02/17/on-software-developer-limits/ http://blog.adamfrieberg.com/2012/02/17/on-software-developer-limits/#respond Sat, 18 Feb 2012 00:01:58 +0000 http://blog.adamfrieberg.com/?p=1463

As a matter of fact, if you try to launch an unsigned or unvalidatable app on a Mac with Gatekeeper enabled, the default button is “Move To Trash”. Pretty hardcore. Kind of awesome.

Panic, the developer of my favorite code editor on Mac, wrote a great review of the need for the recently announced Gatekeeper feature, which protects Macs from villainous programs/code.  While I don’t do any Cocoa or Carbon development (I’m all web-based, baby …) — this feature sounds like exactly what’s needed.

Mac users will proceed at their own risk … knowing Apple has their back.

]]>
http://blog.adamfrieberg.com/2012/02/17/on-software-developer-limits/feed/ 0
Bible Taxonomy – AJAX / WP Integration http://blog.adamfrieberg.com/2011/02/25/bible-taxonomy-ajax-wp-integration/ http://blog.adamfrieberg.com/2011/02/25/bible-taxonomy-ajax-wp-integration/#comments Fri, 25 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=1199 BibleTax_Featured_5Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/ So how does the user input get saved dynamically to the database? It already happens in a similar way with […]]]> BibleTax_Featured_5
Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

So how does the user input get saved dynamically to the database? It already happens in a similar way with post tags. Users enter them on the fly and they’re processed and the user interface is updated without needing a postback or page refresh.

The hard part was figuring how to tap into the existing AJAX / PHP functions from my child theme. After some deep, deep digging, I found the interactions in the WordPress core.

The key: hook into your php function with ‘wp_ajax_’ appended to the javascript function’s name

add_action('wp_ajax_process_bible_verses', 'bible_verses_ADD_callback');
function bible_verses_ADD_callback() { 
	$verses = $_POST['verses'];
	$service = new IDWebService($verses);
	
	$verses = $service->verses;
	$xml = $service->xml;
	$id_firstBook = $service->id_firstBook;
	$id_firstChapter = $service->id_firstChapter;
	$id_firstVerse = $service->id_firstVerse;
	$id_secondBook = $service->id_secondBook;
	$id_secondChapter = $service->id_secondChapter;
	$id_secondVerse = $service->id_secondVerse;
	$id_sanitizedVerses = $service->sanitizedVerses;
	$exceptionBool = $service->exceptionBool;
	$exceptionMessage = $service->exceptionMessage;
	
	$verseformat  = $service->GetVerseFormat($verses);
	
	$service->__destruct(); // Did this to solve memory leak; http://paul-m-jones.com/archives/262
    unset($service); // however, leak probably came about from iterating through objects and not ints
	
	$post_id = intval( $_POST['post_id'] );
	
	try {		
		if ($exceptionBool == true) {
			//_log('exception prior to checking ...'.$exceptionMessage);
			die('<Error msg="'.$exceptionMessage.'" />');
		} else {
			switch ($verseformat) {
				case "B":
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C":
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C:V":
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					$bibleIDs[] = $id_firstVerse;
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C-C":
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					$bibleIDs[] = $id_secondChapter;
					// NOTE: Need to improve the logic to make this "B C-C" rather than "B C,C"
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C:V-V":
					// check to make sure second verse > first verse
					if ($id_firstVerse >= $id_secondVerse) {
						die('<Error msg="Choose a starting verse before the ending verse">');
					}
					
					// _log('id_firstverse = '.$id_firstVerse);
					// _log('id_secondverse = '.$id_secondVerse);
					
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					for ($i = $id_firstVerse*1; $i < $id_secondVerse*1+1; $i++) {
						$bibleIDs[] = $i;
					}
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C:V,V":
					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					$bibleIDs[] = $id_firstVerse;
					$bibleIDs[] = $id_secondVerse;
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "B C:V - C:V":
				case "B C:V - B C:V":
					// check to make sure second verse > first verse
					if ((int)$id_firstVerse >= (int)$id_secondVerse) {
						die('<Error msg="Choose a starting verse before the ending verse">');
					}

					$bibleIDs = array();
					$bibleIDs[] = $id_firstBook;
					$bibleIDs[] = $id_firstChapter;
					for ($i = $id_firstVerse*1; $i < $id_secondVerse*1+1; $i++) {
						$bibleIDs[] = $i;
					}
					$newCollectionID = bible_verses_addCollection($id_sanitizedVerses, $bibleIDs, $post_id);
					die('<collection id="'.$newCollectionID.'" name="'.$id_sanitizedVerses.'" />');
					break;
				case "Error: Unknown Format (Book should be followed by Chapter number)":
				case "Error: Unknown Format (Are you missing a chapter?)":
				case "Error: Unknown Format (Are you trying to reference more than one verse?  We suggest Book Chapter:Verse - Verse format)":
					echo '<Error ';
					echo 'msg="'.$verseformat.'" />';
					break;
				default:
					echo ',';
					echo '<Error msg="Error: unknown Format" />';
			}
		}
		
		// IMPORTANT: don't forget to "exit"
		exit;

	} catch (Exception $e) {
		die('<Error msg="'.$e.'" />');
		exit;
	}
}

Once the interaction’s happening correctly, the database insert statements look like normal SQL statements in the PHP:


function bible_verses_addCollection($collectionName, $arrayIds, $post_id) {
	global $wpdb;
	
	// Add collection to table
	$rowstatus = $wpdb->insert( $wpdb->prefix . "bible_terms_taxonomy", array( 'taxonomy' => $collectionName, 'term_id' => $post_id) );
	$collectionID = $wpdb->insert_id;
	
	if ($rowstatus == 1) {
		foreach($arrayIds as $elemID) {
			$relationshipRowStatus = $wpdb->insert( $wpdb->prefix . "bible_terms_relationships", array('object_id' => $collectionID, 'term_id' => $elemID));
			if ($relationshipRowStatus == 0) {
				$rowstatus = 0;
			}
		}
		return $collectionID;
	} else {
		return -1;
	}
}

I ended up creating similar AJAX methods for the delete and GET / SELECT so that the Javascript methods had easy PHP functions to call.

]]>
http://blog.adamfrieberg.com/2011/02/25/bible-taxonomy-ajax-wp-integration/feed/ 2
Bible Taxonomy – Parsing Input http://blog.adamfrieberg.com/2011/02/24/bible-taxonomy-parsing-input/ http://blog.adamfrieberg.com/2011/02/24/bible-taxonomy-parsing-input/#respond Thu, 24 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=1196 BibleTax_Featured_4aNote: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/ Here’s a confession: this part of the plugin is only half-baked. It’s a work in progress. Users can select Books, […]]]> BibleTax_Featured_4a
Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

Here’s a confession: this part of the plugin is only half-baked. It’s a work in progress.

Users can select Books, Chapters, Verses using the Javascript tool. But what about quick entries? Surely a good user interface maximizes keystrokes and minimizes mouse clicks, right?

Well, I tried. The issue is how many permutations of entry types I’d have to deal with — let alone the abbreviations and commonplace names of books that I’d need to check for.

For instance, how many ways can you cite a scripture reference?

  • B
  • B C
  • B C:V
  • B C-C
  • B C:V-V
  • B C:V, V
  • B C:V – C:V
  • B C:V – B C:V
    • B = Book, C = Chapter, V = Verse. And this obviously doesn’t account for other styles of notation (such as using a period (.) instead of a semi-colon (:) ).

      And it also doesn’t account for complex references, such as B C-C:V, V-V or other similar, complicated references that the lectionary often chooses.

      Note: in the steps that follow, I wrapped it all in a “Service” class since the code would be re-used in several places

      Step 1: Figure out if what users enter is legit
      This wouldn’t be as big of an issue if they just used the Javascript widget. But the first rule of programming: if data comes from a user, don’t trust it. So we do some checks:

      function SanitizeVerses($bibleverseIn) {
      		// Strip Out unsafe characters in $bibleverseIn 
      		$bibleverse = htmlspecialchars($bibleverseIn, ENT_QUOTES, 'UTF-8');
      		
      		// Test string; if "." change to ":"
      		$bibleverse = str_replace(".", ":", $bibleverse);
      		
      		// take out spaces to make consistent and irrelevant on passedin %20 notation
      		$bibleverse = str_replace(" ", "", $bibleverse);
      		
      		return $bibleverse;
      		
      	}
      

      This takes out most punctuation, escape characters, and most importantly, spaces!

      Step 2: Figure out if it’s a ‘numbered book’
      This was actually step 5, but that’s because I didn’t plan properly. Oops.

      function GetNumberedBookStatus($string) {
      		$firstcar = substr($string, 0, 1);
      		if (is_numeric($firstcar)) {
      			return 1;
      		}
      		else {
      			return 0;
      		}
      	}
      

      Step 3: Determine the chapter number location
      If there are any trailing numbers behind the book name we want to know their location in the verses string so we can parse quicker.

      function GetChapterNumLoc($bookchapter) {
      		
      		if ($this->GetNumberedBookStatus($bookchapter) == 1) {
      			$string = substr($bookchapter, 1);
      		} else {
      			$string = $bookchapter;
      		}
      	
      		$nums = array();
      		
      		if (strpos($string, "1") > 0) {
      			$nums[] = strpos($string, "1");
      		}
      		if (strpos($string, "2") > 0) {
      			$nums[] = strpos($string, "2");
      		}
      		if (strpos($string, "3") > 0) {
      			$nums[] = strpos($string, "3");
      		}
      		if (strpos($string, "4") > 0) {
      			$nums[] = strpos($string, "4");
      		}
      		if (strpos($string, "5") > 0) {
      			$nums[] = strpos($string, "5");
      		}
      		if (strpos($string, "6") > 0) {
      			$nums[] = strpos($string, "6");
      		}
      		if (strpos($string, "7") > 0) {
      			$nums[] = strpos($string, "7");
      		}
      		if (strpos($string, "8") > 0) {
      			$nums[] = strpos($string, "8");
      		}
      		if (strpos($string, "9") > 0) {
      			$nums[] = strpos($string, "9");
      		}
      		if (strpos($string, "0") > 0) {
      			$nums[] = strpos($string, "0");
      		}
      		
      		if (count($nums) == 0) {
      			$chapternumloc = 0;
      		} else {
      			if ($this->GetNumberedBookStatus($bookchapter) == 1) {
      				$chapternumloc = min($nums)+1; // to return an accurate number for scrollnum
      			} else {
      				$chapternumloc = min($nums);
      			}
      		}
      		return $chapternumloc;
      	}
      

      I know there had to be an light-weight version that could do the same, but I didn’t want to take the time researching … it works.

      Step 4: Determine the format of the input syntax

      function GetVerseFormat($bibleverse) {
      		$splits = preg_split("/[\,\:\-]/", $bibleverse);
      		
      		$firststring = $splits[0];
      		if ($this->GetNumberedBookStatus($firststring) == 1) {
      			$firststring = substr($firststring, 1);
      		}
      		
      		$nums = array();
      		if (strpos($firststring, "1") > 0) {
      			$nums[] = strpos($firststring, "1");
      		}
      		if (strpos($firststring, "2") > 0) {
      			$nums[] = strpos($firststring, "2");
      		}
      		if (strpos($firststring, "3") > 0) {
      			$nums[] = strpos($firststring, "3");
      		}
      		if (strpos($firststring, "4") > 0) {
      			$nums[] = strpos($firststring, "4");
      		}
      		if (strpos($firststring, "5") > 0) {
      			$nums[] = strpos($firststring, "5");
      		}
      		if (strpos($firststring, "6") > 0) {
      			$nums[] = strpos($firststring, "6");
      		}
      		if (strpos($firststring, "7") > 0) {
      			$nums[] = strpos($firststring, "7");
      		}
      		if (strpos($firststring, "8") > 0) {
      			$nums[] = strpos($firststring, "8");
      		}
      		if (strpos($firststring, "9") > 0) {
      			$nums[] = strpos($firststring, "9");
      		}
      		if (strpos($firststring, "0") > 0) {
      			$nums[] = strpos($firststring, "0");
      		}
      		
      		if (count($nums) == 0) {
      			$chapternumloc = 0;
      		} else {
      			$chapternumloc = min($nums);
      		}
      		
      		$splitscount = count($splits);
      				
      		if ($chapternumloc < 2 || $chapternumloc == null) { // There is no chapter // verify $splits length = 1 before returning "B"
      			if ($splitscount == 1) {
      				return "B";
      			}
      			else {
      				return "Error: Unknown Format (Book should be followed by Chapter number)";
      			}
      		} else {
      			if ($splitscount == 1) {
      				return "B C";
      			} elseif ($splitscount == 2) {
      				if (strpos($bibleverse, ":") > 0) {
      					return "B C:V";
      				} elseif(strpos($bibleverse, "-") > 0) {
      					return "B C-C";
      				} else {
      					return "Error: Unknown Format (Are you missing a chapter?)";
      				}
      			} elseif ($splitscount == 3) {
      				if (substr($bibleverse, strpos($bibleverse,"-")+1, strlen($splits[2])) == $splits[2]) { // B C:V-V
      					return "B C:V-V";
      				} elseif (substr($bibleverse, strpos($bibleverse,",")+1, strlen($splits[2])) == $splits[2]) { // B C:V,V
      					return "B C:V,V";
      				} else {
      					return "Error: Unknown Format (Are you trying to reference more than one verse?  We suggest Book Chapter:Verse - Verse format)";
      				}
      			} elseif ($splitscount == 4) {
      				if (is_numeric($splits[2])) { // 3rd element is numeric, not a BC combo
      					return "B C:V - C:V";
      				} else {
      					return "B C:V - B C:V";
      				}
      			} else {
      				return "Error: Unknown Format";
      			}
      		}
      	}	
      

      Step 5: Setup the helper methods to query the XML using XPath with the input values

      function LookupBookId($xml, $book) {
      		try {
      			$results = $xml->xpath("//book[@name=\"".$book."\"]");
      			foreach($results as $resultnode) {
      				$atts = $resultnode->attributes();
      				return $atts["id"];
      			}
      			return "-1"; // none found
      		} catch (Exception $e) {
      			return "-1";
      		}
      	}
      	
      	function LookupChapterId($xml, $book, $chapter) {
      		try {
      			$results = $xml->xpath("//book[@name=\"".$book."\"]/chapter[@name=\"".$chapter."\"]");
      			foreach($results as $resultnode) {
      				$atts = $resultnode->attributes();
      				return $atts["id"];
      			}
      			return "-1"; // none found
      		} catch (Exception $e) {
      			return "-1";
      		}	
      	}
      	
      	function LookupVerseId($xml, $book, $chapter, $verse) {
      		try {
      			$results = $xml->xpath("//book[@name=\"".$book."\"]/chapter[@name=\"".$chapter."\"]/verse[@name=\"".$verse."\"]");
      			foreach($results as $resultnode) {
      				$atts = $resultnode->attributes();			
      				return $atts["id"];
      			}
      			return "-1"; // none found
      		} catch (Exception $e) {
      			return "-1";
      		}
      	}
      

      Step 6: Setup the class holder variables

      class IDWebService {
      	var $xml;
      	var $id_firstBook;
      	var $id_firstChapter;
      	var $id_firstVerse;
      	var $id_secondBook;
      	var $id_secondChapter;
      	var $id_secondVerse;
      	var $exceptionBool;
      	var $exceptionMessage;
      	var $verses;
      	var $sanitizedVerses;
      	
      	function IDWebService($verses) {
      		try {
      			$this->verses = $this->SanitizeVerses($verses);
      			$xmlurl = STYLESHEETPATH.'/bible-taxonomy/'.'Bible_min_with_IDs.xml';
      			$xml = simplexml_load_file($xmlurl);
      			$this->xml = $xml;
      			$this->ProcessVerses($this->verses);
      			
      			$this->exceptionBool = false;
      		} catch (Exception $e) {
      			$this->id_firstBook = null;
      			$this->id_firstChapter = null;
      			$this->id_firstVerse = null;
      			$this->id_secondBook = null;
      			$this->id_secondChapter = null;
      			$this->id_secondVerse = null;
      			$this->sanitizedVerses = null;
      			$this->exceptionBool = true;
      			$this->exceptionMessage = $e;
      		}
      	}
      	
      	function __destruct() { // to avoid a memory leak
      		unset($this->xml);
      		unset($this->id_firstBook);
      		unset($this->id_firstChapter);
      		unset($this->id_firstVerse);
      		unset($this->id_secondBook);
      		unset($this->id_secondChapter);
      		unset($this->id_secondVerse);
      		unset($this->exceptionBool);
      		unset($this->exceptionMessage);
      		unset($this->verses);
      		unset($this->sanitizedVerses);
      	}
      

      Step 7: Parse the heck out of it

      function ProcessVerses($verses) {
      		$splits = preg_split("/[\,\:\-]/", $verses);
      		
      		$book = $splits[0];
      		if ($this->GetNumberedBookStatus($book) == 1) {
      			$scrollnum = substr($book, 0, 1);
      			$scrollname = substr($book,1);
      			$book = $scrollnum.' '.$scrollname;
      		}
      		
      		$verseformat = $this->GetVerseFormat($verses);
      		switch ($verseformat) {
      			case "B":
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$bookid = $this->LookupBookId($this->xml, $book);
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$this->sanitizedVerses = $book;
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$bookid = $this->LookupBookId($this->xml, $book);
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      					if ($chapterid*1 >= 0) {
      						$this->id_firstChapter = $chapterid;
      						$this->sanitizedVerses = $book.' '.$chapter;
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C:V":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$verse = $splits[1];
      				$bookid = $this->LookupBookId($this->xml, $book);
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      						if ($chapterid*1 >= 0) {
      							$this->id_firstChapter = $chapterid;
      							$verseid = $this->LookupVerseId($this->xml, $book, $chapter, $verse);
      							if ($verseid*1 >= 0) {
      								$this->id_firstVerse = $verseid;
      								$this->sanitizedVerses = $book.' '.$chapter.':'.$verse;
      							} else {
      								$this->ProcessError('Verse not found');
      							}
      						} else {
      							$this->ProcessError('Chapter not found');
      						}
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C-C":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$secondchapter = $splits[1];
      				$bookid = $this->LookupBookId($this->xml, $book);
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      					$secondchapterid = $this->LookupChapterId($this->xml, $book, $secondchapter);
      					if ($chapterid*1 >= 0 && $secondchapterid*1 >= 0) {
      						$this->id_firstChapter = $chapterid;
      						$this->id_secondChapter = $secondchapterid;	
      						$this->sanitizedVerses = $book.' '.$chapter.'-'.$secondchapter;		
      					} else {
      						$this->ProcessError('Chapter(s) not found');
      					}
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C:V-V":
      			case "B C:V,V":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$verse = $splits[1];
      				$secondverse = $splits[2];
      				$bookid = $this->LookupBookId($this->xml, $book);
      			
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      					if ($chapterid*1 >= 0) {
      						$this->id_firstChapter = $chapterid;
      						$verseid = $this->LookupVerseId($this->xml, $book, $chapter, $verse);
      						$secondverseid = $this->LookupVerseId($this->xml, $book, $chapter, $secondverse);
      						if ($verseid*1 >= 0 && $secondverseid *1 >= 0) {
      							$this->id_firstVerse = $verseid;
      							$this->id_secondVerse = $secondverseid;
      							$this->sanitizedVerses = $book.' '.$chapter.':'.$verse.'-'.$secondverse;
      						} else {
      							$this->ProcessError('Verse(s) not found');
      						}
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C:V - C:V":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$verse = $splits[1];
      				$secondchapter = $splits[2];
      				$secondverse = $splits[3];
      				
      				$bookid = $this->LookupBookId($this->xml, $book);
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      					$secondchapterid = $this->LookupChapterId($this->xml, $book, $secondchapter);
      					if ($chapterid*1 >= 0) {
      						$this->id_firstChapter = $chapterid;
      						$verseid = $this->LookupVerseId($this->xml, $book, $chapter, $verse);
      						if ($verseid*1 >= 0) {
      							$this->id_firstVerse = $verseid;
      						} else {
      							$this->ProcessError('Verse not found');
      						}
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      					if ($secondchapterid*1 >= 0) {
      						$this->id_secondChapter = $secondchapterid;
      						$secondverseid = $this->LookupVerseId($this->xml, $book, $secondchapter, $secondverse);
      						if ($secondverseid*1 >= 0) {
      							$this->id_secondVerse = $secondverseid;
      							$this->sanitizedVerses = $book.' '.$chapter.':'.$verse.' - '.$secondchapter.':'.$secondverse;
      						} else {
      							$this->ProcessError('Verse not found');
      						}
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      				} else {
      					$this->ProcessError('Book not found');
      				}
      				break;
      			case "B C:V - B C:V":
      				$book = substr($book, 0, $this->GetChapterNumLoc($book));
      				$chapter = substr($splits[0], $this->GetChapterNumLoc($splits[0]));
      				$verse = $splits[1];
      				
      				if ($book == 'SongofSongs') {
      					$book = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($book == 'ActsoftheApostles') {
      					$book = 'Acts of the Apostles';
      				}
      				if ($secondbook == 'SongofSongs') {
      					$secondbook = 'Song of Songs'; // despacifying makes XML miss ID
      				} elseif($secondbook == 'ActsoftheApostles') {
      					$secondbook = 'Acts of the Apostles';
      				}
      				
      				$secondbook = $splits[2];
      				if ($this->GetNumberedBookStatus($secondbook) == 1) {
      					$scrollnum = substr($secondbook, 0, 1);
      					$scrollname = substr($secondbook,1);
      					$secondbook = $scrollnum.' '.$scrollname;
      				}
      				$secondbook = substr($secondbook, 0, $this->GetChapterNumLoc($secondbook));
      				$secondchapter = substr($splits[2], $this->GetChapterNumLoc($splits[2]));
      				$secondverse = $splits[3];
      				$bookid = $this->LookupBookId($this->xml, $book);
      				$secondbookid = $this->LookupBookId($this->xml, $secondbook);
      	
      				if ($bookid*1 >= 0) {
      					$this->id_firstBook = $bookid;
      					$chapterid = $this->LookupChapterId($this->xml, $book, $chapter);
      					$secondchapterid = $this->LookupChapterId($this->xml, $secondbook, $secondchapter);
      					if ($chapterid*1 >= 0) {
      						$this->id_firstChapter = $chapterid;
      						$verseid = $this->LookupVerseId($this->xml, $book, $chapter, $verse);
      						if ($verseid*1 >= 0) {
      							$this->id_firstVerse = $verseid;
      						} else {
      							$this->ProcessError('Verse not found');
      						}
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      				}
      				if ($secondbookid*1 >= 0) {
      					$this->id_secondBook = $secondbookid;
      					if ($secondchapterid*1 >= 0) {
      						$this->id_secondChapter = $secondchapterid;
      						$secondverseid = $this->LookupVerseId($this->xml, $secondbook, $secondchapter, $secondverse);
      						if ($secondverseid*1 >= 0) {
      							$this->id_secondVerse = $secondverseid;
      							$this->sanitizedVerses = $book.' '.$chapter.':'.$verse.' - '.$secondbook.' '.$secondchapter.':'.$secondverse;
      
      						} else {
      							$this->ProcessError('Verse not found');
      						}
      					} else {
      						$this->ProcessError('Chapter not found');
      					}
      				}						
      				break;
      			case "Error: Unknown Format (Book should be followed by Chapter number)":
      			case "Error: Unknown Format (Are you missing a chapter?)":
      			case "Error: Unknown Format (Are you trying to reference more than one verse?  We suggest Book Chapter:Verse - Verse format)":
      				$this->ProcessError($verseformat);
      				break;
      			default:
      				$this->ProcessError("Error: unknown Format");
      		}
      	}
      

      Not elegant, but it works … :)

      ]]> http://blog.adamfrieberg.com/2011/02/24/bible-taxonomy-parsing-input/feed/ 0 Bible Taxonomy – User Interface Look http://blog.adamfrieberg.com/2011/02/23/bible-taxonomy-user-interface-look/ http://blog.adamfrieberg.com/2011/02/23/bible-taxonomy-user-interface-look/#respond Wed, 23 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=1194 BibleTax_Featured_3Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/ The Bible’s BIG. 32341 elements in the XML file, to be exact. The WordPress custom metabox spaces are pretty small. […]]]> BibleTax_Featured_3
      Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

      The Bible’s BIG. 32341 elements in the XML file, to be exact.

      The WordPress custom metabox spaces are pretty small. I started out wanting to put this in the side panel on the Admin screen. How do I fit that many elements into a 300px width and not have the page go miles and miles deep?

      Javascript was the easy solution — but not so-easy was figuring out what UI tools I could use within the JS. I started with tables, bot those became clunky and inconsisted very quickly. Plus, they broke horribly. I wanted it so that if the user shrunk their window < 800px wide then they'd still be able to have the tool degrade gracefully (and maybe even still work). Tables were shooting off the edges and messing up the spacing. Fluid web design != tables; and in this case, it didn't even = div tags. I did it with unordered lists (ul) and list-items (li) carefully controlled by the CSS.

      Here’s the CSS to get the elements consisten widths, heights, and to get them to reset and space beautifully (and wrap so they didn’t look like a puzzle):

      #scripture_holder {
      	margin: 10px;
      }
      
      #bible_hierarchy
      { 
      	width: 300px;
      	margin: 10px;
      	display: block;
      	font-family: 'Lucida Grande', Verdana, Arial, 'Bitstream Vera Sans', sans-serif;
      	font-size: 12px;
      	line-height: 16px;
      	
      }
      
      #bible_hierarchy a {
      	color: #000;
      	text-decoration: none;
      }
      
      #bible_hierarchy ul
      {
      	padding: 0;
      	margin: 0;
      	
      }
      #bible_hierarchy ul li 
      { 
      	padding: 0; 
      	margin: 0; 
      	border: 1px solid #FFF; 
      	text-align: center;
      	display: inline-block;
      	word-spacing: normal;
      	
      }
      #bible_hierarchy ul a 
      { 
      	padding: 5px; 
      	display: block; 
      }
      
      #bible_hierarchy li.book {
      	background: #c8c8c8;
      	width: 45px;
      }
      
      #bible_hierarchy li.chapter {
      	background: #dfdfdf;
      	width: 35px;
      }
      
      #bible_hierarchy li.clickedbook {
      	background: #dfdfdf;
      	width: 280px;
      }
      
      #bible_hierarchy li.clickedbook ul { /* for space between name of book and chapter <ul> */
      	margin-top: 5px;
      }
      
      #bible_hierarchy li.clickedchapter {
      	background: #FFF;
      	width: 257px;
      }
      
      #bible_hierarchy li.verse {
      	background: #FFF;
      	border: #EEE 1px solid;
      	width: 25px;
      }
      
      #bible_hierarchy li.verse_selected {
      	background: #333;
      	width: 25px;
      }
      
      #bible_hierarchy li.verse_selected a {
      	color: #FFF;
      }
      
      
      
      #verserange {
      	width: 190px;
      	margin-left: 5px;
      }
      
      #btnGoToScripture {
      	margin-left: 5px;
      }
      
      #bible-selected-verses {
      	min-width: 200px;
      	max-width: 350px;
      	margin: 10px;
      }
      
      #bible-selected-verses .verse_range {
      	width: 150px;
      }
      
      ]]>
      http://blog.adamfrieberg.com/2011/02/23/bible-taxonomy-user-interface-look/feed/ 0
      Bible Taxonomy – User Interface Data http://blog.adamfrieberg.com/2011/02/22/bible-taxonomy-user-interface-data/ http://blog.adamfrieberg.com/2011/02/22/bible-taxonomy-user-interface-data/#comments Tue, 22 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=1195 BibleTax_Featured_2Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/ So the CSS was the eas(ier) part. Then came the Javascript. It had to be snappy and non-clunky. I started […]]]> BibleTax_Featured_2
      Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

      So the CSS was the eas(ier) part. Then came the Javascript. It had to be snappy and non-clunky.

      I started out trying to parse the XML with my own logic. Not a good idea. I used the JQuery framework to get it working. Here’s the resulting JS code snippets with commentary:

      1. Declarations + init run:
      numColumns limits the default books per row. Others variables are either data objects or placeholders with a larger scope so they can be accessed across methods (or as statics outside of the iterators)

      var bibleXML;
      var clickedbook;
      var clickedchapter;
      var clickedverse;
      
      var start_green;
      var end_red;
      
      var serviceXML;
      
      var numColumns = 6;
      var numRows = 0;
      var chapterNumRows = 0;
      var verseNumRows = 0;
      
      
      jQuery.ajax({ type: &quot;GET&quot;,
      	url: &quot;http://localhost:8888/DiscipleShare-test/wp-content/themes/discipleshare/bible-taxonomy/Bible_min_2.xml&quot;,
      	dataType: &quot;xml&quot;,
      	success:
      		parseBooks
      });
      

      2. parseXML (called from JQuery successful load of file): this is the framework for the whole bible. It visually splits the books into OT and NT. It also requires no hidden fields to hold values since the JS can hold the tags’ ids.

      function parseBooks(xml)
      {
          bibleXML = xml;
      	divoutput = document.getElementById("bible_hierarchy");
      	var ul = document.createElement("ul");
      	ul.setAttribute("id", "ulOTBooks");
      	//find every book and print the english abbreviation
          jQuery(xml).find("book").each(function()
          {
            if (jQuery(this).attr("abbrev-en")!="Mt") {
            	var li = document.createElement("li");
            	li.setAttribute("class", "book");
            	var a = document.createElement("a");
            	a.setAttribute("id", "book_"+jQuery(this).attr("abbrev-en"));
            	a.setAttribute("onClick", "Javascript: BookClick(this, id);");
            	a.innerHTML = jQuery(this).attr("abbrev-en");
            	li.appendChild(a);
            	ul.appendChild(li);
            }
            else { // start the NT
            	// add previous ul
            	divoutput.appendChild(ul);
            	
            	ul = document.createElement("ul");
            	ul.setAttribute("id", "ulNTBooks");
            	
            	var li = document.createElement("li");
            	li.setAttribute("class", "book");
            	var a = document.createElement("a");
            	a.setAttribute("id", "book_"+jQuery(this).attr("abbrev-en"));
            	a.setAttribute("onClick", "Javascript: BookClick(this, id);");
            	a.innerHTML = jQuery(this).attr("abbrev-en");
            	li.appendChild(a);
            	ul.appendChild(li);
            }
          });
          
          
          divoutput.appendChild(ul);
      
      	// Load for current post -- doing it this way because couldn't find a better function to hook into
      	getBibleVerseCollectionsForPost();
      }
      

      3. Handle when clicks happen. Books, Chapters, Verses — the logic of the methods is similar, but they pull different data from the XML file to create the UI elements and clear out the now irrelevant ones.

      function BookClick(e, linkID) {
      	ResetClickedBook();
      	var sender = (e && e.target) || (window.event && window.event.srcElement);
      	if (sender == null || sender == undefined) {
      		sender = document.getElementById(linkID);
      	}
      	var abbrev = sender.id.substring(5);
      	var book = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr("name");
      	clickedbook = sender.id;
      	
      	sender.parentNode.setAttribute("class", "clickedbook"); // parent node is <li> of <a>
      	ToggleFullBookName(sender.id);
      	
      	selected = sender;
      	UpdateVersesInput(sender, "book");
      	
      	var chaptercount = -1;		
      	chaptercount = jQuery(bibleXML).find('book[name='+book+']').attr("chaptercount");
      	
      	var ul = document.createElement("ul");
      	ul.setAttribute("id", "chapters_"+book);
      	
      	for (var i = 1; i < (chaptercount*1)+1; i++) { // *1 to explicitly type as numeric
            	 var li = document.createElement("li");
            	 li.setAttribute("class", "chapter");
            	 var a = document.createElement("a");
            	 a.setAttribute("id", "chapter_"+i);
            	 a.setAttribute("onClick", "JavaScript: ChapterClick(this, id);");
            	 a.setAttribute("title", book);
            	 a.innerHTML = i;
            	 li.appendChild(a);
            	 ul.appendChild(li);
      	}
      	sender.parentNode.appendChild(ul);
      }
      
      function ChapterClick(e, linkID) {
      	ResetClickedChapter();
      	
      	var sender = (e && e.target) || (window.event && window.event.srcElement);
      	if (sender == null || sender == undefined) {
      		sender = document.getElementById(linkID);
      	}
      	var book = sender.title;
      	var chapter = sender.id.substring(8);
      	clickedchapter = sender.id;
      	
      	sender.parentNode.setAttribute("class", "clickedchapter");
      	ToggleFullChapterName(sender.id);
      	
      	selected = sender;
      	UpdateVersesInput(sender, "chapter");
      			
      	var versecount = -1;
      	versecount = jQuery(bibleXML).find('book[name='+book+']').find('chapter[name='+chapter+']').attr("versecount");
      	
      	var ul = document.createElement("ul");
      	
      	for (var i = 1; i < (versecount*1)+1; i++) { // *1 to force as numeric
      		 var li = document.createElement("li");
            	 li.setAttribute("class", "verse");
            	 var a = document.createElement("a");
            	 a.setAttribute("id", "verse_"+i);
            	 a.setAttribute("onClick", "JavaScript: VerseClick(this, id);");
            	 a.setAttribute("title", book);
            	 a.innerHTML = i;
            	 li.appendChild(a);
            	 ul.appendChild(li);		
      	}
      	sender.parentNode.appendChild(ul);
      }
      
      function VerseClick(e, linkID) {
      	var sender = (e && e.target) || (window.event && window.event.srcElement);
      	if (sender == null || sender == undefined) {
      		sender = document.getElementById(linkID);
      	}
      	var book = clickedbook;
      	var chapter = clickedchapter;
      	var versenum = sender.id.substring(6);
      	
      	sender.parentNode.setAttribute("class", "verse_selected");
      	selected = sender;
      	
      	if (clickedverse != null) {
      		UnselectClickedVerse();
      	}
      	clickedverse = sender.id;
      	
      	UpdateVersesInput(sender, "verse");
      }
      

      4. The small details make the biggest difference. Little details, like toggling the book name from the abbreviation (and toggling it back) make for a better user experience. I also have it update a text field that’s used to collect and input the data for the WordPress backend. Here’re the little details in no particular order:

      function ToggleFullBookName(link)
      {	
      	var a = document.getElementById(link);
      	var abbrev = link.substring(5);
      	if (abbrev == a.innerHTML) { // need to display full name
      		var fullname = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr(&quot;name&quot;);
      		a.innerHTML = fullname;
      	} else { // need to shorten to just abbrev
      		a.innerHTML = abbrev;
      	}
      }	
      
      function ToggleFullChapterName(link)
      {	
      	var a = document.getElementById(link);
      	var chapnum = link.substring(8);
      	if (chapnum == a.innerHTML) { // need to display full chapter label
      		var fullname = &quot;Chapter &quot;+chapnum;
      		a.innerHTML = fullname;
      	} else { // need to shorten to just chapnum
      		a.innerHTML = chapnum;
      	}
      }	
      
      function GetBookNameByElementID(link) {
      	var a = document.getElementById(link);
      	var abbrev = link.substring(5);
      	var fullname = jQuery(bibleXML).find('book[abbrev-en='+abbrev+']').attr(&quot;name&quot;);
      	return fullname;
      }
      
      function ResetClickedBook() {
      	if (clickedbook != null) {
      		var previous = document.getElementById(clickedbook);
      		previous.parentNode.setAttribute(&quot;class&quot;, &quot;book&quot;);
      		while(previous.parentNode.childNodes.length &gt; 1) {
      			previous.parentNode.removeChild(previous.parentNode.lastChild);
      		}
      		ToggleFullBookName(clickedbook);
      		
      		clickedchapter = null; // reset that too so next clickedbook doesn't inherit chpt click
      		start_green = null;
      		end_red = null;
      	}
      }
      
      function ResetClickedChapter() {
      	if (clickedchapter != null) {
      		var previous = document.getElementById(clickedchapter);
      		previous.parentNode.setAttribute(&quot;class&quot;, &quot;chapter&quot;);
      		while(previous.parentNode.childNodes.length &gt; 1) {
      			previous.parentNode.removeChild(previous.parentNode.lastChild);
      		}
      		ToggleFullChapterName(clickedchapter);
      	}
      }
      
      function ToggleSelector() {
      	var selector = document.getElementById(&quot;bible_hierarchy&quot;);
      	if (selector.getAttribute(&quot;style&quot;) == &quot;display: none;&quot;) {
      		selector.setAttribute(&quot;style&quot;, &quot;&quot;);
      	} else {
      		selector.setAttribute(&quot;style&quot;, &quot;display: none;&quot;);
      	}
      }
      
      function UpdateVersesInput(sender, rangeLocation) {
      	var input = document.getElementById("verserange");
      	
      	var ref;
      				
      	if (rangeLocation == "book") {
      		var book = jQuery(bibleXML).find('book[abbrev-en='+sender.id.substring(5)+']').attr("name");
      		ref = book;
      		input.value = ref;
      		
      	} else if (rangeLocation == "chapter") { // appending values to input, not replacing them
      		var book = jQuery(bibleXML).find('book[abbrev-en='+clickedbook.substring(5)+']').attr("name");
      		var chapter = sender.id.substring(8);
      		ref = book+" "+chapter;
      		input.value = ref;
      	}
      	else if (rangeLocation == "verse") {
      		var book = jQuery(bibleXML).find('book[abbrev-en='+clickedbook.substring(5)+']').attr("name");
      		var chapter = clickedchapter.substring(8);
      		var verse = sender.id.substring(6);
      	
      		ref = book+" "+chapter+":"+verse;
      		
      		input.value = ref;
      	}
      	
      }
      
      ]]>
      http://blog.adamfrieberg.com/2011/02/22/bible-taxonomy-user-interface-data/feed/ 1
      Bible Taxonomy – Start Off Right (Minimized XML) http://blog.adamfrieberg.com/2011/02/21/bible-taxonomy-start-off-right-minimized-xml/ http://blog.adamfrieberg.com/2011/02/21/bible-taxonomy-start-off-right-minimized-xml/#respond Mon, 21 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=1193 BibleTax_Featured_1Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/ To start, I looked for data objects that already existed for the bible on the internet. I looked at using […]]]> BibleTax_Featured_1
      Note: This post is series explaining how I created the new Bible Taxonomy tool as seen on DiscipleShare. To see it in action, or to find great, free curriculum to use in churches, visit: http://www.discipleshare.net/

      To start, I looked for data objects that already existed for the bible on the internet. I looked at using existing APIs for popular online Bible services, but didn’t find any that offered the backend database support I’d need for relational tables in MySQL.

      I googled “NRSV xml” and found some good stuff, including a file that’s no doubt a copyright violation and might be taken down at any time.

      (If starting over, I’d use the SBL GNT or a KJV version that’s now public domain)

      The first bottleneck I stumbled on when building the plugin was the bible structure. How could I get an XML file that was currently > 5000 KB to the user without them leaving the site first?

      Answer 1: Strip the verses out.

      The second bottleneck: once I’d stripped the verses (the bulk of the data) out of the file, was there a way to compress it even further?

      Answer 2: All of the data needed are book names + number of chapters + number of verses per chapter. At that point, the XML resembles a nested array of strings + integers — at least more than it resembles a dictionary or catalog of multi-layered data objects.

      I was enough of a newbie at data objects in PHP, so I chose to use C# and the Visual Studio IDE so I could debug and troubleshoot quicker.

      Here’s the resulting file (just kept it all in solution’s default doc: Program.cs)

      using System;
      using System.Collections.Generic;
      using System.Collections.Concurrent;
      using System.Linq;
      using System.Text;
      using System.Xml;
      using System.IO;
      using System.Xml.Linq;
      using System.Dynamic;
      using System.Reflection;
      
      namespace BibleXmlStructurizer
      {
          class Program
          {
      
              static void Main(string[] args)
              {
                  elementid = 0;
      			
      			string filepath = &quot;&quot;;
                  XDocument doc = XDocument.Load(filepath);
      
                  // load Bible
                  XElement bible = doc.Descendants(&quot;bible&quot;).FirstOrDefault();
                  
                  //oldtestament for a tag that helps the JS parse it into separate visual elements
                  bool oldtestament = true;
      
      			// Generic to tell what types of data for keys, values
                  Dictionary&lt;string, string&gt; bookDictionary = new Dictionary&lt;string, string&gt;();
      
                  foreach (var book in bible.Elements(&quot;book&quot;))
                  {
                      // Get name from the attribute value
                      var name = book.Attribute(&quot;name&quot;).Value;
                      
                      // calls method to get XML data
                      string bookXml = GetBookXML(book, oldtestament); 
                      
                      // Add to dictionary with name as the key.
                      bookDictionary[name] = bookXml;
                  }
      
      			// Output
                  StringBuilder sb = new StringBuilder();
                  sb.Append(&quot;&lt;bible&gt;&quot;);
                  foreach (var book in bookDictionary)
                  {
                      sb.Append(book.Value);
                  }
                  sb.Append(&quot;&lt;/bible&gt;&quot;);
      
                  System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
                  byte[] txt = encoding.GetBytes(sb.ToString());
                  outputfilepath = &quot;&quot;;
                  FileStream fs = new FileStream(outputfilepath, FileMode.Create, FileAccess.ReadWrite);
                  BinaryWriter bw = new BinaryWriter(fs);
                  bw.Write(txt);
                  bw.Close();
                  
              }
              
              static string GetBookXML(XElement book, bool oldtestament)
              {
                  var sb = new StringBuilder();
                  XmlTextWriter output = new XmlTextWriter(new StringWriter(sb));
                  output.WriteStartElement(&quot;book&quot;);
                  
                  // splits attributes based on &quot; character ; for newbies, \ is escape character
                  string splitter = &quot;\&quot;&quot;;
                  string[] splitAttribute;
        
                  splitAttribute = book.Attribute(&quot;name&quot;).ToString().Split(splitter.ToCharArray());
                  output.WriteAttributeString(&quot;name&quot;, splitAttribute[1]);
      
                  if (oldtestament)
                      output.WriteAttributeString(&quot;section&quot;, &quot;OT&quot;);
                  else
                      output.WriteAttributeString(&quot;section&quot;, &quot;NT&quot;);
      
                  int chaptercount = book.Elements(&quot;chapter&quot;).Count();
      			output.WriteAttributeString(&quot;chaptercount&quot;, chaptercount.ToString());
      
                  foreach (var chapter in book.Elements(&quot;chapter&quot;))
                  {
                      int versecount = 0;
      
                      output.WriteStartElement(&quot;chapter&quot;); // &lt;chapter&gt;
                      splitAttribute = chapter.Attribute(&quot;name&quot;).ToString().Split(splitter.ToCharArray());
                      output.WriteAttributeString(&quot;name&quot;, splitAttribute[1]);
      
                      output.WriteAttributeString(&quot;id&quot;, elementid.ToString());
      
                      foreach (var verse in chapter.Elements(&quot;verse&quot;))
                      {
                          versecount++;
      
                          output.WriteStartElement(&quot;verse&quot;); // &lt;verse&gt;
                          output.WriteAttributeString(&quot;name&quot;, versecount.ToString());
                          output.WriteAttributeString(&quot;id&quot;, elementid.ToString());
                          output.WriteString(verse.Value.ToString());
                          output.WriteEndElement(); // &lt;/verse&gt;
      
                      }
      
                      output.WriteEndElement(); // &lt;/chapter&gt;
                  }
      
                  if (book.Attribute(&quot;name&quot;).ToString() == &quot;name=\&quot;Malachi\&quot;&quot;)
                  {
                      oldtestament = false;
                  }
      
                  
                  output.WriteEndElement();
                  output.Close();
      
                  return sb.ToString();
              }
          }
      }
      

      The third bottleneck: for the database structure, I needed easier references to each bible element (so that I didn’t have to keep parsing out on every GET or PUT to figure out what it referenced).

      Answer 3: add this to the class

       public static int elementid;

      and then for every element write it out as an attribute:

      output.WriteAttributeString(&quot;id&quot;, elementid.ToString());

      then, after every time you’ve written to the XML file, add incrementing code

      element++;

      While it’s not the most elegant code, it got the job done — and it gave me the resulting lightweight XML file I needed.

      Full Bible: >5000 KB
      Bible_min_with_IDs: 983 KB
      Bible_min: 49KB

      49 KB is acceptable to store in a user’s browser cache so they have a good user experience every time. Also, I’m not too worried about the Bible_min_with_IDs because 983 KB will be loaded locally by the PHP code — not too big of an issue.

      Next up: Javascript + CSS to make this look pretty.

      ]]>
      http://blog.adamfrieberg.com/2011/02/21/bible-taxonomy-start-off-right-minimized-xml/feed/ 0
      Launch – discipleshare.net http://blog.adamfrieberg.com/2011/02/10/launch-discipleshare-net/ http://blog.adamfrieberg.com/2011/02/10/launch-discipleshare-net/#comments Thu, 10 Feb 2011 16:00:00 +0000 http://blog.adamfrieberg.com/?p=850 DS_Evolution_featured_imageSo I dropped the ball. In announcing all the sites I rolled out and launched for different organizations in the fall, I forgot to tell about my pet project. DiscipleShare (click on the pictures and zoom to see full-resolution screen shots of DiscipleShare’s evolution) Way back when … (2003) It originally started as “DisciplesDocs” when […]]]> DS_Evolution_featured_image

      So I dropped the ball. In announcing all the sites I rolled out and launched for different organizations in the fall, I forgot to tell about my pet project.

      DiscipleShare

      (click on the pictures and zoom to see full-resolution screen shots of DiscipleShare’s evolution)

      Way back when … (2003)


      It originally started as “DisciplesDocs” when we launched it in January, 2003. The we was Bill Spangler-Dunning and I. I was in the middle of my first year of college, doing this project while taking computer science and religion classes at TCU. Bill was wanting a way to distribute camp curriculum to counselors. The idea grew: what about all of the other curriculum that leaders around our region were writing? Couldn’t that be shared too? The site was static HTML pages linking to the curriculum. We also had it link to author profiles — but everything had to be written into the HTML by hand. (I don’t miss those days at all).

      Here’s what the profile page looked like. Sweet table action, huh?

      The rest of the site wasn’t much better. Here’s the loop (if only I’d known how to write a loop at that point.

      And notice how strict and legalistic the language is for the submission procedure. Ugh.

      A Framework’s Beginning (2005)

      I did a computer programming internship at a firm in Seattle in the summer of 2003. There, I learned C# and ASP.NET language techniques. During summer 2004 I was taking a Theological German course in New England and had some extra time to keep dreaming about other features people would want it to include. What if camps could share pictures, videos, blogs – and network with other camps going on at the same time around the country? (Remember, this was before Facebook). What if the site was built on a relational database model where users could enter the curriculum themselves? That summer (2004) I developed the prototype. In August 2005, we deployed the new version on a server I managed at TCU (aristotle.rel.tcu.edu). At that point, it was just the curriculum sharing.

      Then, during summer 2006, between my time at TCU and starting my MDiv at UChicago, I developed the camp/conference section.

      While DiscipleShare was about curriculum sharing at it’s core — it expanded to include those features that would eventually become commonplace in social media. Here’re the different layout options camps could choose from (depending on what features they wanted):

      And here’s what it looked like for the camps/conferences. This page was for the Servant Leadership Network retreat, led at Eureka College in July 2005, for different regional youth councils around the midwest.

      Transition to Kentucky

      During my first year at Chicago, our access to and reliance on my TCU server left us vulnerable to the site going down (sometimes for over a week at a time) without having someone on-site to restart and reset the server.

      So we transitioned the site to a server at the Christian Churches in Kentucky. Michael Davison, on Kentucky’s regional staff at the time, found a spare box we could use. A fun, weekend-long trip down I-65 and DiscipleShare was back online at its new home.

      Ironically, the same thing happened during summer 2009. When Michael moved, our access to that server went. Thankfully I had a backup of the install / database before the server went down.

      But DiscipleShare at that point was old and clunky. The open source (software) movement had helped revolutionize internet technology. For DiscipleShare to keep trudging along on an open source curriculum movement, it needed a better framework.

      On to WordPress (2010)

      In January 2010, I started as Regional Minister for Communication for the Christian Church in the Upper MIdwest. Two months before that, I joined the Disciples Youth Ministry Network (DYMN) planning team. DiscipleShare was going to become DYMN’s as part of a grant they’d received the previous year. My regional position structured some of my time to transition DiscipleShare over to WordPress, an open source content management system. (We also used the Genesis Theme Framework so I didn’t have to re-create the wheel, yet again …)

      I “threw the baby out the with bathwater” for the camp/conference sharing. Frankly, Facebook does a better job of that than we ever could. But the curriculum sharing is working better than it ever had before. We also moved it to a cloud-hosting solution and our own domain name. Score!

      Here’s the author view:

      Our WordPress solution was everything we’d dreamed about from the beginning. And it’s just beginning …

      In the coming months you’ll read on this blog about the new features we’re rolling out — including another very soon.

      ]]>
      http://blog.adamfrieberg.com/2011/02/10/launch-discipleshare-net/feed/ 2
      Every ms counts http://blog.adamfrieberg.com/2010/12/23/every-ms-counts/ http://blog.adamfrieberg.com/2010/12/23/every-ms-counts/#respond Thu, 23 Dec 2010 20:52:35 +0000 http://blog.adamfrieberg.com/?p=1122 JSON-Bible-ObjectI’m trying to teach myself this workflow and how to get all of the code to interact. The red connection and path are what I just stopped on.  The JSON is working like a charm.  In creating the PHP Web Service, I ran into a bit of an obstacle with large sections of scripture verses. […]]]> JSON-Bible-Object

      I’m trying to teach myself this workflow and how to get all of the code to interact.

      The red connection and path are what I just stopped on.  The JSON is working like a charm.  In creating the PHP Web Service, I ran into a bit of an obstacle with large sections of scripture verses.  The XPath query on the Full XML File was getting taxed pretty hard when iterating through the bible’s data structure with a do-while loop.  Returning over 300 verses and here’s what the do-while loop looked like:

      Do-While Loop

      The do-while loop was iterating through the xml nodes linearly (since the bounds are known by the bible verse locations).  It had to do a switch statement on the node type to determine how to output the JSON.  Needless to say, it was a lot of operations for every node.

      Instead, I changed it to target the XPath so it iterates with the exact path, already knowing every node type.  The difference was dramatic:

      For Loop

      My next step is to finish connecting the PHP code to the database (green line).  Then, once I know it works, I’ll put it into a WordPress plugin.  Hopefully it will be live on DiscipleShare before 2011!

      ]]>
      http://blog.adamfrieberg.com/2010/12/23/every-ms-counts/feed/ 0
      Custom Bible Taxonomy – Fail http://blog.adamfrieberg.com/2010/12/04/custom-bible-taxonomy-fail/ http://blog.adamfrieberg.com/2010/12/04/custom-bible-taxonomy-fail/#respond Sun, 05 Dec 2010 02:34:05 +0000 http://blog.adamfrieberg.com/?p=1096 bible_taxonomy_fail_1_thumbI’ve spent a couple weeks’ free time building a custom Bible verse tagger for curriculum on DiscipleShare. It’s close, but not close enough that I can post the code for other developers to use. Right now I’m keeping it out of the WordPress structure.  It’ll eventually be its own taxonomy plugin for a WordPress database […]]]> bible_taxonomy_fail_1_thumb

      I’ve spent a couple weeks’ free time building a custom Bible verse tagger for curriculum on DiscipleShare.

      It’s close, but not close enough that I can post the code for other developers to use.

      Right now I’m keeping it out of the WordPress structure.  It’ll eventually be its own taxonomy plugin for a WordPress database — so users can tag with scripture verses and have them database-searchable.  But for now, it’s just a simple Javascript/XML parser (with some wicked CSS to make it great amongst all web browsers).

      As you can probably guess, the “green” box is to set the starting verse; the “red” box is to set the ending verse.  Yep — there’s a problem in the logic.  Two steps forward, one step back.  :)

      More to come.

      ]]>
      http://blog.adamfrieberg.com/2010/12/04/custom-bible-taxonomy-fail/feed/ 0
      Finally! Feeds in Facebook Notes http://blog.adamfrieberg.com/2010/11/20/finally-feeds-in-facebook-notes/ http://blog.adamfrieberg.com/2010/11/20/finally-feeds-in-facebook-notes/#respond Sun, 21 Nov 2010 02:48:35 +0000 http://blog.adamfrieberg.com/?p=1069 Feedburner_FacebookI thought I’d never solve this.  I was event tempted to use a 3rd-party app in Facebook to get this solved.  For weeks I’ve run into errors with the Facebook notes importing tool. The gist of the tool is simple: give us your blog address and we’ll import any new content and sent traffic your […]]]> Feedburner_Facebook

      I thought I’d never solve this.  I was event tempted to use a 3rd-party app in Facebook to get this solved.  For weeks I’ve run into errors with the Facebook notes importing tool.

      The gist of the tool is simple: give us your blog address and we’ll import any new content and sent traffic your way.

      For my blog, it’s not a BIG deal (I don’t have many readers).

      But for my regional position, this is huge!  Users on Facebook can see anything added to the site without remembering to check the site for updates — or without needing to subscribe to the RSS feed.

      So here’s the trick to getting Facebook to recognize your RSS feed from WordPress blogs:  act like protocols don’t exist.  Don’t put an address (either from the blog or the feed) where it begins with anything before the //.

      Good luck!

      ]]>
      http://blog.adamfrieberg.com/2010/11/20/finally-feeds-in-facebook-notes/feed/ 0