##################
Generic Attributes
##################

Attributes are general purpose data that can be associated to any entity. Using an `A3DRootBase` instance, you can browse all its attributes.

Reading or writing generic attributes involves good knowledge on attributes hierarchy:

* An `A3DMiscSingleAttributeData` instance can hold any value of the following types:

  * 32-bit integer.
  * 32-bit floating-point (IEEE 754).
  * [time_t](https://en.cppreference.com/w/c/chrono/time_t).
  * UTF-8 C-Style string.

* An `A3DMiscAttribute` instance groups any number of `A3DMiscSingleAttributeData` together.
* An `A3DRootBase` instance can hold any number of `A3DMiscAttribute`.

This page is a general presentation on how to read attributes from any `\ref A3DRootBase` instance.

***********************************
Reading a Single Attribute Instance
***********************************

A **single attribute** is an instance of `A3DMiscSingleAttributeData`, a data-only structure which is described as:

.. code-block:: c

   typedef struct
   {
     A3DUns16                  m_usStructSize;
     A3DBool                   m_bTitleIsInt;
     A3DUTF8Char*              m_pcTitle;
     A3DEModellerAttributeType m_eType;
     A3DUTF8Char*              m_pcData;
     A3DUns16                  m_usUnit;
   } A3DMiscSingleAttributeData;

Any attribute is a Title/Value pair where:

- `m_pcTitle` is either a C-string or a 32 bit unsigned integer.
- `m_pcData` is of any of the above mentioned type.

32-Bit Integer Data
===================

If `m_eType == kA3DModellerAttributeTypeInt` the data is stored as a 32-bit integer data (`A3DUns32`):

.. code-block:: c

   if(in_sAttr.m_eType == kA3DModellerAttributeTypeInt)
   {
     A3DUns32 uiData;
     memcpy(&uiData, in_sAttr.m_pcData, sizeof(A3DUns32));
     printf("Value: %d", uiData);
   }

32-Bit Floating Point
=====================

If `m_eType == kA3DModellerAttributeTypeReal` the data is stored as a 32-bit floating point data (`A3DFloat`):

.. code-block:: c

   if(in_sAttr.m_eType == kA3DModellerAttributeTypeReal)
   {
      A3DFloat uiData;
      memcpy(&uiData, in_sAttr.m_pcData, sizeof(A3DFloat));
      printf("Value: %f", uiData);
   }

When the value is a floating-point the field `m_usUnit` is used to specify a unit for this value.
If no unit is provided, `m_usUnit` is set to `A3D_DEFAULT_NO_UNIT`.

For more information about generic unit specifiers, see [the unit attributes page](unit_attributes.html).

UTF-8 C-String
==============

If `m_eType == kA3DModellerAttributeTypeString` the data is stored as a null-terminated UTF-8 string (`A3DUTF8Char*`):

.. code-block:: c

   if(in_sAttr.m_eType == kA3DModellerAttributeTypeString)
   {
     A3DUTF8Char* pcData = in_sAttr.m_pcData;
     printf("Value: %s", uiData);
   }

Time Specifier
==============

If `m_eType == kA3DModellerAttributeTypeTime` the data is stored as a time specifier as defined by the [`time_t` specifications](https://en.cppreference.com/w/c/chrono/time_t):

.. code-block:: c

   if(in_sAttr.m_eType == kA3DModellerAttributeTypeString)
   {
     time_t iData;
     memcpy(&iData, in_sAttr.m_pcData, sizeof(A3DInt32));
     printf("Value: %s", asctime(gmtime(&iData)));
   }

********************
Reading an Attribute
********************

An attribute is an instance of `A3DMiscAttribute`. Any attribute has a title and zero or more `A3DMiscSingleAttributeData`:

.. code-block:: c

   void ReadAttribute(const A3DMiscAttribute* in_pAttribute)
   {
     A3DMiscAttributeData sAttributeData;
     A3D_INITIALIZE_DATA(A3DMiscAttributeData, sAttributeData);

     A3DMiscAttributeGet(in_pAttribute, &sAttributeData);

     // Iterate through the single attributes
     const A3DUns32 uiNSingleAttributes = sAttributeData.m_uiSize;
     fo(A3DUns32 sa = 0 ; sa < uiNSingleAttributes ; ++sa)
     {
       const A3DMiscSingleAttributeData* pSingleAttributeData = sAttributeData.m_asSingleAttributesData + sa;
       ReadSingleAttribute(pSingleAttributeData);
     }

     A3DMiscAttributeGet(A3D_NULL_HANDLE, &sAttributeData);
   }

******************************
Getting the List of Attributes
******************************

Generic attributes of an :func:`A3DRootBase` instance are available as an array:

.. code-block:: c

   void ReadAttributes(const A3DRootBase* in_pBase)
   {
     A3DRootBaseData sRootBaseData;
     A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);

     A3DRootBaseGet(in_pBase, &sRootBaseData);

     // Iterate through the attributes
     const A3DUns32 uiNAttributes = sRootBaseData.m_uiSize;
     for(A3DUns32 a = 0 ; a < uiNAttributes ; ++a)
     {
       const A3DMiscAttribute* pAttribute = sRootBaseData.m_ppAttributes[a];
       ReadAttribute(pAttribute);
     }

     A3DRootBaseGet(A3D_NULL_HANDLE, &sRootBaseData);
   }

All attributes are instances of :func:`A3DMiscAttribute`.

****************
Attribute Titles
****************

Within :struct:`A3DMiscAttributeData` and :struct:`A3DMiscSingleAttributeData`, the `m_pcTitle` may contain:

* A 32 bits unsigned integer if `m_bTitleIsInt` is `A3D_TRUE`
* A traditional null-terminated string otherwise

If `m_bTitleIsInt` is `A3D_TRUE`, the `A3DUTF8Char*` value may directly be casted into `A3DUns32*` and dereferenced.

Here's an example of function which gets the title of an attribute given its format, and prints in into a buffer:

.. code-block:: c

   // Reads in_pcTitle and in_bTitleIsInt to print the title into
   // The buffer must be long enough to contains the formatted title.
   // Returns A3D_FALSE if in_pcTitle is NULL, A3D_TRUE otherwise.
   A3DBool SPrintTitle(A3FUTF8Char* in_pcTitle, A3DBool in_bTitleIsInt, A3DUTF8Char* out_pcBuffer)
   {
       if(in_pcTitle == NULL) {
         return A3D_FALSE;
       }

       if (in_bTitleIsInt == A3D_TRUE) {
         sprintf(out_pcBuffer, "%d", *((A3DUns32*)in_pcTitle));
       } else {
         wprintf(out_pcBuffer, "%s'", in_pcTitle);
       }

       return A3D_TRUE;
   }

The following examples use `SPrintTitle()` for readability.

##################
A Complete Example
##################

The code below is a complete example which traverses an `\ref A3DRootBase` instance and print all its attributes in the standard output in the form:

.. code-block:: c

   // Displays attributes as follow:
   // Attribute Title0:
   // SingleAttribute Title00: Value (Type): Value00
   // SingleAttribute Title01: Value (Type): Value01
   // Attribute Title1:
   // SingleAttribute Title10: Value (Type): Value10
   // SingleAttribute Title11: Value (Type): Value11
   // ...
   void PrintGenericAttributes(const A3DRootBase* in_pRootBase)
   {
     A3DRootBaseData sRootBaseData;
     A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);

     A3DRootBaseGet(in_pRootBase, &sRootBaseData);

     A3DUTF8Char pcTitle[64];

     // Iterate through the attributes
     const A3DUns32 uiNAttributes = sRootBaseData.m_uiSize;
     for (A3DUns32 a = 0; a < uiNAttributes; ++a)
     {
       const A3DMiscAttribute* pAttribute = sRootBaseData.m_ppAttributes[a];

       A3DMiscAttributeData sAttributeData;
       A3D_INITIALIZE_DATA(A3DMiscAttributeData, sAttributeData);

       A3DMiscAttributeGet(pAttribute, &sAttributeData);

         // Display the attribute title
       if(SPrintTitle(sAttributeData.m_pcTitle, sAttributeData.m_bTitleIsInt, pcTitle) == A3D_TRUE) {
         printf("Attribute: %s\n", pcTitle);
       }

       // Iterate through the single attributes
       const A3DUns32 uiNSingleAttributes = sAttributeData.m_uiSize;
       for (A3DUns32 sa = 0; sa < uiNSingleAttributes; ++sa)
       {
         A3DMiscSingleAttributeData* pSingleAttributeData = sAttributeData.m_asSingleAttributesData+sa;

         // Display the single attribute title
         if(SPrintTitle(pSingleAttributeData.m_pcTitle, pSingleAttributeData.m_bTitleIsInt, pcTitle) == A3D_TRUE) {
           printf("Attribute: %s\n", pcTitle);
         }

         // Display the single attribute value on the same line
         switch (pSingleAttributeData->m_eType)
         {
           case kA3DModellerAttributeTypeInt:
             printf("Value (A3DUns32): %d\n", *(A3DUns32*)pSingleAttributeData->m_pcData);
             break;

           case kA3DModellerAttributeTypeReal:
             printf("Value (A3DFloat): %f\n", *(A3DFloat*)pSingleAttributeData->m_pcData);
             break;

           case kA3DModellerAttributeTypeTime:
             printf("Value (Time): %s\n", asctime(gmtime((time_t*)pSingleAttributeData->m_pcData)));
             break;

           case kA3DModellerAttributeTypeString:
             printf("Value (String): %s\n", pSingleAttributeData->m_pcData);
             break;

           default:
             break;
         }

         printf("\n");
       }

       A3DMiscAttributeGet(A3D_NULL_HANDLE, &sAttributeData);

     }

     A3DRootBaseGet(A3D_NULL_HANDLE, &sRootBaseData);
   }

*************************
Attributes and Categories
*************************

Organization of :struct:`A3DMiscSingleAttribute` instances within `A3DMiscAttribute` instances is entirely up to the implementation.
Yet, upon reading any format to PRC, HOOPS Exchange organizes attributes into **categories**.

.. note::

   Within a PRC tree, a category is a named `A3DMiscAttribute` instance grouping named `A3DSingleAttributeInstances`.
   This applies **only** if both `A3DMiscAttribute` and `A3DSingleAttributeInstances` instances have a title.

To sum up for any instance of `A3DMiscSingleAttribute` within an `#A3DMiscAttribute`:

* If both the `A3DMiscSingleAttribute` and the `#A3DMiscAttribute` have a title, then:

  * `A3DMiscAttribute::m_pcTitle` is the name of the category.
  * `A3DMiscSingleAttribute::m_pcTitle` is the name of the attribute.

* If only one of the instances have a title, there is no category and `m_pcTitle` is the name of the attribute.

  .. code-block:: c

   void ReadAttributesWithCategories(const A3DRootBase* in_pRootBase)
   {
     A3DRootBaseData sRootBaseData;
     A3D_INITIALIZE_DATA(A3DRootBaseData, sRootBaseData);
     A3DRootBaseGet(in_pRootBase, &sRootBaseData);

     // Iterate through the attributes
     const A3DUns32 uiNAttributes = sRootBaseData.m_uiSize;
     for (A3DUns32 a = 0; a < uiNAttributes; ++a)
     {
       const A3DMiscAttribute* pAttribute = sRootBaseData.m_ppAttributes[a];
       A3DMiscAttributeData sAttributeData;
       A3D_INITIALIZE_DATA(A3DMiscAttributeData, sAttributeData);
       A3DMiscAttributeGet(pAttribute, &sAttributeData);

       // Iterate through the single attributes
       const A3DUns32 uiNSingleAttributes = sAttributeData.m_uiSize;
       for (A3DUns32 sa = 0; sa < uiNSingleAttributes; ++sa)
       {
         A3DMiscSingleAttributeData* pSingleAttribute = sAttributeData.m_asSingleAttributesData+sa;

         A3DUTF8Char pcAttributeTitle[64];
         A3DBool bAttributeHasTitle = SPrintTitle(
           pAttribute.m_pcTitle,
           pAttribute.m_bTitleIsInt,
           pcAttributeTitle
         );

         A3DUTF8Char pcSingleAttributeTitle[64];
         A3DBool bSingleAttributeHasTitle = SPrintTitle(
           pSingleAttribute.m_pcTitle,
           pSingleAttribute.m_bTitleIsInt,
           pcSingleAttributeTitle
         );

         // If both the A3DMiscSingleAttribute and the A3DMiscAttribute have a title
         if(bAttributeHasTitle == A3D_TRUE && bSingleAttributeHasTitle == A3D_TRUE)
         {
           printf("Category: %s, Attribute Name: %s\n", pcAttributeTitle, pcSingleAttributeTitle);
         }
         else if(bAttributeHasTitle == A3D_TRUE || bSingleAttributeHasTitle == A3D_TRUE)
         {
           printf("Attribute Name: %s\n", pcAttributeTitle | pcSingleAttributeTitle);
         }
         else
         {
           printf("No Attribute or category name\n");
         }

         printf("\n");
       }

       A3DMiscAttributeGet(A3D_NULL_HANDLE, &sAttributeData);

     }

     A3DRootBaseGet(A3D_NULL_HANDLE, &sRootBaseData);
   }

While very unlikely, it remains possible that both `A3DMiscAttribute` and `A3DMiscSingleAttribute` have a `NULL` title.

