===================
A generic SCORM API
===================

  ($Id$)

In order to work with the SCORM API we first need a tracking storage.

  >>> from cybertools.tracking.btree import TrackingStorage
  >>> tracks = TrackingStorage()

We can now create a ScormAPI adapter. Note that this adapter is stateless
as usually a new instance is created upon each request. In order to comply
with Zope's adapter protocol the constructor can only have one argument,
the context, i.e. the tracking storage. Therefore we have to set the
basic attributes of the adapter with a separate ``init()`` call.

  >>> from cybertools.scorm.base import ScormAPI
  >>> api = ScormAPI(tracks)
  >>> api.init('a001', 0, 'user1')

The first step is always the initialize() call - though in our case it
does not do anything.

  >>> api.initialize()
  '0'

Then we can set some values.

  >>> rc = api.setValue('cmi.interactions.0.id', 'q007')
  >>> rc = api.setValue('cmi.interactions.0.result', 'correct')
  >>> rc = api.setValue('cmi.comments_from_learner.comment', 'Hello SCORM')
  >>> rc = api.setValue('cmi.interactions.1.id', 'q009')
  >>> rc = api.setValue('cmi.interactions.1.result', 'incorrect')

Depending on the data elements the values entered are kept together in
one track or stored in separate track objects. So there is a separate
track for each interaction and one additional track for all the other elements.

  >>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
  ...     print t.data
  {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM'}
  {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
  {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}

Using the getValue() method we can retrieve certain values without having
to care about the storage in different track objects.

  >>> api.getValue('cmi.comments_from_learner.comment')
  ('Hello SCORM', '0')
  >>> api.getValue('cmi.interactions.0.id')
  ('q007', '0')
  >>> api.getValue('cmi.interactions.1.result')
  ('incorrect', '0')

We can also query special elements like _count and _children.

  >>> api.getValue('cmi.objectives._count')
  (0, '0')
  >>> api.getValue('cmi.interactions._count')
  (2, '0')

  >>> api.getValue('cmi.interactions._children')
  (('id', 'type', 'objectives', 'timestamp', 'correct_responses',
    'weighting', 'learner_response', 'result', 'latency', 'description'), '0')
  >>> api.getValue('cmi.objectives.5.score._children')
  (('scaled', 'raw', 'min', 'max'), '0')

We may also update existing tracks using the ``setValue()`` method.

  >>> rc = api.setValue('cmi.comments_from_learner.location', 'q007')
  >>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
  ...     print t.data
  {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
   'cmi.comments_from_learner.location': 'q007'}
  {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
  {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}

With the ``setValues()`` method we may set more than one element with
one call. (This is not a SCORM-compliant call but is provided for efficiency
reasons as it allows us to update a bunch of elements with just one
XML-RPC call.)

  >>> data = {'cmi.interactions.2.result': 'correct',
  ...         'cmi.interactions.2.learner_response': 'my answer',
  ... }
  >>> rc = api.setValues(data)

  >>> for t in sorted(tracks.values(), key=lambda x: x.data['recnum']):
  ...     print t.data
  {'recnum': -1, 'cmi.comments_from_learner.comment': 'Hello SCORM',
   'cmi.comments_from_learner.location': 'q007'}
  {'cmi.interactions.0.id': 'q007', 'cmi.interactions.0.result': 'correct', 'recnum': 0}
  {'cmi.interactions.1.result': 'incorrect', 'cmi.interactions.1.id': 'q009', 'recnum': 1}
  {'cmi.interactions.2.learner_response': 'my answer',
   'cmi.interactions.2.result': 'correct', 'recnum': 2}

  >>> api.getValue('cmi.interactions.2.result')
  ('correct', '0')
  >>> api.getValue('cmi.interactions.2.learner_response')
  ('my answer', '0')
  >>> api.getValue('cmi.comments_from_learner.comment')
  ('Hello SCORM', '0')
  >>> api.getValue('cmi.comments_from_learner.location')
  ('q007', '0')
