diff --git a/compile_definitions.py b/compile_definitions.py
index 426c5775217432a33f022e0031cf05a047eaa2ec..26c3d933f13a6f4901adaff535b0779f982ffdb4 100644
--- a/compile_definitions.py
+++ b/compile_definitions.py
@@ -7,8 +7,8 @@ from xml.etree import ElementTree
 
 import six
 
-from coolamqp.framing.compilation.utilities import get_constants, get_classes, \
-    get_domains, \
+from coolamqp.framing.compilation.utilities import Constant, Class, \
+    Domain, \
     name_class, format_method_class_name, format_field_name, ffmt, to_docstring, \
     pythonify_name, to_code_binary, \
     frepr, get_size
@@ -77,7 +77,7 @@ Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved
     FRAME_END = None
     con_classes = collections.defaultdict(list)
     line('# Core constants\n')
-    for constant in get_constants(xml):
+    for constant in Constant.findall(xml):
         if pythonify_name(constant.name) == 'FRAME_END':
             FRAME_END = constant.value
         g = ffmt('%s = %s\n', pythonify_name(constant.name), constant.value)
@@ -108,7 +108,7 @@ Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved
     # get domains
     domain_to_basic_type = {}
     line('\n\n\nDOMAIN_TO_BASIC_TYPE = {\n')
-    for domain in get_domains(xml):
+    for domain in Domain.findall(xml):
         line(u'    %s: %s,\n', frepr(domain.name),
              frepr(None if domain.elementary else domain.type))
         domain_to_basic_type[domain.name] = domain.type
