ALib C++ Library
Library Version: 2402 R1
Documentation generated by doxygen
Loading...
Searching...
No Matches
PropertyFormatter Class Reference

Description:


Introduction

This class can be used to offer customized format strings to end users. For example, when it should be allowed to end-users to store output format strings of complex type.

For this, the format string definition of ALib Formatter types is extended to support identifiers, which act as placeholders for object values. The identifiers placed in the string have to match to the normal formatting placeholders in respect to their total number and value type.

Note
In other words: This class allows not only to have custom format strings which consume a list of data objects that is hard-coded, but also to have the replacement data objects defined by the end user.

In the constructor of this class, a custom format string which whose syntax is based on standard ALib format strings is passed. However, before using the format string, it is processed as follows:

  • It is searched for "custom identifiers" within the format string.
  • For each identifier found, a reference to an associated callback function is stored.
  • The "custom identifier" found is removed from the format string.

It could be said, that the format string given "is compiled" with the constructor of this class.

The only interface method Format then accepts a boxed object of custom type that is used to collect the format string's placeholder data, by using the stored callback functions.

The identifiers are denoted by prefixing them with (customizable) character '@'.

A vector of objects of inner type IdentifierEntry needs to be defined and passed to the constructor of this class. Field IdentifierEntry::Name contains the 'identifier' string which is removed from the format string if found. Field IdentifierEntry::Callback has to point to a callback function used to retrieve the data when the identifier is found in the format string.

The whole approach is useful in cases where an application wants to allow a customizable output format of data objects.

Sample

Let us assume, an application uses an enumeration and a simple struct:

enum class Hobbies
{
Hacking,
FineArts,
};
struct Person
{
String Name;
int Age;
Hobbies Hobby;
};

In the application's configuration file, a custom output format for objects of type Person should be user defined. We document to the user that she can use Python-style or Java-style output formats - extended by the ability to place the following three identifiers in the string:

  • @name
  • @age
  • @hobby.

A possible format string then would be:

     "{@name} is aged {@age} and his/her hobby is {@hobby}"
Note
The custom identifiers might appear anywhere in the format string. But as sampled above, placing them in directly in the formatter replacement fields, makes the string very well readable. Also, it is important to have exactly one identifier for each replacement field, which this notation of-course supports well.

To tell this class how to retrieve the replacement values, we need to define three callback functions:

namespace
{
Box getName ( const Box& p, AString& ) { return p.Unbox<Person*>()->Name; }
Box getAge ( const Box& p, AString& ) { return p.Unbox<Person*>()->Age; }
Box getHobby ( const Box& p, AString& ) { return p.Unbox<Person*>()->Hobby == Hobbies::Hacking
? A_CHAR("hacking") : A_CHAR("fine arts") ; }
}

It is a good idea to place the callback functions in an anonymous (sub-) namespace as they are only referred to once (in the next step).
As this sample is very trivial, the second parameter AString& is not used and named. Details on the purpose and use of this parameter is addressed below.

As a next step, these functions need to be collected together in a "translation table". The table primarily holds a string denoting the replacement identifier and a pointer to the corresponding callback function. For convenience, the table type is provided with using definition TCallbackTable.

In our sample, the definition of the table looks like this:

{
{ A_CHAR("name") , 1, getName },
{ A_CHAR("age") , 1, getAge },
{ A_CHAR("hobby") , 1, getHobby },
};

This is all we need! Of-course, an external declaration of our table PersonCallbacks should be placed somewhere in the project's header file. With this, a code like this may now use the custom formatter strings:

// Our data objects
Person p1= { A_CHAR("Sue") , 28, Hobbies::Hacking };
Person p2= { A_CHAR("John"), 35, Hobbies::Hacking };
// The format string. Make this changeable at run-time, e.g. load from INI-file!
String format= A_CHAR("{@name} is aged {@age} and his/her hobby is {@hobby}");
// create a formatter
alib::PropertyFormatter propertyFormatter( format, PersonCallbacks );
// format the two data objects
AString target;
propertyFormatter.Format( target, p1 );
target << NewLine();
propertyFormatter.Format( target, p2 );
target << NewLine();
// that's it!
std::cout << target;

Running the code above produces the following output:

Sue is aged 28 and his/her hobby is hacking
John is aged 35 and his/her hobby is hacking

Serializing objects in a custom format (e.g. a user could define its own JSon object output) should be the most obvious and frequent use case. And this is what the classes name suggests. Nevertheless, the callback methods might be more complex than just returning "properties" of objects. The next sections gives more details on the custom callbacks.

Callback Functions

The call back function's signature is defined with using-statement TCallback which evaluates to

           Box (*)(const Box&, AString&)

The return type is Box, which allows the callback function to return objects of arbitrary type, respectively all types that are are equipped to be used with ALib formatters.

The first input parameter provides the data object passed to method Format. If the callback functions are dedicated to a property formatter that receives a certain object type (what they usually are), the custom type can be straightly unboxed.

Note
The name of the class, as well as the sample above, indicate that the data objects are simple objects with rather simple "properties" to read. While this is the usual use case, a user of this class might pass more complex objects, or even the root object of the application to the formatter. Then, the callback function may retrieve (or calculate) any data that the application provides.

The second parameter is an AString object which optionally can be used to assemble string objects in more complex callback methods. Note, that if this string is not empty after the callback invocation, the result is copied to a string buffer allocated in the heap memory. In this case, the box object returned by the callback is ignored and instead the copy of the string is passed to the formatter. If C++ string constants (e.g "true" are to be returned by the callbacks, those constants do not need to be copied to the buffer, as their memory is statically allocated. In other words, the AString buffer provided in the second parameter is needed to be used only in cases that a string gets assembled in the callback function!

Identifiers And The Escape Character '@'

The escape character used to find custom identifiers in the format string is defined with parameter ESCCharacter of the constructor and defaults to '@'. This escape character is searched in the format string. If found, the identifier is read by consuming alphabetic characters. Optionally, the end of a custom identifier can be marked with an additional escape character. Hence, the two format strings

     "{@name}"
     "{@name@}"

are both valid and equal.

In case of FormatterPythonStyle format strings, it is a good idea to place the identifier right inside the brackets. It just looks very intuitive. However, these versions:

     "@name{}"
     "{}@name"

are also valid custom format strings.

In case of FormatterJavaStyle , which uses '%' as its escape character, we consider the best option to put the custom identifier in front of each '%' symbol. The string of the example given above would then look like this:

     "@name%s is aged @age%d and his/her hobby is @hobby%s"

Doing it this way, the '%' symbol acts as a very natural delimiter for the custom identifier.

Furthermore, field IdentifierEntry::MinimumRecognitionLength allows abbreviations of identifier names. It denotes the minimum number of characters to be* matched. As in the above sample a value of 1 is provided, each identifier of the custom format string can be abbreviated down to one character. Consequently the following format string samples are all equal and allowed:

     "{@name}"
     "{@nam}"
     "{@na}"
     "{@n}"

Finally, to insert the escape character itself into the format string, it has to be doubly inserted ("@@").

Alternative

If ALib Expressions is compiled with the library, a utility class similar to this one is available with ExpressionFormatter . With this, complex expressions and calculations might be used instead of only having simple identifiers as replacements.

Reference Documentation

Exceptions
alib::lang::format::FMTExceptions::UnknownPropertyInFormatString

Definition at line 201 of file propertyformatter.hpp.

#include <propertyformatter.hpp>

Collaboration diagram for PropertyFormatter:
[legend]

Inner Type Index:

struct  IdentifierEntry
 

Public Type Index:

using TCallback = Box (*)(const Box&, AString&)
 
using TCallbackTable = std::vector<IdentifierEntry>
 

Public Method Index:

ALIB_API PropertyFormatter (const String customFormatString, const TCallbackTable &propertyTable, SPFormatter formatter=nullptr, character ESCCharacter='@')
 
ALIB_API void Format (AString &target, const Box &src)
 

Type Definition Details:

◆ TCallback

using TCallback = Box (*)(const Box&, AString&)

The signature of the callback functions. See Callback Functions for more information.

Definition at line 208 of file propertyformatter.hpp.

◆ TCallbackResultTable

using TCallbackResultTable = std::vector<const IdentifierEntry*>
protected

Internal type definition for the list of callback table entries collected in the constructor by parsing the extended format string.

Definition at line 236 of file propertyformatter.hpp.

◆ TCallbackTable

using TCallbackTable = std::vector<IdentifierEntry>

Type definition of the callback table.

Definition at line 228 of file propertyformatter.hpp.

Field Details:

◆ callBacks

TCallbackResultTable callBacks
protected

The callback functions to receive the format data.

Definition at line 248 of file propertyformatter.hpp.

◆ formatString

AString formatString
protected

The resulting format string passed to ALib formatters.

Definition at line 245 of file propertyformatter.hpp.

◆ propertyFormatString

AString propertyFormatString
protected

The original format string. Used only for exception information.

Definition at line 242 of file propertyformatter.hpp.

◆ stdFormatter

SPFormatter stdFormatter
protected

The formatter (as given in the constructor).

Definition at line 239 of file propertyformatter.hpp.

Constructor(s) / Destructor Details::

◆ PropertyFormatter()

PropertyFormatter ( const String customFormatString,
const TCallbackTable & propertyTable,
SPFormatter formatter = nullptr,
character ESCCharacter = '@' )

Constructor. Processes the given format string and builds internal structures which are then used with invocations of method Format.

Parameters
customFormatStringThe format string as described in the class documentation.
propertyTableTable with property identifier names and callback functions to retrieve the property values.
formatterThe formatter to use. Defaults to nullptr which selects default formatter .
ESCCharacterThe prefix used to search identifiers in customFormatString . Defaults to '@'.
Exceptions
alib::lang::format::FMTExceptions::UnknownPropertyInFormatString

Definition at line 20 of file propertyformatter.cpp.

Here is the call graph for this function:

Method Details:

◆ Format()

void Format ( AString & target,
const Box & src )

Writes the formatted output of the properties of the given TFormattable object to a given target string.

Parameters
targetThe target string to write into.
srcThe custom object which is passed to the callback methods to collect the formatter arguments.

Definition at line 96 of file propertyformatter.cpp.

Here is the call graph for this function:

The documentation for this class was generated from the following files: