diff --git a/heading_norm_filter.admin.inc b/heading_norm_filter.admin.inc
new file mode 100644
index 0000000..31df9f2
--- /dev/null
+++ b/heading_norm_filter.admin.inc
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Provides admin form for Heading Normalizer
+ */
+
+/**
+ * Form constructor to configre Heading Normalize settings
+ *
+ * @return
+ *   array
+ */
+function heading_norm_filter_settings() {
+  $form = array();
+
+  if (!_heading_norm_filter_is_enabled()) {
+    drupal_set_message(t('Heading Normalize is not yet enabled for at least one <a href="@input-formats-admin-path">input formats</a>.', array('@input-formats-admin-path' => url('admin/config/content/formats'))), 'error');
+  }
+
+  $form['heading_norm_filter'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Heading Normalize Settings'),
+    '#collapsible' => TRUE,
+    '#description' => t('General Settings'),
+  );
+  $form['heading_norm_filter'][HEADING_NORM_FILTER_VAR_NODE_START_LEVEL] = array(
+    '#type' => 'select',
+    '#title' => t('Set the Highest H-tag that should be allowed in Nodes'),
+    '#description' => t('In HTML5, nodes should typically begin at H1.'),
+    '#default_value' => variable_get(HEADING_NORM_FILTER_VAR_NODE_START_LEVEL, HEADING_NORM_FILTER_VAR_NODE_START_LEVEL_DEFAULT),
+    '#options' => array(
+      '1' => t('<h1>'),
+      '2' => t('<h2>'),
+      '3' => t('<h3>'),
+      '4' => t('<h4>'),
+      '5' => t('<h5>'),
+      '6' => t('<h6>'),
+    ),
+  );
+  $form['heading_norm_filter'][HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL] = array(
+    '#type' => 'select',
+    '#title' => t('Set the Highest H-tag that should be allowed in Views'),
+    '#description' => t('In HTML5, views should typically begin at H3.'),
+    '#default_value' => variable_get(HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL, HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL_DEFAULT),
+    '#options' => array(
+      '1' => t('<h1>'),
+      '2' => t('<h2>'),
+      '3' => t('<h3>'),
+      '4' => t('<h4>'),
+      '5' => t('<h5>'),
+      '6' => t('<h6>'),
+    ),
+  );
+  return system_settings_form($form);
+}
diff --git a/heading_norm_filter.info b/heading_norm_filter.info
index 1eed4e5..043ecd3 100755
--- a/heading_norm_filter.info
+++ b/heading_norm_filter.info
@@ -1,4 +1,4 @@
 name = Heading Normalize
-description = Normalizes Headings...
+description = Normalizes heading tags within content and views
 package = Input filters
-core = 6.x
+core = 7.x
diff --git a/heading_norm_filter.module b/heading_norm_filter.module
index f2940bd..b146b6a 100644
--- a/heading_norm_filter.module
+++ b/heading_norm_filter.module
@@ -1,4 +1,9 @@
 <?php
