diff --git a/includes/media.filter.inc b/includes/media.filter.inc
index 64b72a3..a353d8e 100644
--- a/includes/media.filter.inc
+++ b/includes/media.filter.inc
@@ -313,12 +313,11 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     $fields = media_filter_field_parser($tag_info);
 
     $attributes = is_array($tag_info['attributes']) ? $tag_info['attributes'] : array();
-    $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
+    $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default());
     $settings['attributes'] = array_intersect_key($attributes, array_flip($attribute_whitelist));
     $settings['fields'] = $fields;
 
     if (!empty($tag_info['attributes']) && is_array($tag_info['attributes'])) {
-      $attribute_whitelist = variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align'));
       $settings['attributes'] = array_intersect_key($tag_info['attributes'], array_flip($attribute_whitelist));
       $settings['fields'] = $fields;
 
@@ -379,6 +378,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     // Display the field elements.
     $element = array();
     $element['content']['file'] = media_get_file_without_label($file, $tag_info['view_mode'], $settings);
+
     // Overwrite or set the file #alt attribute if it has been set in this
     // instance.
     if (!empty($element['content']['file']['#attributes']['alt'])) {
@@ -392,6 +392,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
     field_attach_prepare_view('file', array($file->fid => $file), $tag_info['view_mode']);
     entity_prepare_view('file', array($file->fid => $file));
     $element['content'] += field_attach_view('file', $file, $tag_info['view_mode']);
+
     if (count(element_children($element['content'])) > 1) {
       // Add surrounding divs to group them together.
       // We dont want divs when there are no additional fields to allow files
@@ -404,6 +405,7 @@ function media_token_to_markup($match, $wysiwyg = FALSE) {
       );
     }
   }
+
   drupal_alter('media_token_to_markup', $element, $tag_info, $settings);
   return drupal_render($element);
 }
@@ -668,18 +670,46 @@ function media_get_file_without_label($file, $view_mode, $settings = array()) {
   // support simple formatters that don't do this, set the element attributes to
   // what was requested, but not if the formatter applied its own logic for
   // element attributes.
-  if (!isset($element['#attributes']) && isset($settings['attributes'])) {
-    $element['#attributes'] = $settings['attributes'];
+  if (isset($settings['attributes'])) {
+    if (empty($element['#attributes'])) {
+      $element['#attributes'] = $settings['attributes'];
+    }
 
     // While this function may be called for any file type, images are a common
-    // use-case. theme_image() and theme_image_style() require the 'alt'
-    // attribute to be passed separately from the 'attributes' array (see
-    // http://drupal.org/node/999338). Until that's fixed, implement this
-    // special-case logic. Image formatters using other theme functions are
-    // responsible for their own 'alt' attribute handling. See
-    // theme_media_formatter_large_icon() for an example.
-    if (isset($settings['attributes']['alt']) && !isset($element['#alt']) && isset($element['#theme']) && in_array($element['#theme'], array('image', 'image_style'))) {
-      $element['#alt'] = $settings['attributes']['alt'];
+    // use-case, and image theme functions have their own structures for
+    // render arrays.
+    if (isset($element['#theme'])) {
+      switch ($element['#theme']) {
+        case 'image':
+        case 'image_style':
+          // theme_image() and theme_image_style() require the 'alt' attributes to
+          // be passed separately from the 'attributes' array. (see
+          // http://drupal.org/node/999338). Until that's fixed, implement this
+          // special-case logic. Image formatters using other theme functions are
+          // responsible for their own 'alt' attribute handling. See
+          // theme_media_formatter_large_icon() for an example.
+          if (empty($element['#alt']) && isset($settings['attributes']['alt'])) {
+            $element['#alt'] = $settings['attributes']['alt'];
+          }
+          break;
+
+        case 'image_formatter':
+          // theme_image_formatter() requires the attributes to be
+          // set on the item rather than the element itself.
+          if (empty($element['#item']['attributes'])) {
+            $element['#item']['attributes'] = $settings['attributes'];
+          }
+
+          // theme_image_formatter() also requires alt, title, height, and
+          // width attributes to be set on the item rather than within its
+          // attributes array.
+          foreach (array('alt', 'title', 'width', 'height') as $attr) {
+              if (isset($settings['attributes'][$attr])) {
+                  $element['#item'][$attr] = $settings['attributes'][$attr];
+              }
+          }
+          break;
+      }
     }
   }
 
diff --git a/js/media.filter.js b/js/media.filter.js
index 2e4ceda..d58f70c 100644
--- a/js/media.filter.js
+++ b/js/media.filter.js
@@ -14,36 +14,33 @@
      * @param content
      */
     replaceTokenWithPlaceholder: function(content) {
-      var tagmap = Drupal.media.filter.ensure_tagmap(),
-        matches = content.match(/\[\[.*?\]\]/g),
-        media_definition,
-        id = 0;
-
-      if (matches) {
-        var i = 1;
-        for (var macro in tagmap) {
-          var index = matches.indexOf(macro);
-          if (index !== -1) {
-            var media_json = macro.replace('[[', '').replace(']]', '');
-
-            // Make sure that the media JSON is valid.
-            try {
-              media_definition = JSON.parse(media_json);
-            }
-            catch (err) {
-              media_definition = null;
-            }
-            if (media_definition) {
-              // Apply attributes.
-              var element = Drupal.media.filter.create_element(tagmap[macro], media_definition);
-              var markup = Drupal.media.filter.outerHTML(element);
-
-              content = content.replace(macro, Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i));
-            }
+      var matches = content.match(/\[\[.*?\]\]/g);
+
+      if (!!matches) {
+        for (i in matches) {
+          var match = matches[i];
+          var media_json = match.replace('[[', '').replace(']]', '');
+
+          // Ensure that the media JSON is valid.
+          try {
+            var media_definition = JSON.parse(media_json);
+
+            // Create the element.
+            var element = document.createElement(media_definition.tagName);
+            element.src = media_definition.src;
+
+            // Apply attributes.
+            var element = Drupal.media.filter.create_element(element, media_definition);
+            var markup = Drupal.media.filter.outerHTML(element);
+
+            content = content.replace(match, Drupal.media.filter.getWrapperStart(i) + markup + Drupal.media.filter.getWrapperEnd(i));
+          }
+          catch (err) {
+            // TODO: Error logging.
           }
-          i++;
         }
       }
+
       return content;
     },
 
@@ -52,20 +49,26 @@
      * @param content
      */
     replacePlaceholderWithToken: function(content) {
-      var tagmap = Drupal.media.filter.ensure_tagmap();
-      var i = 1;
-      for (var macro in tagmap) {
-        var startTag = Drupal.media.filter.getWrapperStart(i), endTag = Drupal.media.filter.getWrapperEnd(i);
-        var startPos = content.indexOf(startTag), endPos = content.indexOf(endTag);
-        if (startPos !== -1 && endPos !== -1) {
-          // If the placeholder wrappers are empty, remove the macro too.
-          if (endPos - startPos - startTag.length === 0) {
-            macro = '';
-          }
-          content = content.substr(0, startPos) + macro + content.substr(endPos + (new String(endTag)).length);
-        }
-        i++;
-      }
+      Drupal.settings.tagmap = [];
+
+      // Convert all xhtml markup to html for reliable matching/replacing.
+      content = content.replace(/[\s]\/\>/g, '>');
+
+      // Re-build the macros in case any element has changed in the editor.
+      $('.media-element', content).each(function(i, el) {
+        // TODO zero-index wrapper indices.
+        var startTag = Drupal.media.filter.getWrapperStart(i + 1),
+          endTag = Drupal.media.filter.getWrapperEnd(i + 1),
+          macro = Drupal.media.filter.create_macro($(el));
+
+        Drupal.settings.tagmap[macro] = el.outerHTML;
+
+        content = content
+          .replace(el.outerHTML, macro)
+          .replace(startTag, '')
+          .replace(endTag, '');
+      });
+
       return content;
     },
 
@@ -182,12 +185,16 @@
         file_info.attributes = {};
 
         // Extract whitelisted attributes.
-        $.each(Drupal.media.filter.allowed_attributes, function(i, a) {
+        $.each(Drupal.media.filter.allowed_attributes(), function(i, a) {
           if (value = element.attr(a)) {
             file_info.attributes[a] = value;
           }
         });
         delete(file_info.attributes['data-file_info']);
+
+        // Set tagName and src to rebuild later.
+        file_info.tagName = element[0].tagName;
+        file_info.src = element[0].src;
       }
 
       return file_info;
diff --git a/media.module b/media.module
index f2720cb..8eca84e 100644
--- a/media.module
+++ b/media.module
@@ -1292,3 +1292,27 @@ function _media_get_migratable_file_types() {
 
   return array_diff($types, $enabled_types);
 }
+
+/**
+ * Returns the default set of allowed attributes for use with WYSIWYG.
+ *
+ * @return Array of whitelisted attributes.
+ */
+function _media_wysiwyg_allowed_attributes_default() {
+  return array(
+    'alt',
+    'title',
+    'height',
+    'width',
+    'hspace',
+    'vspace',
+    'border',
+    'align',
+    'style',
+    'class',
+    'id',
+    'usemap',
+    'data-picture-group',
+    'data-picture-align',
+  );
+}
diff --git a/wysiwyg_plugins/media.inc b/wysiwyg_plugins/media.inc
index d90b79f..611ee60 100644
--- a/wysiwyg_plugins/media.inc
+++ b/wysiwyg_plugins/media.inc
@@ -68,7 +68,7 @@ function media_include_browser_js() {
     }
   }
   // Add wysiwyg-specific settings.
-  $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', array('height', 'width', 'hspace', 'vspace', 'border', 'align', 'style', 'class', 'id', 'usemap', 'data-picture-group', 'data-picture-align')));
+  $settings = array('wysiwyg_allowed_attributes' => variable_get('media__wysiwyg_allowed_attributes', _media_wysiwyg_allowed_attributes_default()));
   drupal_add_js(array('media' => $settings), 'setting');
 }
 
