3.1. IMS Content Packaging (version 1.2)

The IMS Content Packaging specification defines methods for packaging and organizing resources and their associated metadata for transmission between systems. There is a small amount of information on Wikipedia about content packaging in general, see http://en.wikipedia.org/wiki/Content_package. The main use of IMS Content Packaging in the market place is through the SCORM profile. Content Packaging is also used as the basis for the new IMS Common Cartridge, and a method of packaging assessment materials using the speicifcation is also described by IMS QTI version 2.1.

Official information about the specification is available from the IMS GLC: http://www.imsglobal.org/content/packaging/index.html

3.1.1. Example

The following example script illustrates the use of this module. The script takes two arguments, a resource file to be packaged (such as an index.html file) and the path to save the zipped package to. The script creates a new package containing a single resource with the entry point set to point to the resource file. It also adds any other files in the same directory as the resource file, using the python os.walk function to include files in sub-directories too. The ContentPackage.IgnoreFilePath() method is used to ensure that hidden files are not added:

#! /usr/bin/env python

import sys, os, os.path, shutil
from pyslet.imscpv1p2 import ContentPackage, PathInPath
from pyslet.rfc2396 import URIFactory

def main():
        if len(sys.argv)!=3:
                print "Usage: makecp <resource file> <package file>"
                return
        resFile=sys.argv[1]
        pkgFile=sys.argv[2]
        pkg=ContentPackage()
        try:
                if os.path.isdir(resFile):
                        print "Resource entry point must be a file, not a directory."
                        return
                resHREF=URI.from_path(resFile)
                srcDir,srcFile=os.path.split(resFile)
                r=pkg.manifest.root.Resources.ChildElement(pkg.manifest.root.Resources.ResourceClass)
                r.href=str(resHREF.relative(URI.from_path(os.path.join(srcDir,'imsmanifest.xml'))))
                r.type=='webcontent'
                for dirpath,dirnames,filenames in os.walk(srcDir):
                        for f in filenames:
                                srcPath=os.path.join(dirpath,f)
                                if pkg.IgnoreFilePath(srcPath):
                                        print "Skipping: %s"%srcPath
                                        continue
                                dstPath=os.path.join(pkg.dPath,PathInPath(srcPath,srcDir))
                                # copy the file
                                dName,fName=os.path.split(dstPath)
                                if not os.path.isdir(dName):
                                        os.makedirs(dName)
                                print "Copying: %s"%srcPath
                                shutil.copy(srcPath,dstPath)
                                pkg.File(r,URI.from_path(dstPath))
                if os.path.exists(pkgFile):
                        if raw_input("Are you sure you want to overwrite %s? (y/n) "%pkgFile).lower()!='y':
                                return
                pkg.manifest.Update()
                pkg.ExportToPIF(pkgFile)
        finally:
                pkg.Close()

if __name__ == "__main__":
        main()

Note the use of the try:... finally: construct to ensure that the ContentPackage object is properly closed when it is finished with. Note also the correct way to create elements within the manifest, using the dependency safe *Class attributes:

r=pkg.manifest.root.Resources.ChildElement(pkg.manifest.root.Resources.ResourceClass)

This line creates a new resource element as a child of the (required) Resources element.

At the end of the script the ManifestDocument is updated on the disk using the inherited Update() method. The package can then be exported to the zip file format.

3.1.2. Reference

class pyslet.imscpv1p2.ContentPackage(dPath=None)

Represents a content package.

When constructed with no arguments a new package is created. A temporary folder to hold the contents of the package is created and will not be cleaned up until the Close() method is called.

Alternatively, you can pass an operating system or virtual file path to a content package directory, to an imsmanifest.xml file or to a Package Interchange Format file. In the latter case, the file is unzipped into a temporary folder to facilitate manipulation of the package contents.

A new manifest file is created and written to the file system when creating a new package, or if it is missing from an existing package or directory.

ManifestDocumentClass

the default class for representing the Manifest file

alias of ManifestDocument

dPath = None

the VirtualFilePath to the package’s directory

manifest = None

The ManifestDocument object representing the imsmanifest.xml file.

The file is read (or created) on construction.

fileTable = None

The fileTable is a dictionary that maps package relative file paths to the File objects that represent them in the manifest.

It is possible for a file to be referenced multiple times (although dependencies were designed to take care of most cases it is still possible for two resources to share a physical file, or even for a resource to contain multiple references to the same file.) Therefore, the dictionary values are lists of File objects.

If a file path maps to an empty list then a file exists in the package which is not referenced by any resource. In some packages it is commone for auxiliary files such as supporting schemas to be included in packages without a corresponding File object so an empty list does not indicate that the file can be removed safely. These files are still included when packaging the content package for interchange.

Finally, if a file referred to by a File object in the manifest is missing an entry is still created in the fileTable. You can walk the keys of the fileTable testing if each file exists to determine if some expected files are missing from the package.

The keys in fileTable are VirtualFilePath instances. To convert a string to an appropriate instance use the FilePath() method.

FilePath(*path)

Converts a string into a pyslet.vfs.VirtualFilePath instance suitable for using as a key into the fileTable. The conversion is done using the file system of the content package’s directory, dPath.

