Repository: atviriduomenys/spinta

Issue:
Write `process_simple_content` method
`simpleContent` can be an element that has attributes and text, so it can become a model. the element, described by `simpleContent` can have `text()` also. `simpleContent` is in a few RC documents.

Example of `simpleContent`:

```xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="City">
  <xs:complexType>
    <xs:simpleContent>
      <xs:extension base="xs:string">
        <xs:attribute name="name"         type="xs:string" use="required" />
        <xs:attribute name="code"        type="xs:string" use="optional" />
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
</xs:element>
</xs:schema>
```


This should produce the following DSA:

```
id | d | r | b | m | property | type            | ref | source | prepare | level | access | uri | title | description
   | example9                 |                 |     |        |         |       |        |     |       |
   |   | resource1            | xml             |     |        |         |       |        |     |       |
   |                          |                 |     |        |         |       |        |     |       |
   |   |   |   | City         |                 |     | /City  |         |       |        |     |       |
   |   |   |   |   | code     | string          |     | @code  |         |       |        |     |       |
   |   |   |   |   | name     | string required |     | @name  |         |       |        |     |       |
   |   |   |   |   | text     | string          |     | text() |         |       |        |     |       |
```


- [ ] tests
- [ ] documentation
- [ ] entry in CHANGES.rst

Discussion:

Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 41f126af..198050b9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -3,7 +3,10 @@ Changes
 
 0.1.79 (unreleased)
 ===================
+Bug fix:
 
+- Resolved ambiguity warning messages. (`#895`_).
+.. _#895: https://github.com/atviriduomenys/spinta/issues/895
 
 0.1.78 (2024-10-22)
 ===================
diff --git a/spinta/backends/__init__.py b/spinta/backends/__init__.py
index db1cf4ca..ef6a14e1 100644
--- a/spinta/backends/__init__.py
+++ b/spinta/backends/__init__.py
@@ -849,12 +849,13 @@ def prepare_dtype_for_response(
 ):
     return dtype.default
 
