WWW: http://www.boddie.org.uk/python/xhtmlhook/
Download: http://www.boddie.org.uk/python/downloads/xhtmlhook-1.01.zip
The xhtmlhook import hook was written to allow Python source code to be included in XHTML documents using a particular class of preformatted text. The underlying mechanics of this includes modifications to the mechanism that the Python interpreter uses to import modules through the ihooks module and use of the xml.dom.minidom module to obtain the code included within documents. Modules can now be written, with some effort, in a web browser which supports editing, although a method for enabling Python to run such code as scripts is not yet in place.
The authors appreciate good documentation when they encounter it. However, it is often necessary to rely on documentation generated from comments included in source code. Despite claims that, "The source code is the documentation," by proponents of various software engineering methodologies and language evangelists, such generated documentation often only provides cursory descriptions of the interfaces exposed by libraries and other resources. Learning how to use such resources often requires the developer to inspect the source code itself before tentatively trying various approaches to use within an interactive session.
We decided that we would like to see better documented code included within web pages for convenient browsing. The motivation behind this peculiar aim is to be able to include high quality documentation alongside working code, hopefully making it easier for programmers to produce more maintainable, readable programs. With easy-to-use editing facilities included with web browsers such as Amaya this aim is within reach.
There are a number of steps required to enable the Python interpreter to import code embedded within webpages:
Hooks
class in the
ihooks module.urlopen
function from the
urllib2 module.ModuleLoader
class in
the ihooks module and reimplementing the
find_module
method. If URL support is omitted, the
find_module
class need not be reimplemented.load_module
method in
the ModuleLoader
class in the ihooks module.
Although attempts are made to minimise disruption to the import process
used by the base class, it is necessary to override the import process
both for the case of XHTML documents (locally and remotely stored) and
for all file types when remotely stored.ModuleImporter
class from the
ihooks module. This subclass modifies the behaviour of the
import_it
method to treat XHTML documents as packages when
they contain multiple submodules. This instance itself is registered with
the import hooks mechanism through a call to its install
method.The following section presents the source code used to implement the
module, including comments and docstrings where appropriate. This code is
used by the xhtml2py.py script to generate the module when the setup.py
script is run; the functions used to extract the Python source code are taken
from methods of the subclassed ModuleLoader
class. Hence, the
module can import itself in its original form.
This section contains an implementation of the module within an XHTML document. This is used as a base for implementing more advanced features and better documentation.
Let us begin by importing the modules which will allow us to modify the Python interpreter's module loading behaviour. The ihooks module provides support for changing this behaviour; the os and string modules will be generally useful during the loading process
import ihooks, os, string
The imp module contains the
PY_SOURCE
and PKG_DIRECTORY
constants which is used
when registering new import hooks to handle the ".html" suffix associated
with XHTML files.
from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN
In order to find the preformatted text in XHTML documents, we will use the built in minidom module:
from xml.dom import minidom
The imp module will, for now, be used to create
modules explicitly. Ideally, this would be done through the
load_module
method of the Hooks
object from the
ihooks module.
import imp
Finally, the urllib2 module will be used to retrieve modules from locations other than in the local filesystem.
import urllib2, urlparse from urllib2 import URLError, HTTPError
The ihooks module provides a Hooks
class suitable
for subclassing which determines which types of files can be imported as
modules into Python. To extend the importing behaviour, we need to provide a
subclass which will later be registered with the
ihooks module through instances of another class we will create.
class XHTMLHooks(ihooks.Hooks):
NewHooks(ihooks.Hooks)
Subclass adding a new filename suffix to those already recognised by the
standard library.
def get_suffixes(self): return ihooks.Hooks.get_suffixes(self) + [(os.extsep+'html', 'r', PY_SOURCE)]
Add another method to return the suffixes to be searched for on remote servers, possibly using a different suffix separator character to the local platform.
def get_url_suffixes(self): suffixes = self.get_suffixes() l = len(os.extsep) url_suffixes = [] for suffix, mode, datatype in suffixes: c = 0 url_suffix = "" while 1: at = string.find(suffix, os.extsep, c) if at == -1: break url_suffix = url_suffix + suffix[c:at] + "." c = at + l if c < len(suffix): url_suffix = url_suffix + suffix[c:] url_suffixes.append((url_suffix, mode, datatype)) return url_suffixes
Note that the subclass extends the list of suffixes understood by the imp module by adding the ".html" suffix to the list, indicating that files of this type should be opened using the "r" (read) mode, and specifies that the contents will be Python source code.
This class in itself would allow Python to import XHTML files if it were
registered with an instance of the ModuleImporter
class from
theihooks module. However, the interpreter would fail to parse the
content of such files.
To perform the actual loading of the code contained within XHTML files, we
create a class based on the ModuleLoader
from theihooks
module. This will be instantiated with an XHTMLHooks object and passed as an
argument to the constructor of a ModuleImporter
class from the
ihooks module.
class XHTMLLoader(ihooks.ModuleLoader):
Initially, we define a method for properly indenting docstrings.
def _indent_docstring(self, text, cmtuple):
list = _indent_docstring(self, text, docstring)
Returned an indented docstring based on the indentation used in the text
which it precedes.
line = 0 indentation = 0 while line < len(text): this_line = text[line] spaces = this_line.count(u' ') if spaces < len(this_line):
Not all the characters on the line are spaces.
while indentation <= spaces: if this_line[indentation] != u' ': break indentation = indentation + 1 break
Try the next line.
line = line + 1
Wrap the text found to fit within lines of the required width.
leftover = u''
Add any previous text left over from preceding lines and try to split the line at a space.
cmtype, cmtext = cmtuple if cmtype == "docstring": cmwidth = 78 - indentation else: cmwidth = 76 - indentation new_text = [] leftover = u'' n = 0 while n < len(cmtext) or leftover != u'': if n < len(cmtext) and leftover == u'':
Only read another line if the previous line(s) have been written.
line = cmtext[n] n = n + 1 else: line = leftover
Wrap the text to the desired line width.
at = len(line) while at >= cmwidth: at = line.rfind(u' ', 0, at)
If no spaces are found then keep the whole line together.
if at == -1: at = len(line) line, leftover = line[:at], line[at + 1:]
The comment line contained more than just whitespace so include a leading comment character and a space ("# ").
if cmtype == "comment" and line.count(u' ') < len(line): line = u'# ' + line
Add the new line to the list of lines to write to the output list.
new_text.append((indentation * u' ') + line)
The above loop should catch any left over text so we should be able to just return the list of new lines to the calling method.
return new_text
One of the clearest ways to format source code in an XHTML document is to leave lines containing the appropriate level of indentation before and after blocks of code. This unfortunately creates duplicate lines of whitespace in the extracted source code. We introduce a method to remove a line from each pair of adjacent duplicate lines which contain only whitespace.
Introduce a method for removing single lines from paris of duplicate adjacent lines of whitespace.
def _reduce_empty(self, text):
list = _reduce_empty(self, text)
Try to remove duplicate consecutive lines which only consist of spaces.
Take the first item and place it in a new list. Record the last item added.
new = text[:1] last = text[:1]
Examine the items in the rest of the list.
for item in text[1:]: if last is None:
If there are no restrictions on the next item in the list then add a new item to the list and update the last item variable.
new.append(item) last = item elif item == last:
If the item is the same as the last item added to the new list then check whether it is just made up of spaces.
if item.count(u' ') < len(item):
A non-space character was found so add this item to the new list.
new.append(item)
If all the characters were spaces then nothing is added to the new list and the last item remains the same.
else:
The last item added to the new list and the current item in this list are not the same. Add the item to the new list and update the last item variable.
new.append(item) last = item
Return the list of new items.
return new
A method will be required to extract preformatted text of the relevant
class. This descends an instance of the xml.dom.minidom.Document
class, looking for nodes corresponding to preformatted text of class "Python"
and collecting the contents of the text nodes found beneath. Although the
getElementsByTagName
method of this class could be used to
collect preformatted text we would still need to programatically look for
both text and line break nodes. The _return_text
method, while
long-winded, is considered to be more elegant that using a separate method
for collecting text.
def _return_text(self, document, in_text = None, listings = None, comments = None):
listings, comments = return_text(document)
Return a list of tuples, each containing the name of a code block and a list
of lines in that block.
The comments list returned contains elements which are lists of lines in each
comment or docstring found.
Create a new list of output and comments for this level of recursion.
if listings == None: listings = [[None, []]] if comments is None: comments = [] for node in document.childNodes:
Each node is checked to determine whether it has any child nodes.
if node.hasChildNodes():
If so, then its name and class (if present) are checked against the values we wish to find.
if node.getAttribute(u'class') == u'Python' and \ in_text is None:
If the class used for the node is "Python" then the contents of the relevant child nodes are appended to the list of output. We descend, setting the second parameter to one to indicate that text found beneath this node is to be collected.
listing, cm = self._return_text(node, u'Python')
Join all the text to create a single string and split it at each newline.
text = listing[0][1] joined = u''.join(text) text = joined.split(u'\n')
If a comment or docstring was being remembered then indent
it correctly using the indentation of the first non-empty line as a guide and
reset the docstring to None
.
for comment in comments: listings[-1][1] = listings[-1][1] + self._indent_docstring(text, comment) comments = []
Add the list of preformatted text lines to the output list.
listings[-1][1] = listings[-1][1] + text elif node.getAttribute(u'class') == u'Docstring' and \ in_text is None:
For nodes outside Python code, check whether the "Docstring" class is used and generate docstrings if it is. Ensure that instances of quotes are escaped and that the level of indentation remains the same.
listing, cm = self._return_text(node, u'Docstring')
The docstring value, ds
, is meaningless in
this case as all the text for the docstring is contained within an element in
the listing
list.
text = listing[0][1]
Join the items in the text list then escape all the quotes used in the complete string.
text = u''.join(text)
Join together line breaks (from <br/> elements) and carriage returns (generated from newlines in <p> elements) to form a single newline in each case.
pieces = text.split(u'\n\r') text = u'\n'.join(pieces) new_text = [] for c in text: if c == u'"': new_text.append(u'\\"') elif c == u'\r': new_text.append(u' ') else: new_text.append(c) new_text = u''.join(new_text)
Compile a docstring list from the text returned.
docstring = [u'"""'] docstring = docstring + new_text.split(u'\n') docstring.append(u'"""') comments.append(("docstring", docstring)) elif node.getAttribute(u'class') == u'Comment' and \ in_text is None:
It is useful to be able to include comments in the generated source code, although this is mainly useful for generating a new version of the bootstrap.py file from an XHTML file. Note that we write any docstring being collected since it should appear before comments in the source code.
listing, cm = self._return_text(node, u'Comment') text = listing[0][1] joined = u''.join(text)
Join together line breaks (from <br/> elements) and carriage returns (generated from newlines in <p> elements) to form a single newline in each case.
pieces = joined.split(u'\n\r') joined = u'\n'.join(pieces) pieces = joined.split(u'\r') joined = u' '.join(pieces) text = joined.split(u'\n')
Add the comment lines to the comments list, specifying the appropriate type..
comments.append(("comment", text)) elif node.getAttribute(u'class') == u'Submodule' and in_text is None:
If we encounter a node with children which has the correct
class attribute then collect the textual contents of the child nodes. This
does not affect the persistent listings
or comments
lists
until the text has been assembled.
listing, cm = self._return_text(node, u'Submodule') text = listing[0][1] joined = u''.join(text)
Start a new listing using the text collected as the name of the listing.
listings.append([joined, []]) else:
If the node fails to satisfy the criteria then we continue looking for suitable preformatted text in its child nodes, maintaining the comments list.
listings, comments = self._return_text(node, in_text, listings, comments)
For nodes with no child nodes, we are only concerned with those which are located within preformatted areas of the required class.
elif in_text is not None:
If suitable text has been found the contents of the node are added to the output list.
if node.nodeName == "#text" and in_text == u'Python':
Suitable text has been found. Add the contents of the node to the output list.
listings[-1][1].append(node.data) elif node.nodeName == "#text" and (in_text == u'Docstring' or in_text == u'Comment'):
For paragraph text being collected for comments or docstrings add the contents of the node to the output list.
lines = node.data.split(u'\n') listings[-1][1].append(u'\r'.join(lines)) elif node.nodeName == "#text" and in_text == u'Submodule':
For a submodule name declaration, just collect the text as it is for later processing.
listings[-1][1].append(node.data)
If line breaks are found then insert newlines; this is required because Mozilla appears to find it fashionable to include line break tags in preformatted text rather than actual newlines.
elif node.nodeName == u'br': # Append a newline to the text collected. listings[-1][1].append(u'\n')
The contents of the output list and the docstring list are returned.
return listings, comments
The techniques employed to find modules for Python to use may be modified
by implementing a replacement for the find_module
method. Here,
we add the ability to search for modules stored on remote servers.
def find_module(self, name, path = None):
stuff = find_module(name, path = None)
Return information on a module if it can be found or None
if it
cannot be located. The path
parameter, if specified, should be a
list of paths to search for the module of the name given.
If successful, the stuff
tuple contains an open file-like object
for reading the module, the filename of the module and an information tuple;
this tuple contains the module file's suffix, reading mode and the data type
of its content.
There are two scenarios which concern us: where a module is specified
explicitly as a URL and where it is merely specified as a name. In the first
case, we will ignore the path given and try to locate the module using the
urllib2 module; in the second case, we may encounter paths which are
themselves URLs so will have to be prepared to fetch the module from these.
To avoid duplication of effort, where the name is given as a complete URL we
will split the module filename from the rest of the URL, placing it in the
name
parameter and replacing the path list with the rest of the
URL.
Note: The first case is not pursued in the current version of this module as it is difficult to supply module names with the sufficient range of characters required to specify URLs.
We determine whether the module is hosted remotely by looking at the form of the name.
#print "find_module:", name, path if ":" in name: proto, host, fullpath, x1, x2 = urlparse.urlsplit(name, "file", 0) print proto, host, fullpath, x1, x2 at = string.rfind(fullpath, "/") if at != -1: direct = fullpath[:at+1] file = fullpath[at+1:] else: direct = "" file = fullpath url = urlparse.urlunsplit([proto, host, direct, x1, x2]) path = [url] name = file
If no path is supplied then we must use the default path, but first we must check for built in modules.
if path is None: stuff = self.find_builtin_module(name) if stuff is not None: return stuff
No built in module was found, so check paths in the default list.
path = self.default_path()
We now examine each path in turn, trying to find the module.
for direct in path:
We test whether the directory exists on the local filesystem as a crude way to determine whether the directory is a non-URL based path.
if os.path.exists(direct):
For directories which are assumed to be non-URL based paths, just use the base class's method for finding the module.
stuff = ihooks.ModuleLoader.find_module(self, name, [direct]) if stuff is not None: return stuff
For all URL paths, check that the module name is not "_winreg". This is to prevent Windows platforms from looking for non-existant modules on remote servers then complaining when it can't find them.
elif name != "_winreg":
For a path which is assumed to be in the form of a URL, check that the name of the module has a suitable suffix. If the module has a suffix then remove it, placing it in a list, otherwise use the name with the standard list of suffixes recognised by the importer.
at = string.rfind(name, ".") if at != -1: suffix = name[at:] name = name[:at]
Look up the corresponding reading mode and type value for the suffix in use, providing a fallback option in case the suffix is not found.
suffixes = [] for s, mode, datatype in self.hooks.get_url_suffixes(): if s == suffix: suffixes = [(suffix, mode, datatype)] break if suffixes == []: suffixes = self.hooks.get_url_suffixes() else:
For module names which are specified without a suffix, ensure that we check for a package of that name. We may want to add a check for "__init__" files at this point to prevent further searching for packages called "__init__". Typically, this isn't required because the remote server should inform us that there is no directory called "__init__" at the URL given. We place the package directory at the end of the suffix list because it is more intensive to determine whether a package exists than it is to find a module.
suffixes = self.hooks.get_url_suffixes() + [("", "", PKG_DIRECTORY)]
For each suffix, join the directory and file names
together using the urljoin
function from the urlparse
module to create a URL. The directory name is assumed to be missing a
terminating slash character ("/") so this will be added.
for suffix, mode, datatype in suffixes: url = urlparse.urljoin(direct + "/", name + suffix)
Try to open this URL constructed from the path and the
module name with the urlopen
function from the urllib2
module.
try: file = urllib2.urlopen(url)
Check that the file descriptor's corresponding URL is the same as the URL asked for.
if file.url != url: raise URLError, "URL used was not the URL asked for."
If the URL had no suffix and yet returned a valid file descriptor for the content we asked for, we assume that a directory was found and some form of content returned. We then go on to assume that a file called "__init__.py" exists beneath the directory on the server and try to import that.
if suffix == "": stuff = self.find_module("__init__", [url]) if stuff: file = stuff[0] if file: file.close() return None, url, ("", "", PKG_DIRECTORY)
Return the file object, filename, suffix, mode and data type.
else: return file, url, (suffix, mode, datatype)
If the URL created by combining the path and name does not
represent a URL then try and open it using the default
find_module
method from the base class.
except ValueError: stuff = ihooks.ModuleLoader.find_module(self, name, [direct]) if stuff is not None: return stuff
If the URL cannot be resolved or cannot be found then continue searching through the suffixes and paths.
except (URLError, HTTPError): pass
If no modules were found then return None
to
indicate failure.
return None
The load_module
method is used by the following method which
is called by an instance of the ModuleImporter
class when a
module is to be imported from a file.
def load_module(self, name, stuff):
module = load_module(self, name, stuff)
Return a module object using the imp.load_source function or raise an
ImportError if there is a problem with the file in question.
The name
parameter passed to this method is
the name of the module to be created; the stuff
parameter is a
tuple commonly used by the imp module to manage information related
to modules.
# Unpack the stuff tuple. file, filename, info = stuff # Unpack the info tuple. (suffix, mode, datatype) = info #print "load_module:", name, filename
We try to deal with the data of whatever type from the
stuff
tuple.
try:
For built in modules, load the module and return.
if datatype == C_BUILTIN: return self.hooks.init_builtin(name)
We need to deal with remote Python code as well since the
load_package
function in the imp module insists on
getting file objects, which is not going to happen for remote objects. For
package directories, we need to manually deal with importing the "__init__"
files.
path = None if datatype == PKG_DIRECTORY:
Load a variant of the "__init__.py" file inside the
directory using the filename
variable given, which will contain
the full URL, as the path to the module.
new_stuff = self.find_module("__init__", [filename]) if not new_stuff: raise ImportError, "No module named %s" % name init_file, init_filename, init_info = new_stuff
Ensure that the information for the "__init__" file is used instead of that for the directory.
file = init_file path = [filename] filename = init_filename suffix = init_info[0] mode = init_info[1] datatype = init_info[2]
We are interested in source code found in files ending in
".html". That such files are recognised as source code by the calling object
is due to an instance of the replacement XHTMLHooks
class which
we defined above. Alternatively, if the module to be imported is stored
remotely then we will need to perform the compilation and installation since
the default loader will refuse to deal with URLs rather than files.
if os.path.exists(filename): is_url = 0 else: is_url = 1 if is_url or (datatype == PY_SOURCE and (suffix == os.extsep+"html" or suffix == ".html")):
Within the safety of an exception clause, read the contents of the file or URL:
try: data = file.read()
For consistency with other import methods an
ImportError
is thrown if any of the file operations fail.
Ideally, XML parsing exceptions should also be caught in the same way.
except IOError: # Raise an ImportError. raise ImportError, "Failed to import %s" % filename
Files specified by URLs are read using the read binary ("rb") mode so objects which should be read using the textual read ("r") mode will need some modification.
if mode == "r": pieces = string.split(data, "\r\n") data = string.join(pieces, "\n")
For XHTML documents, create an
xml.dom.minidom.Document
object using the contents of the file
or URL in question.
if suffix == os.extsep+"html" or suffix == ".html": d = minidom.parseString(data)
Retrieve the Python code from the document (the first item
in the tuple returned from _return_text
) and check for missing
newlines.
code_listings = self._return_text(d)[0]
For any pair of empty but indented lines in the source code, remove one of the pair.
listings = [] for code_name, code_listing in code_listings: new_listing = self._reduce_empty(code_listing)
Join the list items together to make a unicode string, checking for the absence of a terminating newline character.
code_string = u'\n'.join(new_listing) if code_string != u'' and code_string[-1] != u'\n': code_string = code_string + u'\n' listings.append((code_name, code_string))
Some platforms may have difficulty in encoding the unicode string as ASCII so just leave it in the encoding found.
else: code_string = data listings = [(None, code_string)]
We encode the source code as ASCII since it may have been encoded in a form which Python cannot understand.
ascii_string = code_string.encode("ASCII") else: ascii_string = data.encode("ASCII")
Ideally, we would pass the source code found to the imp module's
load_source
function via the hooks mechanism but this appears to
fail because an actual file object is required rather than
acStringIO.StringIO
object.
# Store the code_string in a stream object. #stream = StringIO.StringIO(code_string) # Pass the stream object to the relevant hook's # load_source function in order to obtain a module object. #module = self.hooks.load_source(name, filename, stream)
We manually create a new module for this source to reside in.
module = self.hooks.add_module(name)
The source code from each part of the file is compiled.
for code_name, code_string in listings:
If there is a corresponding name for the code then create a submodule for it. The resulting bytecode is executed within the context of the new module.
code = compile(code_string, filename, "exec") if code_name is not None:
Split the name associated with the code string at each "." to generate a list of submodules in descending order.
sub_names = string.split(code_name, ".") parent = module parent.__path__ = [filename] for sub_name in sub_names:
Compile code to generate a submodule from within the newly created module and execute it..
if sub_name not in parent.__dict__: mksub = compile( "import imp\n%s = imp.new_module('%s')\n" % (sub_name, sub_name), filename, "exec" ) exec mksub in parent.__dict__
For deeper submodules, use the current submodule as the parent module.
submodule = parent.__dict__[sub_name] submodule.__file__ = filename parent = submodule
Execute the code listing in the lowest submodule created.
exec code in submodule.__dict__ else: exec code in module.__dict__
The base class is called on to deal with files other than those handled by the specific functions introduced in this class.
else: # Use the base class's method. #print "Use base class:", name, stuff return ihooks.ModuleLoader.load_module(self, name, stuff)
Like the base class, some tidying up is required if the import operation fails.
finally: if file: file.close()
For successful imports, we return the module object to the
caller. Using the technique found in the FancyModuleLoader
class
from the ihooks module, we assign the path to the module to the
__path__
attribute of the module object created.
if path is not None: module.__path__ = path module.__file__ = filename return module
We are able to import XHTML files as single modules, with some support for submodules, using just the above classes. However, it is helpful to be able to treat an XHTML document which contains submodules as a simple kind of package. To do this, it is necessary to modify how the class which imports the modules into the correct namespaces so that it can deal with
class XHTMLImporter(ihooks.ModuleImporter): def import_it(self, partname, fqname, parent, force_load=0): try: return parent.__dict__[partname] except (KeyError, AttributeError): return ihooks.ModuleImporter.import_it( self, partname, fqname, parent, force_load )
With the above classes in place, it now only remains for us to create an instance of the new hooks mechanism:
new_hooks = XHTMLHooks()
This will be passed as a parameter of the constructor as we create an
instance of the new loader class.
new_loader = XHTMLLoader(hooks = new_hooks)
We create an instance of the XHTMLImporter
class, using the new
loader and import hooks to extend its range of supported source files to
include Python in XHTML.
importer = XHTMLImporter(loader = new_loader)
Install the new importer with the ihooks module and therefore the
Python interpreter.
importer.install()
A simple test of the remote importing features of the module is as
follows:
def test(): test_url = "http://www.boddie.org.uk/python/modules" print "Testing the module by trying to import a module from %s" % test_url print import sys print "Appending %s to sys.path" % test_url print sys.path.append(test_url) try: import xhh_test_module except ImportError: print "Import failed." return print print "Import was successful."
Note: This section contains source code which is not used in the module. Further work needs to be done to determine the feasibility of this objective.
Being able to import XHTML documents containing Python code is useful even
if it only from local files. However, in the XHTMLLoader
class
we implemented URL loading in the find_module
method with the
intention of allowing modules to be imported from remote servers. In order to
achieve this we must modify Python's built in function for importing modules:
the function __import__
in the __builtins__ module,
which is loaded by default by the interpreter.
We define a new function for importing modules which relies on the old one. First, we copy the old function into our namespace:
# #old_import = __import__ #
Next, we define a new import function:
#def new_import(name, globals, locals, fromlist): #
We check the name to determine whether it is referring to a URL. We use the convention that modules which can be found at a given URL are given in the form:
"_url_"<protocol>[number of underscores]<server><path>
where each underscore between the protocol and the server name represents a slash character. Similarly, in the path on the server, underscores can be used to represent slashes.
To provide support for URLs, we translate any such names in the new import function:
if name[:5] == "_url_": new = "" first_under = 1 print name for c in name[5:]: if c == "_":
Always place a colon character before the first underscore.
if first_under == 1: new = new + ":/" first_under = 0 else: new = new + "/" else: new = new + c
Once the new name is constructed, try to import it using the old import function.
print new old_import(new, globals, locals, fromlist)
For module which are not specified in the new form, just use the old module as usual.
else: old_import(name, globals, locals, fromlist)
Possible future additions may include support for:
load_module
method of the XHTMLLoader
class. Corrected conversion of
platform suffixes to those suitable for URLs in the
get_url_suffixes
method of the XHTMLHooks
class.ModuleImporter
class from ihooks
in order to treat some XHTML documents like packages.find_module
method now checks whether the URL
corresponding to the file descriptor returned by the
urlopen
function is the same as the URL asked for.sys.path
list. Try appending a URL for some online
resources to this list to see how it works.
For example, try the built in test:
>>> import xhtmlhook >>> xhtmlhook.test()
From the included LICENSE.txt file:
xhtmlhook license: Copyright (c) 2003, David Boddie, Paul Boddie Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.