Repository: Python-Markdown/markdown
Pull request #626 (2018-01-11 04:30:04)

Deprecations
Cherry-picked a bunch of commits from md3 branch which remove deprecated code.

facelessuser: Nice to see some of the old baggage removed.

Patch:
diff --git a/docs/extensions/header_id.md b/docs/extensions/header_id.md
deleted file mode 100644
index 82e8e2c..0000000
--- a/docs/extensions/header_id.md
+++ /dev/null
@@ -1,131 +0,0 @@
-title: HeaderId Extension
-
-HeaderId
-========
-
-Summary
--------
-
-The HeaderId extension automatically generates `id` attributes for the header
-elements (`h1`-`h6`) in the resulting HTML document.
-
-This extension is included in the standard Markdown library.
-
-!!! warning
-    This extension is **Pending Deprecation**. The [Table of Contents][toc]
-    Extension should be used instead, which offers most the features of this
-    extension and more.
-
-[toc]: toc.md
-
-Syntax
-------
-
-By default, all headers will automatically have unique `id` attributes
-generated based upon the text of the header (see below to turn this off).
-Note this example, in which all three headers would have the same `id`:
-
-```md
-#Header
-#Header
-#Header
-```
-
-Results in:
-
-```html
-<h1 id="header">Header</h1>
-<h1 id="header_1">Header</h1>
-<h1 id="header_2">Header</h1>
-```
-
-Usage
------
-
-See [Extensions](index.md) for general extension usage, specify
-`markdown.extensions.headerid` as the name of the extension.
-
-See the [Library Reference](../reference.md#extensions) for information about
-configuring extensions.
-
-The following options are provided to configure the output:
-
-* **`level`**: Base level for headers.
-
-    Default: `1`
-
-    The `level` setting allows you to automatically adjust the header levels to
-    fit within the hierarchy of your HTML templates. For example, suppose the
-    markdown text for a page should not contain any headers higher than level 3
-    (`<h3>`). The following will accomplish that:
-
-        :::pycon
-        >>>  text = '''
-        ... #Some Header
-        ... ## Next Level'''
-        >>> from markdown.extensions.headerid import HeaderIdExtension
-        >>> html = markdown.markdown(text, extensions=[HeaderIdExtension(level=3)])
-        >>> print html
-        <h3 id="some_header">Some Header</h3>
-        <h4 id="next_level">Next Level</h4>'
-
-* **`forceid`**: Force all headers to have an id.
-
-    Default: `True`
-
-    The `forceid` setting turns on or off the automatically generated ids for
-    headers that do not have one explicitly defined (using the
-    [Attribute List](attr_list.md) extension).
-
-        :::pycon
-        >>> text = '''
-        ... # Some Header
-        ... # Header with ID # { #foo }'''
-        >>> html = markdown.markdown(text,
-                                     extensions=['markdown.extensions.attr_list',
-                                                 HeaderIdExtension(forceid=False)])
-        >>> print html
-        <h1>Some Header</h1>
-        <h1 id="foo">Header with ID</h1>
-
-* **`separator`**: Word separator. Character which replaces white space in id.
-
-    Default: `-`
-
-* **`slugify`**: Callable to generate anchors.
-
-    Default: `markdown.extensions.headerid.slugify`
-
-    If you would like to use a different algorithm to define the ids, you can
-    pass in a callable which takes two arguments:
-
-    * `value`: The string to slugify.
-    * `separator`: The Word Separator.
-
-Using with Meta-Data
---------------------
-
-The HeaderId extension also supports the [Meta-Data](meta_data.md) extension.
-Please see the documentation for that extension for specifics. The supported
-meta-data keywords are:
-
-* `header_level`
-* `header_forceid`
-
-When used, the meta-data will override the settings provided through the
-`extension_configs` interface.
-
-This document:
-
-```md
-header_level: 2
-header_forceid: Off
-
-# A Header
-```
-
-Will result in the following output:
-
-```html
-<h2>A Header</h2>
-```
diff --git a/docs/reference.md b/docs/reference.md
index 7e5a30a..55fb501 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -88,7 +88,7 @@ __extensions__{: #extensions }
 
     If an extension name is provided as a string, the extension must be
     importable as a python module on your PYTHONPATH. Python's dot notation is
-    supported. Therefore, to import the 'extra' extension, one could do
+    required. Therefore, to import the 'extra' extension, one would do
     `extensions=['markdown.extensions.extra']`
 
     Additionally, a Class may be specified in the name. The class must be at the
@@ -106,7 +106,7 @@ __extensions__{: #extensions }
 
     !!! note
         You should only need to specify the class name if more than one extension
-        is defined  within the same module. The extensions that come with
+        is defined within the same module. The extensions that come with
         Python-Markdown do *not* need to have the class name specified. However,
         doing so will not effect the behavior of the parser.
 
@@ -167,91 +167,13 @@ __output_format__{: #output_format }:
         `"html4"`) be used as the more general formats (`"xhtml"` or `"html"`) may
         change in the future if it makes sense at that time.
 
-__safe_mode__{: #safe_mode }:
-
-:    Disallow raw HTML.
-
-    !!! warning
-        "`safe_mode`" is deprecated and should not be used.
-
-        HTML sanitizers (like [Bleach]) provide a better solution for
-        dealing with markdown text submitted by untrusted users.
-
-            :::python
-            import markdown
-            import bleach
-            html = bleach.clean(markdown.markdown(untrusted_text))
-
-        See the [release notes] for more info.
-
-    The following values are accepted:
-
-    `False` (Default):
-
-    : Raw HTML is passed through unaltered.
-
-    `replace`:
-
-    : Replace all HTML blocks with the text assigned to
-      `html_replacement_text`. To maintain backward compatibility, setting
-      `safe_mode=True` will have the same effect as `safe_mode='replace'`.
-
-        To replace raw HTML with something other than the default, do:
-
-            :::python
-            md = markdown.Markdown(
-                safe_mode='replace',
-                html_replacement_text='--RAW HTML NOT ALLOWED--'
-            )
-
-    `remove`:
-
-    : All raw HTML will be completely stripped from the text with
-      no warning to the author.
-
-    `escape`:
-
-    : All raw HTML will be escaped and included in the document.
-
-        For example, the following source:
-
-            :::md
-            Foo <b>bar</b>.
-
-        Will result in the following HTML:
-
-            :::html
-            <p>Foo &lt;b&gt;bar&lt;/b&gt;.</p>
-
-    !!! Note
-        "safe_mode" also alters the default value for the
-        [`enable_attributes`](#enable_attributes) option.
-
-[Bleach]: https://github.com/jsocol/bleach
-[release notes]: change_log/release-2.6.md
-
-__html_replacement_text__{: #html_replacement_text }:
-
-: Text used when safe_mode is set to `replace`. Defaults to `[HTML_REMOVED]`.
-
-    !!! warning
-        "`html_replacement_text`" is deprecated and should not be used.
-        See the [release notes] for more info.
-
 __tab_length__{: #tab_length }:
 
 : Length of tabs in the source. Default: 4
 
 __enable_attributes__{: #enable_attributes}:
 
-: Enable the conversion of attributes. Defaults to `True`, unless
-  [`safe_mode`](#safe_mode) is enabled, in which case the default is `False`.
-
-    !!! Note
-        `safe_mode` only overrides the default. If `enable_attributes`
-        is explicitly set, the explicit value is used regardless of `safe_mode`.
-        However, this could potentially allow an untrusted user to inject
-        JavaScript into your documents.
+: Enable the conversion of attributes. Defaults to `True`.
 
 __smart_emphasis__{: #smart_emphasis }:
 
diff --git a/markdown/__init__.py b/markdown/__init__.py
index 5b8be47..584990f 100644
--- a/markdown/__init__.py
+++ b/markdown/__init__.py
@@ -32,498 +32,37 @@ License: BSD (see LICENSE for details).
 
 from __future__ import absolute_import
 from __future__ import unicode_literals
-from .__version__ import version, version_info  # noqa
-import codecs
-import sys
-import logging
-import warnings
-import importlib
-from . import util
-from .preprocessors import build_preprocessors
-from .blockprocessors import build_block_parser
-from .treeprocessors import build_treeprocessors
-from .inlinepatterns import build_inlinepatterns
-from .postprocessors import build_postprocessors
-from .extensions import Extension
-from .serializers import to_html_string, to_xhtml_string
+from .core import Markdown, markdown, markdownFromFile
 
-__all__ = ['Markdown', 'markdown', 'markdownFromFile']
-
-
-logger = logging.getLogger('MARKDOWN')
-
-
-class Markdown(object):
-    """Convert Markdown to HTML."""
-
-    doc_tag = "div"     # Element used to wrap document - later removed
-
-    option_defaults = {
-        'html_replacement_text': '[HTML_REMOVED]',
-        'tab_length':            4,
-        'enable_attributes':     True,
-        'smart_emphasis':        True,
-        'lazy_ol':               True,
-    }
-
-    output_formats = {
-        'html':   to_html_string,
-        'html4':  to_html_string,
-        'html5':  to_html_string,
-        'xhtml':  to_xhtml_string,
-        'xhtml1': to_xhtml_string,
-        'xhtml5': to_xhtml_string,
-    }
-
-    def __init__(self, *args, **kwargs):
-        """
-        Creates a new Markdown instance.
-
-        Keyword arguments:
-
-        * extensions: A list of extensions.
-           If they are of type string, the module mdx_name.py will be loaded.
-           If they are a subclass of markdown.Extension, they will be used
-           as-is.
-        * extension_configs: Configuration settings for extensions.
-        * output_format: Format of output. Supported formats are:
-            * "xhtml1": Outputs XHTML 1.x. Default.
-            * "xhtml5": Outputs XHTML style tags of HTML 5
-            * "xhtml": Outputs latest supported version of XHTML
-              (currently XHTML 1.1).
-            * "html4": Outputs HTML 4
-            * "html5": Outputs HTML style tags of HTML 5
-            * "html": Outputs latest supported version of HTML
-              (currently HTML 4).
-            Note that it is suggested that the more specific formats ("xhtml1"
-            and "html4") be used as "xhtml" or "html" may change in the future
-            if it makes sense at that time.
-        * safe_mode: Deprecated! Disallow raw html. One of "remove", "replace"
-          or "escape".
-        * html_replacement_text: Deprecated! Text used when safe_mode is set
-          to "replace".
-        * tab_length: Length of tabs in the source. Default: 4
-        * enable_attributes: Enable the conversion of attributes. Default: True
-        * smart_emphasis: Treat `_connected_words_` intelligently Default: True
-        * lazy_ol: Ignore number of first item of ordered lists. Default: True
-
-        """
-
-        # For backward compatibility, loop through old positional args
-        pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format']
-        for c, arg in enumerate(args):
-            if pos[c] not in kwargs:
-                kwargs[pos[c]] = arg
-            if c+1 == len(pos):  # pragma: no cover
-                # ignore any additional args
-                break
-        if len(args):
-            warnings.warn('Positional arguments are deprecated in Markdown. '
-                          'Use keyword arguments only.',
-                          DeprecationWarning)
-
-        # Loop through kwargs and assign defaults
-        for option, default in self.option_defaults.items():
-            setattr(self, option, kwargs.get(option, default))
-
-        self.safeMode = kwargs.get('safe_mode', False)
-        if self.safeMode and 'enable_attributes' not in kwargs:
-            # Disable attributes in safeMode when not explicitly set
-            self.enable_attributes = False
-
-        if 'safe_mode' in kwargs:
-            warnings.warn('"safe_mode" is deprecated in Python-Markdown. '
-                          'Use an HTML sanitizer (like '
-                          'Bleach https://bleach.readthedocs.io/) '
-                          'if you are parsing untrusted markdown text. '
-                          'See the 2.6 release notes for more info',
-                          DeprecationWarning)
-
-        if 'html_replacement_text' in kwargs:
-            warnings.warn('The "html_replacement_text" keyword is '
-                          'deprecated along with "safe_mode".',
-                          DeprecationWarning)
-
-        self.ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']',
-                              '(', ')', '>', '#', '+', '-', '.', '!']
-
-        self.registeredExtensions = []
-        self.docType = ""
-        self.stripTopLevelTags = True
-
-        self.build_parser()
-
-        self.references = {}
-        self.htmlStash = util.HtmlStash()
-        self.registerExtensions(extensions=kwargs.get('extensions', []),
-                                configs=kwargs.get('extension_configs', {}))
-        self.set_output_format(kwargs.get('output_format', 'xhtml1'))
-        self.reset()
-
-    def build_parser(self):
-        """ Build the parser from the various parts. """
-        self.preprocessors = build_preprocessors(self)
-        self.parser = build_block_parser(self)
-        self.inlinePatterns = build_inlinepatterns(self)
-        self.treeprocessors = build_treeprocessors(self)
-        self.postprocessors = build_postprocessors(self)
-        return self
-
-    def registerExtensions(self, extensions, configs):
-        """
-        Register extensions with this instance of Markdown.
-
-        Keyword arguments:
-
-        * extensions: A list of extensions, which can either
-           be strings or objects.  See the docstring on Markdown.
-        * configs: A dictionary mapping module names to config options.
-
-        """
-        for ext in extensions:
-            if isinstance(ext, util.string_type):
-                ext = self.build_extension(ext, configs.get(ext, {}))
-            if isinstance(ext, Extension):
-                ext.extendMarkdown(self, globals())
-                logger.debug(
-                    'Successfully loaded extension "%s.%s".'
-                    % (ext.__class__.__module__, ext.__class__.__name__)
-                )
-            elif ext is not None:
-                raise TypeError(
-                    'Extension "%s.%s" must be of type: "markdown.Extension"'
-                    % (ext.__class__.__module__, ext.__class__.__name__))
-
-        return self
-
-    def build_extension(self, ext_name, configs):
-        """Build extension by name, then return the module.
-
-        The extension name may contain arguments as part of the string in the
-        following format: "extname(key1=value1,key2=value2)"
-
-        """
-
-        configs = dict(configs)
-
-        # Parse extensions config params (ignore the order)
-        pos = ext_name.find("(")  # find the first "("
-        if pos > 0:
-            ext_args = ext_name[pos+1:-1]
-            ext_name = ext_name[:pos]
-            pairs = [x.split("=") for x in ext_args.split(",")]
-            configs.update([(x.strip(), y.strip()) for (x, y) in pairs])
-            warnings.warn('Setting configs in the Named Extension string is '
-                          'deprecated. It is recommended that you '
-                          'pass an instance of the extension class to '
-                          'Markdown or use the "extension_configs" keyword. '
-                          'The current behavior will raise an error in version 2.7. '
-                          'See the Release Notes for Python-Markdown version '
-                          '2.6 for more info.', DeprecationWarning)
-
-        # Get class name (if provided): `path.to.module:ClassName`
-        ext_name, class_name = ext_name.split(':', 1) \
-            if ':' in ext_name else (ext_name, '')
-
-        # Try loading the extension first from one place, then another
-        try:
-            # Assume string uses dot syntax (`path.to.some.module`)
-            module = importlib.import_module(ext_name)
-            logger.debug(
-                'Successfuly imported extension module "%s".' % ext_name
-            )
-            # For backward compat (until deprecation)
-            # check that this is an extension.
-            if ('.' not in ext_name and not (hasattr(module, 'makeExtension') or
-               (class_name and hasattr(module, class_name)))):
-                # We have a name conflict
-                # eg: extensions=['tables'] and PyTables is installed
-                raise ImportError
-        except ImportError:
-            # Preppend `markdown.extensions.` to name
-            module_name = '.'.join(['markdown.extensions', ext_name])
-            try:
-                module = importlib.import_module(module_name)
-                logger.debug(
-                    'Successfuly imported extension module "%s".' %
-                    module_name
-                )
-                warnings.warn('Using short names for Markdown\'s builtin '
-                              'extensions is deprecated. Use the '
-                              'full path to the extension with Python\'s dot '
-                              'notation (eg: "%s" instead of "%s"). The '
-                              'current behavior will raise an error in version '
-                              '2.7. See the Release Notes for '
-                              'Python-Markdown version 2.6 for more info.' %
-                              (module_name, ext_name),
-                              DeprecationWarning)
-            except ImportError:
-                # Preppend `mdx_` to name
-                module_name_old_style = '_'.join(['mdx', ext_name])
-                try:
-                    module = importlib.import_module(module_name_old_style)
-                    logger.debug(
-                        'Successfuly imported extension module "%s".' %
-                        module_name_old_style)
-                    warnings.warn('Markdown\'s behavior of prepending "mdx_" '
-                                  'to an extension name is deprecated. '
-                                  'Use the full path to the '
-                                  'extension with Python\'s dot notation '
-                                  '(eg: "%s" instead of "%s"). The current '
-                                  'behavior will raise an error in version 2.7. '
-                                  'See the Release Notes for Python-Markdown '
-                                  'version 2.6 for more info.' %
-                                  (module_name_old_style, ext_name),
-                                  DeprecationWarning)
-                except ImportError as e:
-                    message = "Failed loading extension '%s' from '%s', '%s' " \
-                        "or '%s'" % (ext_name, ext_name, module_name,
-                                     module_name_old_style)
-                    e.args = (message,) + e.args[1:]
-                    raise
-
-        if class_name:
-            # Load given class name from module.
-            return getattr(module, class_name)(**configs)
-        else:
-            # Expect  makeExtension() function to return a class.
-            try:
-                return module.makeExtension(**configs)
-            except AttributeError as e:
-                message = e.args[0]
-                message = "Failed to initiate extension " \
-                          "'%s': %s" % (ext_name, message)
-                e.args = (message,) + e.args[1:]
-                raise
-
-    def registerExtension(self, extension):
-        """ This gets called by the extension """
-        self.registeredExtensions.append(extension)
-        return self
-
-    def reset(self):
-        """
-        Resets all state variables so that we can start with a new text.
-        """
-        self.htmlStash.reset()
-        self.references.clear()
-
-        for extension in self.registeredExtensions:
-            if hasattr(extension, 'reset'):
-                extension.reset()
-
-        return self
+# For backward compatability as some extensions expect it...
+from .extensions import Extension  # noqa
 
-    def set_output_format(self, format):
-        """ Set the output format for the class instance. """
-        self.output_format = format.lower()
-        try:
-            self.serializer = self.output_formats[self.output_format]
-        except KeyError as e:
-            valid_formats = list(self.output_formats.keys())
-            valid_formats.sort()
-            message = 'Invalid Output Format: "%s". Use one of %s.' \
-                % (self.output_format,
-                   '"' + '", "'.join(valid_formats) + '"')
-            e.args = (message,) + e.args[1:]
-            raise
-        return self
-
-    def convert(self, source):
-        """
-        Convert markdown to serialized XHTML or HTML.
-
-        Keyword arguments:
-
-        * source: Source text as a Unicode string.
-
-        Markdown processing takes place in five steps:
-
-        1. A bunch of "preprocessors" munge the input text.
-        2. BlockParser() parses the high-level structural elements of the
-           pre-processed text into an ElementTree.
-        3. A bunch of "treeprocessors" are run against the ElementTree. One
-           such treeprocessor runs InlinePatterns against the ElementTree,
-           detecting inline markup.
-        4. Some post-processors are run against the text after the ElementTree
-           has been serialized into text.
-        5. The output is written to a string.
-
-        """
-
-        # Fixup the source text
-        if not source.strip():
-            return ''  # a blank unicode string
-
-        try:
-            source = util.text_type(source)
-        except UnicodeDecodeError as e:
-            # Customise error message while maintaining original trackback
-            e.reason += '. -- Note: Markdown only accepts unicode input!'
-            raise
-
-        # Split into lines and run the line preprocessors.
-        self.lines = source.split("\n")
-        for prep in self.preprocessors.values():
-            self.lines = prep.run(self.lines)
-
-        # Parse the high-level elements.
-        root = self.parser.parseDocument(self.lines).getroot()
-
-        # Run the tree-processors
-        for treeprocessor in self.treeprocessors.values():
-            newRoot = treeprocessor.run(root)
-            if newRoot is not None:
-                root = newRoot
-
-        # Serialize _properly_.  Strip top-level tags.
-        output = self.serializer(root)
-        if self.stripTopLevelTags:
-            try:
-                start = output.index(
-                    '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
-                end = output.rindex('</%s>' % self.doc_tag)
-                output = output[start:end].strip()
-            except ValueError:  # pragma: no cover
-                if output.strip().endswith('<%s />' % self.doc_tag):
-                    # We have an empty document
-                    output = ''
-                else:
-                    # We have a serious problem
-                    raise ValueError('Markdown failed to strip top-level '
-                                     'tags. Document=%r' % output.strip())
-
-        # Run the text post-processors
-        for pp in self.postprocessors.values():
-            output = pp.run(output)
-
-        return output.strip()
-
-    def convertFile(self, input=None, output=None, encoding=None):
-        """Converts a Markdown file and returns the HTML as a Unicode string.
-
-        Decodes the file using the provided encoding (defaults to utf-8),
-        passes the file content to markdown, and outputs the html to either
-        the provided stream or the file with provided name, using the same
-        encoding as the source file. The 'xmlcharrefreplace' error handler is
-        used when encoding the output.
-
-        **Note:** This is the only place that decoding and encoding of Unicode
-        takes place in Python-Markdown.  (All other code is Unicode-in /
-        Unicode-out.)
-
-        Keyword arguments:
-
-        * input: File object or path. Reads from stdin if `None`.
-        * output: File object or path. Writes to stdout if `None`.
-        * encoding: Encoding of input and output files. Defaults to utf-8.
-
-        """
-
-        encoding = encoding or "utf-8"
-
-        # Read the source
-        if input:
-            if isinstance(input, util.string_type):
-                input_file = codecs.open(input, mode="r", encoding=encoding)
-            else:
-                input_file = codecs.getreader(encoding)(input)
-            text = input_file.read()
-            input_file.close()
-        else:
-            text = sys.stdin.read()
-            if not isinstance(text, util.text_type):
-                text = text.decode(encoding)
-
-        text = text.lstrip('\ufeff')  # remove the byte-order mark
-
-        # Convert
-        html = self.convert(text)
-
-        # Write to file or stdout
-        if output:
-            if isinstance(output, util.string_type):
-                output_file = codecs.open(output, "w",
-                                          encoding=encoding,
-                                          errors="xmlcharrefreplace")
-                output_file.write(html)
-                output_file.close()
-            else:
-                writer = codecs.getwriter(encoding)
-                output_file = writer(output, errors="xmlcharrefreplace")
-                output_file.write(html)
-                # Don't close here. User may want to write more.
-        else:
-            # Encode manually and write bytes to stdout.
-            html = html.encode(encoding, "xmlcharrefreplace")
-            try:
-                # Write bytes directly to buffer (Python 3).
-                sys.stdout.buffer.write(html)
-            except AttributeError:
-                # Probably Python 2, which works with bytes by default.
-                sys.stdout.write(html)
-
-        return self
-
-
-"""
-EXPORTED FUNCTIONS
-=============================================================================
-
-Those are the two functions we really mean to export: markdown() and
-markdownFromFile().
-"""
-
-
-def markdown(text, *args, **kwargs):
-    """Convert a Markdown string to HTML and return HTML as a Unicode string.
-
-    This is a shortcut function for `Markdown` class to cover the most
-    basic use case.  It initializes an instance of Markdown, loads the
-    necessary extensions and runs the parser on the given text.
-
-    Keyword arguments:
-
-    * text: Markdown formatted text as Unicode or ASCII string.
-    * Any arguments accepted by the Markdown class.
-
-    Returns: An HTML document as a string.
+__all__ = ['Markdown', 'markdown', 'markdownFromFile']
 
-    """
-    md = Markdown(*args, **kwargs)
-    return md.convert(text)
+# version_info should conform to PEP 386
+# (major, minor, micro, alpha/beta/rc/final, #)
+# (1, 1, 2, 'alpha', 0) => "1.1.2.dev"
+# (1, 2, 0, 'beta', 2) => "1.2b2"
+__version_info__ = (3, 0, 0, 'alpha', 0)
 
 
-def markdownFromFile(*args, **kwargs):
-    """Read markdown code from a file and write it to a file or a stream.
+def _get_version():
+    " Returns a PEP 386-compliant version number from version_info. "
+    assert len(__version_info__) == 5
+    assert __version_info__[3] in ('alpha', 'beta', 'rc', 'final')
 
-    This is a shortcut function which initializes an instance of Markdown,
-    and calls the convertFile method rather than convert.
+    parts = 2 if __version_info__[2] == 0 else 3
+    main = '.'.join(map(str, __version_info__[:parts]))
 
-    Keyword arguments:
+    sub = ''
+    if __version_info__[3] == 'alpha' and __version_info__[4] == 0:
+        # TODO: maybe append some sort of git info here??
+        sub = '.dev'
+    elif __version_info__[3] != 'final':
+        mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'c'}
+        sub = mapping[__version_info__[3]] + str(__version_info__[4])
 
-    * input: a file name or readable object.
-    * output: a file name or writable object.
-    * encoding: Encoding of input and output.
-    * Any arguments accepted by the Markdown class.
+    return str(main + sub)
 
-    """
-    # For backward compatibility loop through positional args
-    pos = ['input', 'output', 'extensions', 'encoding']
-    c = 0
-    for arg in args:
-        if pos[c] not in kwargs:
-            kwargs[pos[c]] = arg
-        c += 1
-        if c == len(pos):
-            break
-    if len(args):
-        warnings.warn('Positional arguments are depreacted in '
-                      'Markdown and will raise an error in version 2.7. '
-                      'Use keyword arguments only.',
-                      DeprecationWarning)
 
-    md = Markdown(**kwargs)
-    md.convertFile(kwargs.get('input', None),
-                   kwargs.get('output', None),
-                   kwargs.get('encoding', None))
+__version__ = _get_version()
diff --git a/markdown/__main__.py b/markdown/__main__.py
index c24f756..c29687b 100644
--- a/markdown/__main__.py
+++ b/markdown/__main__.py
@@ -28,7 +28,7 @@ def parse_options(args=None, values=None):
        (STDIN is assumed if no INPUTFILE is given)"""
     desc = "A Python implementation of John Gruber's Markdown. " \
            "https://Python-Markdown.github.io/"
-    ver = "%%prog %s" % markdown.version
+    ver = "%%prog %s" % markdown.__version__
 
     parser = optparse.OptionParser(usage=usage, description=desc, version=ver)
     parser.add_option("-f", "--file", dest="filename", default=None,
@@ -36,10 +36,6 @@ def parse_options(args=None, values=None):
                       metavar="OUTPUT_FILE")
     parser.add_option("-e", "--encoding", dest="encoding",
                       help="Encoding for input and output files.",)
-    parser.add_option("-s", "--safe", dest="safe", default=False,
-                      metavar="SAFE_MODE",
-                      help="Deprecated! 'replace', 'remove' or 'escape' HTML "
-                      "tags in input")
     parser.add_option("-o", "--output_format", dest="output_format",
                       default='xhtml1', metavar="OUTPUT_FORMAT",
                       help="'xhtml1' (default), 'html4' or 'html5'.")
@@ -102,10 +98,6 @@ def parse_options(args=None, values=None):
         'lazy_ol': options.lazy_ol
     }
 
-    if options.safe:
-        # Avoid deprecation warning if user didn't set option
-        opts['safe_mode'] = options.safe
-
     return opts, options.verbose
 
 
diff --git a/markdown/core.py b/markdown/core.py
new file mode 100644
index 0000000..7d9d839
--- /dev/null
+++ b/markdown/core.py
@@ -0,0 +1,429 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+import codecs
+import sys
+import warnings
+import logging
+import importlib
+from . import util
+from .preprocessors import build_preprocessors
+from .blockprocessors import build_block_parser
+from .treeprocessors import build_treeprocessors
+from .inlinepatterns import build_inlinepatterns
+from .postprocessors import build_postprocessors
+from .extensions import Extension
+from .serializers import to_html_string, to_xhtml_string
+
+__all__ = ['Markdown', 'markdown', 'markdownFromFile']
+
+
+logger = logging.getLogger('MARKDOWN')
+
+
+class Markdown(object):
+    """Convert Markdown to HTML."""
+
+    doc_tag = "div"     # Element used to wrap document - later removed
+
+    option_defaults = {
+        'html_replacement_text': '[HTML_REMOVED]',
+        'tab_length':            4,
+        'enable_attributes':     True,
+        'smart_emphasis':        True,
+        'lazy_ol':               True,
+    }
+
+    output_formats = {
+        'html':   to_html_string,
+        'html4':  to_html_string,
+        'html5':  to_html_string,
+        'xhtml':  to_xhtml_string,
+        'xhtml1': to_xhtml_string,
+        'xhtml5': to_xhtml_string,
+    }
+
+    def __init__(self, **kwargs):
+        """
+        Creates a new Markdown instance.
+
+        Keyword arguments:
+
+        * extensions: A list of extensions.
+           If they are of type string, the module mdx_name.py will be loaded.
+           If they are a subclass of markdown.Extension, they will be used
+           as-is.
+        * extension_configs: Configuration settingis for extensions.
+        * output_format: Format of output. Supported formats are:
+            * "xhtml1": Outputs XHTML 1.x. Default.
+            * "xhtml5": Outputs XHTML style tags of HTML 5
+            * "xhtml": Outputs latest supported version of XHTML
+              (currently XHTML 1.1).
+            * "html4": Outputs HTML 4
+            * "html5": Outputs HTML style tags of HTML 5
+            * "html": Outputs latest supported version of HTML
+              (currently HTML 4).
+            Note that it is suggested that the more specific formats ("xhtml1"
+            and "html4") be used as "xhtml" or "html" may change in the future
+            if it makes sense at that time.
+        * tab_length: Length of tabs in the source. Default: 4
+        * enable_attributes: Enable the conversion of attributes. Default: True
+        * smart_emphasis: Treat `_connected_words_` intelligently Default: True
+        * lazy_ol: Ignore number of first item of ordered lists. Default: True
+
+        """
+
+        # Loop through kwargs and assign defaults
+        for option, default in self.option_defaults.items():
+            setattr(self, option, kwargs.get(option, default))
+
+        self.ESCAPED_CHARS = ['\\', '`', '*', '_', '{', '}', '[', ']',
+                              '(', ')', '>', '#', '+', '-', '.', '!']
+
+        self.registeredExtensions = []
+        self.docType = ""
+        self.stripTopLevelTags = True
+
+        self.build_parser()
+
+        self.references = {}
+        self.htmlStash = util.HtmlStash()
+        self.registerExtensions(extensions=kwargs.get('extensions', []),
+                                configs=kwargs.get('extension_configs', {}))
+        self.set_output_format(kwargs.get('output_format', 'xhtml1'))
+        self.reset()
+
+    def build_parser(self):
+        """ Build the parser from the various parts. """
+        self.preprocessors = build_preprocessors(self)
+        self.parser = build_block_parser(self)
+        self.inlinePatterns = build_inlinepatterns(self)
+        self.treeprocessors = build_treeprocessors(self)
+        self.postprocessors = build_postprocessors(self)
+        return self
+
+    def registerExtensions(self, extensions, configs):
+        """
+        Register extensions with this instance of Markdown.
+
+        Keyword arguments:
+
+        * extensions: A list of extensions, which can either
+           be strings or objects.  See the docstring on Markdown.
+        * configs: A dictionary mapping module names to config options.
+
+        """
+        for ext in extensions:
+            if isinstance(ext, util.string_type):
+                ext = self.build_extension(ext, configs.get(ext, {}))
+            if isinstance(ext, Extension):
+                ext.extendMarkdown(self, globals())
+                logger.debug(
+                    'Successfully loaded extension "%s.%s".'
+                    % (ext.__class__.__module__, ext.__class__.__name__)
+                )
+            elif ext is not None:
+                raise TypeError(
+                    'Extension "%s.%s" must be of type: "markdown.Extension"'
+                    % (ext.__class__.__module__, ext.__class__.__name__))
+
+        return self
+
+    def build_extension(self, ext_name, configs):
+        """
+        Build extension by name, then return the module.
+
+        """
+
+        configs = dict(configs)
+
+        # Get class name (if provided): `path.to.module:ClassName`
+        ext_name, class_name = ext_name.split(':', 1) \
+            if ':' in ext_name else (ext_name, '')
+
+        # Try loading the extension first from one place, then another
+        try:
+            # Assume string uses dot syntax (`path.to.some.module`)
+            module = importlib.import_module(ext_name)
+            logger.debug(
+                'Successfuly imported extension module "%s".' % ext_name
+            )
+            # For backward compat (until deprecation)
+            # check that this is an extension.
+            if ('.' not in ext_name and not (hasattr(module, 'makeExtension') or
+               (class_name and hasattr(module, class_name)))):
+                # We have a name conflict
+                # eg: extensions=['tables'] and PyTables is installed
+                raise ImportError
+        except ImportError:
+            # Preppend `markdown.extensions.` to name
+            module_name = '.'.join(['markdown.extensions', ext_name])
+            try:
+                module = importlib.import_module(module_name)
+                logger.debug(
+                    'Successfuly imported extension module "%s".' %
+                    module_name
+                )
+                warnings.warn('Using short names for Markdown\'s builtin '
+                              'extensions is deprecated. Use the '
+                              'full path to the extension with Python\'s dot '
+                              'notation (eg: "%s" instead of "%s"). The '
+                              'current behavior will raise an error in version '
+                              '2.7. See the Release Notes for '
+                              'Python-Markdown version 2.6 for more info.' %
+                              (module_name, ext_name),
+                              DeprecationWarning)
+            except ImportError:
+                # Preppend `mdx_` to name
+                module_name_old_style = '_'.join(['mdx', ext_name])
+                try:
+                    module = importlib.import_module(module_name_old_style)
+                    logger.debug(
+                        'Successfuly imported extension module "%s".' %
+                        module_name_old_style)
+                    warnings.warn('Markdown\'s behavior of prepending "mdx_" '
+                                  'to an extension name is deprecated. '
+                                  'Use the full path to the '
+                                  'extension with Python\'s dot notation '
+                                  '(eg: "%s" instead of "%s"). The current '
+                                  'behavior will raise an error in version 2.7. '
+                                  'See the Release Notes for Python-Markdown '
+                                  'version 2.6 for more info.' %
+                                  (module_name_old_style, ext_name),
+                                  DeprecationWarning)
+                except ImportError as e:
+                    message = "Failed loading extension '%s' from '%s', '%s' " \
+                        "or '%s'" % (ext_name, ext_name, module_name,
+                                     module_name_old_style)
+                    e.args = (message,) + e.args[1:]
+                    raise
+
+        if class_name:
+            # Load given class name from module.
+            return getattr(module, class_name)(**configs)
+        else:
+            # Expect  makeExtension() function to return a class.
+            try:
+                return module.makeExtension(**configs)
+            except AttributeError as e:
+                message = e.args[0]
+                message = "Failed to initiate extension " \
+                          "'%s': %s" % (ext_name, message)
+                e.args = (message,) + e.args[1:]
+                raise
+
+    def registerExtension(self, extension):
+        """ This gets called by the extension """
+        self.registeredExtensions.append(extension)
+        return self
+
+    def reset(self):
+        """
+        Resets all state variables so that we can start with a new text.
+        """
+        self.htmlStash.reset()
+        self.references.clear()
+
+        for extension in self.registeredExtensions:
+            if hasattr(extension, 'reset'):
+                extension.reset()
+
+        return self
+
+    def set_output_format(self, format):
+        """ Set the output format for the class instance. """
+        self.output_format = format.lower()
+        try:
+            self.serializer = self.output_formats[self.output_format]
+        except KeyError as e:
+            valid_formats = list(self.output_formats.keys())
+            valid_formats.sort()
+            message = 'Invalid Output Format: "%s". Use one of %s.' \
+                % (self.output_format,
+                   '"' + '", "'.join(valid_formats) + '"')
+            e.args = (message,) + e.args[1:]
+            raise
+        return self
+
+    def convert(self, source):
+        """
+        Convert markdown to serialized XHTML or HTML.
+
+        Keyword arguments:
+
+        * source: Source text as a Unicode string.
+
+        Markdown processing takes place in five steps:
+
+        1. A bunch of "preprocessors" munge the input text.
+        2. BlockParser() parses the high-level structural elements of the
+           pre-processed text into an ElementTree.
+        3. A bunch of "treeprocessors" are run against the ElementTree. One
+           such treeprocessor runs InlinePatterns against the ElementTree,
+           detecting inline markup.
+        4. Some post-processors are run against the text after the ElementTree
+           has been serialized into text.
+        5. The output is written to a string.
+
+        """
+
+        # Fixup the source text
+        if not source.strip():
+            return ''  # a blank unicode string
+
+        try:
+            source = util.text_type(source)
+        except UnicodeDecodeError as e:
+            # Customise error message while maintaining original trackback
+            e.reason += '. -- Note: Markdown only accepts unicode input!'
+            raise
+
+        # Split into lines and run the line preprocessors.
+        self.lines = source.split("\n")
+        for prep in self.preprocessors.values():
+            self.lines = prep.run(self.lines)
+
+        # Parse the high-level elements.
+        root = self.parser.parseDocument(self.lines).getroot()
+
+        # Run the tree-processors
+        for treeprocessor in self.treeprocessors.values():
+            newRoot = treeprocessor.run(root)
+            if newRoot is not None:
+                root = newRoot
+
+        # Serialize _properly_.  Strip top-level tags.
+        output = self.serializer(root)
+        if self.stripTopLevelTags:
+            try:
+                start = output.index(
+                    '<%s>' % self.doc_tag) + len(self.doc_tag) + 2
+                end = output.rindex('</%s>' % self.doc_tag)
+                output = output[start:end].strip()
+            except ValueError:  # pragma: no cover
+                if output.strip().endswith('<%s />' % self.doc_tag):
+                    # We have an empty document
+                    output = ''
+                else:
+                    # We have a serious problem
+                    raise ValueError('Markdown failed to strip top-level '
+                                     'tags. Document=%r' % output.strip())
+
+        # Run the text post-processors
+        for pp in self.postprocessors.values():
+            output = pp.run(output)
+
+        return output.strip()
+
+    def convertFile(self, input=None, output=None, encoding=None):
+        """Converts a markdown file and returns the HTML as a unicode string.
+
+        Decodes the file using the provided encoding (defaults to utf-8),
+        passes the file content to markdown, and outputs the html to either
+        the provided stream or the file with provided name, using the same
+        encoding as the source file. The 'xmlcharrefreplace' error handler is
+        used when encoding the output.
+
+        **Note:** This is the only place that decoding and encoding of unicode
+        takes place in Python-Markdown.  (All other code is unicode-in /
+        unicode-out.)
+
+        Keyword arguments:
+
+        * input: File object or path. Reads from stdin if `None`.
+        * output: File object or path. Writes to stdout if `None`.
+        * encoding: Encoding of input and output files. Defaults to utf-8.
+
+        """
+
+        encoding = encoding or "utf-8"
+
+        # Read the source
+        if input:
+            if isinstance(input, util.string_type):
+                input_file = codecs.open(input, mode="r", encoding=encoding)
+            else:
+                input_file = codecs.getreader(encoding)(input)
+            text = input_file.read()
+            input_file.close()
+        else:
+            text = sys.stdin.read()
+            if not isinstance(text, util.text_type):
+                text = text.decode(encoding)
+
+        text = text.lstrip('\ufeff')  # remove the byte-order mark
+
+        # Convert
+        html = self.convert(text)
+
+        # Write to file or stdout
+        if output:
+            if isinstance(output, util.string_type):
+                output_file = codecs.open(output, "w",
+                                          encoding=encoding,
+                                          errors="xmlcharrefreplace")
+                output_file.write(html)
+                output_file.close()
+            else:
+                writer = codecs.getwriter(encoding)
+                output_file = writer(output, errors="xmlcharrefreplace")
+                output_file.write(html)
+                # Don't close here. User may want to write more.
+        else:
+            # Encode manually and write bytes to stdout.
+            html = html.encode(encoding, "xmlcharrefreplace")
+            try:
+                # Write bytes directly to buffer (Python 3).
+                sys.stdout.buffer.write(html)
+            except AttributeError:
+                # Probably Python 2, which works with bytes by default.
+                sys.stdout.write(html)
+
+        return self
+
+
+"""
+EXPORTED FUNCTIONS
+=============================================================================
+
+Those are the two functions we really mean to export: markdown() and
+markdownFromFile().
+"""
+
+
+def markdown(text, **kwargs):
+    """Convert a markdown string to HTML and return HTML as a unicode string.
+
+    This is a shortcut function for `Markdown` class to cover the most
+    basic use case.  It initializes an instance of Markdown, loads the
+    necessary extensions and runs the parser on the given text.
+
+    Keyword arguments:
+
+    * text: Markdown formatted text as Unicode or ASCII string.
+    * Any arguments accepted by the Markdown class.
+
+    Returns: An HTML document as a string.
+
+    """
+    md = Markdown(**kwargs)
+    return md.convert(text)
+
+
+def markdownFromFile(**kwargs):
+    """Read markdown code from a file and write it to a file or a stream.
+
+    This is a shortcut function which initializes an instance of Markdown,
+    and calls the convertFile method rather than convert.
+
+    Keyword arguments:
+
+    * input: a file name or readable object.
+    * output: a file name or writable object.
+    * encoding: Encoding of input and output.
+    * Any arguments accepted by the Markdown class.
+
+    """
+    md = Markdown(**kwargs)
+    md.convertFile(kwargs.get('input', None),
+                   kwargs.get('output', None),
+                   kwargs.get('encoding', None))
diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py
index 894e545..6ce73a6 100644
--- a/markdown/extensions/attr_list.py
+++ b/markdown/extensions/attr_list.py
@@ -24,12 +24,6 @@ from ..treeprocessors import Treeprocessor
 from ..util import isBlockLevel
 import re
 
-try:
-    Scanner = re.Scanner
-except AttributeError:  # pragma: no cover
-    # must be on Python 2.4
-    from sre import Scanner
-
 
 def _handle_double_quote(s, t):
     k, v = t.split('=', 1)
@@ -53,7 +47,7 @@ def _handle_word(s, t):
     return t, t
 
 
-_scanner = Scanner([
+_scanner = re.Scanner([
     (r'[^ =]+=".*?"', _handle_double_quote),
     (r"[^ =]+='.*?'", _handle_single_quote),
     (r'[^ =]+=[^ =]+', _handle_key_value),
diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py
index ee42d57..9c91c37 100644
--- a/markdown/extensions/codehilite.py
+++ b/markdown/extensions/codehilite.py
@@ -215,8 +215,7 @@ class HiliteTreeprocessor(Treeprocessor):
                     tab_length=self.markdown.tab_length,
                     use_pygments=self.config['use_pygments']
                 )
-                placeholder = self.markdown.htmlStash.store(code.hilite(),
-                                                            safe=True)
+                placeholder = self.markdown.htmlStash.store(code.hilite())
                 # Clear codeblock in etree instance
                 block.clear()
                 # Change to p element which will later
diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py
index 587ba64..f59e09e 100644
--- a/markdown/extensions/extra.py
+++ b/markdown/extensions/extra.py
@@ -58,15 +58,14 @@ class ExtraExtension(Extension):
     def extendMarkdown(self, md, md_globals):
         """ Register extension instances. """
         md.registerExtensions(extensions, self.config)
-        if not md.safeMode:
-            # Turn on processing of markdown text within raw html
-            md.preprocessors['html_block'].markdown_in_raw = True
-            md.parser.blockprocessors.add('markdown_block',
-                                          MarkdownInHtmlProcessor(md.parser),
-                                          '_begin')
-            md.parser.blockprocessors.tag_counter = -1
-            md.parser.blockprocessors.contain_span_tags = re.compile(
-                r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
+        # Turn on processing of markdown text within raw html
+        md.preprocessors['html_block'].markdown_in_raw = True
+        md.parser.blockprocessors.add('markdown_block',
+                                      MarkdownInHtmlProcessor(md.parser),
+                                      '_begin')
+        md.parser.blockprocessors.tag_counter = -1
+        md.parser.blockprocessors.contain_span_tags = re.compile(
+            r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
 
 
 def makeExtension(*args, **kwargs):
diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py
index 392c654..0975bb5 100644
--- a/markdown/extensions/fenced_code.py
+++ b/markdown/extensions/fenced_code.py
@@ -92,7 +92,7 @@ class FencedBlockPreprocessor(Preprocessor):
                     code = self.CODE_WRAP % (lang,
                                              self._escape(m.group('code')))
 
-                placeholder = self.markdown.htmlStash.store(code, safe=True)
+                placeholder = self.markdown.htmlStash.store(code)
                 text = '%s\n%s\n%s' % (text[:m.start()],
                                        placeholder,
                                        text[m.end():])
diff --git a/markdown/extensions/headerid.py b/markdown/extensions/headerid.py
deleted file mode 100644
index fdcc8f2..0000000
--- a/markdown/extensions/headerid.py
+++ /dev/null
@@ -1,97 +0,0 @@
-"""
-HeaderID Extension for Python-Markdown
-======================================
-
-Auto-generate id attributes for HTML headers.
-
-See <https://Python-Markdown.github.io/extensions/header_id>
-for documentation.
-
-Original code Copyright 2007-2011 [Waylan Limberg](http://achinghead.com/).
-
-All changes Copyright 2011-2014 The Python Markdown Project
-
-License: [BSD](http://www.opensource.org/licenses/bsd-license.php)
-
-"""
-
-from __future__ import absolute_import
-from __future__ import unicode_literals
-from . import Extension
-from ..treeprocessors import Treeprocessor
-from ..util import parseBoolValue
-from .toc import slugify, unique, stashedHTML2text
-import warnings
-
-
-class HeaderIdTreeprocessor(Treeprocessor):
-    """ Assign IDs to headers. """
-
-    IDs = set()
-
-    def run(self, doc):
-        start_level, force_id = self._get_meta()
-        slugify = self.config['slugify']
-        sep = self.config['separator']
-        for elem in doc:
-            if elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
-                if force_id:
-                    if "id" in elem.attrib:
-                        id = elem.get('id')
-                    else:
-                        id = stashedHTML2text(''.join(elem.itertext()), self.md)
-                        id = slugify(id, sep)
-                    elem.set('id', unique(id, self.IDs))
-                if start_level:
-                    level = int(elem.tag[-1]) + start_level
-                    if level > 6:
-                        level = 6
-                    elem.tag = 'h%d' % level
-
-    def _get_meta(self):
-        """ Return meta data suported by this ext as a tuple """
-        level = int(self.config['level']) - 1
-        force = parseBoolValue(self.config['forceid'])
-        if hasattr(self.md, 'Meta'):
-            if 'header_level' in self.md.Meta:
-                level = int(self.md.Meta['header_level'][0]) - 1
-            if 'header_forceid' in self.md.Meta:
-                force = parseBoolValue(self.md.Meta['header_forceid'][0])
-        return level, force
-
-
-class HeaderIdExtension(Extension):
-    def __init__(self, *args, **kwargs):
-        # set defaults
-        self.config = {
-            'level': ['1', 'Base level for headers.'],
-            'forceid': ['True', 'Force all headers to have an id.'],
-            'separator': ['-', 'Word separator.'],
-            'slugify': [slugify, 'Callable to generate anchors']
-        }
-
-        super(HeaderIdExtension, self).__init__(*args, **kwargs)
-
-        warnings.warn(
-            'The HeaderId Extension is pending deprecation. Use the TOC Extension instead.',
-            PendingDeprecationWarning
-        )
-
-    def extendMarkdown(self, md, md_globals):
-        md.registerExtension(self)
-        self.processor = HeaderIdTreeprocessor()
-        self.processor.md = md
-        self.processor.config = self.getConfigs()
-        if 'attr_list' in md.treeprocessors.keys():
-            # insert after attr_list treeprocessor
-            md.treeprocessors.add('headerid', self.processor, '>attr_list')
-        else:
-            # insert after 'prettify' treeprocessor.
-            md.treeprocessors.add('headerid', self.processor, '>prettify')
-
-    def reset(self):
-        self.processor.IDs = set()
-
-
-def makeExtension(*args, **kwargs):
-    return HeaderIdExtension(*args, **kwargs)
diff --git a/markdown/extensions/smarty.py b/markdown/extensions/smarty.py
index c2af7cd..ba5b6b6 100644
--- a/markdown/extensions/smarty.py
+++ b/markdown/extensions/smarty.py
@@ -163,7 +163,7 @@ class SubstituteTextPattern(HtmlPattern):
             if isinstance(part, int):
                 result += m.group(part)
             else:
-                result += self.markdown.htmlStash.store(part, safe=True)
+                result += self.markdown.htmlStash.store(part)
         return result
 
 
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index b222cb4..f884f9b 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -49,11 +49,9 @@ def stashedHTML2text(text, md):
     def _html_sub(m):
         """ Substitute raw html with plain text. """
         try:
-            raw, safe = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
+            raw = md.htmlStash.rawHtmlBlocks[int(m.group(1))]
         except (IndexError, TypeError):  # pragma: no cover
             return m.group(0)
-        if md.safeMode and not safe:  # pragma: no cover
-            return ''
         # Strip out tags and entities - leaveing text
         return re.sub(r'(<[^>]+>)|(&[\#a-zA-Z0-9]+;)', '', raw)
 
diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py
index bfdffb3..dbb4d06 100644
--- a/markdown/inlinepatterns.py
+++ b/markdown/inlinepatterns.py
@@ -46,10 +46,6 @@ from __future__ import unicode_literals
 from . import util
 from . import odict
 import re
-try:  # pragma: no cover
-    from urllib.parse import urlparse, urlunparse
-except ImportError:  # pragma: no cover
-    from urlparse import urlparse, urlunparse
 try:  # pragma: no cover
     from html import entities
 except ImportError:  # pragma: no cover
@@ -73,8 +69,7 @@ def build_inlinepatterns(md_instance, **kwargs):
     inlinePatterns["autolink"] = AutolinkPattern(AUTOLINK_RE, md_instance)
     inlinePatterns["automail"] = AutomailPattern(AUTOMAIL_RE, md_instance)
     inlinePatterns["linebreak"] = SubstituteTagPattern(LINE_BREAK_RE, 'br')
-    if md_instance.safeMode != 'escape':
-        inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance)
+    inlinePatterns["html"] = HtmlPattern(HTML_RE, md_instance)
     inlinePatterns["entity"] = HtmlPattern(ENTITY_RE, md_instance)
     inlinePatterns["not_strong"] = SimpleTextPattern(NOT_STRONG_RE)
     inlinePatterns["em_strong"] = DoubleTagPattern(EM_STRONG_RE, 'strong,em')
@@ -204,8 +199,6 @@ class Pattern(object):
         self.compiled_re = re.compile(r"^(.*?)%s(.*)$" % pattern,
                                       re.DOTALL | re.UNICODE)
 
-        # Api for Markdown to pass safe_mode into instance
-        self.safe_mode = False
         if markdown_instance:
             self.markdown = markdown_instance
 
@@ -236,19 +229,6 @@ class Pattern(object):
         except KeyError:  # pragma: no cover
             return text
 
-        def itertext(el):  # pragma: no cover
-            ' Reimplement Element.itertext for older python versions '
-            tag = el.tag
-            if not isinstance(tag, util.string_type) and tag is not None:
-                return
-            if el.text:
-                yield el.text
-            for e in el:
-                for s in itertext(e):
-                    yield s
-                if e.tail:
-                    yield e.tail
-
         def get_stash(m):
             id = m.group(1)
             if id in stash:
@@ -257,7 +237,7 @@ class Pattern(object):
                     return value
                 else:
                     # An etree Element - return text content only
-                    return ''.join(itertext(value))
+                    return ''.join(value.itertext())
         return util.INLINE_PLACEHOLDER_RE.sub(get_stash, text)
 
 
@@ -369,7 +349,7 @@ class LinkPattern(Pattern):
         if href:
             if href[0] == "<":
                 href = href[1:-1]
-            el.set("href", self.sanitize_url(self.unescape(href.strip())))
+            el.set("href", self.unescape(href.strip()))
         else:
             el.set("href", "")
 
@@ -378,52 +358,6 @@ class LinkPattern(Pattern):
             el.set("title", title)
         return el
 
-    def sanitize_url(self, url):
-        """
-        Sanitize a url against xss attacks in "safe_mode".
-
-        Rather than specifically blacklisting `javascript:alert("XSS")` and all
-        its aliases (see <http://ha.ckers.org/xss.html>), we whitelist known
-        safe url formats. Most urls contain a network location, however some
-        are known not to (i.e.: mailto links). Script urls do not contain a
-        location. Additionally, for `javascript:...`, the scheme would be
-        "javascript" but some aliases will appear to `urlparse()` to have no
-        scheme. On top of that relative links (i.e.: "foo/bar.html") have no
-        scheme. Therefore we must check "path", "parameters", "query" and
-        "fragment" for any literal colons. We don't check "scheme" for colons
-        because it *should* never have any and "netloc" must allow the form:
-        `username:password@host:port`.
-
-        """
-        if not self.markdown.safeMode:
-            # Return immediately bipassing parsing.
-            return url
-
-        try:
-            scheme, netloc, path, params, query, fragment = url = urlparse(url)
-        except ValueError:  # pragma: no cover
-            # Bad url - so bad it couldn't be parsed.
-            return ''
-
-        locless_schemes = ['', 'mailto', 'news']
-        allowed_schemes = locless_schemes + ['http', 'https', 'ftp', 'ftps']
-        if scheme not in allowed_schemes:
-            # Not a known (allowed) scheme. Not safe.
-            return ''
-
-        if netloc == '' and scheme not in locless_schemes:  # pragma: no cover
-            # This should not happen. Treat as suspect.
-            return ''
-
-        for part in url[2:]:
-            if ":" in part:
-                # A colon in "path", "parameters", "query"
-                # or "fragment" is suspect.
-                return ''
-
-        # Url passes all tests. Return url as-is.
-        return urlunparse(url)
-
 
 class ImagePattern(LinkPattern):
     """ Return a img element from the given match. """
@@ -434,7 +368,7 @@ class ImagePattern(LinkPattern):
             src = src_parts[0]
             if src[0] == "<" and src[-1] == ">":
                 src = src[1:-1]
-            el.set('src', self.sanitize_url(self.unescape(src)))
+            el.set('src', self.unescape(src))
         else:
             el.set('src', "")
         if len(src_parts) > 1:
@@ -476,7 +410,7 @@ class ReferencePattern(LinkPattern):
     def makeTag(self, href, title, text):
         el = util.etree.Element('a')
 
-        el.set('href', self.sanitize_url(href))
+        el.set('href', href)
         if title:
             el.set('title', title)
 
@@ -488,7 +422,7 @@ class ImageReferencePattern(ReferencePattern):
     """ Match to a stored reference and return img element. """
     def makeTag(self, href, title, text):
         el = util.etree.Element("img")
-        el.set("src", self.sanitize_url(href))
+        el.set("src", href)
         if title:
             el.set("title", title)
 
diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py
index 7b9aa0b..f59e070 100644
--- a/markdown/postprocessors.py
+++ b/markdown/postprocessors.py
@@ -50,19 +50,11 @@ class RawHtmlPostprocessor(Postprocessor):
     """ Restore raw html to the document. """
 
     def run(self, text):
-        """ Iterate over html stash and restore "safe" html. """
+        """ Iterate over html stash and restore html. """
         replacements = OrderedDict()
         for i in range(self.markdown.htmlStash.html_counter):
-            html, safe = self.markdown.htmlStash.rawHtmlBlocks[i]
-            if self.markdown.safeMode and not safe:
-                if str(self.markdown.safeMode).lower() == 'escape':
-                    html = self.escape(html)
-                elif str(self.markdown.safeMode).lower() == 'remove':
-                    html = ''
-                else:
-                    html = self.markdown.html_replacement_text
-            if (self.isblocklevel(html) and
-               (safe or not self.markdown.safeMode)):
+            html = self.markdown.htmlStash.rawHtmlBlocks[i]
+            if self.isblocklevel(html):
                 replacements["<p>%s</p>" %
                              (self.markdown.htmlStash.get_placeholder(i))] = \
                     html + "\n"
@@ -74,13 +66,6 @@ class RawHtmlPostprocessor(Postprocessor):
 
         return text
 
-    def escape(self, html):
-        """ Basic html escaping """
-        html = html.replace('&', '&amp;')
-        html = html.replace('<', '&lt;')
-        html = html.replace('>', '&gt;')
-        return html.replace('"', '&quot;')
-
     def isblocklevel(self, html):
         m = re.match(r'^\<\/?([^ >]+)', html)
         if m:
diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py
index 1e99afa..d0af51a 100644
--- a/markdown/preprocessors.py
+++ b/markdown/preprocessors.py
@@ -17,8 +17,7 @@ def build_preprocessors(md_instance, **kwargs):
     """ Build the default set of preprocessors used by Markdown. """
     preprocessors = odict.OrderedDict()
     preprocessors['normalize_whitespace'] = NormalizeWhitespace(md_instance)
-    if md_instance.safeMode != 'escape':
-        preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
+    preprocessors["html_block"] = HtmlBlockPreprocessor(md_instance)
     preprocessors["reference"] = ReferencePreprocessor(md_instance)
     return preprocessors
 
diff --git a/markdown/util.py b/markdown/util.py
index 9e87019..8897195 100644
--- a/markdown/util.py
+++ b/markdown/util.py
@@ -141,7 +141,7 @@ class HtmlStash(object):
         self.tag_counter = 0
         self.tag_data = []  # list of dictionaries in the order tags appear
 
-    def store(self, html, safe=False):
+    def store(self, html):
         """
         Saves an HTML segment for later reinsertion.  Returns a
         placeholder string that needs to be inserted into the
@@ -150,12 +150,11 @@ class HtmlStash(object):
         Keyword arguments:
 
         * html: an html segment
-        * safe: label an html segment as safe for safemode
 
         Returns : a placeholder string
 
         """
-        self.rawHtmlBlocks.append((html, safe))
+        self.rawHtmlBlocks.append(html)
         placeholder = self.get_placeholder(self.html_counter)
         self.html_counter += 1
         return placeholder
diff --git a/mkdocs.yml b/mkdocs.yml
index a732af4..20872e4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -27,7 +27,6 @@ pages:
       - Extra: extensions/extra.md
       - Fenced Code Blocks: extensions/fenced_code_blocks.md
       - Footnotes: extensions/footnotes.md
-      - HeaderId: extensions/header_id.md
       - Meta-Data: extensions/meta_data.md
       - New Line to Break: extensions/nl2br.md
       - Sane Lists: extensions/sane_lists.md
diff --git a/setup.py b/setup.py
index fc7ee49..6f0a1e7 100755
--- a/setup.py
+++ b/setup.py
@@ -1,34 +1,20 @@
 #!/usr/bin/env python
 
 from setuptools import setup
-import os
-import imp
+from markdown import __version__, __version_info__
 
 
-def get_version():
-    " Get version & version_info without importing markdown.__init__ "
-    path = os.path.join(os.path.dirname(__file__), 'markdown')
-    fp, pathname, desc = imp.find_module('__version__', [path])
-    try:
-        v = imp.load_module('__version__', fp, pathname, desc)
-    finally:
-        fp.close()
-
-    dev_status_map = {
-        'alpha': '3 - Alpha',
-        'beta':  '4 - Beta',
-        'rc':    '4 - Beta',
-        'final': '5 - Production/Stable'
-    }
-    if v.version_info[3] == 'alpha' and v.version_info[4] == 0:
-        status = '2 - Pre-Alpha'
-    else:
-        status = dev_status_map[v.version_info[3]]
-    return v.version, v.version_info, status
-
-
-version, version_info, DEVSTATUS = get_version()
-
+# Get development Status for classifiers
+dev_status_map = {
+    'alpha': '3 - Alpha',
+    'beta':  '4 - Beta',
+    'rc':    '4 - Beta',
+    'final': '5 - Production/Stable'
+}
+if __version_info__[3] == 'alpha' and __version_info__[4] == 0:
+    DEVSTATUS = '2 - Pre-Alpha'
+else:
+    DEVSTATUS = dev_status_map[__version_info__[3]]
 
 # The command line script name.  Currently set to "markdown_py" so as not to
 # conflict with the perl implimentation (which uses "markdown").
@@ -59,9 +45,9 @@ You may ask for help and discuss various other issues on the
 
 setup(
     name='Markdown',
-    version=version,
+    version=__version__,
     url='https://Python-Markdown.github.io/',
-    download_url='http://pypi.python.org/packages/source/M/Markdown/Markdown-%s-py2.py3-none-any.whl' % version,
+    download_url='http://pypi.python.org/packages/source/M/Markdown/Markdown-%s-py2.py3-none-any.whl' % __version__,
     description='Python implementation of Markdown.',
     long_description=long_description,
     author='Manfred Stienstra, Yuri takhteyev and Waylan limberg',


Test patch:
diff --git a/tests/safe_mode/html_then_blockquote.html b/tests/safe_mode/html_then_blockquote.html
deleted file mode 100644
index 62d24e4..0000000
--- a/tests/safe_mode/html_then_blockquote.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<p>to:</p>
-<p>&lt;td /&gt;&lt;td style="text-align: center; white-space: nowrap;"&gt;&lt;br /&gt;</p>
-<blockquote>
-<p>3) You don't need to alter all localization files.
-   Adding the new labels to the en_US files will do it.</p>
-</blockquote>
\ No newline at end of file
diff --git a/tests/safe_mode/html_then_blockquote.txt b/tests/safe_mode/html_then_blockquote.txt
deleted file mode 100644
index 544df67..0000000
--- a/tests/safe_mode/html_then_blockquote.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-to:
-
-<td /><td style="text-align: center; white-space: nowrap;"><br />
-
-> 3) You don't need to alter all localization files.
->    Adding the new labels to the en_US files will do it.
diff --git a/tests/safe_mode/inline-html-advanced.html b/tests/safe_mode/inline-html-advanced.html
deleted file mode 100644
index e9dd2ec..0000000
--- a/tests/safe_mode/inline-html-advanced.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<p>Simple block on one line:</p>
-<p>&lt;div&gt;foo&lt;/div&gt;</p>
-<p>And nested without indentation:</p>
-<p>&lt;div&gt;
-&lt;div&gt;
-&lt;div&gt;
-foo
-&lt;/div&gt;
-&lt;/div&gt;
-&lt;div&gt;bar&lt;/div&gt;
-&lt;/div&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-advanced.txt b/tests/safe_mode/inline-html-advanced.txt
deleted file mode 100644
index 9d71ddc..0000000
--- a/tests/safe_mode/inline-html-advanced.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-Simple block on one line:
-
-<div>foo</div>
-
-And nested without indentation:
-
-<div>
-<div>
-<div>
-foo
-</div>
-</div>
-<div>bar</div>
-</div>
diff --git a/tests/safe_mode/inline-html-comments.html b/tests/safe_mode/inline-html-comments.html
deleted file mode 100644
index 0f1e417..0000000
--- a/tests/safe_mode/inline-html-comments.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<p>Paragraph one.</p>
-<p>&lt;!-- This is a simple comment --&gt;</p>
-<p>&lt;!--
-    This is another comment.
---&gt;</p>
-<p>Paragraph two.</p>
-<p>&lt;!-- one comment block -- -- with two comments --&gt;</p>
-<p>The end.</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-comments.txt b/tests/safe_mode/inline-html-comments.txt
deleted file mode 100644
index 41d830d..0000000
--- a/tests/safe_mode/inline-html-comments.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-Paragraph one.
-
-<!-- This is a simple comment -->
-
-<!--
-	This is another comment.
--->
-
-Paragraph two.
-
-<!-- one comment block -- -- with two comments -->
-
-The end.
diff --git a/tests/safe_mode/inline-html-simple.html b/tests/safe_mode/inline-html-simple.html
deleted file mode 100644
index 1e5df17..0000000
--- a/tests/safe_mode/inline-html-simple.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<p>Here's a simple block:</p>
-<p>&lt;div&gt;
-    foo
-&lt;/div&gt;</p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p>&lt;div&gt;
-    &lt;div&gt;
-        &lt;div&gt;
-            foo
-        &lt;/div&gt;
-    &lt;/div&gt;
-&lt;/div&gt;</p>
-<p>This should just be an HTML comment:</p>
-<p>&lt;!-- Comment --&gt;</p>
-<p>Multiline:</p>
-<p>&lt;!--
-Blah
-Blah
---&gt;</p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p>&lt;!-- foo --&gt;   </p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p>&lt;hr&gt;</p>
-<p>&lt;hr/&gt;</p>
-<p>&lt;hr /&gt;</p>
-<p>&lt;hr&gt;   </p>
-<p>&lt;hr/&gt;  </p>
-<p>&lt;hr /&gt; </p>
-<p>&lt;hr class="foo" id="bar" /&gt;</p>
-<p>&lt;hr class="foo" id="bar"/&gt;</p>
-<p>&lt;hr class="foo" id="bar" &gt;</p>
-<p>&lt;some <a href="http://example.com">weird</a> stuff&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/inline-html-simple.txt b/tests/safe_mode/inline-html-simple.txt
deleted file mode 100644
index 7210750..0000000
--- a/tests/safe_mode/inline-html-simple.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/link-targets.html b/tests/safe_mode/link-targets.html
deleted file mode 100644
index 768ae5b..0000000
--- a/tests/safe_mode/link-targets.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<p><a href="">XSS</a>
-See http://security.stackexchange.com/q/30330/1261 for details.</p>
\ No newline at end of file
diff --git a/tests/safe_mode/link-targets.txt b/tests/safe_mode/link-targets.txt
deleted file mode 100644
index 10eebda..0000000
--- a/tests/safe_mode/link-targets.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-[XSS](javascript://%0Aalert%28'XSS'%29;)
-See http://security.stackexchange.com/q/30330/1261 for details.
-
diff --git a/tests/safe_mode/remove.html b/tests/safe_mode/remove.html
deleted file mode 100644
index d86b2b4..0000000
--- a/tests/safe_mode/remove.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<p>Here's a simple block:</p>
-<p></p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p></p>
-<p>This should just be an HTML comment:</p>
-<p></p>
-<p>Multiline:</p>
-<p></p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p></p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
-<p></p>
\ No newline at end of file
diff --git a/tests/safe_mode/remove.txt b/tests/safe_mode/remove.txt
deleted file mode 100644
index 7210750..0000000
--- a/tests/safe_mode/remove.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/replace.html b/tests/safe_mode/replace.html
deleted file mode 100644
index cb6bfb5..0000000
--- a/tests/safe_mode/replace.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<p>Here's a simple block:</p>
-<p>[HTML_REMOVED]</p>
-<p>This should be a code block, though:</p>
-<pre><code>&lt;div&gt;
-    foo
-&lt;/div&gt;
-</code></pre>
-<p>As should this:</p>
-<pre><code>&lt;div&gt;foo&lt;/div&gt;
-</code></pre>
-<p>Now, nested:</p>
-<p>[HTML_REMOVED]</p>
-<p>This should just be an HTML comment:</p>
-<p>[HTML_REMOVED]</p>
-<p>Multiline:</p>
-<p>[HTML_REMOVED]</p>
-<p>Code block:</p>
-<pre><code>&lt;!-- Comment --&gt;
-</code></pre>
-<p>Just plain comment, with trailing spaces on the line:</p>
-<p>[HTML_REMOVED]</p>
-<p>Code:</p>
-<pre><code>&lt;hr /&gt;
-</code></pre>
-<p>Hr's:</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
-<p>[HTML_REMOVED]</p>
\ No newline at end of file
diff --git a/tests/safe_mode/replace.txt b/tests/safe_mode/replace.txt
deleted file mode 100644
index 7210750..0000000
--- a/tests/safe_mode/replace.txt
+++ /dev/null
@@ -1,70 +0,0 @@
-Here's a simple block:
-
-<div>
-	foo
-</div>
-
-This should be a code block, though:
-
-	<div>
-		foo
-	</div>
-
-As should this:
-
-	<div>foo</div>
-
-Now, nested:
-
-<div>
-	<div>
-		<div>
-			foo
-		</div>
-	</div>
-</div>
-
-This should just be an HTML comment:
-
-<!-- Comment -->
-
-Multiline:
-
-<!--
-Blah
-Blah
--->
-
-Code block:
-
-	<!-- Comment -->
-
-Just plain comment, with trailing spaces on the line:
-
-<!-- foo -->   
-
-Code:
-
-	<hr />
-	
-Hr's:
-
-<hr>
-
-<hr/>
-
-<hr />
-
-<hr>   
-
-<hr/>  
-
-<hr /> 
-
-<hr class="foo" id="bar" />
-
-<hr class="foo" id="bar"/>
-
-<hr class="foo" id="bar" >
-
-<some [weird](http://example.com) stuff>
diff --git a/tests/safe_mode/script_tags.html b/tests/safe_mode/script_tags.html
deleted file mode 100644
index 6c1501b..0000000
--- a/tests/safe_mode/script_tags.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<p>This should be stripped/escaped in safe_mode.</p>
-<p>&lt;script&gt;
-alert("Hello world!")
-&lt;/script&gt;</p>
-<p>With blank lines.</p>
-<p>&lt;script&gt;</p>
-<p>alert("Hello world!")</p>
-<p>&lt;/script&gt;</p>
-<p>Now with some weirdness</p>
-<p><code>&lt;script &lt;!--
-alert("Hello world!")
-&lt;/script &lt;&gt;</code> `</p>
-<p>Try another way.</p>
-<p>&lt;script &lt;!--
-alert("Hello world!")
-&lt;/script &lt;&gt;</p>
-<p>This time with blank lines.</p>
-<p>&lt;script &lt;!--</p>
-<p>alert("Hello world!")</p>
-<p>&lt;/script &lt;&gt;</p>
\ No newline at end of file
diff --git a/tests/safe_mode/script_tags.txt b/tests/safe_mode/script_tags.txt
deleted file mode 100644
index 44041c2..0000000
--- a/tests/safe_mode/script_tags.txt
+++ /dev/null
@@ -1,33 +0,0 @@
-This should be stripped/escaped in safe_mode.
-
-<script>
-alert("Hello world!")
-</script>
-
-With blank lines.
-
-<script>
-
-alert("Hello world!")
-
-</script>
-
-Now with some weirdness
-
-``<script <!--
-alert("Hello world!")
-</script <>`` `
-
-Try another way.
-
-<script <!--
-alert("Hello world!")
-</script <>
-
-This time with blank lines.
-
-<script <!--
-
-alert("Hello world!")
-
-</script <>
diff --git a/tests/safe_mode/unsafe_urls.html b/tests/safe_mode/unsafe_urls.html
deleted file mode 100644
index e617f35..0000000
--- a/tests/safe_mode/unsafe_urls.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<p>These links should be unsafe and not allowed in safe_mode</p>
-<p><a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a>
-<a href="">link</a></p>
-<p><img alt="img" src="" />
-<a href="">ref</a>
-<img alt="imgref" src="" /></p>
-<p>These should work regardless:</p>
-<p><a href="relative/url.html">relative</a>
-<a href="mailto:foo@bar.com">email</a>
-<a href="news:some.news.group.com">news scheme</a>
-<a href="http://example.com">http link</a></p>
\ No newline at end of file
diff --git a/tests/safe_mode/unsafe_urls.txt b/tests/safe_mode/unsafe_urls.txt
deleted file mode 100644
index 7bfd81d..0000000
--- a/tests/safe_mode/unsafe_urls.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-These links should be unsafe and not allowed in safe_mode
-
-[link](javascript:alert%28'Hello%20world!'%29)
-[link](vbscript:msgbox%28%22Hello%20world!%22%29)
-[link](livescript:alert%28'Hello%20world!'%29)
-[link](mocha:[code])
-[link](jAvAsCrIpT:alert%28'Hello%20world!'%29)
-[link](ja&#32;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja&#00032;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja&#x00020;vas&#32;cr&#32;ipt:alert%28'Hello%20world!'%29)
-[link](ja%09&#x20;%0Avas&#32;cr&#x0a;ipt:alert%28'Hello%20world!'%29)
-[link](ja%20vas%20cr%20ipt:alert%28'Hello%20world!'%29)
-[link](live%20script:alert%28'Hello%20world!'%29)
-
-![img](javascript:alert%29'XSS'%29)
-[ref][]
-![imgref][]
-
-[ref]: javascript:alert%29'XSS'%29
-[imgref]: javascript:alert%29'XSS'%29
-
-These should work regardless:
-
-[relative](relative/url.html)
-[email](mailto:foo@bar.com)
-[news scheme](news:some.news.group.com)
-[http link](http://example.com)
diff --git a/tests/test_apis.py b/tests/test_apis.py
index 48e79e8..6a1829b 100644
--- a/tests/test_apis.py
+++ b/tests/test_apis.py
@@ -133,7 +133,7 @@ class TestHtmlStash(unittest.TestCase):
         """ Test HtmlStash.store. """
         self.assertEqual(self.placeholder, self.stash.get_placeholder(0))
         self.assertEqual(self.stash.html_counter, 1)
-        self.assertEqual(self.stash.rawHtmlBlocks, [('foo', False)])
+        self.assertEqual(self.stash.rawHtmlBlocks, ['foo'])
 
     def testStoreMore(self):
         """ Test HtmlStash.store with additional blocks. """
@@ -142,15 +142,7 @@ class TestHtmlStash(unittest.TestCase):
         self.assertEqual(self.stash.html_counter, 2)
         self.assertEqual(
             self.stash.rawHtmlBlocks,
-            [('foo', False), ('bar', False)]
-        )
-
-    def testSafeStore(self):
-        """ Test HtmlStash.store with 'safe' html. """
-        self.stash.store('bar', True)
-        self.assertEqual(
-            self.stash.rawHtmlBlocks,
-            [('foo', False), ('bar', True)]
+            ['foo', 'bar']
         )
 
     def testReset(self):
@@ -159,25 +151,6 @@ class TestHtmlStash(unittest.TestCase):
         self.assertEqual(self.stash.html_counter, 0)
         self.assertEqual(self.stash.rawHtmlBlocks, [])
 
-    def testUnsafeHtmlInSafeMode(self):
-        """ Test that unsafe HTML gets escaped in safe_mode. """
-        output = markdown.markdown('foo', extensions=[self.build_extension()], safe_mode='escape')
-        self.assertEqual(output, '<p>&lt;script&gt;print(&quot;evil&quot;)&lt;/script&gt;</p>')
-
-    def build_extension(self):
-        """ Build an extention that addes unsafe html to Stash in same_mode. """
-        class Unsafe(markdown.treeprocessors.Treeprocessor):
-            def run(self, root):
-                el = root.find('p')
-                el.text = self.markdown.htmlStash.store('<script>print("evil")</script>', safe=False)
-                return root
-
-        class StoreUnsafeHtml(markdown.extensions.Extension):
-            def extendMarkdown(self, md, md_globals):
-                md.treeprocessors.add('unsafe', Unsafe(md), '_end')
-
-        return StoreUnsafeHtml()
-
 
 class TestOrderedDict(unittest.TestCase):
     """ Test OrderedDict storage class. """
@@ -385,13 +358,6 @@ class TestErrors(unittest.TestCase):
             markdown.Markdown, extensions=['footnotes']
         )
 
-    def testStringConfigExtention(self):
-        """ Test that passing configs to an Extension in the name raises a DeprecationWarning. """
-        self.assertRaises(
-            DeprecationWarning,
-            markdown.Markdown, extensions=['markdown.extension.footnotes(PLACE_MARKER=FOO)']
-        )
-
 
 def _create_fake_extension(name, has_factory_func=True, is_wrong_type=False, use_old_style=False):
     """ Create a fake extension module for testing. """
@@ -662,11 +628,6 @@ class TestCliOptionParsing(unittest.TestCase):
         self.default_options['encoding'] = 'utf-8'
         self.assertEqual(options, self.default_options)
 
-    def testSafeModeOption(self):
-        options, logging_level = parse_options(['-s', 'escape'])
-        self.default_options['safe_mode'] = 'escape'
-        self.assertEqual(options, self.default_options)
-
     def testOutputFormatOption(self):
         options, logging_level = parse_options(['-o', 'html5'])
         self.default_options['output_format'] = 'html5'
diff --git a/tests/test_extensions.py b/tests/test_extensions.py
index 490d55b..5dee0fd 100644
--- a/tests/test_extensions.py
+++ b/tests/test_extensions.py
@@ -388,60 +388,6 @@ line 3
         self.assertTrue('<code class="language-python">' in md.convert(text))
 
 
-class TestHeaderId(unittest.TestCase):
-    """ Test HeaderId Extension. """
-
-    def setUp(self):
-        self.md = markdown.Markdown(extensions=['markdown.extensions.headerid'])
-
-    def testBasicHeaderId(self):
-        """ Test Basic HeaderID """
-
-        text = "# Some Header #"
-        self.assertEqual(
-            self.md.convert(text),
-            '<h1 id="some-header">Some Header</h1>'
-        )
-
-    def testNoAutoIds(self):
-        """ Test HeaderIDs with no auto generated IDs. """
-
-        text = '# Some Header\n# Another Header'
-        self.assertEqual(
-            markdown.markdown(text, [markdown.extensions.headerid.HeaderIdExtension(forceid=False)]),
-            '<h1>Some Header</h1>\n'
-            '<h1>Another Header</h1>'
-        )
-
-    def testHeaderIdWithMetaData(self):
-        """ Test Header IDs with MetaData extension. """
-
-        text = '''header_level: 2
-header_forceid: Off
-
-# A Header'''
-        self.assertEqual(
-            markdown.markdown(text, ['markdown.extensions.headerid', 'markdown.extensions.meta']),
-            '<h2>A Header</h2>'
-        )
-
-    def testHeaderIdWithAttr_List(self):
-        """ Test HeaderIDs with Attr_List extension. """
-
-        text = '# Header1 {: #foo }\n# Header2 {: .bar }'
-        self.assertEqual(
-            markdown.markdown(text, ['markdown.extensions.headerid', 'markdown.extensions.attr_list']),
-            '<h1 id="foo">Header1</h1>\n'
-            '<h1 class="bar" id="header2">Header2</h1>'
-        )
-        # Switch order extensions are loaded - should be no change in behavior.
-        self.assertEqual(
-            markdown.markdown(text, ['markdown.extensions.attr_list', 'markdown.extensions.headerid']),
-            '<h1 id="foo">Header1</h1>\n'
-            '<h1 class="bar" id="header2">Header2</h1>'
-        )
-
-
 class TestMetaData(unittest.TestCase):
     """ Test MetaData extension. """
 
@@ -540,7 +486,7 @@ class TestWikiLinks(unittest.TestCase):
         """ Test Simple Settings. """
 
         self.assertEqual(markdown.markdown(
-            self.text, [
+            self.text, extensions=[
                 markdown.extensions.wikilinks.WikiLinkExtension(
                     base_url='/wiki/',
                     end_url='.html',
diff --git a/tests/test_legacy.py b/tests/test_legacy.py
index 17c4282..ddc54bb 100644
--- a/tests/test_legacy.py
+++ b/tests/test_legacy.py
@@ -31,15 +31,6 @@ class TestOptions(LegacyTestCase):
     no_smart_emphasis = Kwargs(smart_emphasis=False)
 
 
-class TestSafeMode(LegacyTestCase):
-    location = os.path.join(parent_test_dir, 'safe_mode')
-    default_kwargs = Kwargs(safe_mode='escape')
-
-    remove = Kwargs(safe_mode='remove')
-
-    replace = Kwargs(safe_mode='replace')
-
-
 class TestPhp(LegacyTestCase):
     """
     Notes on "excluded" tests: