diff --git a/glossify.module b/glossify.module
index 3b85e19..eba3654 100644
--- a/glossify.module
+++ b/glossify.module
@@ -364,6 +364,11 @@ function glossify_filter($op, $delta = 0, $format = -1, $text = '', $cache_id =
     case 'process':
       // TODO: option should be to only do this on a node page
       // or have a list of paths where this filter will not be invoked
+      if (empty($text)) {
+        // nothing to do
+        return $text;
+      }
+
       if (($node = menu_get_object()) == NULL) {
         $q = db_query("SELECT nid FROM {node_revisions} WHERE '%s'=CONCAT(format, ':', MD5(body))", $cache_id);
         $r = db_fetch_object($q);
@@ -377,23 +382,37 @@ function glossify_filter($op, $delta = 0, $format = -1, $text = '', $cache_id =
 
       if (!isset($configurations)) {
         $configurations = variable_get('glossify_configurations', array());
+        foreach ($configurations as $key => &$config) {
+          if (isset($config['exclude_tags']) && !empty($config['exclude_tags'])) {
+            $html_tags = preg_split("/\r\n|\r|\n|,/", $config['exclude_tags']);
+            $config['exclude_tags'] = array_combine($html_tags, $html_tags);
+          }
+        }
       }
 
+      if (class_exists('DOMDocument', false)) {
+        $text = _glossify_replace_terms_phpdom($text, $configurations, $node);
+      }
+      else {
+        $text = _glossify_replace_terms_simplehtmldom($text, $configurations, $node);
+      }
+
+      return $text;
+
+    default:
+      return $text;
+  }
+}
+
+function _glossify_replace_terms_simplehtmldom($text, $configurations, $node) {
       $html_body = str_get_html($text);
       foreach ($configurations as $config_name => $configuration) {
-        if (isset($configuration['from'][$node->type]) && ($configuration['style']['hovertip'] || $configuration['style']['links'])) {  // We only want to replace stuff for links and hovertip styles
+    if (isset($configuration['from'][$node->type])) {
           $enabled_styles = array_filter($configuration['style']);
-          foreach (_fetch_possible_keywords($config_name, $configuration, $node->nid) as $kwd) {
-            list($term_title, $target_url) = $kwd;
+      foreach (_fetch_possible_keywords($configuration, $node->nid) as $term_title => $target_url) {
             $replaced = 0;
-            if (($configuration['link_self'] == 1) || (($configuration['link_self'] == 0) && ($term_title !== $node->title))) {
               foreach ($enabled_styles as $style => $enabled) {
-                if (!empty($term_title) && false !== strpos($html_body->innertext, $term_title)) {
-                  if (isset($configuration['excl_tags']) && !empty($configuration['excl_tags'])) {
-                    $html_tags = preg_split("/\r\n|\r|\n|,/", $configuration['excl_tags']);
-                    $config['excl_tags'] = array_combine($html_tags, $html_tags);
-                  }
-                  
+          if ($enabled && !empty($term_title) && false !== strpos($html_body->innertext, $term_title)) {
                   $replacement = _fetch_replacement($style, $term_title, $target_url);
                   _glossify_replace($configuration, $html_body, $term_title, $replacement, $replaced);
                   // update the state of the DOM to reflect new tags added.
@@ -405,15 +424,103 @@ function glossify_filter($op, $delta = 0, $format = -1, $text = '', $cache_id =
             }
           }
         }
-      }
       return $html_body->innertext;
+}
 
-    default:
-      return $text;
+/**
+ * Uses PHP's built in DOM extension if available.
+ * Adapted from http://stackoverflow.com/questions/3151064/find-and-replace-keywords-by-hyperlinks-in-an-html-fragment-via-php-dom
+ *
+ * @param  $text
+ * @param  $configurations
+ * @param  $node
+ * @return text
+ */
+function _glossify_replace_terms_phpdom($text, $configurations, $node) {
+  $original = $text;
+  $text = '<meta http-equiv="content-type" content="text/html; charset=utf-8">'
+        . $text;
+
+  $dom = new DOMDocument;
+  $dom->formatOutput = true;
+  @$dom->loadHTML($text);
+  $xpath = new DOMXpath($dom);
+
+  // if there are no text nodes, there is nothing to do
+  $tnodes = $xpath->query('//text()');
+  if (0 == $tnodes->length) {
+    return $original;
+  }
+
+  $has_replacements = false;
+  foreach ($configurations as $config_name => $configuration) {
+    if (isset($configuration['from'][$node->type])) {
+      $enabled_styles = array_filter($configuration['style']);
+      $exclude_tags = $configuration['exclude_tags']
+               + array('iframe' => 'iframe', 'a' => 'a', 'object' => 'object');
+      foreach (_fetch_possible_keywords($configuration, $node->nid) as $term_title => $target_url) {
+        foreach ($enabled_styles as $style => $enabled) {
+          $rx = '/\b' . $term_title . '\b/';
+          if ($enabled
+              && !empty($term_title)
+              && 0 < preg_match($rx, $text)) {
+            $replacement = _fetch_replacement($style, $term_title, $target_url);
+            $nodes = $xpath->query('//text()[contains(., "' . $term_title . '")]');
+            $count = 0;
+            foreach ($nodes as $node) {
+
+              // don't replace within excluded tags
+              if (isset($exclude_tags[$node->parentNode->nodeName])) {
+                continue;
+              }
+
+              // if configured for only first occurrence, bail early
+              if (1 == $configuration['only_first']
+                  && $count >= 1) {
+                break 1;
+              }
+
+              // if configured for only first occurrence,
+              // we need to use preg_replace to limit replacements
+              if (1 == $configuration['only_first']) {
+                $replaced = preg_replace($rx, $replacement, $node->wholeText, 1);
+                $count = $count + 1;
+              } else {
+                $replaced = str_replace($term_title, $replacement, $node->wholeText, $result);
+                $count += $result;
+              }
+              $newNode = $dom->createDocumentFragment();
+              $newNode->appendXML($replaced);
+              $node->parentNode->replaceChild($newNode, $node);
+              $has_replacements = true;
+            }
+          }
+        }
+      }
   }
 }
 
+  if (false == $has_replacements) {
+    return $original;
+  }
 
+  // return just what is in the body tags of our document
+  $body = $dom->GetElementsByTagName('body')->item(0);
+  if ($body instanceof DOMNode) {
+    // PHP 5.2 compatibility, saveHTML does not accept args.
+    $return = new DOMDocument;
+    $root = $return->createElement('body');
+    $root = $return->appendChild($root);
+    $node = $return->importNode($body, true);
+    $return->documentElement->appendChild($node);
+    $out = $return->saveHTML();
+    $out = str_replace(array('<body>', '</body>'), '', $out);
+    return $out;
+  }
+  else {
+    return $original;
+  }
+}
 /**
  * Helper function that emulates a contributing module's nodeapi load operation
  * @param stdObject