-@commands.prepare_dtype_for_response.register(Context, Format, UUID, uuid.UUID)
+
+@commands.prepare_dtype_for_response.register(Context, Format, UUID, object)
 def prepare_dtype_for_response(
     context: Context,
     fmt: Format,
     dtype: UUID,
-    value: uuid.UUID,
+    value: object,
     *,
     data: Dict[str, Any],
     action: Action,
@@ -1388,6 +1389,20 @@ def prepare_dtype_for_response(
     return {}
 
 
+@commands.prepare_dtype_for_response.register(Context, Format, Inherit, NotAvailable)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Format,
+    dtype: Inherit,
+    value: NotAvailable,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None,
+):
+    return None
+
+
 @commands.prepare_dtype_for_response.register(Context, Format, ArrayBackRef, (list, tuple))
 def prepare_dtype_for_response(
     context: Context,
diff --git a/spinta/backends/postgresql/ufuncs/query/ufuncs.py b/spinta/backends/postgresql/ufuncs/query/ufuncs.py
index a3ff4904..5c7facfb 100644
--- a/spinta/backends/postgresql/ufuncs/query/ufuncs.py
+++ b/spinta/backends/postgresql/ufuncs/query/ufuncs.py
@@ -418,6 +418,16 @@ def select(env, column):
     return Selected(env.add_column(column))
 
 
+@ufunc.resolver(PgQueryBuilder, ForeignProperty, PrimaryKey)
+def select(
+    env: PgQueryBuilder,
+    fpr: ForeignProperty,
+    dtype: PrimaryKey,
+) -> Selected:
+    super_ = ufunc.resolver[env, fpr, dtype]
+    return super_(env, fpr, dtype)
+
+
 @ufunc.resolver(PgQueryBuilder, int)
 def limit(env, n):
     env.limit = n
diff --git a/spinta/commands/write.py b/spinta/commands/write.py
index 93df55fd..8e0b42b4 100644
--- a/spinta/commands/write.py
+++ b/spinta/commands/write.py
@@ -23,6 +23,8 @@ from spinta.backends.components import Backend
 from spinta.backends.constants import BackendFeatures
 from spinta.backends.helpers import get_select_prop_names
 from spinta.backends.helpers import get_select_tree
+from spinta.backends.mongo.components import Mongo
+from spinta.backends.mongo.helpers import inserting
 from spinta.components import Context, Node, UrlParams, Action, DataItem, Namespace, Model, Property, DataStream, \
     DataSubItem
 from spinta.core.ufuncs import asttoexpr
@@ -1204,6 +1206,22 @@ def before_write(
     }
 
 
+@commands.before_write.register(Context, ExternalRef, Mongo)
+def before_write(
+    context: Context,
+    dtype: ExternalRef,
+    backend: Mongo,
+    *,
+    data: DataSubItem,
+) -> dict:
+
+    patch = take(['_id'], data.patch) or None
+    if inserting(data):
+        return {dtype.prop.name: patch}
+    else:
+        return {dtype.prop.place: patch}
+
+
 @commands.before_write.register(Context, Denorm, Backend)
 def before_write(
     context: Context,
@@ -1228,6 +1246,17 @@ def before_write(
         return {}
 
 
+@commands.before_write.register(Context, Denorm, Mongo)
+def before_write(
+    context: Context,
+    dtype: Denorm,
+    backend: Mongo,
+    *,
+    data: DataSubItem,
+) -> dict:
+    return take(all, {dtype.prop.place: data.patch})
+
+
 async def _summary_response(context: Context, results: AsyncIterator[DataItem]) -> dict:
     errors = 0
     async for data in results:
diff --git a/spinta/datasets/backends/sql/ufuncs/query/ufuncs.py b/spinta/datasets/backends/sql/ufuncs/query/ufuncs.py
index 3ef3a3b1..f21ab551 100644
--- a/spinta/datasets/backends/sql/ufuncs/query/ufuncs.py
+++ b/spinta/datasets/backends/sql/ufuncs/query/ufuncs.py
@@ -1,11 +1,12 @@
 from __future__ import annotations
 
-from typing import Any
+from typing import Any, Tuple
 from typing import List
 from typing import Tuple
 from typing import TypeVar
 from typing import Union
 from typing import overload
+from typing import Dict
 
 import sqlalchemy as sa
 from sqlalchemy.sql.functions import Function
@@ -32,9 +33,10 @@ from spinta.types.datatype import String
 from spinta.types.datatype import UUID
 from spinta.types.text.components import Text
 from spinta.types.text.helpers import determine_language_property_for_text
-from spinta.ufuncs.basequerybuilder.components import LiteralProperty
+from spinta.ufuncs.basequerybuilder.components import LiteralProperty, Selected
 from spinta.ufuncs.basequerybuilder.helpers import get_language_column, process_literal_value
 from spinta.ufuncs.basequerybuilder.ufuncs import Star
+from spinta.ufuncs.basequerybuilder.ufuncs import ResultProperty, NestedProperty, ReservedProperty
 from spinta.ufuncs.components import ForeignProperty
 from spinta.utils.data import take
 from spinta.utils.itertools import flatten
@@ -317,11 +319,9 @@ def select(env: SqlQueryBuilder, expr: Expr):
             )
 
 
-@ufunc.resolver(SqlQueryBuilder, object)
-def select(env: SqlQueryBuilder, value: Any) -> Selected:
-    """For things like select(1, count())."""
-    return Selected(item=env.add_column(value))
-
+@ufunc.resolver(SqlQueryBuilder, sa.sql.expression.ColumnElement)
+def select(env, column):
+    return Selected(env.add_column(column))
 
 @ufunc.resolver(SqlQueryBuilder, Bind)
 def select(env: SqlQueryBuilder, item: Bind, *, nested: bool = False):
@@ -791,3 +791,13 @@ def distinct(env: SqlQueryBuilder):
 def swap(env: SqlQueryBuilder, expr: Expr):
     args, kwargs = expr.resolve(env)
     return Expr('swap', *args, **kwargs)
+
+
+@ufunc.resolver(SqlQueryBuilder, ForeignProperty, PrimaryKey)
+def select(
+    env: SqlQueryBuilder,
+    fpr: ForeignProperty,
+    dtype: PrimaryKey,
+) -> Selected:
+    super_ = ufunc.resolver[env, fpr, dtype]
+    return super_(env, fpr, dtype)
diff --git a/spinta/formats/html/commands.py b/spinta/formats/html/commands.py
index 9ea46444..db60e5fe 100644
--- a/spinta/formats/html/commands.py
+++ b/spinta/formats/html/commands.py
@@ -432,7 +432,25 @@ def prepare_dtype_for_response(
             dtype.prop.model,
             pk=value,
         ))
-    return Cell(value)
+    return Cell(str(value))
+
+
+@commands.prepare_dtype_for_response.register(Context, Html, UUID, (NotAvailable, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Html,
+    dtype: UUID,
+    value: NotAvailable,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None,
+):
+    if value is None:
+        return Cell('', color=Color.null)
+
+    super_ = commands.prepare_dtype_for_response[Context, Format, UUID, NotAvailable]
+    return super_(context, fmt, dtype, value, data=data, action=action, select=select)
 
 
 @commands.prepare_dtype_for_response.register(Context, Html, Date, datetime.date)
@@ -748,6 +766,24 @@ def prepare_dtype_for_response(
     return res
 
 
+@commands.prepare_dtype_for_response.register(Context, Html, Inherit, (NotAvailable, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Html,
+    dtype: Inherit,
+    value: NotAvailable,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None,
+):
+    if value is None:
+        return Cell('', color=Color.null)
+
+    super_ = commands.prepare_dtype_for_response[Context, Format, File, NotAvailable]
+    return super_(context, fmt, dtype, value, data=data, action=action, select=select)
+
+
 @commands.prepare_dtype_for_response.register(Context, Html, ArrayBackRef, tuple)
 def prepare_dtype_for_response(
     context: Context,
@@ -811,6 +847,22 @@ def prepare_dtype_for_response(
     return value
 
 
+@commands.prepare_dtype_for_response.register(Context, Html, BackRef, str)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Html,
+    dtype: BackRef,
+    value: str,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None,
+):
+    super_ = commands.prepare_dtype_for_response[Context, Format, BackRef, str]
+    value = super_(context, fmt, dtype, value, data=data, action=action, select=select)
+    return value
+
+
 @commands.prepare_dtype_for_response.register(Context, Html, BackRef, type(None))
 def prepare_dtype_for_response(
     context: Context,
diff --git a/spinta/formats/rdf/commands.py b/spinta/formats/rdf/commands.py
index 0a09db0d..ef442a8c 100644
--- a/spinta/formats/rdf/commands.py
+++ b/spinta/formats/rdf/commands.py
@@ -1,3 +1,4 @@
+import base64
 import datetime
 
 from decimal import Decimal
@@ -11,10 +12,15 @@ from lxml.etree import Element, QName
 from starlette.requests import Request
 from starlette.responses import StreamingResponse
 
+from shapely.geometry.base import BaseGeometry
+
+from geoalchemy2.elements import WKBElement
+
 from spinta import commands
 from spinta.backends.components import SelectTree
 from spinta.backends.helpers import get_model_reserved_props
 from spinta.backends.helpers import select_model_props
+from spinta.backends.helpers import get_select_prop_names, select_props
 from spinta.components import Action, Namespace, page_in_data
 from spinta.components import Context
 from spinta.components import Model
@@ -31,6 +37,8 @@ from spinta.types.datatype import Date
 from spinta.types.datatype import Time
 from spinta.types.datatype import DateTime
 from spinta.types.datatype import Number
+from spinta.types.datatype import ExternalRef, Array, BackRef, ArrayBackRef, Inherit, JSON, UUID, Object, Binary
+from spinta.types.geometry.components import Geometry
 from spinta.types.text.components import Text
 from spinta.utils.encoding import encode_page_values
 from spinta.utils.schema import NotAvailable
@@ -672,3 +680,345 @@ def prepare_dtype_for_response(
     return result
 
 
+@commands.prepare_dtype_for_response.register(Context, Rdf, ExternalRef, (dict, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: ExternalRef,
+    value: dict,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    super_ = commands.prepare_dtype_for_response[Context, Format, Ref, type(value)]
+    data_dict = super_(context, fmt, dtype, value, data=data, action=action, select=select)
+    prefixes = data['_available_prefixes']
+    attributes = {}
+    children = []
+    if data_dict is None:
+        return None
+
+    if len(data_dict) == 1 and '_id' in data_dict or '_uri' in data_dict:
+        if '_uri' in data_dict:
+            attributes[data['_resource_name']] = data_dict['_uri'].text
+        else:
+            attributes[data['_resource_name']] = get_model_link(
+                dtype.model,
+                pk=data_dict['_id'].text
+            )
+    else:
+        if isinstance(data['_elem_name'], QName):
+            prefix, name = _get_prefix_and_name(dtype.model.uri)
+            if name:
+                ref_model_name = _get_attribute_name(name, prefix, prefixes)
+            else:
+                ref_model_name = _get_attribute_name(dtype.model.basename, prefix, prefixes)
+            data_dict, ref_model_attrs = _get_attributes(
+                dtype.model,
+                data_dict,
+                data['_about_name'],
+                data['_type_name'],
+                data['_revision_name'],
+            )
+            ref_model_elem = _create_element(
+                name=ref_model_name,
+                attributes=ref_model_attrs,
+                children=list(data_dict.values())
+            )
+            children.append(ref_model_elem)
+        else:
+            data_dict['_type'] = _create_element(
+                name='_type',
+                text=dtype.model.model_type()
+            )
+            data_dict, description_attrs = _get_attributes(
+                dtype.model,
+                data_dict,
+                data['_about_name'],
+                data['_type_name'],
+                data['_revision_name'],
+            )
+            description_elem = _create_element(
+                name=_get_attribute_name(DESCRIPTION, RDF, prefixes),
+                attributes=description_attrs,
+                children=list(data_dict.values())
+            )
+            children.append(description_elem)
+
+    if attributes or children:
+        return _create_element(
+            name=data['_elem_name'],
+            attributes=attributes,
+            children=children
+        )
+    else:
+        return None
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Geometry, BaseGeometry)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Geometry,
+    value: BaseGeometry,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle Geometry dtype with BaseGeometry value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Geometry, WKBElement)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Geometry,
+    value: WKBElement,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle Geometry dtype with WKBElement value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, BackRef, (dict, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: BackRef,
+    value: dict,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None:
+        return None
+
+    # TODO: implement appropriate prepare support to handle BackRef dtype with dict value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, BackRef, str)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: BackRef,
+    value: str,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle BackRef dtype with str value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, ArrayBackRef, (tuple, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: ArrayBackRef,
+    value: tuple,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None:
+        return None
+
+    # TODO: implement appropriate prepare support to handle ArrayBackRef dtype with tuple value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, ArrayBackRef, list)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: ArrayBackRef,
+    value: list,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle ArrayBackRef dtype with list value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, JSON, (object, type(None), NotAvailable))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: JSON,
+    value: object,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None or isinstance(value, NotAvailable):
+        return None
+
+    # TODO: implement appropriate prepare support to handle JSON dtype with object value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Object, dict)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Object,
+    value: dict,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle Object dtype with dict value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Binary, bytes)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Binary,
+    value: bytes,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    return _create_element(
+        name=data['_elem_name'],
+        text=base64.b64encode(value).decode('ascii')
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Array, (list, type(None)))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Array,
+    value: List[Any],
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None:
+        return None
+
+    # TODO: implement appropriate prepare support to handle Array dtype with list value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Array, tuple)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Array,
+    value: tuple,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle Array dtype with tuple value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Inherit, (object, type(None), NotAvailable))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Inherit,
+    value: object,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None or isinstance(value, NotAvailable):
+        return None
+
+    # TODO: implement appropriate prepare support to handle Inherit dtype with object value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, Inherit, dict)
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: Inherit,
+    value: dict,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    # TODO: implement appropriate prepare support to handle Inherit dtype with dict value
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
+
+
+@commands.prepare_dtype_for_response.register(Context, Rdf, UUID, (object, type(None), NotAvailable))
+def prepare_dtype_for_response(
+    context: Context,
+    fmt: Rdf,
+    dtype: UUID,
+    value: object,
+    *,
+    data: Dict[str, Any],
+    action: Action,
+    select: dict = None
+):
+    if value is None or isinstance(value, NotAvailable):
+        return None
+
+    return _create_element(
+        name=data['_elem_name'],
+        text=str(value)
+    )
diff --git a/spinta/manifests/xsd2/helpers.py b/spinta/manifests/xsd2/helpers.py
index cec5af58..3312fe56 100644
--- a/spinta/manifests/xsd2/helpers.py
+++ b/spinta/manifests/xsd2/helpers.py
@@ -651,6 +651,11 @@ class XSDReader:
                 else:
                     prepare_statement = None
 
+            elif local_name == "simpleContent":
+                simple_content_properties: List[XSDProperty] = self.process_simple_content(child, state)
+                for group in property_groups:
+                    group.extend(simple_content_properties)
+
             else:
                 raise RuntimeError(f"This node type cannot be in the complex type: {local_name}")
 
@@ -816,8 +821,32 @@ class XSDReader:
     def process_all(self, node: _Element, state: State) -> None:
         pass
 
-    def process_simple_content(self, node: _Element, state: State) -> None:
-        pass
+    def process_simple_content(self, node: _Element, state: State) -> list[XSDProperty]:
+        properties: list = []
+
+        for child in node:
+            if isinstance(child, etree._Comment):
+                continue
+
+            local_name = QName(child).localname
+
+            if local_name == "extension":
+                extension_properties: list[XSDProperty] = self.process_simple_type_extension(child, state)
+                properties.extend(extension_properties)
+
+            elif local_name == "restriction":
+                restriction_prop_type: XSDType = self.process_restriction(child, state)
+                prop_name = node.attrib.get("name", "value")
+                restriction_prop = XSDProperty(
+                    xsd_name=prop_name,
+                    property_type=restriction_prop_type,
+                )
+                properties.append(restriction_prop)
+
+            else:
+                raise RuntimeError(f"Unexpected element '{local_name}' in simpleContent")
+
+        return properties
 
     def process_complex_content(self, node: _Element, state: State) -> list[list[XSDModel]]:
         property_groups: list[list] = [[]]
@@ -829,7 +858,7 @@ class XSDReader:
             local_name = QName(child).localname
 
             if local_name == "extension":
-                extension_property_groups: List[List[XSDProperty]] = self.process_complex_type_extension(child, state)
+                extension_property_groups: list[list[XSDProperty]] = self.process_complex_type_extension(child, state)
                 new_property_groups = []
                 for group in property_groups:
                     for ext_group in extension_property_groups:
@@ -880,32 +909,35 @@ class XSDReader:
     def process_documentation(self, node: _Element, state: State) -> str:
         return node.text or ""
 
-    def process_simple_type_extension(self, node: _Element, state: State) -> tuple[str, list] | None:
-        # this is an initial implementation, this method needs to be finished together with process_simple_type
+    def process_simple_type_extension(self, node: _Element, state: State) -> list[XSDProperty]:
         base = node.attrib.get("base")
         if not base:
             raise RuntimeError("Extension must have a 'base' attribute.")
 
+        properties = []
+
         base_type_name = base.split(":")[-1]
+        base_type: XSDType = self._map_type(base_type_name)
+        text_prop = XSDProperty(
+            xsd_name="text",
+            property_type=base_type,
+        )
+        properties.append(text_prop)
 
-        if base_type_name in DATATYPES_MAPPING:
-            type_name = DATATYPES_MAPPING[base_type_name]
-            attributes = []
-
-            for child in node:
-                if isinstance(child, etree._Comment):
-                    continue
-                local_name = QName(child).localname
-                if local_name == "attribute":
-                    prop = self.process_attribute(child, state)
-                    attributes.append(prop)
-                elif local_name == "annotation":
-                    prop = self.process_annotation(child, state)
-                    attributes.append(prop)
-                else:
-                    raise RuntimeError(f"Unexpected element '{local_name}' in simpleType extension")
+        for child in node:
+            if isinstance(child, etree._Comment):
+                continue
+            local_name = QName(child).localname
+            if local_name == "attribute":
+                prop: XSDProperty = self.process_attribute(child, state)
+                properties.append(prop)
+            elif local_name == "annotation":
+                # Add annotation description to text_prop
+                text_prop.description = self.process_annotation(child, state)
+            else:
+                raise RuntimeError(f"Unexpected element '{local_name}' in simpleType extension")
 
-            return type_name, attributes
+        return properties
 
     def process_complex_type_extension(self, node: _Element, state: State) -> List[List[XSDProperty]]:
         base = node.attrib.get("base")


Test patch:
diff --git a/tests/dtypes/test_array.py b/tests/dtypes/test_array.py
index c098f8c2..d650700d 100644
--- a/tests/dtypes/test_array.py
+++ b/tests/dtypes/test_array.py
@@ -1,6 +1,7 @@
 from spinta.testing.client import create_test_client
 from spinta.testing.manifest import bootstrap_manifest
 from _pytest.fixtures import FixtureRequest
+import xml.etree.ElementTree as ET
 import pytest
 
 
@@ -312,3 +313,74 @@ def test_array_select_only_array(manifest_type, rc, tmp_path, postgresql: str, r
             '_id': LIT
         }
     ]}]
+
+
+@pytest.mark.skip(reason="format support not implemented")
+def test_array_rdf(
+    manifest_type: str,
+    tmp_path,
+    rc,
+    postgresql: str,
+    request: FixtureRequest,
+):
+    context = bootstrap_manifest(
+        rc, '''
+    d | r | b | m | property    | type    | ref      | access | level
+    example/dtypes/array/rdf    |         |          |        |
+                                |         |          |        |
+      |   |   | Item            |         | id, name |        |
+      |   |   |   | id          | integer |          | open   |
+      |   |   |   | name        | string  |          | open   |
+      |   |   |   | tags[]      | string  |          | open   |
+    ''',
+        backend=postgresql,
+        tmp_path=tmp_path,
+        manifest_type=manifest_type,
+        request=request,
+        full_load=True
+    )
+
+    app = create_test_client(context)
+    app.authorize(['spinta_set_meta_fields'])
+    app.authmodel('example/dtypes/array/rdf', ['insert', 'getone', 'getall'])
+
+    item1_id = "c8e4cd60-0b15-4b23-a691-09cdf2ebd9c0"
+    item2_id = "1cccf6f6-9fe2-4055-b003-915a7c1abee8"
+    app.post('example/dtypes/array/rdf/Item', json={
+        '_id': item1_id,
+        'id': 0,
+        'name': 'Item1',
+        'tags': ['tag1', 'tag2']
+    })
+    app.post('example/dtypes/array/rdf/Item', json={
+        '_id': item2_id,
+        'id': 1,
+        'name': 'Item2',
+        'tags': ['tag3']
+    })
+
+    resp = app.get('example/dtypes/array/rdf/Item/:format/rdf?expand()')
+    assert resp.status_code == 200
+
+    root = ET.fromstring(resp.text)
+    rdf_descriptions = root.findall('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+    version_values = [desc.attrib['{http://purl.org/pav/}version'] for desc in rdf_descriptions]
+    assert resp.text == f'<?xml version="1.0" encoding="UTF-8"?>\n' \
+                        f'<rdf:RDF\n' \
+                        f' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' \
+                        f' xmlns:pav="http://purl.org/pav/"\n' \
+                        f' xmlns:xml="http://www.w3.org/XML/1998/namespace"\n' \
+                        f' xmlns="https://testserver/">\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/array/rdf/Item/c8e4cd60-0b15-4b23-a691-09cdf2ebd9c0" rdf:type="example/dtypes/array/rdf/Item" pav:version="{version_values[0]}">\n' \
+                        f'  <_page>WzAsICJJdGVtMSIsICJjOGU0Y2Q2MC0wYjE1LTRiMjMtYTY5MS0wOWNkZjJlYmQ5YzAiXQ==</_page>\n' \
+                        f'  <id>0</id>\n' \
+                        f'  <name>Item1</name>\n' \
+                        f"  <tags>['tag1', 'tag2']</tags>\n" \
+                        f'</rdf:Description>\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/array/rdf/Item/1cccf6f6-9fe2-4055-b003-915a7c1abee8" rdf:type="example/dtypes/array/rdf/Item" pav:version="{version_values[1]}">\n' \
+                        f'  <_page>WzEsICJJdGVtMiIsICIxY2NjZjZmNi05ZmUyLTQwNTUtYjAwMy05MTVhN2MxYWJlZTgiXQ==</_page>\n' \
+                        f'  <id>1</id>\n' \
+                        f'  <name>Item2</name>\n' \
+                        f"  <tags>['tag3']</tags>\n" \
+                        f'</rdf:Description>\n' \
+                        f'</rdf:RDF>\n'
diff --git a/tests/dtypes/test_backref.py b/tests/dtypes/test_backref.py
index 1d9e64e5..d5fd84d3 100644
--- a/tests/dtypes/test_backref.py
+++ b/tests/dtypes/test_backref.py
@@ -1,5 +1,7 @@
 import pathlib
 
+import xml.etree.ElementTree as ET
+
 import pytest
 
 from pytest import FixtureRequest
@@ -1320,3 +1322,116 @@ def test_backref_error_one_to_many(
         manifest_type=manifest_type,
         full_load=True
     )
+
+
+@pytest.mark.skip(reason="format support not implemented")
+def test_backref_many_to_one_level_4_rdf(
+    manifest_type: str,
+    tmp_path: pathlib.Path,
+    rc: RawConfig,
+    postgresql: str,
+    request: FixtureRequest,
+):
+    context = bootstrap_manifest(
+        rc, '''
+    d | r | b | m | property    | type    | ref      | access | level
+    example/dtypes/backref/mto/level4  |         |          |        |
+                                |         |          |        |
+      |   |   | Language        |         | id, name |        |
+      |   |   |   | id          | integer |          | open   |
+      |   |   |   | name        | string  |          | open   |
+      |   |   |   | country[]   | backref | Country  | open   |
+                                |         |          |        |
+      |   |   | Country         |         | id       |        |
+      |   |   |   | id          | integer |          | open   |
+      |   |   |   | name        | string  |          | open   |
+      |   |   |   | language    | ref     | Language | open   | 4
+    ''',
+        backend=postgresql,
+        tmp_path=tmp_path,
+        manifest_type=manifest_type,
+        request=request,
+        full_load=True
+    )
+
+    app = create_test_client(context)
+    app.authorize(['spinta_set_meta_fields'])
+    app.authmodel('example/dtypes/backref/mto/level4', ['insert', 'getone', 'getall'])
+
+    lithuanian = "c8e4cd60-0b15-4b23-a691-09cdf2ebd9c0"
+    english = "1cccf6f6-9fe2-4055-b003-915a7c1abee8"
+    empty_language = "090875d3-aaaa-422f-984a-733acc0a7c77"
+    app.post('example/dtypes/backref/mto/level4/Language', json={
+        '_id': lithuanian,
+        'id': 0,
+        'name': 'Lithuanian',
+    })
+    app.post('example/dtypes/backref/mto/level4/Language', json={
+        '_id': english,
+        'id': 1,
+        'name': 'English',
+    })
+    app.post('example/dtypes/backref/mto/level4/Language', json={
+        '_id': empty_language,
+        'id': 3,
+        'name': 'Empty',
+    })
+
+    lithuania_id = 'd73306fb-4ee5-483d-9bad-d86f98e1869c'
+    england_id = '25c3f7bf-67f5-47cc-8d40-297c499e8067'
+    us_id = 'e33b20aa-7311-49d5-86a4-566961dda218'
+    app.post('example/dtypes/backref/mto/level4/Country', json={
+        '_id': lithuania_id,
+        'id': 0,
+        'name': 'Lithuania',
+        'language': {
+            "_id": lithuanian
+        }
+    })
+    app.post('example/dtypes/backref/mto/level4/Country', json={
+        '_id': england_id,
+        'id': 1,
+        'name': 'England',
+        'language': {
+            "_id": english
+        }
+    })
+    app.post('example/dtypes/backref/mto/level4/Country', json={
+        '_id': us_id,
+        'id': 2,
+        'name': 'US',
+        'language': {
+            "_id": english
+        }
+    })
+    result = app.get('example/dtypes/backref/mto/level4/Language/:format/rdf?expand()')
+    assert result.status_code == 200
+
+    root = ET.fromstring(result.text)
+    rdf_descriptions = root.findall('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+    version_values = [desc.attrib['{http://purl.org/pav/}version'] for desc in rdf_descriptions]
+    assert result.text == f'<?xml version="1.0" encoding="UTF-8"?>\n' \
+                        f'<rdf:RDF\n' \
+                        f' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' \
+                        f' xmlns:pav="http://purl.org/pav/"\n' \
+                        f' xmlns:xml="http://www.w3.org/XML/1998/namespace"\n' \
+                        f' xmlns="https://testserver/">\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/backref/mto/level4/Language/c8e4cd60-0b15-4b23-a691-09cdf2ebd9c0" rdf:type="example/dtypes/backref/mto/level4/Language" pav:version="{version_values[0]}">\n' \
+                        f'  <_page>WzAsICJMaXRodWFuaWFuIiwgImM4ZTRjZDYwLTBiMTUtNGIyMy1hNjkxLTA5Y2RmMmViZDljMCJd</_page>\n' \
+                        f'  <id>0</id>\n' \
+                        f'  <name>Lithuanian</name>\n' \
+                        f"  <country>[{{'_id': 'd73306fb-4ee5-483d-9bad-d86f98e1869c'}}]</country>\n" \
+                        f'</rdf:Description>\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/backref/mto/level4/Language/1cccf6f6-9fe2-4055-b003-915a7c1abee8" rdf:type="example/dtypes/backref/mto/level4/Language" pav:version="{version_values[1]}">\n' \
+                        f'  <_page>WzEsICJFbmdsaXNoIiwgIjFjY2NmNmY2LTlmZTItNDA1NS1iMDAzLTkxNWE3YzFhYmVlOCJd</_page>\n' \
+                        f'  <id>1</id>\n' \
+                        f'  <name>English</name>\n' \
+                        f"  <country>[{{'_id': '25c3f7bf-67f5-47cc-8d40-297c499e8067'}}, {{'_id': 'e33b20aa-7311-49d5-86a4-566961dda218'}}]</country>\n" \
+                        f'</rdf:Description>\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/backref/mto/level4/Language/090875d3-aaaa-422f-984a-733acc0a7c77" rdf:type="example/dtypes/backref/mto/level4/Language" pav:version="{version_values[2]}">\n' \
+                        f'  <_page>WzMsICJFbXB0eSIsICIwOTA4NzVkMy1hYWFhLTQyMmYtOTg0YS03MzNhY2MwYTdjNzciXQ==</_page>\n' \
+                        f'  <id>3</id>\n' \
+                        f'  <name>Empty</name>\n' \
+                        f'</rdf:Description>\n' \
+                        f'</rdf:RDF>\n' \
+
diff --git a/tests/dtypes/test_binary.py b/tests/dtypes/test_binary.py
index 2a7283bf..a2706b33 100644
--- a/tests/dtypes/test_binary.py
+++ b/tests/dtypes/test_binary.py
@@ -2,6 +2,11 @@ import base64
 
 import pytest
 
+import xml.etree.ElementTree as ET
+
+from spinta.testing.client import create_test_client
+from spinta.testing.manifest import bootstrap_manifest
+from _pytest.fixtures import FixtureRequest
 
 @pytest.mark.models(
     'backends/postgres/dtypes/Binary',
@@ -75,3 +80,50 @@ def test_getall(model, app):
     resp = app.get(f'/{model}')
     assert resp.status_code == 200, resp.json()
     assert resp.json()['_data'][0]['blob'] == data
+
+@pytest.mark.manifests('internal_sql', 'csv')
+def test_rdf(manifest_type: str, tmp_path, rc, postgresql: str, request: FixtureRequest):
+    context = bootstrap_manifest(
+        rc, '''
+    d | r | b | m | property    | type    | ref      | access | level
+    example/dtypes/binary/rdf   |         |          |        |
+                                |         |          |        |
+      |   |   | Item            |         | id, blob |        |
+      |   |   |   | id          | integer |          | open   |
+      |   |   |   | blob        | binary  |          | open   |
+    ''',
+        backend=postgresql,
+        tmp_path=tmp_path,
+        manifest_type=manifest_type,
+        request=request,
+        full_load=True
+    )
+
+    app = create_test_client(context)
+    app.authmodel('example/dtypes/binary/rdf', ['insert', 'getone', 'getall'])
+
+    data = base64.b64encode(b'data').decode('ascii')
+    resp = app.post('example/dtypes/binary/rdf/Item', json={'blob': data})
+    assert resp.status_code == 201, resp.json()
+    assert resp.json()['blob'] == data
+
+    resp = app.get(f'example/dtypes/binary/rdf/Item/:format/rdf/')
+    assert resp.status_code == 200, resp.json()
+
+    root = ET.fromstring(resp.text)
+    rdf_description = root.find('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+    version_value = rdf_description.attrib['{http://purl.org/pav/}version']
+    page_value = rdf_description.find('{https://testserver/}_page').text
+    item_id = rdf_description.attrib['{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about'].split('/')[-1]
+
+    assert resp.text == f'<?xml version="1.0" encoding="UTF-8"?>\n' \
+                        f'<rdf:RDF\n' \
+                        f' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' \
+                        f' xmlns:pav="http://purl.org/pav/"\n' \
+                        f' xmlns:xml="http://www.w3.org/XML/1998/namespace"\n' \
+                        f' xmlns="https://testserver/">\n' \
+                        f'<rdf:Description rdf:about="/example/dtypes/binary/rdf/Item/{item_id}" rdf:type="example/dtypes/binary/rdf/Item" pav:version="{version_value}">\n' \
+                        f'  <_page>{page_value}</_page>\n' \
+                        f"  <blob>{data}</blob>\n" \
+                        f'</rdf:Description>\n' \
+                        f'</rdf:RDF>\n'
diff --git a/tests/dtypes/test_external_ref.py b/tests/dtypes/test_external_ref.py
index a50158ad..a43935f6 100644
--- a/tests/dtypes/test_external_ref.py
+++ b/tests/dtypes/test_external_ref.py
@@ -1,5 +1,7 @@
 from pathlib import Path
 
+import xml.etree.ElementTree as ET
+
 from _pytest.fixtures import FixtureRequest
 
 from spinta.core.config import RawConfig
@@ -357,3 +359,77 @@ def test_external_ref_unassign_invalid_no_pk(
     })
     assert resp.status_code == 400
     assert get_error_codes(resp.json()) == ["DirectRefValueUnassignment"]
+
+
+@pytest.mark.skip(reason="format support not implemented")
+def test_external_ref_format_rdf(
+    manifest_type: str,
+    tmp_path: Path,
+    rc: RawConfig,
+    postgresql: str,
+    request: FixtureRequest,
+):
+    context = bootstrap_manifest(
+        rc, '''
+    d | r | b | m | property | type   | ref                           | source       | level | access
+    datasets/externalref     |        |                               |              |       |
+      | external             | sql    |                               | sqlite://    |       |
+      |   |   | Country      |        | code                          |              |       |
+      |   |   |   | code     | string |                               |              |       | open
+      |   |   |   | name     | string |                               |              |       | open
+    datasets/internal        |        |                               |              |       |
+      |   |   | City         |        |                               |              |       |
+      |   |   |   | name     | string |                               |              |       | open
+      |   |   |   | country  | ref    | /datasets/externalref/Country |              | 3     | open
+    ''',
+        backend=postgresql,
+        tmp_path=tmp_path,
+        manifest_type=manifest_type,
+        request=request,
+        full_load=True
+    )
+
+    app = create_test_client(context)
+    app.authmodel('datasets/internal/City', [
+        'insert',
+        'getall',
+        'search'
+    ])
+
+    resp = app.post('/datasets/internal/City', json={
+        'country': {'code': "lt"},
+        'name': 'Vilnius',
+    })
+    assert resp.status_code == 201
+
+    resp = app.get('/datasets/internal/City/:format/rdf')
+    assert resp.status_code == 200
+
+    namespaces = {
+        'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+        'pav': 'http://purl.org/pav/',
+        '': 'https://testserver/'
+    }
+    root = ET.fromstring(resp.text)
+    rdf_description = root.find('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+    id_value = rdf_description.attrib['{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about'].split('/')[-1]
+    version_value = rdf_description.attrib['{http://purl.org/pav/}version']
+    page_value = rdf_description.find('_page', namespaces).text
+
+    assert resp.text == f'<?xml version="1.0" encoding="UTF-8"?>\n' \
+                        f'<rdf:RDF\n' \
+                        f' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' \
+                        f' xmlns:pav="http://purl.org/pav/"\n' \
+                        f' xmlns:xml="http://www.w3.org/XML/1998/namespace"\n' \
+                        f' xmlns="https://testserver/">\n' \
+                        f'<rdf:Description rdf:about="/datasets/internal/City/{id_value}" rdf:type="datasets/internal/City" pav:version="{version_value}">\n ' \
+                        f' <_page>{page_value}</_page>\n ' \
+                        f' <name>Vilnius</name>\n ' \
+                        f' <country>\n' \
+                        f'    <rdf:Description rdf:type="datasets/externalref/Country">\n' \
+                        f'      <country>lt</country>\n' \
+                        f'    </rdf:Description>\n' \
+                        f"  </country>\n" \
+                        f'</rdf:Description>\n' \
+                        f'</rdf:RDF>\n'
+
diff --git a/tests/dtypes/test_geometry.py b/tests/dtypes/test_geometry.py
index 3dac4ca4..3cb81da7 100644
--- a/tests/dtypes/test_geometry.py
+++ b/tests/dtypes/test_geometry.py
@@ -3,6 +3,8 @@ from typing import cast
 from typing import Optional
 from pathlib import Path
 
+import xml.etree.ElementTree as ET
+
 import sqlalchemy as sa
 
 import pytest
@@ -758,3 +760,65 @@ def test_geometry_crs_bounding_area(
         bbox,
         tolerance=0.1
     )
+
+@pytest.mark.manifests('internal_sql', 'csv')
+def test_geometry_rdf(
+    manifest_type: str,
+    tmp_path: Path,
+    rc: RawConfig,
+    postgresql: str,
+    request: FixtureRequest,
+):
+    context = bootstrap_manifest(
+        rc, '''
+    d | r | b | m | property          | type     | ref
+    backends/postgres/dtypes/geometry |          |
+      |   |   | City                  |          |
+      |   |   |   | name              | string   |
+      |   |   |   | coordinates       | geometry |
+    ''',
+        backend=postgresql,
+        tmp_path=tmp_path,
+        manifest_type=manifest_type,
+        request=request,
+        full_load=True
+    )
+
+    app = create_test_client(context)
+    app.authmodel('backends/postgres/dtypes/geometry/City', [
+        'insert',
+        'getall',
+    ])
+
+    # Write data
+    resp = app.post('/backends/postgres/dtypes/geometry/City', json={
+        'name': "Vilnius",
+        'coordinates': 'POINT(54.6870458 25.2829111)',
+    })
+    assert resp.status_code == 201
+
+    # Read data
+    resp = app.get('/backends/postgres/dtypes/geometry/City/:format/rdf')
+    namespaces = {
+        'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
+        'pav': 'http://purl.org/pav/',
+        '': 'https://testserver/'
+    }
+    root = ET.fromstring(resp.text)
+    rdf_description = root.find('{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description')
+    id_value = rdf_description.attrib['{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about'].split('/')[-1]
+    version_value = rdf_description.attrib['{http://purl.org/pav/}version']
+    page_value = rdf_description.find('_page', namespaces).text
+
+    assert resp.text == f'<?xml version="1.0" encoding="UTF-8"?>\n' \
+                        f'<rdf:RDF\n' \
+                        f' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"\n' \
+                        f' xmlns:pav="http://purl.org/pav/"\n' \
+                        f' xmlns:xml="http://www.w3.org/XML/1998/namespace"\n' \
+                        f' xmlns="https://testserver/">\n' \
+                        f'<rdf:Description rdf:about="/backends/postgres/dtypes/geometry/City/{id_value}" rdf:type="backends/postgres/dtypes/geometry/City" pav:version="{version_value}">\n ' \
+                        f' <_page>{page_value}</_page>\n ' \
+                        f' <name>Vilnius</name>\n ' \
+                        f" <coordinates>POINT (54.6870458 25.2829111)</coordinates>\n" \
+                        f'</rdf:Description>\n' \
+                        f'</rdf:RDF>\n'
diff --git a/tests/manifests/xsd2/test_helpers.py b/tests/manifests/xsd2/test_helpers.py
index 7d36f23d..997f9dd5 100644
--- a/tests/manifests/xsd2/test_helpers.py
+++ b/tests/manifests/xsd2/test_helpers.py
@@ -373,6 +373,45 @@ def test_process_complex_type_with_extension(xsd_reader, create_xsd_model):
     assert "attr1" in prop_names
     assert model.prepare == 'extend("BaseType")'
 
+def test_process_complex_type_with_simple_content(xsd_reader):
+    complex_type_xml = """
+    <complexType name="CityType">
+        <simpleContent>
+            <extension base="xs:string">
+                <attribute name="name" type="xs:string" use="required"/>
+                <attribute name="code" type="xs:string" use="optional"/>
+            </extension>
+        </simpleContent>
+    </complexType>
+    """
+    complex_type_node = etree.fromstring(complex_type_xml)
+
+    xsd_reader.process_simple_content = MagicMock(return_value=[
+        XSDProperty(xsd_name="text", property_type=XSDType(name="string")),
+        XSDProperty(xsd_name="name", property_type=XSDType(name="string"), required=True),
+        XSDProperty(xsd_name="code", property_type=XSDType(name="string"), required=False),
+    ])
+
+    state = State()
+    models = xsd_reader.process_complex_type(complex_type_node, state)
+
+    assert len(models) == 1
+    model = models[0]
+    assert model.name == "CityType"
+    assert "text" in model.properties
+    assert "name" in model.properties
+    assert "code" in model.properties
+
+    text_prop = model.properties["text"]
+    name_prop = model.properties["name"]
+    code_prop = model.properties["code"]
+
+    assert text_prop.type.name == "string"
+    assert name_prop.required is True
+    assert code_prop.required is False
+
+    xsd_reader.process_simple_content.assert_called_once_with(complex_type_node[0], state)
+
 def test_process_element_ref():
     xsd_schema = """
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
@@ -1152,3 +1191,72 @@ def test_process_complex_content_with_mixed_content(xsd_reader):
     prop_group = property_groups[0]
     prop_names = [prop.xsd_name for prop in prop_group]
     assert "text" in prop_names
+
+def test_process_simple_type_extension_with_attributes_and_annotation(xsd_reader):
+    extension_xml = """
+    <extension base="xs:string">
+        <attribute name="code" type="xs:string" use="optional"/>
+        <annotation>
+            <documentation>Text property description</documentation>
+        </annotation>
+    </extension>
+    """
+    extension_node = etree.fromstring(extension_xml)
+    state = State()
+
+    xsd_reader.process_attribute = MagicMock(return_value=XSDProperty(xsd_name="code", property_type=XSDType(name="string")))
+    xsd_reader.process_annotation = MagicMock(return_value="Text property description")
+
+    properties = xsd_reader.process_simple_type_extension(extension_node, state)
+
+    assert properties[0].xsd_name == "text"
+    assert properties[0].type.name == "string"
+    assert properties[0].description == "Text property description"
+
+    assert properties[1].xsd_name == "code"
+    assert properties[1].type.name == "string"
+
+def test_process_simple_content_with_extension(xsd_reader):
+    simple_content_xml = """
+    <simpleContent>
+        <extension base="xs:string">
+            <attribute name="code" type="xs:string" use="optional"/>
+            <annotation>
+                <documentation>Text property description</documentation>
+            </annotation>
+        </extension>
+    </simpleContent>
+    """
+    simple_content_node = etree.fromstring(simple_content_xml)
+    state = State()
+
+    xsd_reader.process_simple_type_extension = MagicMock(return_value=[
+            XSDProperty(xsd_name="text", property_type=XSDType(name="string")),
+            XSDProperty(xsd_name="code", property_type=XSDType(name="string"), source="@code")
+        ]
+    )
+
+    properties = xsd_reader.process_simple_content(simple_content_node, state)
+
+    assert len(properties) == 2
+    assert properties[0].xsd_name == "text"
+    assert properties[0].type.name == "string"
+    assert properties[1].xsd_name == "code"
+    assert properties[1].type.name == "string"
+
+def test_process_simple_content_with_restriction(xsd_reader):
+    simple_content_xml = """
+    <simpleContent>
+        <restriction base="xs:integer"/>
+    </simpleContent>
+    """
+    simple_content_node = etree.fromstring(simple_content_xml)
+    state = State()
+
+    xsd_reader.process_restriction = MagicMock(return_value=XSDType(name="integer"))
+
+    properties = xsd_reader.process_simple_content(simple_content_node, state)
+
+    assert len(properties) == 1
+    assert properties[0].xsd_name == "value"
+    assert properties[0].type.name == "integer"
\ No newline at end of file