Philadelphia Reflections

The musings of a physician who has served the community for over six decades

0 Volumes

No volumes are associated with this topic

Programming;GRF4

Program notes by GRF4

New Topic CONTENTS

SQL To Exclude A List Of Items

Let's say you have a table "Primary" that contains an "Email" field.

You would like to select all the email addresses in Primary except for the list of email addresses in the Email field in a table "Exclude".

This SQL will exclude the emails in "Primary" based on those contained in "Exclude".

SELECT * FROM Primary WHERE ((Primary.Email) Not In (SELECT Email FROM Exclude))

mysql_insert_assoc

Function to make inserting new rows into a database table easier (and safe because quote_smart logic is included inline)

thanks to R. Bradley @ php.net; I have fixed a number of bugs and added quote_smart functionality

My own contribution to php.net is here: george at georgefisher dot com

<?php
function mysql_insert_assoc ($my_table, $my_array) {
   
//
// Insert values into a MySQL database
// Includes quote_smart code to foil SQL Injection
//
// A call to this function of:
//
//  $val1 = "foobar";
//  $val2 = 495;
//  mysql_insert_assoc("tablename", array(col1=>$val1, col2=>$val2, col3=>"val3", col4=>720));
//
// Sends the following query:
//  INSERT INTO tablename (col1, col2, col3, col4) values ('foobar', 495, 'val3', 720)
//
 
    global $db_link;
    
    // Find all the keys (column names) from the array $my_array
    $columns = array_keys($my_array);

    // Find all the values from the array $my_array
    $values = array_values($my_array);
       
    // quote_smart the values
    $values_number = count($values);
    for ($i = 0; $i < $values_number; $i++)
      {
      $value = $values[$i];
      if (get_magic_quotes_gpc()) { $value = stripslashes($value); }
      if (!is_numeric($value))    { $value = "'" . mysql_real_escape_string($value, $db_link) . "'"; }
      $values[$i] = $value;
      }
         
    // Compose the query
    $sql = "INSERT INTO $my_table ";

    // create comma-separated string of column names, enclosed in parentheses
    $sql .= "(" . implode(", ", $columns) . ")";
    $sql .= " values ";

    // create comma-separated string of values, enclosed in parentheses
    $sql .= "(" . implode(", ", $values) . ")";
       
    $result = @mysql_query ($sql) 
              OR die ("<br />\n<span style=\"color:red\">Query: $sql UNsuccessful :</span> " . mysql_error() . "\n<br />");

    return ($result) ? true : false;
}
?>

mysql_update_assoc is a similar function that updates existing records.

Also thanks to http://centricle.com/tools/html-entities/ for encoding

DHTML, PHP and MySQL References

Start with

DHTML and CSS for the World Wide Web:
Visual QuickStart Guide
by Jason Cranford Teague

DHTML = HTML + CSS + DOM + JavaScript

This book will get you up and running quickly with client-side programming.

Then you need to learn server-side programming.

PHP is an open source server-side scripting language that is easy to learn and very powerful. MySQL is the same ... open source relational database.

The text book for these technologies is

PHP and MySQL for Dynamic Web Sites:
Visual QuickPro Guide
by Larry Ullman

Master those two books and you'll be creating very powerful scripts on both the client and server side that produce dynamic and elegant results.

While you're in the process of doing this, you will constantly need to reference manuals for syntax, functions, etc. There are many, but two will suffice for 90% of what you need:

PHP Manual

W3 Schools

MySQL server has gone away

MySQL timeout? Probably a new error after years of working perfectly, resulting from an ISP change which they will neither acknowledge nor fix. Sound familiar?

Charming people.

Try this:

$db_link = @mysql_connect(DB_HOST, DB_USER, DB_PSWD,'',MYSQL_CLIENT_INTERACTIVE);

... instead of what you used to do:

$db_link = @mysql_connect(DB_HOST, DB_USER, DB_PSWD);

mysql_update_assoc

Function to make updating rows in a database table easier (and safe: quote_smart logic is implented inline).

<?php
function mysql_update_assoc ($my_table, $my_array, $where_conditions) {

//
// Update values in a MySQL database table
// Includes quote_smart code to foil SQL Injection
//
// A call to this function of:
//
//  $val1 = "foobar";
//  $val2 = 495;
//  mysql_update_assoc("tablename", array(col1=>$val1, col2=>$val2), array(table_key=>52, age=>"old"));
//
// Sends the following query:
//  UPDATE tablename SET col1 = 'foobar', col2 = 495 WHERE table_key = 52 AND age = 'old'
// 
//                  -- and --
//
//  $table_name = "tablename";
//  mysql_update_assoc($table_name, array(col1=>$val1, col2=>$val2), array(table_key=>52));
//
// Sends this:
//  UPDATE tablename SET col1 = 'foobar', col2 = 495 WHERE table_key = 52
//
// Note: the WHERE clause is always "=" and always AND
//

global $db_link;

$sql = "UPDATE $my_table SET ";

// quote_smart the data values and create a comma-separated string of column_name = value
foreach ($my_array as $key => $value)
  {
  if (get_magic_quotes_gpc()) { $value = stripslashes($value); }
  if (!is_numeric($value))    { $value = "'" . mysql_real_escape_string($value, $db_link) . "'"; }
  $sql .= "$key = $value, ";
  }
$sql = substr($sql, 0, -2);  // remove trailing ", "

// quote_smart the conditional values and create a comma-separated string of column_name = value AND
$conditional_pairs = NULL;
foreach ($where_conditions as $key => $value)
  {
  if (get_magic_quotes_gpc()) { $value = stripslashes($value); }
  if (!is_numeric($value))    { $value = "'" . mysql_real_escape_string($value, $db_link) . "'"; }
  $conditional_pairs .= "$key = $value AND ";
  }
$conditional_pairs = substr($conditional_pairs, 0, -5);  // remove trailing " AND "

$sql .= " WHERE $conditional_pairs";

$result = @mysql_query ($sql) 
          OR die ("<br />\n<span style=\"color:red\">Query: $sql UNsuccessful :</span> " . mysql_error() . "\n<br />");

return ($result) ? true : false;
}
?>

mysql_insert_assoc is a similar function that adds new records.

Thanks to http://www.primitivetype.com/resources/htmlentities.php for encoding

HTML Anchor without an HREF?

Sometimes I want to execute a JavaScript function when a user clicks a link, but nothing else.

If I omit the href entirely, the cursor doesn't change and some browsers don't recognize the text as a link:

<a onclick="function();">

If I include the pound sign, which seems to be a very popular trick, I get sent to the top of the current page, which messes up both History and the backspace button; to say nothing of the fact that I don't want to jump to the top of the page:

<a href="#" onclick="function();">

Including the function in the href and omitting the onclick seems to be the answer to my specific problem:

<a href="javascript: function();">

Once again, my thanks to http://centricle.com/tools/html-entities/ for HTML encoding.

PHP script to display Google PageRank

Like so many things on this website, the code to find the Google PageRank of the pages was lifted from someone else's work. This work is particularly praiseworthy because it worked exactly as described the minute I got it implemented.

See PHP script to display Google PageRank


pagerank.php

<?php
define('GOOGLE_MAGIC', 0xE6359A60);
class pageRank{
var $pr; 
 function zeroFill($a, $b){
 $z = hexdec(80000000);
  if ($z & $a){
   $a = ($a>>1);
   $a &= (~$z);
   $a |= 0x40000000;
   $a = ($a>>($b-1));
  }else{
   $a = ($a>>$b);
  }
 return $a;
 } 
 
 function mix($a,$b,$c) {
   $a -= $b; $a -= $c; $a ^= ($this->zeroFill($c,13));
   $b -= $c; $b -= $a; $b ^= ($a<<8);
   $c -= $a; $c -= $b; $c ^= ($this->zeroFill($b,13));
   $a -= $b; $a -= $c; $a ^= ($this->zeroFill($c,12));
   $b -= $c; $b -= $a; $b ^= ($a<<16);
   $c -= $a; $c -= $b; $c ^= ($this->zeroFill($b,5));
   $a -= $b; $a -= $c; $a ^= ($this->zeroFill($c,3));
   $b -= $c; $b -= $a; $b ^= ($a<<10);
   $c -= $a; $c -= $b; $c ^= ($this->zeroFill($b,15));
   return array($a,$b,$c);
 }
 
 function GoogleCH($url, $length=null, $init=GOOGLE_MAGIC) {
  if(is_null($length)) {
   $length = sizeof($url);
  }
  $a = $b = 0x9E3779B9;
  $c = $init;
  $k = 0;
  $len = $length;
  while($len >= 12) {
   $a += ($url[$k+0] +($url[$k+1]<<8) +($url[$k+2]<<16) +($url[$k+3]<<24));
   $b += ($url[$k+4] +($url[$k+5]<<8) +($url[$k+6]<<16) +($url[$k+7]<<24));
   $c += ($url[$k+8] +($url[$k+9]<<8) +($url[$k+10]<<16)+($url[$k+11]<<24));
   $mix = $this->mix($a,$b,$c);
   $a = $mix[0]; $b = $mix[1]; $c = $mix[2];
   $k += 12;
   $len -= 12;
  }
  $c += $length;
  switch($len){
   case 11: $c+=($url[$k+10]<<24);
   case 10: $c+=($url[$k+9]<<16);
   case 9 : $c+=($url[$k+8]<<8);
   /* the first byte of c is reserved for the length */
   case 8 : $b+=($url[$k+7]<<24);
   case 7 : $b+=($url[$k+6]<<16);
   case 6 : $b+=($url[$k+5]<<8);
   case 5 : $b+=($url[$k+4]);
   case 4 : $a+=($url[$k+3]<<24);
   case 3 : $a+=($url[$k+2]<<16);
   case 2 : $a+=($url[$k+1]<<8);
   case 1 : $a+=($url[$k+0]);
  }
  $mix = $this->mix($a,$b,$c);
 /* report the result */
 return $mix[2];
 }
 
 //converts a string into an array of integers containing the numeric value of the char
 
 function strord($string) {
  for($i=0;$i<strlen($string);$i++) {
   $result[$i] = ord($string{$i});
  }
 return $result;
 }
 
 function printrank($url){
  $ch = "6".$this->GoogleCH($this->strord("info:" . $url));
  
  $fp = fsockopen("www.google.com", 80, $errno, $errstr, 30);
  if (!$fp) {
     echo "$errstr ($errno)<br />\n";
  } else {
     $out = "GET /search?client=navclient-auto&ch=" . $ch .  

"&features=Rank&q=info:" . $url . " HTTP/1.1\r\n" ;
     $out .= "Host: www.google.com\r\n" ;
     $out .= "Connection: Close\r\n\r\n" ; 
     fwrite($fp, $out);
     while (!feof($fp)) {
       $data = fgets($fp, 128);
       $pos = strpos($data, "Rank_");
         if($pos === false){
         }else{
           $pagerank = substr($data, $pos + 9);
           $this->pr_image($pagerank);
         }
     }
     fclose($fp);
  }
 }

//
// Display pagerank image. Create your own or download images I made for this script. 
// If you make your own make sure to call them pr0.gif, pr1.gif, pr2.gif etc.
//

 function pr_image($pagerank){
  if($pagerank == 0){
   $this->pr = "<img src=\"images/pr0.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 1){
   $this->pr = "<img src=\"images/pr1.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 2){
   $this->pr = "<img src=\"images/pr2.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 3){
   $this->pr = "<img src=\"images/pr3.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 4){
   $this->pr = "<img src=\"images/pr4.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 5){
   $this->pr = "<img src=\"images/pr5.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 6){
   $this->pr = "<img src=\"images/pr6.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 7){
   $this->pr = "<img src=\"images/pr7.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 8){
   $this->pr = "<img src=\"images/pr8.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }elseif($pagerank == 9){
   $this->pr = "<img src=\"images/pr9.gif\" alt=\"PageRank " .$pagerank. " 

out of 10\">" ;
   }else{
   $this->pr = "<img src=\"images/pr10.gif\" alt=\"PageRank " .$pagerank. 

" out of 10\">" ;
  }
 }
 function get_pr(){
  return $this->pr;
 }
}
?>

Usage

Do following:

   1. Save the code above as pagerank.php.
   2. Download or create your own images to display each rank.
   3. Create a directory "images" containing all page rank images. 
   4. See code below on how to use the class. 

<?php
include("pagerank.php");
$gpr = new pageRank();
$gpr->printrank("http://www.yahoo.com");
//display image
echo $gpr->get_pr();
?>


(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Create and send CSV files from PHP

Here's how CSV files are created and downloaded on this site. No saving the file or data import into Excel ... Excel just opens with the data automatically. Very handy.

This function as shown pulls all the field names to create a CSV header and then pulls every field from every row in the table. There is no need to know the field names, the data types or the size of the table. Quotes in the data are double-quoted and the result is surrounded by quotes.

CSV calls for each field to be contained in double quotes.


Pull data from a database using standard PHP MySQL functions:

<?php

$db_link        = mysql_connect(DB_HOST, DB_USER, DB_PSWD);
$db_selected    = mysql_select_db(DB_DATABASE, $db_link);

# Create the CSV file header from the database-table field names
$query          = "SHOW COLUMNS FROM table";
$result         = mysql_query($query);

$csv_output     = NULL;
while ($row = mysql_fetch_assoc($result))
	{
	$csv_output .= '"' . str_replace('"', '""', $row["Field"]) . '",';
	}
$csv_output  = substr($csv_output, 0, -1) . "\n";  // remove trailing "," and add a line break

# Pull all the rows
$query          = "SELECT * FROM table";
$result         = mysql_query($query);

# loop through database records creating one comma-delimed line per row
while ($row = mysql_fetch_assoc($result))
	{
	foreach ($row as $key => $value)
	  {
	  $$key = $value;
	  $$key = str_replace('"', '""', $$key);
	  $var  = $$key;
	  $csv_output .= "\"$var\",";
	  }
	$csv_output .= "\n";
	}

# send the file
$size_in_bytes		= strlen($csv_output);
$csv_file		= "filename_" . date("Y-m-d") . ".csv";
	
$ContentType		= "Content-type: application/vnd.ms-excel";
$ContentLength		= "Content-Length: $size_in_bytes";
$ContentDisposition	= "Content-Disposition: attachment; filename=\"$csv_file\"";

header($ContentType);
header($ContentLength);
header($ContentDisposition);

echo "$csv_output"; 

?>

I generally use compression with output buffering to speed things up:

ob_start("ob_gzhandler");
      .
      .
      .
ob_end_flush();

To use in your HTML:

<button onclick="window.location='CSVoutput.php'" 
	style="font-size:85%;width:100px;">Download<br />CSV file</button>

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Editing HTML with PHP scripts

When creating scripts that allow a user to edit HTML, you have to ensure that the browser doesn't confuse the input with HTML to be rendered. I struggled with this long and hard and throughout the utilities section of this website are various hacks that I created with brute force. They work, but they are mostly ugly and all were time consuming.

Well, guess what? The PHP manual has a section on this subject and the solution is really rather elegant. Chaper 56. PHP and HTML. It's worth reading, but the essential bits are reproduced below:


Example 56-1. A hidden HTML form element
<?php
   echo "<input type='hidden' value='" . htmlspecialchars($data) . "' />\n";
?> 

Example 56-2. Data to be edited by the user
<?php
   echo "<textarea name='mydata'>\n";
   echo htmlspecialchars($data)."\n";
   echo "</textarea>";
?> 


Example 56-3. In a URL
<?php
   echo "<a href='" . htmlspecialchars("/nextpage.php?stage=23&data=" .
       urlencode($data)) . "'>\n";
?> 

Escaping for PHP Output to JavaScript

To send data to a JavaScript script from PHP, three levels of escaping are required as shown in the snippet below:

<script type="text/javascript">
// <![CDATA[


// escape for JavaScript
$message   = preg_replace("/\r?\n/", "\\n", addslashes($message));

// escape for XHTML
$message   = preg_replace('%</%i', '<\/', $message);

// send to JavaScript
echo "   var message = \"$message\";\n";


// ]]>
</script>

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Google Maps Icons

Google Maps/Earth do not make icon creation & manipulation easy. Here are a couple of tips:

GIcon (look here: http://code.google.com/apis/maps/documentation/reference.html#GIcon) has a number of methods for creating and modifying an icon. I've found it's best to start with the default because adding features you expect is harder than you think.

// Here's how to create a new icon with the defaults
var newIcon = new GIcon(G_DEFAULT_ICON);

// To create a new icon like the default but yellow:
var yellowIcon = new GIcon(G_DEFAULT_ICON, "http://www.philadelphia-reflections.com/images/googlemapsmarkeryellow.png");

// To make use of that yellow icon and give it a tooltip
var point   = new GLatLng(40.39, -75.34);
var marker  = new GMarker(point, {icon:yellowIcon, title:"View Above Philadelphia"});
map.addOverlay(marker);

// To open a balloon when clicked
var message = " ... fill with text and HTML ... I've found tables are very helpful ";
GEvent.addListener(marker, 'click', function() {marker.openInfoWindowHtml(message);});

// Change icon on mouseover (see http://www.cems.uwe.ac.uk/~cjwallac/apps/phpxml/showIcons.php)
var msoverIcon = new GIcon(G_DEFAULT_ICON, "http://maps.google.com/mapfiles/kml/pal2/icon1.png");
GEvent.addListener(marker, 'mouseover', function() { marker.seticon(msoverIcon); });

Note: the "message" part of that snippet has to be escaped correctly.
See http://www.philadelphia-reflections.com/blog/1783.htm


(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

XHTML vs. HTML

The markup language used by web browsers continues to evolve. The most current version (as of April 2009) is XHTML 1.1, an XML version of HTML.

Many browsers, most particularly IE, do not support XHTML. Technically speaking, they support only the "text/html" mime type, not "application/xhtml+xml". Lots of web developers have gone to the trouble of sticking closing tags ( />) in their BR, HR, META and INPUT tags and a DOCTYPE at the top but then serve the code as "text/html".

This produces a syntactic mish mash which may be worse than using strict HTML 4.01.

Why "worse"? Because of the possibility of unintended results from providing incorrect instructions to the browser. If you care about the output produced by the browser, which most developers and content providers emphatically do, then you have to be careful about what instructions you give the browser. You simply cannot count on getting what you want if what you're telling the browser to do is syntactically incorrect.

However, it's a little difficult to see just what good XHTML is:

Internet cognoscenti speak disparagingly of "tag soup" but the Internet is a lot more about content than it is about syntax, so who really cares?

Well, somehow, I do. A little. Since we use PHP on this site, we have the opportunity to figure out what features are supported by a browser and render the correct types of tags, mime-types, etc.

Check out the HTTP headers and the page source to see the following script in action:

  1. It renders XHTML 1.1 whenever it encounters a browser that can support it
  2. It uses output buffering (which demonstrably if illogically improves rendering response time)
  3. It sends the whole thing using gzip compression if the browser will support it
  4. But also, it concedes certain issues based on experience for the sake of a smoothly-operating website
<?php
//
//  This script figures out what kind of mime type (HTML vs XHTML) the browser supports and sends the correct headers
//  It also initiates compression, specifies cache-ing and sends other <meta http-equiv headers
// 
//  My thanks to http://www.workingwith.me.uk/articles/scripting/mimetypes for the basic idea and structure
// 
//  $_SERVER["ACCEPT"] describes the mime_types a browser supports in a comma-separated list:
// 
//    mime_type,mime_type,mime_type
// 
//  If a browser prefers one mime_type or group of mime_types, it adds a q-value
// 
//    mime_type,mime_type;q=x.x, mime_type,mime_type,mime_type,...,mime_type;q=x.x
// 
//  The q-value is a number between 0.0 and 1.0 ... the higher the number, the greater the preference
//  The idea is that if we can serve more than one mime_type we should serve the browser's higher preference
//
//  ob_start("ob_gzhandler"); does all the work to compress the output if the browser can handle it
//  ob_start("fix_code"); calls the "fix_code" function instead, so initiating gzip is my responsibility
//
//  $_SERVER["HTTP_USER_AGENT"] is an opaque decription of the browser itself
//
//  $_SERVER['HTTP_ACCEPT_ENCODING'] describes compression capabilities
//
//  I output these three variables as an HTML comment so I can debug things more easily
//
//  Despite my desire to do things "right", you will see I accomodate myself to the reality of user-supplied content 
//  and browser peculiarities in order to have a working website
//

function fix_code($buffer)
  {
  #
  # Called for HTML browsers to delete all the lovely close-brackets
  # it's up to me to initiate the gzipping because ob_start is called by "fix_code" instead of "ob_gzhandler"
  #
  if (stristr($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'))
    {
    header("Content-Encoding: gzip"); // notifies the far-end to un-gzip 
    return (gzencode(str_replace(" />", ">", $buffer),6,FORCE_GZIP));
    }
    else
      {
      return (str_replace(" />", ">", $buffer));
      }
  }

#
# default values
#
$charset          = "UTF-8";       # See http://en.wikipedia.org/wiki/UTF-8
$mime             = "text/html";   # Plain vanilla
$cache_control    = "max-age=200"; # Cache expires after 200 seconds

$xhtml_q          = 0;
$html_q           = 0;

# see http://www.w3.org/QA/2002/04/valid-dtd-list.html
$DOCTYPE_xhtml11  = "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.1//EN' 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'>\n"; 
$DOCTYPE_xhtml10  = "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Strict//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd'>\n";
$DOCTYPE_wap      = "<!DOCTYPE html PUBLIC '-//WAPFORUM//DTD XHTML Mobile 1.2//EN' 'http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd'>\n";
$DOCTYPE_html401  = "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>\n";
$DOCTYPE_html401l = "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN' 'http://www.w3.org/TR/html4/loose.dtd'>\n";

$html_xhtml       = "<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en'>\n\n";
$html_iphone      = "<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' manifest='iphone.manifest'>\n\n";
$html_html401     = "<html lang='en'>\n\n";
$html_html401_IE  = "<html lang='en' xmlns:v='urn:schemas-microsoft-com:vml'>\n\n";  # xmlns:v='urn:schemas-microsoft-com:vml' is recommended by Google for maps display using IE
$html_plain       = "<html>\n\n";

# parental control tag
$pics_Label       = '(pics-1.1 "http://www.icra.org/pics/vocabularyv03/" l 
	gen true for "http://philadelphia-reflections.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://www.philadelphia-reflections.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://search.freefind.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://www.search.freefind.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://statcounter.com" r (n 0 s 0! v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://www.statcounter.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://c3.statcounter.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0) 
	gen true for "http://www.c3.statcounter.com" r (n 0 s 0 v 0 l 0 oa 0 ob 0 oc 0 od 0 oe 0 of 0 og 0 oh 0 c 0))';

# I include the following HTML comment for my ongoing debugging purposes
$show_info        = "<!-- \nHTTP_USER_AGENT      $_SERVER[HTTP_USER_AGENT]\nHTTP_ACCEPT_ENCODING $_SERVER[HTTP_ACCEPT_ENCODING]\nHTTP_ACCEPT          $_SERVER[HTTP_ACCEPT]\n -->\n\n";

# note that I eval $prolog_type below so that the xml header (if any) gets the right charset
$prolog_type      = '$DOCTYPE_html401l $html_plain $show_info';

#
# the logic
# 

# W3C Validator
if (stristr($_SERVER["HTTP_USER_AGENT"],"W3C_Validator")) 
  {
  ob_start("ob_gzhandler");
  $mime        = "application/xhtml+xml";
    # UTF-8 produces character-type errors
    $charset     = "iso-8859-1";
  $prolog_type = '$xml_header $DOCTYPE_xhtml11 $html_xhtml $show_info';
  }
  else
    {
    # fancy wap-enabled handheld device
    if(stristr($_SERVER["HTTP_ACCEPT"],"application/vnd.wap.xhtml+xml")) 
      { 
      ob_start("ob_gzhandler");
        # per http://www.ready.mobi/ and http://www.w3.org/TR/mobileOK-basic10-tests/ application/xhtml+xml is preferred
//      $mime        = "application/vnd.wap.xhtml+xml";
        $mime        = "application/xhtml+xml";
      $prolog_type = '$xml_header $DOCTYPE_wap $html_plain $show_info';
      }
      else
        {
        # non-wap xhtml-enabled browser
        if(stristr($_SERVER["HTTP_ACCEPT"],"application/xhtml+xml")) 
          { 
          # retrieve the q values for "application/xhtml+xml" and "text/html"

          if (preg_match('%application/xhtml\+xml[^;]*?;q=([1|0]\.[1-9]+)%i', $_SERVER["HTTP_ACCEPT"], $matches)) 
            {
            $xhtml_q = (float)$matches[1];
            }

          if (preg_match('%text/html[^;]*?;q=([1|0]\.[1-9]+)%i', $_SERVER["HTTP_ACCEPT"], $matches)) 
            {
            $html_q = (float)$matches[1];
            }

          # if the q value for HTML is greater than for XHTML
          # then treat output as HTML 4.01 strict (Opera 9.64, for instance)

          if($html_q > $xhtml_q) 
            {
            ob_start("fix_code");
            $mime        = "text/html";
              # UTF-8 produces character-type errors
              $charset     = "iso-8859-1";
            $prolog_type = '$DOCTYPE_html401 $html_html401 $show_info';
            }

            # otherwise, go with XHTML
            else
              {
              ob_start("ob_gzhandler");
                # for the time-being application/xhtml+xml is too strict for us: unless your tags are PERFECT, it blows up
//              $mime        = "application/xhtml+xml";
                $mime        = "text/html";
                # UTF-8 produces character-type errors
                $charset = "iso-8859-1";

              # see "Safari Web Content Guide for iPhone OS" for cache manifest description
              if (stristr($_SERVER["HTTP_USER_AGENT"],"iPhone")) 
                {
                $prolog_type = '$xml_header $DOCTYPE_xhtml11  $html_iphone $show_info';
                }
                else
                 {
                  $prolog_type = '$xml_header $DOCTYPE_xhtml11  $html_xhtml $show_info';
                 }
              }
            }
          
          else
            {
            # plain text/html browser
            if(stristr($_SERVER["HTTP_ACCEPT"],"text/html")) 
              { 
              ob_start("fix_code");
              $mime        = "text/html";
                # UTF-8 produces character-type errors
                $charset     = "iso-8859-1";
              $prolog_type = '$DOCTYPE_html401 $html_html401 $show_info';
              }
              else
                {
                # if the browser doesn't specify any X/HTML mime type, treat like HTML 4.01 Transitional (IE 7, for instance)
                ob_start("fix_code");
                $mime        = "text/html";
                  # UTF-8 produces character-type errors
                  $charset     = "iso-8859-1";
                $prolog_type = '$DOCTYPE_html401l $html_plain $show_info';
                # if IE then include Google's recommended "xmlns:v  ..." 
                if(stristr($_SERVER["HTTP_USER_AGENT"],"MSIE")) 
                  {
                  $prolog_type = '$DOCTYPE_html401l $html_html401_IE $show_info';
                  }
                }
            }
        }
    }

#
# output the mime type, prolog type and other <meta http-equiv= variables
#
header("Content-Type: $mime; charset=$charset");
header("Content-Language: en-us");
header("Vary: Accept");

header("Cache-Control: $cache_control");

header("Content-Script-Type: text/javascript");
header("Content-Style-Type: text/css");
header("imagetoolbar: no");

// parental controls from http://www.icra.org/
header("pics-Label: $pics_Label");

// privacy header created at http://www.p3pwiz.com/
header("P3P: policyref=\"http://www.philadelphia-reflections.com/w3c/p3p.xml\", CP=\"NID DSP NOI COR\"");

$xml_header       = "<?xml version='1.0' encoding='$charset' ?>\n";
eval("\$prolog_type = \"$prolog_type\";");

print $prolog_type;
?>

Here's an interesting article on Doctype Switching: http://gutfeldt.ch/matthias/articles/doctypeswitch.html

The Philadelphia Reflections webmaster: George IV

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Converting Mayan Dates

The Mayans had a number of date systems, the Long Count being the most satisfactory relative to the motion of the earth and sun.

In C++, here are the fundamental routines for converting back and forth.

The Julian Date Count functions can be found here: http://www.philadelphia-reflections.com/blog/1722.htm


void MayanToGregorian (int baktun, int katun, int tun, int uinal, int kin,
		       int& gregorianMonth, int& gregorianDay, int& gregorianYear)
	{
	// Convert a Mayan Long Count date into Gregorian

	// 1. convert Mayan date into days from the Mayan 0 date ... the beginning of their calendar
	// 2. convert Mayan days into a Julian Period Date
	// 3. convert Julian Period Date into Gregorian MM DD YYYY


	// 1. find the total number of Mayan days

	long int totalMayanDays;
	
	totalMayanDays = (baktun * 144000) + (katun * 7200) + (tun * 360) + (uinal * 20) + kin;


	// 2. convert to Julian Period Date (per http://www.pauahtun.org/Calendar/longcount.html)

	long int julianPeriodDate;
	const long int correlationConstant = 584285;

	julianPeriodDate = totalMayanDays + correlationConstant;

	// 3. Convert the Julian Period Date number to Gregorian MM DD YYYY
	
	JulianToGregorian (julianPeriodDate, gregorianMonth, gregorianDay, gregorianYear);
	
	return;
	}


void JulianToMayan (long int julianDateNumber, 
		    int& baktun, int& katun, int& tun, int& uinal, int& kin)
	{
	// Convert a Julian Period Date number to Mayan Long Count date

	const long int kinCount    = 1;		      // 1 kin                          = 1 day
	const long int uinalCount  = 20 * kinCount;   // 1 uinal  = 20 kins             = 20 days
	const long int tunCount    = 18 * uinalCount; // 1 tun    = 18 uinals = 18*20   = 360 days
	const long int katunCount  = 20 * tunCount;   // 1 katun  = 20 tuns   = 20*360  = 7,200 days
	const long int baktunCount = 20 * katunCount; // 1 baktun = 20 katuns = 20*7200 = 144,000 days

	long int totalMayanDays;
	const long int correlationConstant = 584285;
	
	// reverse engineer the Mayan to Julian

	totalMayanDays = (julianDateNumber - correlationConstant) + 1;

	baktun = totalMayanDays / baktunCount;
	totalMayanDays = totalMayanDays % baktunCount;

	katun = totalMayanDays / katunCount;
	totalMayanDays = totalMayanDays % katunCount;

	tun = totalMayanDays / tunCount;
	totalMayanDays = totalMayanDays % tunCount;
	
	uinal = totalMayanDays / uinalCount;
	totalMayanDays = totalMayanDays % uinalCount;

	kin = totalMayanDays / kinCount;
			
	return;
	}

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Date Math

Figuring out how many days there are between two dates is either built in to the system you are using or else you will tear your hair out what with leap years, floating leap days, etc.

There are two ways to do this fairly simply:

  1. Using Julian Date Counts
  2. Using the system clock functions

Julian Date Counts


Julian Date Counts are not related to Julius Caesar's calendar. They are a way to assign a sequential number to every date going way back into antiquity. Convert any two dates to their Julian Date Count number and the difference is the number of days between the dates.

See Julian Day Numbers

In C++ ...

long int GregorianToJulian(int gregorianMonth, int gregorianDay, int gregorianYear)
	{
	// this function calculates the Julian Date Number from a Gregorian date
	//
	// astronomers and others use these numbers becasue the difference between two
	// Julian Date Numbers is the number of days between the dates
	//
	// see http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html

	long int A;
	long int B;
	long int C;
	long int E;
	long int F;
	long int JD;

	long int Y, M, D;

	Y = gregorianYear;
	M = gregorianMonth;
	D = gregorianDay;

	if (M <= 2)
	  {
	  Y = Y - 1;
	  M = M + 12;
	  }
	
	A = Y/100;
	B = A/4;
	C = 2-A+B;
	E = 365.25*(Y+4716);
	F = 30.6001*(M+1);
	JD= C+D+E+F-1524.5;

	return JD;
	}


void JulianToGregorian (long int julianDateNumber, int& gregorianMonth, int& gregorianDay, int& gregorianYear)
	{
	// Convert a Julian Date Number to a Gregorian Date
	//
	// see http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html

	long int Z;
	long int W;
	long int X;
	long int A;
	long int B;
	long int C;
	long int D;
	long int E;
	long int F;

	Z = julianDateNumber+0.5;
	W = (Z - 1867216.25)/36524.25;
	X = W/4;
	A = Z+1+W-X;
	B = A+1524;
	C = (B-122.1)/365.25;
	D = 365.25*C;
	E = (B-D)/30.6001;
	F = 30.6001*E;

	gregorianDay   = B-D-F;
	gregorianMonth = ((E-1) > 12) ? E-13 : E-1;
	gregorianYear  = (gregorianMonth>2) ? C-4716 : C-4715;
	
	return;
	}


Using the system clock functions


Again in C++ ...

/*

Find the days between two dates

Found at 
http://stackoverflow.com/questions/1381832/how-to-calculate-the-number-of-days-between-two-given-dates-leap-year-obstacle
and http://www.cplusplus.com/reference/clibrary/ctime/mktime/

The sources were helpful but I had to extensively modify/hack to make this actually work

*/

#include <time.h>
#define SECONDS_PER_DAY (24 * 60 * 60)

time_t time_from_date(int year, unsigned int month, unsigned int day)
{
    // Return the time from the year 1900 to the date entered
	
    struct tm a = {0,0,0,day,month - 1,year - 1900};
    time_t x = mktime(&a);

    return x;
}

int days_between(int year0, unsigned month0, unsigned day0,
                 int year1, unsigned month1, unsigned day1)
{
	// The difference in seconds between two dates,
	//   divided by the number of seconds in a day ...
	//
	//     is the number of days between the dates

    return difftime(time_from_date(year1, month1, day1),
                    time_from_date(year0, month0, day0)) / SECONDS_PER_DAY;
}

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Embed Flash as Valid XHTML

The problem to be solved is that you want to embed YouTube (or other Flash movies) but the <embed> tag is deprecated in XHTML and the <param> tags don't validate, either. Here are the steps to clean things up:

The original HTML from YouTube

<object
  width="425"
  height="344">
<param
  name="movie"
  value="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1">
</param>
<param
  name="allowFullScreen"
  value="true">
</param>
<param
  name="allowscriptaccess"
  value="always">
</param>
<embed
  src="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"
  type="application/x-shockwave-flash"
  allowfullscreen="true"
  width="425"
  height="344">
</embed>
</object>

Step 1: replace the closing "</param>" tags with trailing " />"

<object
  width="425"
  height="344">
<param
  name="movie"
  value="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"  />
<param
  name="allowFullScreen"
  value="true"  />
<param
  name="allowscriptaccess"
  value="always"  />
<embed
  src="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"
  type="application/x-shockwave-flash"
  allowfullscreen="true"
  width="425"
  height="344">
</embed>
</object>

Step 2: put type="application/x-shockwave-flash" into the <object> tag:

<object
  width="425"
  height="344"
  type="application/x-shockwave-flash">
<param
 name="movie"
  value="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1" />
<param
  name="allowFullScreen"
  value="true" />
<param
  name="allowscriptaccess"
  value="always" />
<embed
  src="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"
  type="application/x-shockwave-flash"
  allowfullscreen="true"
  width="425"
  height="344">
</embed>
</object>

Step 3: move src="..." from the <embed> tag to a data="..." attribute in the <object> tag:

<object
  width="425"
  height="344"
  type="application/x-shockwave-flash"
  data="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1">
<param
  name="movie"
  value="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1" />
<param
  name="allowFullScreen"
  value="true" />
<param
  name="allowscriptaccess"
  value="always" />
<embed
  src="http://www.youtube.com/v/zhWkTiMVWVI&color1=0xb1b1b1&color2=0xcfcfcf&hl=en&feature=player_embedded&fs=1"
  type="application/x-shockwave-flash"
  allowfullscreen="true"
  width="425"
  height="344">
</embed>
</object>

Step 4: remove the <embed> tag:

<object
  width="425"
  height="344"
  type="application/x-shockwave-flash"
  data="http://www.youtube.com/v/zhWkTiMVWVI&amp;hl=en&amp;fs=1">
<param
  name="movie"
  value="http://www.youtube.com/v/zhWkTiMVWVI&amp;hl=en&amp;fs=1" />
<param
  name="allowFullScreen"
  value="true" />
<param
  name="allowscriptaccess"
  value="always" />
</object>


You can View > Source to see that the code shown here does actually produce the YouTube video displayed.


Step X: it should be noted that in Firefox you don't need any "<param>" tags at all, which makes things very simple and clean:

<object
  width="425"
  height="344"
  type="application/x-shockwave-flash"
  data="http://www.youtube.com/v/zhWkTiMVWVI&amp;hl=en&amp;fs=1">
</object>

Not in IE, though; nope. (Why the </object> instead of a closing " />"? Because it seems to work more reliably in Firefox 3.0.5; I don't know why.)

What's a Tranche?

Tranche is a French word, referring to a slice of something. In the bond market, a bundle of bonds can be divided into tranches according to the date of issue or the duration to maturity. In recent years, the term has suddenly become synonymous with segmentation of securitized mortgages according to the probable risk of default. More briefly, the tranches refer to quality ratings by independent rating agencies, like Moody's or Standard and Poor.

Parsing name-value pair attributes in an HTML tag

Not only do the attributes in an HTML tag come in random order but many are optional

Here's a regex solution:

<?php
function tagAttr($matches) {print_r($matches);}

$string = '<img src="/images/picture.jpg" width="300" class="left" alt="alt keywords" />';

$foo	= preg_replace_callback(
'/<img\b(?>\s+(?:alt="([^"]*)"|class="([^"]*)"|style="([^"]*)"|src="([^"]*)"|height="([^"]*)"|width="([^"]*)")|[^\s>]+|\s+)*>/i',
"tagAttr",
$string);
?>

Produces the following:

Array
(
    [0] => <img src="/images/picture.jpg" width="300" class="left" alt="alt keywords" />
    [1] => alt keywords
    [2] => left
    [3] => 
    [4] => /images/picture.jpg
    [5] => 
    [6] => 300
)

The regex is a series of alternating sequences; so, add href="([^"]*)"| in front of alt="([^"]*)" to select an additional attribute.

$matches[0] is the complete match
$matches[1] is alt=
$matches[2] is class=
$matches[3] is style=
$matches[4] is src=
$matches[5] is height=
$matches[6] is width=

My thanks (a) to Flagrant Badassery for putting me onto the idea and (b) to http://centricle.com/tools/html-entities/ for HTML encoding

Regex URL Matching

On this site we check for the existence of a URL whenever an entry is updated

There are two key technologies at work


function url_exists($url) 
{
// 
// checks whether a URL actually exists on the Internet
//
$handle   = curl_init($url);
if (false === $handle)
   {
    return false;
   }
curl_setopt($handle, CURLOPT_HEADER, false);
curl_setopt($handle, CURLOPT_FAILONERROR, true); 
curl_setopt($handle, CURLOPT_NOBODY, true);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, false);
$connectable = curl_exec($handle);
curl_close($handle);   
return $connectable;
}


function aExists($matches)
{
//
// function called by preg_replace_callback
//
// $matches[0] is the complete match
// $matches[1] the match for the first subpattern
//	enclosed in '(...)' and so on

//
// checks to see if a regular link exists
// something similar is done for img src= also
//

$srcURL = $matches[3];
		
if (url_exists($srcURL)) {do something; return "";}  
else {do something else; return "";}
}

$foo = preg_replace_callback(
            '/(.*?)(<a .*?href=")([^"]*)("[^>]*>)(.*?)(<\/a>)/i',
            "aExists",
            $source_string);

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

HTML Forms

How do you (a) open a form when a radio button is clicked (b) in a new window?

Here's how it's done on this website.

<html>
<head>

<script type="text/javascript">

	/* javascript function called by the radio buttons 
	     to submit the form when clicked */

	function formSubmit()
	{
	document.getElementById("form_x").submit()
	}
	
</script>

</head>
<body>

  <form name="form_x" id="form_x"
	action="some_routine.php"
	target="newIMGwin"
	method="post" 
	style="whatever">

	<fieldset>
	<legend>legend surrounding the form</legend>

	<input type="radio" name="key" value="1269" onclick="formSubmit()" />

	</fieldset>
  </form>

</body>
</html>

(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Static vs Dynamic URLs

It used to be that no spiders or search engines could index a dynamic URL, namely one that contained a "?" followed by parameters to be used by PHP, ASP or other server-side scripting languages to drive a website using a database.

Nowadays, Google and Yahoo seem to do a perfectly fine job of indexing dynamic URLs but Google has a disclaimer warning that it may still encounter problems with dynamic URLs and the SEO literature is still full of warnings that other spiders and search engines may be blind to everything to the right of the "?".

Furthermore, a *.php extension is an invitation to bad guys to try to break in and wreak many sorts of havoc: this site was hacked by Nigerians a few years ago using PHP tricks and they managed to use it as an email factory until our ISP shut us down. I came on the scene at that point and implemented every safeguard I could find, but the concern still lingers.

Finally, dynamic URLs are not user friendly ... human beings generally do not know what to make of long strings of obscure parameters.

Apache has a feature called "mod_rewrite" that allows you to specify, via regex, that you want incoming URLs to be transformed in some way. Apache's instructions on this subject are here: URL Rewriting Guide. I have used that facility at Philadelphia Reflections to use static URLs for public use while still allowing me to use parameters to drive the website with the database.

Two excellent articles on this got me started:

Here's what I did:

Step 1: htaccess

I added these lines to the htaccess file

Options +FollowSymLinks
RewriteEngine on
RewriteRule ^(blog|topic)/([0-9]+)\.html?$ reflections.php?type=$1&key=$2
  1. ^(blog|topic)/([0-9]+)\.html?$ is the pattern to be compared against all incoming URLs.

    If matched, it changes the URL to reflections.php?type=$1&key=$2

  2. The ^ ... $ sequence in the pattern says that we will match the whole string, not just some part in the middle
     
  3. (blog|topic)/ matches either   "blog"   or   "topic"   followed by   "/"  
     
  4. ([0-9]+) matches one or more digits
     
  5. \.htm matches   ".htm"  
     
  6. l? matches 0 or 1 lower-case Ls (so that we will match either htm or html)
     
  7. In the replacement string $1 is replaced with the contents of the first () in the pattern: either "blog" or "topic"
    ... and $2 is replaced by the second () in the pattern, namely the numeric ID on the database of the blog or topic

The result is that

http://www.philadelphia-reflections.com/blog/906.htm

is transformed into

http://www.philadelphia-reflections.com/reflections.php?type=blog&key=906

The latter is what is passed in to me in the reflections.php routine, which tells me to pull up blog #906 from the database.

Both of these URLs are equivalent to the old, ugly dynamic URL

http://www.philadelphia-reflections.com/reflections.php?content=blogs_alpha/zmadame_butterfly.html

which still works, in case there are any legacy bookmarks or links out there, but going forward the new, simple, static URL is the face we will present to the world.

Step 2. SMOP

After the htaccess regex was debugged, all that was left was a simple matter of programming. In fact, I had to completely rewrite the driver script, reflections.php, and the XML creation script which creates the RSS, sitemap, etc. files; plus a lot more besides. It was a lot of work but the breakthrough was in figuring out the htaccess trick; everything else was just work.


In July 2008, after Volumes were implemented, another RewriteRule was implemented:

RewriteRule ^volumes?/([0-9]+)\.html?$ volume.php?table_key=$1
Converts
http://www.philadelphia-reflections.com/volumes/3.htm
into
http://www.philadelphia-reflections.com/volume.php?table_key=3

BU to Pier 7 GPS Tour

Example of Including a KML file using the GPS tour from BU to Pier 7

Send a KML file from disk using PHP

Sending a kml or kmz disk file is as easy as clicking on it. But different browsers react differently, some asking you which program to use others storing the file on your desk top, etc. Preprocessing the file through PHP can reduce some of these annoyances.

<?php

//
// reads and sends a kml or kmz file
// located in /whatever/kml/
//
// calling protocol:
// this-program.php?file=somefile.kml
//

// read the input and check that it's a kmz or kml file
// ....................................................

$kml_file	= $_GET['file'];

if (($kml_file === NULL) or ($kml_file == "")) {exit ("error message");}
		
if ((substr($kml_file, -4) != ".kmz") AND (substr($kml_file, -4) != ".kml"))	
	{
	exit ("error message");
	}


// prepend the file path information to the file name and check that it exists
// ...........................................................................

$kml_file_name = "/whatever/kml/" . $kml_file;
	
if (!file_exists($kml_file_name)) { die ("error message");}


// send out the HTTP header information followed by the file contents
// ..................................................................
	
header("Cache-Control: no-cache, no-store, must-revalidate"); // trying to keep from getting the files stored on the local computer
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");

if (substr($kml_file, -4) == ".kml") {header('Content-Type: application/vnd.google-earth.kml+xml');}
if (substr($kml_file, -4) == ".kmz") {header('Content-Type: application/vnd.google-earth.kmz');}

header("Content-Disposition: inline");
header("Content-Description: KML or KMZ data intended for Google Earth");

readfile ($kml_file_name);

?>

Call KML files from within a blog or topic

Here's how to create a button in your blogs or topics that calls KML or KMZ files you create. In the Modify A Blog utility you can include a KML or KMZ file, but to call it explicity from within the blog, you can create a button as shown here.

  1. Create and save your KML file.
  2. FTP the file to the kml folder in Philadelphia Reflections
  3. Use the following code in a blog or topic to call the kml file


<button onclick="location.href='http://www.philadelphia-reflections.com/kml-read.php?file=Franklin.kmz'">Button Label</button>

To create this button:



Instead of Franklin.kmz, put any .kml or .kmz file that is in the kml folder:
http://www.philadelphia-reflections.com/kml/

Process .htm and .html as php

It is sometimes helpful to include php scripting in files that do not have the file extension of php.

There is quite a lot of discussion on the web about this but at least on this server the answer is not what most people think.

In the .htaccess file in the root folder include these two lines:

AddType application/x-httpd-php .html .php .htm
AddHandler application/x-httpd-php .html .php

Geo Positioning

Geo Tagging refers to adding latitude and longitude information to websites and photographs. This has been around for a long time but it has taken the advent of Google Earth for it to really start to catch on.

This blog entry has geo meta tags that you can see if you look at the HTML source ("View > [Page] Source"). The input was as follows:

Address: 82 Devonshire St Boston MA

Lat: 42.3578 Lon: -71.0577

Descriptive Place Name: Fidelity Investments headquarters

Region: US-MA Country Code: US Country Name: United States


This creates meta tags in the HTML Header as follows:

<!-- geo tags for 82 Devonshire St Boston MA -->
<meta name="ICBM"          content="42.3578, -71.0577" />

<meta name="geo.country"   content="US" />
<meta name="geo.region"    content="US-MA" />
<meta name="geo.placename" content="Fidelity Investments headquarters" />
<meta name="geo.position"  content="42.3578; -71.0577" />

<meta name="tgn.name"      content="Fidelity Investments headquarters" />
<meta name="tgn.nation"    content="United States" />

The Region, Country Code and Country Name can be found here: ISO-3166-1 Country Names

geo.placename and tgn.name are often rendered as the city name but are intended to describe the geographical feature ("Pyramids of Giza" or something). This tag is optional.

HTML geo meta tags can be validated here: {geo tag validation}



There is a search engine of long standing that reads HTML geo meta tags and indexes the website based upon its location; for searching, it groups sites based on their geographic proximity: GeoURL.



Photographs can also contain geo meta data, so-called EXIF data (Firefox has an EXIF viewer AddOn).

JPEG is the most common image format and the easiest to deal with. The combination of Picassa2 and Google Earth allow you easily to add this information to your own photos.

The process of adding lat and lon to your photographs is this:

1. Select one or more photos in Picassa
2. Select Tools > GeoTag > GeoTag with Google Earth ...
3. This starts Google Earth and you can "fly" to the location of the picture

4. A small Picasa window will appear in Earth's lower-right corner displaying thumbnails of the pictures you selected; press the "Geotag" button.
5. When all of your pictures are tagged, press the "Done" button

Slowly, camera manufacturers are providing GPS capability. Some few have GPS devices built in and some others allow an external GPS device to be attached, although both Canon and Nikon are way behind the curve ... if you own either, you can essentially forget it: the best - lousy - solution is to carry around a GPS with you and synchronize the times ... ugly.


The Google Maps API allows maps to be embedded in a website as is done here. Google Maps API

The JavaScript required to embed the map on this page can also be seen in the HTML source ("View > [Page] Source"). In addition to JavaScript, you need a DIV with an ID of "map" or whatever is specified in the JavaScript document.getElementById entry, which specifies the height and width of the map to be displayed.

To embed these maps you must register with Google


In addition, there are extensions to ATOM and RSS to include lat and lon in your syndication feeds; there are three standards that I have found: GeoRSS (ATOM example) , W3C Geo (RSS example) and an "ICBM RSS Module". This website extends the namespaces of both its ATOM feed and its RSS feed to include all the tags.

Google, Yahoo and Microsoft all now support GeoRSS as a feed to their map programs. My sense of it is that KML is a richer protocol, allowing more features, but fundamentally all these XML variants do mostly the same thing.

Google Sitemaps can include links to KML files (and ATOM, now, too). Part of the sitemap generation on this site is some code that picks up every *.kml and *.kmz file in the /kml/ folder and adds them to our sitemap.xml file.


Google Earth is filled with delights, not the least of which is a Flight Simulator! Google Earth Flight Simulator Keyboard Controls


KML ( Keyhole Markup Language, Keyhole being the predecessor to Google Earth) is an XML protocol that allows you to incorporate Google Earth into graphical presentations. Google KML Overview

Google Earth Outreach helps you get started: Google Earth Outreach

An extraordinary collection of KML files you can view is found here: Spectacular satellite images of the world

I found a KML editor here: NorthGates' KML Editor for Windows. It's rudimentary but very handy for what it does do.

Here's the Google Earth tools list where I found the KML editor: EarthPlot Software Tools For Google Earth


The way we serve the KML in the link that connects to Google Earth from individual blogs uses the following PHP script as its base:

<?php

// See Google Earth's KML 2.1 Reference
// http://code.google.com/apis/kml/documentation/kml_tags_21.html

$lat			= $_GET['lat'];
$lon			= $_GET['lon'];
$placename		= $_GET['placename'];
	
$altitude		= $_GET['altitude'];
$range			= $_GET['range'];
$heading		= $_GET['heading'];
$tilt			= $_GET['tilt'];
	
if ($altitude	== NULL) {$altitude	= 0;}
if ($range	== NULL) {$range	= 1000;}
if ($heading	== NULL) {$heading	= 0;}
if ($tilt	== NULL) {$tilt		= 0;}
	
$description	= "<h3><font color=\"#ea9f20\"><a href=\"http://www.philadelphia-reflections.com/\">
		PHILADELPHIA REFLECTIONS</font></a></h3>
		<p>The musings of a Philadelphia physician who has served the community for six decades.</p>";
	
	
header('Content-Type: application/vnd.google-earth.kml+xml');
header('Content-Disposition: inline; filename="philadelphia-reflections.kml"');

echo '<?xml version="1.0" encoding="UTF-8"?>'; 

?>

<kml xmlns="http://earth.google.com/kml/2.0">

  <Placemark>
    <name><?php echo $placename; ?></name>
    <description>
        <![CDATA[<?php echo $description; ?>]]>
    </description>
    
    <LookAt>
      <longitude><?php echo $lon; ?></longitude>
      <latitude><?php echo $lat; ?></latitude>
      
      <altitude><?php echo $altitude; ?></altitude>
      <range><?php echo $range; ?></range>
      <tilt><?php echo $tilt; ?></tilt>
      <heading><?php echo $heading; ?></heading>
      
      <altitudeMode>relativeToGround</altitudeMode>
    </LookAt>
    
    <Point>
      <coordinates><?php echo "$lon,$lat,$altitude"; ?></coordinates>
    </Point>
    
  </Placemark>

</kml>


Of course, GPS devices are an integral part of this process of Geo Positioning. GPS devices are supposed to support the open-source protocol GPX,
which is an XML-based description of waypoints and routes. Wikipedia describes GPX here: GPS eXchange Format

The GPX protocol's official website is here: GPX The GPS Exchange Format

Google Earth supports raw GPX (File > Open ...) and when you open a GPX file in Google Earth, it converts it to KML. But if you want stand alone programs to do this:

If you have non-standard GPS data, you may want to have a look at GPS Babel for conversion of native GPS formats as well as the GPS Utility and G7ToWin

A nice blog on these things relative to Google Maps is here: Using XSL to Transform Google Earth (KML) and GPX to Google Maps API


At Philadelphia Reflections, we are creating tours by carrying a GPS and a camera around on our travels. The GPS track becomes a path and waypoints become placemarks. When you come home, download the GPS data in GPX format and open up the GPX file in Google Earth. Use Google Earth to edit the placemark balloons, including pictures and text.



There are many, many sightseeing blogs around that take you to interesting places on Google Maps and Google Earth. A place to start looking is Sightseeing with Google Satellite Maps


Somehow, the concept of "mashup" is related to all of this but it sort of sounds like the term "multimedia" a few years ago ... fancy in concept but somewhat vague in reality.

Google has a Mashup Editor and Wikipedia has a definition but it's not clear what it all adds up to.



(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

RSS Feeds

NameFeed
RSS 2.0rss.xml
Atom 1.0atom.xml
Yahoo! urllisturllist.txt
Yahoo! IDIF pointeridifpointer.txt
Google Sitemapsitemap.xml
A9/Amazon siteinfositeinfo.xml
Add to Yahoo! {Add to My Yahoo!}
Add to Google {Google}
{[Valid RSS]} {[Valid Atom 1.0]}

Font Families

The following link shows the results of a survey done to find out which font families are installed on Windows machines. This should help determine which fonts to use.

http://www.codestyle.org/css/font-family/sampler-WindowsResults.shtml

Identifont is a site that helps identify good font choices.

CSS Zen Garden Suggestions

Here is a list of links (that open in their own pages) that show some of my favorite web designs. The CSS Zen Garden is a website that illustrates what can be done with clever CSS design. The HTML and the content are exactly the same in each of these links, only the CSS changes; but what a difference!


C Note

Dark Rose

Dead or Alive

Egyptian Dawn

Invitation

Mediterranean

Mozart

Odyssey

Tranquille

Zen Pool

webZine

White Lily

Another website to consider is http://www.freecsstemplates.org/

Ampersand Madness: Convert & to &amp; to prevent XHTML errors

The whole subject of "encoding" gives me a headache.



Encoding In General

The first thing you have to know is: what is HTML encoding ... so look here:
http://htmlhelp.com/reference/html40/entities/
or here:
http://www.cookwood.com/html/extras/entities.html

(These are HTML encodings; URL encoding is something else again ... look here:
» http://www.blooberry.com/indexdot/html/topics/urlencoding.htm)

Ampersand Encoding and Conversion

Later on, you'll find out that the ampersand is a huge source of XHTML errors because it has to be written

but you will struggle endlessly with how to get the darn thing to stay converted. First of all, content providers feel justifiably justified in including bare naked "&"s wherever they please; second of all, you will find that encoded ampersands get stripped back to their bare naked selves by browsers and other well-meaning sorts.

So, my undying thanks to Michael Ash's Regex Blog for providing the regex pattern in the following bit of PHP code:


$pattern = '/&(?!(?i:\#((x([\dA-F]){1,5})|(104857[0-5]|10485[0-6]\d|1048[0-4]\d\d|104[0-7]\d{3}|10[0-3]\d{4}|0?\d{1,6}))|([A-Za-z\d.]{2,31}));)/i';
				
$replacement = '&amp;';
				
$string = preg_replace ( $pattern, $replacement, $string);

I don't know how it can possibly work, and I may yet eat my words, but for the moment it seems to do the trick.

Ampersand Encoding In RSS

Another thing: &#x26; is the only ampersand encoding form acceptable to both RSS and Atom. So, look at the souce of this page and you will find that I use this encoding in the title ... that's because the title goes into the Title field of my RSS and Atom feeds.

RSS, Atom, Syndication, etc.

The world is full of XML and XML-like file formats for syndication purposes

Here's the list of files we generate automatically for submission to search engines and such.

(For right now, things are a bit abbreviated)

http://www.philadelphia-reflections.com/reflectionsRSS.xml (RSS Syndication file)
http://www.philadelphia-reflections.com/reflectionsATOM.xml (Atom Syndication file)
http://www.philadelphia-reflections.com/sitemap.xml (Google sitemap)
http://www.philadelphia-reflections.com/siteinfo.xml (A9/Amazon siteinfo.xml)
http://www.philadelphia-reflections.com/reflectionsIDIF1.xml (Yahoo IDIF file 1)
http://www.philadelphia-reflections.com/reflectionsIDIF2.xml (Yahoo IDIF file 2)
http://www.philadelphia-reflections.com/reflectionsIDIF3.xml (Yahoo IDIF file 3)
http://www.philadelphia-reflections.com/reflectionsIDIF4.xml (Yahoo IDIF file 4)
http://www.philadelphia-reflections.com/reflectionsIDIF5.xml (Yahoo IDIF file 5)
http://www.philadelphia-reflections.com/IDIFpointer.txt (Yahoo IDIF pointer file)
http://www.philadelphia-reflections.com/urllist.txt (Yahoo urllist.txt)

Validate Short RSS | The Short RSS File itself
Validate Short rss (lower case) | The Short RSS File itself (lower case)
Validate Short ATOM | The Short ATOM File itself

Weblogs.com extended successfully pinged
Weblogs.com successfully pinged
blo.gs successfully pinged
Technorati successfully pinged
Ping-O-Matic successfully pinged
Syndic8 successfully pinged (Feed ID 477463)

Ping Blogroller manually
Ping MyYahoo manually



The RSS and Atom validator (http://feedvalidator.org/) has a length restriction. I don't know what it is, exactly, but it bombs if your file is "too long". Since most syndication readers run the validator before they'll accept a feed, I have resorted to creating a short file, which is what I point to in my meta tags.



Here's how I provide change frequency and priority for our Google sitemap (in PHP ... $mod is the variable containing the date last modified)

$GOOGLEpriority = "0.0"; $GOOGLEfreq = "yearly";	// default

if ($mod > mktime(0,0,0) - 86400*210)	{$GOOGLEpriority = "0.1"; $GOOGLEfreq = "monthly";}	// past 210 days
if ($mod > mktime(0,0,0) - 86400*180)	{$GOOGLEpriority = "0.2"; $GOOGLEfreq = "monthly";}	// past 180 days
if ($mod > mktime(0,0,0) - 86400*150)	{$GOOGLEpriority = "0.3"; $GOOGLEfreq = "monthly";}	// past 150 days
if ($mod > mktime(0,0,0) - 86400*120)	{$GOOGLEpriority = "0.4"; $GOOGLEfreq = "monthly";}	// past 120 days
if ($mod > mktime(0,0,0) - 86400*90)	{$GOOGLEpriority = "0.5"; $GOOGLEfreq = "monthly";}	// past 90 days
if ($mod > mktime(0,0,0) - 86400*60)	{$GOOGLEpriority = "0.6"; $GOOGLEfreq = "monthly";}	// past 60 days
if ($mod > mktime(0,0,0) - 86400*30)	{$GOOGLEpriority = "0.7"; $GOOGLEfreq = "monthly";}	// past 30 days
if ($mod > mktime(0,0,0) - 86400*7)	{$GOOGLEpriority = "0.8"; $GOOGLEfreq = "weekly";}	// past 7 days
if ($mod > mktime(0,0,0) - 86400)	{$GOOGLEpriority = "0.9"; $GOOGLEfreq = "daily";}	// yesterday
if ($GOOGLEmoddate == date("Y-m-d"))	{$GOOGLEpriority = "1.0"; $GOOGLEfreq = "hourly";}	// today



IDIF is a stupid format: it includes the entire blog_contents, so the files are huge. In the process of setting this up, I learned that flat files have a maximum size of 1.4 megs or so (the size of an old floppy disk), so I had to create more than one.

Which explains the stupid concept of a "pointer file"; instead of just giving Yahoo the IDIF file itself, you give it a pointer file with URLs pointing to the multitude of IDIF files. Really stupid.

News flash, after finding the Journal Of Ovid on the web, I learned about length restrictions for the input fields (described below). This information was not contained on the Yahoo web site describing their file format. It considerably reduced the file sizes but I retained the structure of multiple files because who knows what I'll learn next?

IDIF title must be a maximum of 80 characters
IDIF description must be a maximum of 180 characters
IDIF body must be a maximum of 1000 characters
I'm only guessing about keywords

Thanks to the Journal Of Ovid on the web for this secret information

From the inside out: trim, replace whitespace (thanks to the PHP manual for this), shorten to maximum length

$IDIFtitle		= substr( preg_replace ('/\s\s+/', ' ', trim($title) ), 0, 80 );
$IDIFdescription	= substr( preg_replace ('/\s\s+/', ' ', trim($description) ), 0, 180 );
$IDIFkeywords		= substr( preg_replace ('/\s\s+/', ' ', trim($keywords) ), 0, 79 ) . " ";
$IDIFblog_contents	= substr( preg_replace ('/\s\s+/', ' ', trim($blog_contents) ), 0, 1000 );

Yahoo is said to support a simple text file list of URLs "urllist.txt" Documentation, of course, is scarce


(my thanks to http://centricle.com/tools/html-entities/ for HTML encoding)

Captcha

With the rise of spam entries in web forms, a security feature called "captcha" has been developed.

CAPTCHA stands for "Completely Automated Public Turing test to tell Computers and Humans Apart". The idea is that only a human could read the letters contained in the image and then enter them in the form. "Accessibility", ie., designing websites to accommodate people with handicaps is obviously hindered by Captcha; but at least given our experience with this website, spamming is a huge problem and the inability of handicapped people to leave comments is a price we are willing to pay to rid ourselves of spam. The W3C, the Godhead of web standards, does not agree with me and lectures at length on the futility of captcha: Inaccessibility of CAPTCHA. Whatever. I may get around to implementing some of their recommendations later, if we continue to be spammed.

Spammers have countered captcha in a number of ways. The first is OCR, which is why the images have fuzzy backgrounds and distorted letters: trying to defeat OCR programs. As OCR techniques have improved, captcha programs have moved from letters to "objects" such as kittens, boxes, etc., which are thought to be harder for computers to recognize; harder for people, too: cat vs kitten, for example. I am amazed to learn during my captcha research that there are spammers who offer micro-payments to people in India, etc. to enter hundreds of spam manually in captcha-ed websites that have defeated their automated spamming systems. Move, counter move; seemingly endlessly.

In this website captcha has been implemented using PHP: the comments form that appears at the end of every page has an image created using the PHP image-creation routines which has random characters in it. If the characters in the image are entered correctly in the form, the comments are entered into the database.

I cribbed the PHP captcha code from http://www.white-hat-web-design.co.uk/articles/php-captcha.php and it worked right out of the box with the minor exception that the form HTML didn't quite pass XHTML muster; easily fixed. (I have subsequently discovered that PHP security and sessions don't play well together; this problem remains unresolved and I've had to turn off captcha processing for my secure pages.)

I implemented a number of other spam counter measures before I got around to captcha, which involved noticing what the spammers did and writing code to frustrate it. I am constantly on the lookout for new security techniques to implement.

Javascript: document.write and XHTML

For reasons that make no sense to me, the Javascript command document.write does not work when your page is rendered properly in XHMTL (as described elsewhere in this Topic).

I have searched the web in vain to find a Javascript solution. Many are offered but none work worth a damn.

So don't bother. Use PHP's echo function. It works perfectly.

Open a new window with XHTML

Once upon a time you could say

<a href="link" target="_blank">Click to open a new window</a>

and a new window would open. Highly annoying if used very often, but sometimes it's the right thing to do.

And then XHTML comes along and this is not longer legal.

target="_blank" is "deprecated" without a single word as to what a poor developer is to substitute.

Here's what you do:

<a href="link" onclick="window.open(this.href); return false;">Click to open a new window</a>

Web hosting providers

Choosing a web hosting service provider is difficult. There's no brand to rely on and an Internet search turns up confusing claims and offers. We have used two providers:

Based on our experience, Internet Planners is a reasonable choice for web hosting; Network Solutions is a bad choice.

Regular Expressions

Anyone who has used the expression *.doc to search for Word files has used Regular Expressions ("regex") without realizing it. Regex arose from mathematical theory and is available in many programming languages; it is simply the only way to deal with large amounts of text. And yet most people are completely unaware of it.

Philadelphia Reflections uses regex extensively for two primary purposes: (1) checking input from forms and (2) modifying HTML input in during the creation of articles for the site.

The text PHP and MySQL by Larry Ullman has a very good introduction to regex in his chapter on security.

The great advantage of regex is that it can identify very complex patterns in a mass of text. The great disadvantage of regex is that it has developed in sort of an underground way and there exist numerous varieties that are essentially incompatible. PHP offers two regex functions: one for the POSIX Extended variety of regex and he other for the Perl language compatible vesion called PCRE. POSIX is less powerful but far easier to learn. JavaScript offers its own variety of regex which isn't quite the same as either of the two PHP versions.

References include the Ullman book, the PHP online manual has a number of handy tips on regex use in its two supported varieties, the O'Reilly book Mastering Regular Expressions is interesting and Jan Goyvaerts has a very helpful website (http://www.regular-expressions.info/) and book Regular Expressions: The Complete Tutorial.

My experience is that this area requires diligent hacking which may be sub optimal but unavoidable ... for this purpose, Jan Goyvaerts' Regex Buddy is indispensible; you simply must get this program if you hope to make anything of Regex.

Here are examples of checking for a valid email address in both Javascript and PHP:

Javascript


// check email

var namePattern = /^[a-zA-Z0-9][a-z0-9_.-]*@[a-z0-9.-]+\.[a-z]{2,4}$/i;

document.comment_form.email.value = trim(document.comment_form.email.value);
					
if	(
	(  document.comment_form.email.value.length > 0)
			&&
	(! document.comment_form.email.value == "[none]")
			&&
	(! document.comment_form.email.value.match(namePattern))
	) 
		{
			alert("Please enter a valid email address");
    			document.comment_form.email.focus();
       			document.comment_form.email.select();
			problem = "yes";
       			return false;
   		}

PHP


// check email

$emailpattern = "^[a-zA-Z0-9][a-z0-9_.-]*@[a-z0-9.-]+\.[a-z]{2,4}$";
		
if	(
	(trim(strlen($_POST['email']))  > 0)
		
			and
					
	(!$_POST['email'] == "[none]")
			
			and

	(!eregi ($emailpattern, stripslashes(trim($_POST['email']))))
	)
		{
		$inputerror		=	TRUE;
		$inputerrormessage	.=	"<br />* An invalid email address was entered";
		}


Incomprehensible? Yes, absolutely.

Useful? More than you can realize until you are actually faced with the problem of, say, verifying that a user has input a valid email format, or trying to figure out whether a user-input IMG tag is using the correct syntax; or else maybe trying to convert a huge web page from XHTML 1.1 to HTML 4.01 because you've determined that the browser is syntactically crippled.

And, once you get deep into it, the stuff is actually intriguing and fun.

 

Please Let Us Know What You Think

 
 

(HTML tags provide better formatting)
 

35 Blogs

SQL To Exclude A List Of Items
How do you select everything from one table except for a list contained in another table?

mysql_insert_assoc
How to make MySQL insertions easier (and safe)

DHTML, PHP and MySQL References
What are good references for advanced website development?

MySQL server has gone away
What to do when your MySQL connection is timing out for no apparently good reason, all of a sudden.

mysql_update_assoc
Easily (and quote_smart-ly) update a record in a MySQL database table

HTML Anchor without an HREF?
How to execute a JavaScript function and nothing else.

PHP script to display Google PageRank
It is very handy to know the Google PageRank of your pages. Here's a PHP script that figures it out for you.

Create and send CSV files from PHP
The ability to create a CSV file from MySQL data in PHP, download it and have it open automatically in Excel is very handy.

Editing HTML with PHP scripts
To provide a PHP script that allows a user to edit HTML requires a few tricks that are hard to hack through but are elegantly documented in the manual.

Escaping for PHP Output to JavaScript
To send data to a JavaScript script from PHP, three levels of escaping are required

Google Maps Icons
Google Maps/Earth do not make icon creation & manipulation easy.

XHTML vs. HTML
XHTML is advanced HTML. Not all browsers support it, so pages must test first and serve what's supported.

PHP output buffering improves response time. Gzip speeds internet transmission, compressing text volume (not images) up to 80%.

Converting Mayan Dates
Routines to convert to and from the Mayan Long Count date system

Date Math
If the function to calculate the number of days between two dates isn't built in, it's a pain to figure out

Embed Flash as Valid XHTML
The embed tag doesn't validate with XHTML. Here's what to do.

What's a Tranche?
The French term for slice.

Parsing name-value pair attributes in an HTML tag
Regexp HTML Attribute Parsing: Pulling out the value of numerous attributes in an HTML tag is a mind bender

Regex URL Matching
On this site we check for the existence of a URL whenever an entry is updated. A Regex (regular expression) string was the breakthrough.

HTML Forms
How to open a form in a new window when a radio button is clicked.

Static vs Dynamic URLs
Implementing static URLs for a website driven by PHP and MySQL is as easy as a little regex and htaccess magic.

BU to Pier 7 GPS Tour
Example of Including a KML file

Send a KML file from disk using PHP
Preprocessing a kml or kmz disk file improves the user experience

Call KML files from within a blog or topic
How to create a button to call a KML or KMZ file

Process .htm and .html as php
How to include php scripts in html files

Geo Positioning
With the advent of Google Earth, the tagging of websites, blogs and photographs with latitude and longitude information has taken a great leap forward.

RSS Feeds
RSS (syndication) feeds come in many flavors. We provide the most common.

Font Families
A survey of the most-commonly installed fonts found on Windows machines

CSS Zen Garden Suggestions
Here's a list of the designs I think are worth a look, illustrating the great power of CSS.

Ampersand Madness: Convert &#x26; to &#x26;amp; to prevent XHTML errors
A regex solution to the huge problem of ampersand encoding in XHTML

RSS, Atom, Syndication, etc.
The world is full of XML and XML-like file formats for syndication-feed purposes. Why they must all be different, God alone can tell. But the reason there is lousy documentation is the evil work of Man.

Captcha
Captcha is the term for security codes the user must enter into a form

Javascript: document.write and XHTML
Document.write does not work with "true" XHTML. Don't bother trying to fix the javascript.

Open a new window with XHTML
Prior to XHTML, you could open a new window with a link by saying target="_blank". That's no longer allowed, but what can you do?

Web hosting providers
We have used two service providers: one good, the other poor.

Regular Expressions
Regular Expressions, regex, are an obscure but very powerful pattern-matching tool that every developer should learn.