SetIgnoreFiles(ignoreFiles)

Sets the regular expression used to determine if a file should be ignored.

Some operating systems and utilities create hidden files or other spurious data inside the content package directory. For example, Apple’s OS X creates .DS_Store files and the svn source control utility creates .svn directories. The files shouldn’t generally be included in exported packages as they may confuse the recipient (who may be using a system on which these files and directories are not hidden) and be deemed to violate the specification, not to mention adding unnecessarily to the size of the package and perhaps even leaking information unintentionally.

To help avoid this type of problem the class uses a regular expression to determine if a file should be considered part of the package. When listing directories, the names of the files found are compared against this regular expression and are ignored if they match.

By default, the pattern is set to match all directories and files with names beginning ‘.’ so you will not normally need to call this method.

IgnoreFile(f)

Compares a file or directory name against the pattern set by SetIgnoreFiles().

f is a unicode string.

IgnoreFilePath(fPath)

Compares a file path against the pattern set by SetIgnoreFiles()

The path is normalised before comparison and any segments consisting of the string ‘..’ are skipped. The method returns True if any of the remaining path components matches the ignore pattern. In other words, if the path describes a file that is is in a directory that should be ignored it will also be ignored.

The path can be relative or absolute. Relative paths are not made absolute prior to comparison so this method is not affected by the current directory, even if the current diretory would itself be ignored.

RebuildFileTable()

Rescans the file system and manifest and rebuilds the fileTable.

PackagePath(fPath)

Converts an absolute file path into a canonical package-relative path

Returns None if fPath is not inside the package.

ExportToPIF(zPath)

Exports the content package, saving the zipped package in zPath

zPath is overwritten by this operation.

In order to make content packages more interoperable this method goes beyond the basic zip specification and ensures that pathnames are always UTF-8 encoded when added to the archive. When creating instances of ContentPackage from an existing archive the reverse transformation is performed. When exchanging PIF files between systems with different native file path encodings, encoding erros may occurr.

GetUniqueFile(suggestedPath)

Returns a unique file path suitable for creating a new file in the package.

suggestedPath is used to provide a suggested path for the file. This may be relative (to the root and manifest) or absolute but it must resolve to a file (potentially) in the package. The suggestedPath should either be a VirtualFilePath (of the same type as the content package’s dPath) or a string suitable for conversion to a VirtualFilePath.

When suggestedPath is relative, it is forced to lower-case. This is consistent with the behaviour of normcase on systems that are case insensitive. The trouble with case insensitive file systems is that it may be impossible to unpack a content package created on a case sensitive system and store it on a case insenstive one. By channelling all file storage through this method (and constructing any URIs after the file has been stored) the resulting packages will be more portable.

If suggestedPath already corresponds to a file already in the package, or to a file already referred to in the manifest, then a random string is added to it while preserving the suggested extension in order to make it unique.

The return result is always normalized and returned relative to the package root.

File(resource, href)

Returns a new File object attached to resource

href is the URI of the file expressed relative to the resource element in the manifest. Although this is normally the same as the URI expressed relative to the package, a resource may have an xml:base attribute that alters the base for resolving relative URIs.

href may of course be an absolute URI to an external resource. If an absolute URI is given to a local file it must be located inside the package.

Attempting to add a File object representing the manifest file iteself will raise CPFilePathError.

The fileTable is updated automatically by this method.

FileCopy(resource, srcURL)

Returns a new File object copied into the package from srcURL, attached to resource.

The file is copied to the same directory as the resource’s entry point or to the main package directory if the resource has no entry point.

The File object is actually created with the File() method.

Note that if srcURL points to a missing file then no file is copied to the package but the associated File is still created. It will point to a missing file.

DeleteFile(href)

Removes the file at href from the file system

This method also removes any file references to it from resources in the manifest. href may be given relative to the package root directory. The entry in fileTable is also removed.

CPFileTypeError is raised if the file is not a regular file

CPFilePathError is raised if the file is an IgnoreFile(), the manifest itself or outside of the content package.

CPProtocolError is raised if the indicated file is not in the local file system.

GetPackageName()

Returns a human readable name for the package

The name is determined by the method used to create the object. The purpose is to return a name that would be intuitive to the user if it were to be used as the name of the package directory or the stem of a file name when exporting to a PIF file.

Note that the name is returned as a unicode string suitable for showing to the user and may need to be encoded before being used in file path operations.

Close()

Closes the content package, removing any temporary files.

This method must be called to clean up any temporary files created when processing the content package. Temporary files are created inside a special temporary directory created using the builtin python tempdir.mkdtemp function. They are not automatically cleaned up when the process exits or when the garbage collector disposes of the object. Use of try:... finally: to clean up the package is recommended. For example:

pkg=ContentPackage("MyPackage.zip")
try:
        # do stuff with the content package here
finally:
        pkg.Close()
class pyslet.imscpv1p2.ManifestDocument(**args)

Bases: pyslet.xmlnames20091208.XMLNSDocument

Represents the imsmanifest.xml file itself.