+
+/*****************************************************************************\
+ * HOOKS
+ \*****************************************************************************/
+
 /**
  * @file
  * Filter to normalize html headings
@@ -10,152 +15,64 @@
 
 define('HEADING_NORM_PROTECTOR', '_heading_norm_filter_');
 
-/**
- * Implementation of hook_filter().
- */
-function heading_norm_filter_filter($op, $delta = 0, $format = -1, $text = '') {
-  switch ($op) {
-    case 'list':
-      return array(0 => t('Heading Normalize'));
+define('HEADING_NORM_FILTER_VAR_NODE_START_LEVEL', 'heading_norm_filter_node_start_level');
+define('HEADING_NORM_FILTER_VAR_NODE_START_LEVEL_DEFAULT', 1);
 
-    case 'description':
-      return t('Normalize Heading (h1..h6).');
+define('HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL', 'heading_norm_filter_view_start_level');
+define('HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL_DEFAULT', 3);
 
-    case 'prepare':
-      return $text;
-
-    case 'process':
-      return heading_norm_filter_process($text);
-
-    case 'default':
-      return $text;
-  }
+/**
+ * Implements hook_filter_info().
+ */
+function heading_norm_filter_filter_info() {
+  return array(
+    'heading_norm_filter' => array(
+      'title' => t('Normalize headings'),
+      'description' => t('Adjusts headings within content to a maximum level and maintains consistency within child levels.'),
+      'process callback' => '_heading_norm_filter_process',
+      // TODO remove this line when development is complete
+      'cache' => FALSE,
+    ),
+  );
 }
 
 /**
- * Implementation of hook_menu().
- *
- * @param $may_cache
- *   boolean indicating whether cacheable menu items should be returned
- *
- * @return
- *   array of menu information
+ * Implements hook_menu().
  */
 function heading_norm_filter_menu() {
-  $items = array();
-  $items['admin/settings/heading_norm_filter'] = array(
-    'title' => 'Heading Normalize',
-    'description' => 'Normalize Headings for Node Text.',
+  $items['admin/config/content/heading_norm_filter'] = array(
+    'title' => 'Heading Normalizer',
+    'description' => 'Configure the maximum heading levels allowed in nodes and views.',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('heading_norm_filter_settings'),
     'access arguments' => array('administer filters'),
+    'file' => 'heading_norm_filter.admin.inc',
   );
   return $items;
 }
 
-/*
- * Implementation of hook_theme().
- *
- * @param $may_cache
- *   boolean indicating whether cacheable menu items should be returned
- *
- * @return
- *   array of menu information
- */
-function heading_norm_filter_theme($existing, $type, $theme, $path) {
-  return array(
-    'heading_norm_filter_heading' => array(
-      'arguments' => array('level' => NULL, 'origlevel' => NULL),
-    ),
-  );
-}
-
-/*
- * Theme heading out of h1..h6
- *
- * @param $level
- *   output level for headings of level>6 7..8..9
- * @param $origlevel
- *   level before normalization
- *
- * @return
- *   array [0] starting, [1] closing tag
- */
-function theme_heading_norm_filter_heading($level, $origlevel) {
-  // protect heading_norm_filter by h_heading_norm_filte
-  if ($level>6) {
-    return array(
-      0 => '<p class="heading-norm-orig-h' . $origlevel . ' heading-norm-h' . $level . '">',
-      1 => '</p>',
-    );
-  }
-  else {
-    return array(
-      0 => '<h' . HEADING_NORM_PROTECTOR . $level . ' class="heading-norm-orig-h' . $origlevel . '">',
-      1 => '</h' . HEADING_NORM_PROTECTOR . $level . '>',
-  );
-  }
-}
+/*****************************************************************************\
+ * PRIVATE FUNCTIONS
+ \*****************************************************************************/
 
 /**
- * heading_norm_filter settings form builder function.
+ * Indicates whether or not heading_norm_filter is enabled for at least on
+ * input format.
  *
  * @return
- *   form
+ *   boolean
  */
-function heading_norm_filter_settings() {
-  $form = array();
-
-  // Check if filter is enabled
-  $inline_activated = FALSE;
+function _heading_norm_filter_is_enabled() {
   foreach (filter_formats() as $format) {
     foreach (filter_list_format($format->format) as $filter) {
       if ($filter->module == 'heading_norm_filter') {
-        $heading_norm_filter_activated = TRUE;
-        break 2;
+        return TRUE;
       }
     }
   }
-  if ($heading_norm_filter_activated == FALSE) {
-    drupal_set_message(t('Heading Normalize is not yet enabled for at least one <a href="!formats">input format</a>.', array('!formats' => url('admin/settings/filters'))), 'error');
-  }
-
-  $form['heading_norm_filter'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Heading Normalize Settings'),
-    '#collapsible' => TRUE,
-    '#description' => t('General Settings'),
-  );
-  $form['heading_norm_filter']['heading_norm_filter_starth'] = array(
-    '#type' => 'select',
-    '#title' => t('Set the Highest H-tag that should be allowed in Nodes'),
-    '#default_value' => variable_get('heading_norm_filter_starth', 1),
-    '#options' => array(
-      '0' => t('<h1>'),
-      '1' => t('<h2>'),
-      '2' => t('<h3>'),
-      '3' => t('<h4>'),
-      '4' => t('<h5>'),
-      '5' => t('<h6>'),
-    ),
-  );
-  $form['heading_norm_filter']['heading_norm_filter_viewsh'] = array(
-    '#type' => 'select',
-    '#title' => t('Set the Highest H-tag that should be allowed in Views'),
-    '#default_value' => variable_get('heading_norm_filter_viewsh', 2),
-    '#options' => array(
-      '0' => t('<h1>'),
-      '1' => t('<h2>'),
-      '2' => t('<h3>'),
-      '3' => t('<h4>'),
-      '4' => t('<h5>'),
-      '5' => t('<h6>'),
-    ),
-  );
-  return system_settings_form($form);
+  return FALSE;
 }
 
-
 /**
  * Process the filter on text
  *
@@ -167,58 +84,82 @@ function heading_norm_filter_settings() {
  * @return
  *   string replaced text
  */
-function heading_norm_filter_process($text) {
+function _heading_norm_filter_process($text, $filter, $format, $langcode, $cache, $cacheid) {
   $view = FALSE;
   if (module_exists('views')) {
     $view = views_get_current_view();
   }
   if ($view) {
-    $starttag = variable_get('heading_norm_filter_viewsh', 2);
+    $starttag = variable_get(HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL, HEADING_NORM_FILTER_VAR_VIEW_START_LEVEL_DEFAULT);
   }
   else {
-    $starttag = variable_get('heading_norm_filter_starth', 1);
+    $starttag = variable_get(HEADING_NORM_FILTER_VAR_NODE_START_LEVEL, HEADING_NORM_FILTER_VAR_NODE_START_LEVEL_DEFAULT);
   }
-  $starttag++;
-  $h = heading_norm_filter_tag_find($text);
-  $text = heading_norm_filter_tag_replace($text, $starttag, $h);
+
+  $h = _heading_norm_filter_tag_find($text);
+
+  $text = _heading_norm_filter_tag_replace($text, $starttag, $h);
   return $text;
 }
 
-/*
- * Check h1..6 tag occurence
+/**
+ * Determine which heading tags exist within the provided content
  */
-function heading_norm_filter_tag_find($text) {
-  for ($i=1; $i<=6; $i++) {
-    // TODO match <h with classes & more
-    if (strpos($text, '<h' . $i . '>') !== FALSE) {
-      $h[$i] = 1;
+function _heading_norm_filter_tag_find($text) {
+  for ($i = 1; $i <= 6; $i++) {
+    if (stripos($text, '<h' . $i) !== FALSE) {
+      $h[$i] = TRUE;
     }
     else {
-      $h[$i] = 0;
+      $h[$i] = FALSE;
     }
   }
   return $h;
 }
 
-/*
- * Replace tags according to our state information
- *
- * A temporary protector is getting applied to the text
+/**
+ * @todo Remove .*? regex
  */
-
-function heading_norm_filter_tag_replace($text, $start, $existent) {
-  // check global state
+function _heading_norm_filter_tag_replace($text, $start, $headings) {
   $j = $start;
-  for ($i=1; $i<=6; $i++) {
-    // TODO check markup structure h up/same/+1
-    if ($existent[$i]==1) {
-      $replace = theme('heading_norm_filter_heading', $j, $i);
-      $text = str_replace('<h' . $i . '>', $replace[0], $text);
-      $text = str_replace('</h' . $i . '>', $replace[1], $text);
+
+  for ($i = 1; $i <= 6; $i++) {
+    if ($headings[$i] === TRUE) {
+      $callback = function($match) use($j) {
+        $level = $match[1];
+        $atts = $match[2];
+
+        // Initialize the $attributes array with a class element to prevent
+        // PHP notices
+        $attributes = array(
+          'class' => NULL,
+        );
+
+        // If the heading tag already contains attributes, split them apart and
+        // put them in the attributes array so that the heading-norm-filter
+        // class can be added to them.
+        // We can't do a simple split on spaces since some attributes (e.g.
+        // class) may have spaces within their respective values
+        $pattern = '/\s*(\w+)="(.*?)"\s*/';
+        if (drupal_strlen($atts) > 0 && preg_match_all($pattern, $atts, $matches)) {
+          for ($i = 0; $i < count($matches[0]); $i++) {
+            $attributes[$matches[1][$i]] = $matches[2][$i];
+          }
+        }
+
+        $attributes['class'] .= empty($attributes['class']) ? 'heading-norm-filter-orig-h' . $level : ' heading-norm-filter-orig-h' . $level;
+        $ret = '<h' . HEADING_NORM_PROTECTOR . $j . ' ' . drupal_attributes($attributes) . '>';
+
+        return $ret;
+      };
+
+      $text = preg_replace_callback('/<h(' . $i . ')(.*?)>/i', $callback, $text);
+      $text = str_ireplace('</h' . $i . '>', '</h' . $j . '>', $text);
     }
     $j++;
   }
-  // remove protector
   $text = str_replace(HEADING_NORM_PROTECTOR, '', $text);
+
   return $text;
 }
+