@@ -122,7 +122,7 @@ Field = collections.namedtuple('Field', ('name', 'type', 'basic_type', 'reserved
     methods_that_are_replies_for = {}  # eg. ConnectionOk: [ConnectionOpenOk]
 
     # Output classes
-    for cls in get_classes(xml):
+    for cls in Class.findall(xml):
 
         cls = cls._replace(
             properties=[p._replace(basic_type=domain_to_basic_type[p.type]) for
diff --git a/coolamqp/framing/compilation/utilities.py b/coolamqp/framing/compilation/utilities.py
index 724932059a78ee961a3a60a64eeb03952c058380..22f2f54c8c48eb8f2d2a56e3885895c5df9b5ab0 100644
--- a/coolamqp/framing/compilation/utilities.py
+++ b/coolamqp/framing/compilation/utilities.py
@@ -2,7 +2,7 @@
 from __future__ import absolute_import, division, print_function
 
 import math
-from collections import namedtuple
+from satella.coding import typednamedtuple
 
 import six
 
@@ -10,33 +10,171 @@ from coolamqp.framing.base import BASIC_TYPES, DYNAMIC_BASIC_TYPES
 
 # docs may be None
 
-Constant = namedtuple('Constant', (
-    'name', 'value', 'kind',
-    'docs'))  # kind is AMQP constant class # value is int
-Field = namedtuple('Field', (
-    'name', 'type', 'label', 'docs', 'reserved',
-    'basic_type'))  # reserved is bool
-# synchronous is bool, constant is bool
-# repponse is a list of method.name
-Class_ = namedtuple('Class_', (
-    'name', 'index', 'docs', 'methods', 'properties'))  # label is int
-Domain = namedtuple('Domain',
-                    ('name', 'type', 'elementary'))  # elementary is bool
-
-
-class Method(object):
-    def __init__(self, name, synchronous, index, label, docs, fields, response,
-                 sent_by_client, sent_by_server):
-        self.name = name
-        self.synchronous = synchronous
-        self.index = index
-        self.fields = fields
-        self.response = response
-        self.label = label
-        self.docs = docs
-        self.sent_by_client = sent_by_client
-        self.sent_by_server = sent_by_server
-        self.constant = all(f.reserved for f in self.fields)
+
+
+
+__name = ('name', 'name', str)
+
+
+class optgetter(object):
+    def __init__(self, callable):
+        self.callable = callable
+
+    def __call__(self, elem):
+        return self.callable(elem)
+
+
+_Required = type('_Required')
+
+class _Field(object):
+
+    def set(self, obj, elem):
+        obj.__dict__[self.field_name] = self.find(elem)
+
+    def __init__(self, field_name):
+        self.field_name = field_name
+
+    def find(self, elem):
+        raise NotImplementedError('abstract')
+
+
+class _ComputedField(_Field):
+    def __init__(self, field_name, find_fun):
+        super(_ComputedField, self).__init__(field_name)
+        self.find = find_fun
+
+
+class _ValueField(_Field):
+    def __init__(self, xml_names, field_name, field_type=lambda x: x,
+                 default=_Required):
+        self.xml_names = (xml_names,) if isinstance(xml_names,
+                                                    six.text_type) else xml_names
+        self.field_name = field_name
+        self.field_type = field_type
+        self.default = default
+
+    def find(self, elem):
+        return self.field_type(self._find(elem))
+
+    def _find(self, elem):
+        for xmln in self.xml_names:
+            if xmln in elem.attrib:
+                return elem.attrib[xmln]
+        else:
+            if self.default is _Required:
+                raise TypeError('Expected field')
+            else:
+                return self.default
+
+
+class _SimpleField(_ValueField):
+    def __init__(self, name, field_type=None, default=_Required):
+        super(_SimpleField, self).__init__(name, name, field_type, default=default)
+
+
+def get_docs(elem, label=False):
+    """Parse an XML element. Return documentation"""
+    for kid in elem.getchildren():
+
+        if kid.tag == 'rule':
+            return get_docs(kid)
+
+        s = kid.text.strip().split('\n')
+        return u'\n'.join([u.strip() for u in s if len(u.strip()) > 0])
+
+    if label:
+        return elem.attrib.get('label', None)
+
+_name = _SimpleField('name', unicode)
+_docs = _ComputedField('docs', lambda elem: get_docs(elem))
+_docsl = _ComputedField('docs', lambda elem: get_docs(elem, label=True))
+
+class BaseObject(object):
+
+    FIELDS = []
+    # tuples of (xml name, field name, type, (optional) default value)
+
+    def __init__(self, *args):
+
+        if len(args) == 1:
+            elem, = args
+
+            self.docs = get_docs(elem)
+
+            for ft in (_Field(*args) for args in self.FIELDS):
+                ft.set(self, elem)
+        else:
+            for fname, value in zip(['name'] + [k[1] for k in self.FIELDS] + ['docs']):
+                self.__dict__[fname] = value
+
+    @classmethod
+    def findall(cls, xml):
+        return [cls(p) for p in xml.findall(cls.NAME)]
+
+
+class Constant(BaseObject):
+    NAME = 'constant'
+    FIELDS = [
+        _name,
+        _SimpleField('value', int),
+        _ValueField('class', 'kind', default=''),
+        _docs,
+    ]
+
+class Field(BaseObject):
+    NAME = 'field'
+    FIELDS = [
+        _name,
+        _ValueField(('domain', 'type'), 'type', str),
+        _SimpleField('label', None),
+        _SimpleField('reserved', lambda x: bool(int(x)), default=0),
+        _ComputedField('basic_type', lambda elem: elem.attrib['type'] == elem.attrib['name']),
+        _docs
+    ]
+
+class Class(BaseObject):
+    NAME = 'class'
+    FIELDS = [
+        _name,
+        _ValueField('index', int),
+        _docsl,
+        _ComputedField('methods', lambda elem: sorted(
+            [Method(me) for me in elem.getchildren() if me.tag == 'method'],
+            key=lambda m: (m.name.strip('-')[0], -len(m.response)))),
+        _ComputedField('properties', lambda elem: [Field(e) for e in elem.getchildren() if
+                   e.tag == 'field'])
+    ]
+
+
+class Domain(BaseObject):
+    NAME = 'domain'
+    FIELDS = [
+        _name,
+        _SimpleField('type'),
+        _ComputedField('elementary', lambda a: a.attrib['type'] == a.attrib['name'])
+    ]
+
+
+def _get_tagchild(elem, tag):
+    return [e for e in elem.getchildren() if e.tag == tag]
+
+class Method(BaseObject):
+
+    FIELDS = [
+        _name,
+        _SimpleField('synchronous', _boolint, default=False),
+        _SimpleField('index', int),
+        _SimpleField('label', default=None),
+        _docs,
+        _ComputedField('fields', lambda elem: [Field(fie) for fie in _get_tagchild(elem, 'field')]),
+        _ComputedField('response', lambda elem: [e.attrib['name'] for e in elem.findall('response')]),
+        _ComputedField('sent_by_client', lambda elem:  any(e.attrib.get('name', '') == 'server' for e in
+                           _get_tagchild(elem, 'chassis'))),
+        _ComputedField('sent_by_server', lambda elem: any(e.attrib.get('name', '') == 'client' for e in
+                           _get_tagchild(elem, 'chassis'))),
+        _ComputedField('constant', lambda elem: all(Field(fie).reserved for fie in _get_tagchild(elem, 'field'))),
+    ]
+
 
     def get_static_body(self):  # only arguments part
         body = []
@@ -85,90 +223,9 @@ def get_size(fields):  # assume all fields have static length
     return size
 
 
-def get_docs(elem):
-    """Parse an XML element. Return documentation"""
-    for kid in elem.getchildren():
-
-        if kid.tag == 'rule':
-            return get_docs(kid)
-
-        s = kid.text.strip().split('\n')
-        return u'\n'.join([u.strip() for u in s if len(u.strip()) > 0])
-
-    return None
-
-
-def for_domain(elem):
-    """Parse XML document. Return domains"""
-    a = elem.attrib
-    return Domain(six.text_type(a['name']), a['type'], a['type'] == a['name'])
-
-
-def for_field(elem):  # for <field> in <method>
-    """Parse method. Return fields"""
-    a = elem.attrib
-    return Field(six.text_type(a['name']),
-                 a['domain'] if 'domain' in a else a['type'],
-                 a.get('label', None),
-                 get_docs(elem),
-                 a.get('reserved', '0') == '1',
-                 None)
 
 _boolint = lambda x: bool(int(x))
 
-def for_method(elem):  # for <method>
-    """Parse class, return methods"""
-    a = elem.attrib
-    return Method(six.text_type(a['name']),
-                  _boolint(a.get('synchronous', '0')), int(a['index']),
-                  a.get('label', None),
-                  get_docs(elem),
-                  [for_field(fie) for fie in elem.getchildren() if
-                   fie.tag == 'field'],
-                  [e.attrib['name'] for e in elem.findall('response')],
-                  # if chassis=server that means server has to accept it
-                  any([e.attrib.get('name', '') == 'server' for e in
-                       elem.getchildren() if e.tag == 'chassis']),
-                  any([e.attrib.get('name', '') == 'client' for e in
-                       elem.getchildren() if e.tag == 'chassis'])
-                  )
-
-
-def for_class(elem):  # for <class>
-    """Parse XML, return classes"""
-    a = elem.attrib
-    methods = sorted(
-        [for_method(me) for me in elem.getchildren() if me.tag == 'method'],
-        key=lambda m: (m.name.strip('-')[0], -len(m.response)))
-    return Class_(six.text_type(a['name']), int(a['index']),
-                  get_docs(elem) or a['label'], methods,
-                  [for_field(e) for e in elem.getchildren() if
-                   e.tag == 'field'])
-
-
-def for_constant(elem):  # for <constant>
-    """Parse XML, return constants"""
-    a = elem.attrib
-    return Constant(a['name'], int(a['value']), a.get('class', ''),
-                    get_docs(elem))
-
-
-def _findall_apply(xml, what, fun):
-    return map(fun, xml.findall(what))
-
-
-def get_constants(xml):
-    return _findall_apply(xml, 'constant', for_constant)
-
-
-def get_classes(xml):
-    return _findall_apply(xml, 'class', for_class)
-
-
-def get_domains(xml):
-    return _findall_apply(xml, 'domain', for_domain)
-
-
 def as_unicode(callable):
     def roll(*args, **kwargs):
         return six.text_type(callable(*args, **kwargs))
diff --git a/requirements.txt b/requirements.txt
index 897b05d1921931d68d0b05ef436e47211ec1df65..db1d2d782b8fb08772c3577ac03cb240c982b291 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,4 @@
 six
 monotonic
 futures
+satella