Buildong on pyslet.xmlnames20091208.XMLNSDocument this class is used for parsing and writing manifest files.

The constructor defines three additional prefixes using MakePrefix(), mapping xsi onto XML schema, imsmd onto the IMS LRM namespace and imsqti onto the IMS QTI 2.1 namespace. It also adds a schemaLocation attribute. The elements defined by the pyslet.imsmdv1p2p1 and pyslet.imsqtiv2p1 modules are added to the classMap to ensure that metadata from those schemas are bound to the special classes defined there.

defaultNS = None

the default namespace is set to IMSCP_NAMESPACE

get_element_class(name)

Overrides pyslet.xmlnames20091208.XMLNSDocument.get_element_class() to look up name.

The class contains a mapping from (namespace,element name) pairs to class objects representing the elements. Any element not in the class map returns XMLNSElement() instead.

3.1.2.1. Constants

The following constants are used for setting and interpreting XML documents that conform to the Content Packaging specification

pyslet.imscpv1p2.IMSCP_NAMESPACE = 'http://www.imsglobal.org/xsd/imscp_v1p1'

str(object=’‘) -> string

Return a nice string representation of the object. If the argument is a string, the return value is the same object.

pyslet.imscpv1p2.IMSCP_SCHEMALOCATION = 'http://www.imsglobal.org/xsd/imscp_v1p1.xsd'

str(object=’‘) -> string

Return a nice string representation of the object. If the argument is a string, the return value is the same object.

pyslet.imscpv1p2.IMSCPX_NAMESPACE = 'http://www.imsglobal.org/xsd/imscp_extensionv1p2'

str(object=’‘) -> string

Return a nice string representation of the object. If the argument is a string, the return value is the same object.

3.1.2.2. Elements

class pyslet.imscpv1p2.CPElement(parent, name=None)

Bases: pyslet.xmlnames20091208.XMLNSElement

Base class for all elements defined by the Content Packaging specification.

class pyslet.imscpv1p2.Manifest(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the manifest element, the root element of the imsmanifest file.

MetadataClass

the default class to represent the metadata element

alias of Metadata

OrganizationsClass

the default class to represent the organizations element

alias of Organizations

ResourcesClass

the default class to represent the resources element

alias of Resources

ManifestClass

the default class to represent child manifest elements

alias of Manifest

Metadata = None

the manifest’s metadata element

Organizations = None

the organizations element

Resources = None

the resources element

Manifest = None

a list of child manifest elements

class pyslet.imscpv1p2.Metadata(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the Metadata element.

SchemaClass

the default class to represent the schema element

alias of Schema

SchemaVersionClass

alias of SchemaVersion

Schema = None

the optional schema element

SchemaVersion = None

the optional schemaversion element

class pyslet.imscpv1p2.Schema(parent, name=None)

Bases: pyslet.imscpv1p2.CPElement

Represents the schema element.

class pyslet.imscpv1p2.SchemaVersion(parent, name=None)

Bases: pyslet.imscpv1p2.CPElement

Represents the schemaversion element.

class pyslet.imscpv1p2.Organizations(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the organizations element.

OrganizationClass

the default class to represent the organization element

alias of Organization

Organization = None

a list of organization elements

class pyslet.imscpv1p2.Organization(parent, name=None)

Bases: pyslet.imscpv1p2.CPElement

Represents the organization element.

class pyslet.imscpv1p2.Resources(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the resources element.

ResourceClass

the default class to represent the resource element

alias of Resource

Resource = None

the list of resources in the manifest

class pyslet.imscpv1p2.Resource(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the resource element.

MetadataClass

the default class to represent the metadata element

alias of Metadata

FileClass

the default class to represent the file element

alias of File

DependencyClass

the default class to represent the dependency element

alias of Dependency

type = None

the type of the resource

href = None

the href pointing at the resource’s entry point

Metadata = None

the resource’s optional metadata element

File = None

a list of file elements associated with the resource

Dependency = None

a list of dependencies of this resource

GetEntryPoint()

Returns the File object that is identified as the entry point.

If there is no entry point, or no File object with a matching href, then None is returned.

SetEntryPoint(f)

Set’s the File object that is identified as the resource’s entry point.

The File must already exist and be associated with the resource.

class pyslet.imscpv1p2.File(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the file element.

href = None

the href used to locate the file object

PackagePath(cp)

Returns the normalized file path relative to the root of the content package, cp.

If the href does not point to a local file then None is returned. Otherwise, this function calculates an absolute path to the file and then calls the content package’s ContentPackage.PackagePath() method.

class pyslet.imscpv1p2.Dependency(parent)

Bases: pyslet.imscpv1p2.CPElement

Represents the dependency element.

identifierref = None

the identifier of the resource in this dependency

3.1.2.3. Utilities

pyslet.imscpv1p2.PathInPath(childPath, parentPath)

Utility function that returns childPath expressed relative to parentPath

This function processes file system paths, not the path components of URI.

Both paths are normalized to remove any redundant navigational segments before any processing, the resulting path will not contain these either.

If childPath is not contained in parentPath then None is returned.

If childPath and parentPath are equal an empty string is returned.