text: """
  YouTrack 2.0 REST API
  """
  
  import re
  from xml.dom import Node
  from xml.dom.minidom import Document
  from xml.dom import minidom
  from xml.sax.saxutils import escape, quoteattr
  
  
  EXISTING_FIELD_TYPES = {
      'numberInProject'   :   'integer',
      'summary'           :   'string',
      'description'       :   'string',
      'created'           :   'date',
      'updated'           :   'date',
      'updaterName'       :   'user[1]',
      'resolved'          :   'date',
      'reporterName'      :   'user[1]',
      'watcherName'       :   'user[*]',
      'voterName'         :   'user[*]'
  }
  
  EXISTING_FIELDS = ['numberInProject', 'projectShortName'] + EXISTING_FIELD_TYPES.keys()
  
  class YouTrackException(Exception):
      def __init__(self, url, response, content):
          self.response = response
          self.content = content
          msg = 'Error for [' + url + "]: " + str(response.status)
  
          if response.reason is not None:
              msg += ": " + response.reason
  
          if response.has_key('content-type'):
              ct = response["content-type"]
              if ct is not None and ct.find('text/html') == -1:
                  try:
                      xml = minidom.parseString(content)
                      self.error = YouTrackError(xml, self)
                      msg += ": " + self.error.error
                  except:
                      self.error = content
                      msg += ": " + self.error
  
          if isinstance(msg, unicode):
              msg = msg.encode('utf-8')
  
          Exception.__init__(self, msg)
  
  
  class YouTrackObject(object):
      def __init__(self, xml=None, youtrack=None):
          self.youtrack = youtrack
          self._attribute_types = dict()
          self._update(xml)
  
      def toXml(self):
          raise NotImplementedError
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self._updateFromAttrs(xml)
          self._updateFromChildren(xml)
  
      def _updateFromAttrs(self, el):
          if el.attributes is not None:
              for i in range(el.attributes.length):
                  a = el.attributes.item(i)
                  setattr(self, a.name, a.value)
  
      def _updateFromChildren(self, el):
          children = [e for e in el.childNodes if e.nodeType == Node.ELEMENT_NODE]
          if children:
              for c in children:
                  name = c.getAttribute('name')
                  value = None
                  if not len(name):
                      continue
                  if isinstance(name, unicode):
                      name = name.encode('utf-8')
                  values = c.getElementsByTagName('value')
                  if (values is not None) and len(values):
                      if values.length == 1:
                          value = self._text(values.item(0))
                      elif values.length > 1:
                          value = [self._text(value) for value in values]
                  elif c.hasAttribute('value'):
                      value = c.getAttribute('value')
                  if value is not None:
                      setattr(self, name, value)
                      if c.hasAttribute('xsi:type'):
                          self._attribute_types[name] = c.getAttribute('xsi:type')
  
      def _text(self, el):
          return "".join([e.data for e in el.childNodes if e.nodeType == Node.TEXT_NODE])
  
      def __repr__(self):
          _repr = ''
          for k, v in self.__dict__.items():
              if k in ('youtrack', '_attribute_types'):
                  continue
              if isinstance(k, unicode):
                  k = k.encode('utf-8')
              if isinstance(v, unicode):
                  v = v.encode('utf-8')
              _repr += k + ' = ' + str(v) + '\n'
          return _repr
  
      def __iter__(self):
          for item in self.__dict__:
              if item == '_attribute_types':
                  continue
              attr = self.__dict__[item]
              if isinstance(attr, basestring) or isinstance(attr, list) \
                      or getattr(attr, '__iter__', False):
                  yield item
  
      def __getitem__(self, key):
          return self.__dict__[key]
  
      def __setitem__(self, key, value):
          self.__dict__[key] = value
  
  
  class YouTrackError(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml.documentElement.tagName == 'error':
              self.error = self._text(xml.documentElement)
          else:
              self.error = xml.toxml()
  
  
  class Issue(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          if xml is not None:
              if len(xml.getElementsByTagName('links')) > 0:
                  self.links = [Link(e, youtrack) for e in xml.getElementsByTagName('issueLink')]
              else:
                  self.links = None
  
              if len(xml.getElementsByTagName('attachments')) > 0:
                  self.attachments = [Attachment(e, youtrack) for e in xml.getElementsByTagName('fileUrl')]
              else:
                  self.attachments = None
              for m in ['fixedVersion', 'affectsVersion']: self._normilizeMultiple(m)
              if hasattr(self, 'fixedInBuild') and (self.fixedInBuild == 'Next build'):
                  self.fixedInBuild = None
  
      def _normilizeMultiple(self, name):
          if hasattr(self, name):
              attrValue = self[name]
              if not isinstance(attrValue, list):
                  if isinstance(attrValue, unicode):
                      attrValue = attrValue.encode('utf-8')
                  if attrValue is None or not len(attrValue):
                      delattr(self, name)
                  else:
                      self[name] = [value.strip() for value in attrValue.split(',')]
  
      def getReporter(self):
          return self.youtrack.getUser(self.reporterName)
  
      def hasAssignee(self):
          return getattr(self, 'Assignee', None) is not None
  
      def getAssignee(self):
          return self.youtrack.getUser(self.Assignee)
  
      def getUpdater(self):
          return self.youtrack.getUser(self.updaterName)
  
      def hasVoters(self):
          return getattr(self, 'voterName', None) is not None
  
      def getVoters(self):
          voters = self.voterName
          if voters:
              if isinstance(voters, list):
                  voters = [self.youtrack.getUser(v) for v in voters]
              else:
                  voters = [self.youtrack.getUser(voters)]
          return voters
  
      def getComments(self):
          #TODO: do not make rest request if issue was initied with comments
          if not hasattr(self, 'comments'):
              setattr(self, 'comments', self.youtrack.getComments(self.id))
          return self.comments
  
      def getAttachments(self):
          if getattr(self, 'attachments', None) is None:
              return self.youtrack.getAttachments(self.id)
          else:
              return self.attachments
  
      def deleteAttachment(self, attachment):
          return self.youtrack.deleteAttachment(self.id, attachment.id)
  
      def getLinks(self, outwardOnly=False):
          if getattr(self, 'links', None) is None:
              return self.youtrack.getLinks(self.id, outwardOnly)
          else:
              return [l for l in self.links if l.source == self.id or not outwardOnly]
  
      @property
      def events(self):
          return self.youtrack.getEvents(self.id)
  
      @property
      def custom_fields(self):
          cf = []
          for attr_name, attr_type in self._attribute_types.items():
              if attr_type in ('CustomFieldValue', 'MultiUserField'):
                  cf.append(self[attr_name])
          return cf
  
  
  class Comment(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          if not hasattr(self, 'text'):
              self.text = ''
  
      def getAuthor(self):
          return self.youtrack.getUser(self.author)
  
  
  class IssueChange(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          self.fields = []
          self.updated = 0
          self.updater_name = None
          self.comments = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          for field in xml.getElementsByTagName('field'):
              name = field.getAttribute('name')
              if name == 'updated':
                  self.updated = int(self._text(field.getElementsByTagName('value')[0]))
              elif name == 'updaterName':
                  self.updater_name = self._text(field.getElementsByTagName('value')[0])
              elif name == 'links':
                  pass
              else:
                  self.fields.append(ChangeField(field, self.youtrack))
  
          for comment in xml.getElementsByTagName('comment'):
              self.comments.append(comment.getAttribute('text'))
  
  
  class ChangeField(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          self.name = None
          self.old_value = []
          self.new_value = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          self.name = xml.getAttribute('name')
          old_value = xml.getElementsByTagName('oldValue')
          for value in old_value:
              self.old_value.append(self._text(value))
          new_value = xml.getElementsByTagName('newValue')
          for value in new_value:
              self.new_value.append(self._text(value))
  
  class Link(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
      def __hash__(self):
          return hash((self.typeName, self.source, self.target))
  
      def __eq__(self, other):
          return isinstance(other, Link) and self.typeName == other.typeName and self.source == other.source and self.target == other.target
  
      def __ne__(self, other):
          return not self.__eq__(other)
  
  class Attachment(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          # Workaround for JT-18936
          self.url = re.sub(r'^.*?(?=/_persistent)', '', self.url)
  
      def getContent(self):
          return self.youtrack.getAttachmentContent(self.url.encode('utf-8'))
  
      def getAuthor(self):
          if self.authorLogin == '<no user>':
              return None
          return self.youtrack.getUser(self.authorLogin)
  
  
  class User(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          self.getGroups = lambda : []
  
      def __hash__(self):
          return hash(self.login)
  
      def __cmp__(self, other):
          if isinstance(other, User):
              return cmp(self.login, other.login)
          else:
              return cmp(self.login, other)
  
  
  class Group(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  
  class Role(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  class UserRole(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          self.name = ''
          self.projects = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self.name = xml.getAttribute("name")
          projects = xml.getElementsByTagName("projectRef")
          self.projects = [p.getAttribute("id") for p in projects] if projects is not None else []
  
      def toXml(self):
          result = '<userRole name="%s">' % self.name.encode('utf-8')
          if len(self.projects):
              result += '<projects>'
              result += "".join('<projectRef id="%s" url="dirty_hack"/>' % project.encode('utf-8') for project in self.projects)
              result += '</projects>'
          else:
              result += '<projects/>'
          result += '</userRole>'
          return result
  
  class Permission(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  class Project(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          if not hasattr(self, 'description'):
              self.description = ''
  
      def getSubsystems(self):
          return self.youtrack.getSubsystems(self.id)
  
      def createSubsystem(self, name, isDefault, defaultAssigneeLogin):
          return self.youtrack.createSubsystem(self.id, name, isDefault, defaultAssigneeLogin)
  
  
  class Subsystem(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  
  class Version(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
          if not hasattr(self, 'description'):
              self.description = ''
  
          if not hasattr(self, 'releaseDate'):
              self.releaseDate = None
  
  
  class IssueLinkType(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  
  class WorkItem(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self.url = xml.getAttribute('url')
          for e in xml.childNodes:
              if e.tagName == 'author':
                  self.authorLogin = e.getAttribute('login')
              elif e.tagName.lower() == 'worktype':
                  self['worktype'] = self._text(e.getElementsByTagName('name')[0])
              else:
                  self[e.tagName] = self._text(e)
      
  
  class CustomField(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
  
  class ProjectCustomField(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _updateFromChildren(self, el):
          self.params = {}
          for c in el.getElementsByTagName('param'):
              name = c.getAttribute('name')
              value = c.getAttribute('value')
              self[name] = value
              self.params[name] = value
  
  
  class UserBundle(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          self.users = []
          self.groups = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self.name = xml.getAttribute("name")
          users = xml.getElementsByTagName("user")
          if users is not None:
              self.users = [self.youtrack.getUser(v.getAttribute("login")) for v in users]
          else:
              self.users = []
          groups = xml.getElementsByTagName("userGroup")
          if groups is not None:
              self.groups = [self.youtrack.getGroup(v.getAttribute("name")) for v in groups]
          else:
              self.groups = []
  
      def toXml(self):
          result = '<userBundle name="%s">' % self.name.encode('utf-8')
          result += "".join(
              '<userGroup name="%s" url="dirty_hack"></userGroup>' % group.name.encode('utf-8') for group in self.groups)
          result += "".join(
              '<user login="%s" url="yet_another_dirty_hack"></user>' % user.login.encode('utf-8') for user in self.users)
          result += '</userBundle>'
          return result
  
      def get_field_type(self):
          return "user"
  
      def get_all_users(self):
          all_users = self.users
          for group in self.groups:
              #returns objects containing only login and url info
              group_users = self.youtrack.getUsers({'group': group.name.encode('utf-8')})
              for user in group_users:
                  # re-request credentials separately for each user to get more details
                  try:
                      refined_user = self.youtrack.getUser(user.login)
                      all_users.append(refined_user)
                  except YouTrackException, e:
                      print "Error on extracting user info for [" + str(user.login) + "] user won't be imported"
                      print e
          return list(set(all_users))
  
  
  class Bundle(YouTrackObject):
      def __init__(self, element_tag_name, bundle_tag_name, xml=None, youtrack=None):
          self._element_tag_name = element_tag_name
          self._bundle_tag_name = bundle_tag_name
          self.values = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self.name = xml.getAttribute("name")
          values = xml.getElementsByTagName(self._element_tag_name)
          if values is not None:
              self.values = [self._createElement(value) for value in values]
          else:
              self.values = []
  
      def toXml(self):
          result = '<%s name="%s">' % (self._bundle_tag_name, escape(self.name.encode('utf-8')))
          result += ''.join(v.toXml() for v in self.values)
          result += '</%s>' % self._bundle_tag_name
          return result
  
      def get_field_type(self):
          return self._element_tag_name
  
      def createElement(self, name):
          element = self._createElement(None)
          element.name = name
          return element
  
      def _createElement(self, xml):
          pass
  
  
  class BundleElement(YouTrackObject):
      def __init__(self, element_tag_name, xml=None, youtrack=None):
          self.element_name = element_tag_name
          YouTrackObject.__init__(self, xml, youtrack)
  
      def toXml(self):
          result = '<' + self.element_name
          for elem in self:
              if elem in ("name", "element_name"):
                  continue
              value = self[elem]
              if value is None or not len(value):
                  continue
              if isinstance(elem, unicode):
                  elem = elem.encode('utf-8')
              if isinstance(value, unicode):
                  value = value.encode('utf-8')
              result += ' %s="%s"' % (escape(elem), escape(str(value)))
          result += ">%s</%s>" % (escape(self.name.encode('utf-8')), self.element_name)
          return result
  
      def _update(self, xml):
          if xml is None:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
  
          self.name = [e.data for e in xml.childNodes if e.nodeType == Node.TEXT_NODE][0]
          self.description = xml.getAttribute('description')
          self.colorIndex = xml.getAttribute('colorIndex')
          self._update_specific_attributes(xml)
  
      def _update_specific_attributes(self, xml):
          pass
  
  
  class EnumBundle(Bundle):
      def __init__(self, xml=None, youtrack=None):
          Bundle.__init__(self, "value", "enumeration", xml, youtrack)
  
      def _createElement(self, xml):
          return EnumField(xml, self.youtrack)
  
      def get_field_type(self):
          return "enum"
  
  
  class EnumField(BundleElement):
      def __init__(self, xml=None, youtrack=None):
          BundleElement.__init__(self, "value", xml, youtrack)
  
  
  class BuildBundle(Bundle):
      def __init__(self, xml=None, youtrack=None):
          Bundle.__init__(self, "build", "buildBundle", xml, youtrack)
  
      def _createElement(self, xml):
          return Build(xml, self.youtrack)
  
  
  class Build(BundleElement):
      def __init__(self, xml=None, youtrack=None):
          BundleElement.__init__(self, "build", xml, youtrack)
  
      def _update_specific_attributes(self, xml):
          self.assembleDate = xml.getAttribute('assembleName')
  
  
  class OwnedFieldBundle(Bundle):
      def __init__(self, xml=None, youtrack=None):
          Bundle.__init__(self, "ownedField", "ownedFieldBundle", xml, youtrack)
  
      def _createElement(self, xml):
          return OwnedField(xml, self.youtrack)
  
  
  class OwnedField(BundleElement):
      def __init__(self, xml=None, youtrack=None):
          BundleElement.__init__(self, "ownedField", xml, youtrack)
  
      def _update_specific_attributes(self, xml):
          owner = xml.getAttribute("owner")
          if owner != '<no user>':
              self.owner = owner
          else:
              self.owner = None
  
  
  class StateBundle(Bundle):
      def __init__(self, xml=None, youtrack=None):
          Bundle.__init__(self, "state", "stateBundle", xml, youtrack)
  
      def _createElement(self, xml):
          return StateField(xml, self.youtrack)
  
  
  class StateField(BundleElement):
      def __init__(self, xml=None, youtrack=None):
          BundleElement.__init__(self, "state", xml, youtrack)
  
      def _update_specific_attributes(self, xml):
          self.is_resolved = xml.getAttribute("isResolved")
  
  
  class VersionBundle(Bundle):
      def __init__(self, xml=None, youtrack=None):
          Bundle.__init__(self, "version", "versions", xml, youtrack)
  
      def _createElement(self, xml):
          return VersionField(xml, self.youtrack)
  
  
  class VersionField(BundleElement):
      def __init__(self, xml=None, youtrack=None):
          BundleElement.__init__(self, "version", xml, youtrack)
  
      def _update_specific_attributes(self, xml):
          self.releaseDate = xml.getAttribute("releaseDate")
          self.released = xml.getAttribute("released").lower() == "true"
          self.archived = xml.getAttribute("archived").lower() == "true"
  
  
  class IntelliSense(YouTrackObject):
      def __init__(self, xml=None, youtrack=None):
          self.suggestions = []
          self.highlights = []
          self.queries = []
          YouTrackObject.__init__(self, xml, youtrack)
  
      def _update(self, xml):
          if not xml:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
          for c in xml.childNodes:
              if c.tagName in ('suggest', 'recent'):
                  for item in c.getElementsByTagName('item'):
                      suggest = {}
                      for i in item.childNodes:
                          if i.tagName in ('completion', 'match'):
                              suggest[i.tagName] = {
                                  'start': int(i.getAttribute('start')),
                                  'end': int(i.getAttribute('end'))}
                          else:
                              if i.tagName == 'caret':
                                  suggest[i.tagName] = int(self._text(i))
                              else:
                                  suggest[i.tagName] = self._text(i)
                      if 'option' not in suggest:
                          continue
                      if c.tagName == 'suggest':
                          self.suggestions.append(suggest)
                      else:
                          self.queries.append(suggest)
              elif c.tagName == 'highlight':
                  for item in c.getElementsByTagName('range'):
                      rng = {}
                      for i in item.childNodes:
                          if i.tagName in ('start', 'end'):
                              rng[i.tagName] = int(self._text(i))
                          else:
                              rng[i.tagName] = self._text(i)
                      self.highlights.append(rng)
  
  
  class GlobalTimeTrackingSettings(YouTrackObject):
      def _update(self, xml):
          if not xml:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
          for e in xml.childNodes:
              self[e.tagName] = self._text(e)
  
  
  class ProjectTimeTrackingSettings(YouTrackObject):
      def _update(self, xml):
          if not xml:
              return
          if isinstance(xml, Document):
              xml = xml.documentElement
          self['Enabled'] = xml.getAttribute('enabled').lower() == 'true'
          self['EstimateField'] = None
          self['TimeSpentField'] = None
          for e in xml.childNodes:
              if e.tagName.lower() == 'estimation':
                  self['EstimateField'] = e.getAttribute('name')
              elif e.tagName.lower() == 'spenttime':
                  self['TimeSpentField'] = e.getAttribute('name')
  