Fleshed out SubstitutionField spec and implementation.

- Added newline modifier to spec and implementation
- Does not belong in Merge, so moved back to glabels
- Incorporated SubstitutionField into RawText
This commit is contained in:
Jim Evins
2017-11-22 13:33:30 -05:00
parent acfa5e9a32
commit 8bec3594ec
14 changed files with 864 additions and 622 deletions
+1
View File
@@ -30,3 +30,4 @@ core
*.safe *.safe
*.sav* *.sav*
.directory .directory
TEST-DATA
+36 -13
View File
@@ -3,27 +3,27 @@ DRAFT gLabels Substitution Field Specification
> :warning: This is a very early draft specification. There is no guarantee that any of the capabilities will be implemented as described in gLabels 4.0. Prior to 4.0, this specification is subject to change. > :warning: This is a very early draft specification. There is no guarantee that any of the capabilities will be implemented as described in gLabels 4.0. Prior to 4.0, this specification is subject to change.
This specification describes gLabels substitution fields. Substitution fields can be embedded in the data of gLabels text and barcode objects. When printing, these fields are replaced with their respective values. Fields can represent document merge fields or built-in variables. This specification describes gLabels substitution fields. Substitution fields can be embedded in the data of gLabels text and barcode objects. When printing, these fields are replaced with their respective values. Fields can represent document merge fields or user defined variables.
In its simplest and most common form, the format is simply `${ field-name }`. For example `${FIRST_NAME}`. However, modifiers can be added to the field to control how values are printed.
Syntax Syntax
------ ------
The general syntax of a substitution field is The general syntax of a substitution field is
```ebnf ```ebnf
substitution-field = "${", field-name, [ ":" modifiers ], "}" ; substitution-field = "${" field-name [ ":" modifiers ] "}" ;
modifiers = modifier, [ ":", modifiers ] ; modifiers = modifier [ ":" modifiers ] ;
modifier = format-modifier | default-value-modifier ; modifier = format-modifier | default-value-modifier | new-line-modifier;
``` ```
In its simplest and most common form, the format is simply `${ field-name }`. For example `${FIRST_NAME}`.
Modifiers Modifiers
--------- ---------
### Format-Modifier (`%`) ### Format-Modifier (`%`)
A format modifier is used to control the format of numerical and string values. It is a subset of a single printf format placeholder. Its syntax is A format modifier is used to control the format of numerical and string values. It is a subset of a single printf format placeholder. Its syntax is
```ebnf ```ebnf
format-modifier = "%", [ flags ], [ width ], [ ".", precision ], type ; format-modifier = "%" [ flags ] [ width ] [ ".", precision ] type ;
``` ```
#### Flags #### Flags
@@ -57,7 +57,7 @@ Character | Description
`s` | string value. `s` | string value.
### Default-Value-Modifier (`=`) ### Default-Value-Modifier (`=`)
The default value modifier is used to set a default value for the field if its value is undefined. It can also be used to set the initial value of some built-in variables, such as `${LABEL_NUMBER}`. Its syntax is The default value modifier is used to set a default value for the field if its value is undefined or empty. Its syntax is
```ebnf ```ebnf
default-value-modifier = "=" value ; default-value-modifier = "=" value ;
@@ -71,15 +71,38 @@ Escape sequence | actual character
`\}` | right bracket `}` `\}` | right bracket `}`
`\\` | backslash `\` `\\` | backslash `\`
> :arrow_right: This modifier does not modify the value of a variable, it only uses this value if the variable is not defined. For example, if the variable `${x}` is undefined, the string "`${x:=1} ${x:=2}`" would be printed as "`1 2`". Otherwise, if `${x}` was defined as "`3`", it would be printed as "`3 3`".
### New-Line-Modifier ('n')
This modifier is used to prepend a newline to the printed value, if the value is undefined or empty. Its syntax is simply:
```ebnf
new-line-modifier = "n" ;
```
For example, this modifier is primarily intended to handle the following use case:
```
${NAME}
${ADDR1}${ADDR2:n}
${CITY} ${STATE} ${ZIP}
```
`${ADDR2}` would be printed on its own line, only if it is set and non-empty.
Document Merge Fields Document Merge Fields
--------------------- ---------------------
Document merge fields are the primary source of substitution fields. A document merge field represents a field from an external data source, such as a CSV file. Document merge fields are the primary source of substitution fields. A document merge field represents a field from an external data source, such as a CSV file.
User Defined Variables
----------------------
Alternatively, merge fields can refer to user defined variables.
Built-In Variables Built-In Variables
------------------ ------------------
### LABEL_NUMBER Potentially, merge fields may also refer to built-in variables. Candidates include:
### PAGE_NUMBER - LABEL_NUMBER
### DATE - PAGE_NUMBER
### TIME - DATE
### FILE_NAME - TIME
- FILE_NAME
+3
View File
@@ -112,6 +112,7 @@ set (glabels_sources
Size.cpp Size.cpp
StartupView.cpp StartupView.cpp
StrUtil.cpp StrUtil.cpp
SubstitutionField.cpp
Template.cpp Template.cpp
TemplatePicker.cpp TemplatePicker.cpp
TemplatePickerItem.cpp TemplatePickerItem.cpp
@@ -256,6 +257,7 @@ set (glabels-batch_sources
Settings.cpp Settings.cpp
Size.cpp Size.cpp
StrUtil.cpp StrUtil.cpp
SubstitutionField.cpp
Template.cpp Template.cpp
TextNode.cpp TextNode.cpp
Units.cpp Units.cpp
@@ -336,6 +338,7 @@ link_directories (
#======================================= #=======================================
add_subdirectory (BarcodeBackends) add_subdirectory (BarcodeBackends)
add_subdirectory (Merge) add_subdirectory (Merge)
add_subdirectory (unit_tests)
#======================================= #=======================================
-2
View File
@@ -15,7 +15,6 @@ set (merge_sources
TextColonKeys.cpp TextColonKeys.cpp
TextSemicolon.cpp TextSemicolon.cpp
TextSemicolonKeys.cpp TextSemicolonKeys.cpp
SubstitutionField.cpp
) )
set (merge_qobject_headers set (merge_qobject_headers
@@ -44,7 +43,6 @@ link_directories (
#======================================= #=======================================
# Subdirectories # Subdirectories
#======================================= #=======================================
add_subdirectory (unit_tests)
#======================================= #=======================================
-291
View File
@@ -1,291 +0,0 @@
/* SubstitutionField.cpp
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SubstitutionField.h"
#include <QTextStream>
namespace glabels
{
merge::SubstitutionField::SubstitutionField( const QString& string )
{
QStringRef s(&string);
parseSubstitutionField( s );
}
QString merge::SubstitutionField::evaluate( const merge::Record& record ) const
{
QString value = mDefaultValue;
if ( record.contains(mFieldName) )
{
value = record[mFieldName];
}
if ( mFormatType.isNull() )
{
return value;
}
else
{
return formatValue( value );
}
}
QString merge::SubstitutionField::fieldName() const
{
return mFieldName;
}
QString merge::SubstitutionField::defaultValue() const
{
return mDefaultValue;
}
QString merge::SubstitutionField::format() const
{
return mFormat;
}
QChar merge::SubstitutionField::formatType() const
{
return mFormatType;
}
QString merge::SubstitutionField::formatValue( const QString& value ) const
{
switch (mFormatType.unicode())
{
case 'd':
case 'i':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toLongLong(nullptr,0) );
break;
case 'u':
case 'x':
case 'X':
case 'o':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toULongLong(nullptr,0) );
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toDouble() );
break;
case 's':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toStdString().c_str() );
break;
default:
// Invalid format
return "";
break;
}
}
void merge::SubstitutionField::parseSubstitutionField( QStringRef& s )
{
if ( s.startsWith( "${" ) )
{
s = s.mid(2);
parseFieldName( s );
while ( s.size() && s[0] == ':' )
{
s = s.mid(1);
parseModifier( s );
}
if ( s.size() && s[0] == '}' )
{
s = s.mid(1);
if ( s.size() )
{
// Invalid -- extraneous input
}
}
else
{
// Invalid -- expected '}'
}
}
else
{
// Invalid -- expected '${'
}
}
void merge::SubstitutionField::parseFieldName( QStringRef& s )
{
while ( s.size() && (s[0].isDigit() || s[0].isLetter() || s[0] == '_' || s[0] == '-') )
{
mFieldName.append( s[0] );
s = s.mid(1);
}
}
void merge::SubstitutionField::parseModifier( QStringRef& s )
{
if ( s.size() && s[0] == '%' )
{
s = s.mid(1);
parseFormatModifier( s );
}
else if ( s.size() && s[0] == '=' )
{
s = s.mid(1);
parseDefaultValueModifier( s );
}
else
{
// Invalid -- unrecognized modifier, expecting one of '%' or '='
}
}
void merge::SubstitutionField::parseDefaultValueModifier( QStringRef& s )
{
while ( s.size() && s[0] != ':' && s[0] != '}' )
{
if ( s[0] == '\\' )
{
s = s.mid(1); // Skip escape
if ( s.size() )
{
mDefaultValue.append( s[0] );
s = s.mid(1);
}
{
// Invalid -- end of string encountered during escape
}
}
else
{
mDefaultValue.append( s[0] );
s = s.mid(1);
}
}
}
void merge::SubstitutionField::parseFormatModifier( QStringRef& s )
{
mFormat = "%";
mFormat += parseFormatFlags( s );
mFormat += parseFormatWidth( s );
if ( s.size() && s[0] == '.' )
{
s = s.mid(1);
mFormat += "." + parseFormatPrecision( s );
}
mFormatType = parseFormatType( s );
mFormat += mFormatType;
}
QString merge::SubstitutionField::parseFormatFlags( QStringRef& s )
{
QString flags;
while ( s.size() && QString( "-+ 0" ).contains( s[0] ) )
{
flags.append( s[0] );
s = s.mid(1);
}
return flags;
}
QString merge::SubstitutionField::parseFormatWidth( QStringRef& s )
{
return parseNaturalInteger( s );
}
QString merge::SubstitutionField::parseFormatPrecision( QStringRef& s )
{
return parseNaturalInteger( s );
}
QChar merge::SubstitutionField::parseFormatType( QStringRef& s )
{
QChar type = 0;
if ( s.size() && QString( "diufFeEgGxXos" ).contains( s[0] ) )
{
type = s[0];
s = s.mid(1);
}
return type;
}
QString merge::SubstitutionField::parseNaturalInteger( QStringRef& s )
{
QString value = "";
if ( s.size() && s[0] >= '1' && s[0] <= '9' )
{
value += s[0];
s = s.mid(1);
while ( s.size() && s[0].isDigit() )
{
value += s[0];
s = s.mid(1);
}
}
return value;
}
} // namespace glabels
-77
View File
@@ -1,77 +0,0 @@
/* SubstitutionField.h
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef glabels_SubstitutionField_h
#define glabels_SubstitutionField_h
#include "Record.h"
#include <QString>
#include <QStringRef>
namespace glabels
{
namespace merge
{
class SubstitutionField
{
public:
SubstitutionField() = default;
SubstitutionField( const QString& string );
QString evaluate( const merge::Record& record ) const;
QString fieldName() const;
QString defaultValue() const;
QString format() const;
QChar formatType() const;
private:
QString formatValue( const QString& value ) const;
void parseSubstitutionField( QStringRef& s );
void parseFieldName( QStringRef& s );
void parseModifier( QStringRef& s );
void parseDefaultValueModifier( QStringRef& s );
void parseFormatModifier( QStringRef& s );
QString parseFormatFlags( QStringRef& s );
QString parseFormatWidth( QStringRef& s );
QString parseFormatPrecision( QStringRef& s );
QChar parseFormatType( QStringRef& s );
QString parseNaturalInteger( QStringRef& s );
QString mFieldName;
QString mDefaultValue;
QString mFormat;
QChar mFormatType;
};
}
}
#endif // glabels_SubstitutionField_h
@@ -1,222 +0,0 @@
/* TestSubstitutionField.cpp
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestSubstitutionField.h"
#include "SubstitutionField.h"
QTEST_MAIN(TestSubstitutionField)
void TestSubstitutionField::construction()
{
glabels::merge::SubstitutionField f1( "${1234}" );
QCOMPARE( f1.fieldName(), QString( "1234" ) );
glabels::merge::SubstitutionField f2( "${abc:=ABC}" );
QCOMPARE( f2.fieldName(), QString( "abc" ) );
QCOMPARE( f2.defaultValue(), QString( "ABC" ) );
glabels::merge::SubstitutionField f3( "${x:%08.2f}" );
QCOMPARE( f3.fieldName(), QString( "x" ) );
QCOMPARE( f3.format(), QString( "%08.2f" ) );
QCOMPARE( f3.formatType(), QChar('f') );
glabels::merge::SubstitutionField f4( "${y:%08.2f:=12.34}" );
QCOMPARE( f4.fieldName(), QString( "y" ) );
QCOMPARE( f4.defaultValue(), QString( "12.34" ) );
QCOMPARE( f4.format(), QString( "%08.2f" ) );
QCOMPARE( f4.formatType(), QChar('f') );
}
void TestSubstitutionField::simpleEvaluation()
{
glabels::merge::SubstitutionField f1( "${1}" );
glabels::merge::SubstitutionField f2( "${2}" );
glabels::merge::SubstitutionField f3( "${3}" );
glabels::merge::SubstitutionField f4( "${4}" );
glabels::merge::Record record1;
record1[ "1" ] = "Abcdefg";
record1[ "2" ] = "Hijklmn";
record1[ "3" ] = "Opqrstu";
record1[ "4" ] = "Vwxyz!@";
QCOMPARE( f1.evaluate( record1 ), QString( "Abcdefg" ) );
QCOMPARE( f2.evaluate( record1 ), QString( "Hijklmn" ) );
QCOMPARE( f3.evaluate( record1 ), QString( "Opqrstu" ) );
QCOMPARE( f4.evaluate( record1 ), QString( "Vwxyz!@" ) );
glabels::merge::Record record2;
record2[ "1" ] = "1234567";
record2[ "2" ] = "FooBar";
record2[ "3" ] = "8901234";
record2[ "4" ] = "#$%^&*";
QCOMPARE( f1.evaluate( record2 ), QString( "1234567" ) );
QCOMPARE( f2.evaluate( record2 ), QString( "FooBar" ) );
QCOMPARE( f3.evaluate( record2 ), QString( "8901234" ) );
QCOMPARE( f4.evaluate( record2 ), QString( "#$%^&*" ) );
}
void TestSubstitutionField::defaultValueEvaluation()
{
glabels::merge::SubstitutionField f1( "${1:=foo1}" );
glabels::merge::SubstitutionField f2( "${2:=foo2}" );
glabels::merge::SubstitutionField f3( "${3:=foo3}" );
glabels::merge::SubstitutionField f4( "${4:=foo4}" );
glabels::merge::Record record1;
record1[ "1" ] = "Abcdefg";
record1[ "2" ] = "Hijklmn";
record1[ "3" ] = "Opqrstu";
record1[ "4" ] = "Vwxyz!@";
QCOMPARE( f1.evaluate( record1 ), QString( "Abcdefg" ) );
QCOMPARE( f2.evaluate( record1 ), QString( "Hijklmn" ) );
QCOMPARE( f3.evaluate( record1 ), QString( "Opqrstu" ) );
QCOMPARE( f4.evaluate( record1 ), QString( "Vwxyz!@" ) );
glabels::merge::Record record2; // All fields empty
QCOMPARE( f1.evaluate( record2 ), QString( "foo1" ) );
QCOMPARE( f2.evaluate( record2 ), QString( "foo2" ) );
QCOMPARE( f3.evaluate( record2 ), QString( "foo3" ) );
QCOMPARE( f4.evaluate( record2 ), QString( "foo4" ) );
glabels::merge::Record record3;
record3[ "1" ] = "xyzzy";
// Field "2" empty
// Field "3" empty
record3[ "4" ] = "plugh";
QCOMPARE( f1.evaluate( record3 ), QString( "xyzzy" ) );
QCOMPARE( f2.evaluate( record3 ), QString( "foo2" ) );
QCOMPARE( f3.evaluate( record3 ), QString( "foo3" ) );
QCOMPARE( f4.evaluate( record3 ), QString( "plugh" ) );
}
void TestSubstitutionField::formattedStringEvaluation()
{
glabels::merge::SubstitutionField f1( "${1:%10s}" );
glabels::merge::SubstitutionField f2( "${2:%10s}" );
glabels::merge::SubstitutionField f3( "${3:%10s}" );
glabels::merge::SubstitutionField f4( "${4:%10s}" );
glabels::merge::SubstitutionField f5( "${5:%-10s}" );
glabels::merge::SubstitutionField f6( "${6:%-10s}" );
glabels::merge::SubstitutionField f7( "${7:%-10s}" );
glabels::merge::SubstitutionField f8( "${8:%-10s}" );
glabels::merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "0";
record1[ "6" ] = "100";
record1[ "7" ] = "-100";
record1[ "8" ] = "3.14";
QCOMPARE( f1.evaluate( record1 ), QString( " 0" ) );
QCOMPARE( f2.evaluate( record1 ), QString( " 1" ) );
QCOMPARE( f3.evaluate( record1 ), QString( " -1" ) );
QCOMPARE( f4.evaluate( record1 ), QString( " 3.14" ) );
QCOMPARE( f5.evaluate( record1 ), QString( "0 " ) );
QCOMPARE( f6.evaluate( record1 ), QString( "100 " ) );
QCOMPARE( f7.evaluate( record1 ), QString( "-100 " ) );
QCOMPARE( f8.evaluate( record1 ), QString( "3.14 " ) );
}
void TestSubstitutionField::formattedFloatEvaluation()
{
glabels::merge::SubstitutionField f1( "${1:%+5.2f}" );
glabels::merge::SubstitutionField f2( "${2:%+5.2f}" );
glabels::merge::SubstitutionField f3( "${3:%+5.2f}" );
glabels::merge::SubstitutionField f4( "${4:%+5.2f}" );
glabels::merge::SubstitutionField f5( "${5:%+5.2e}" );
glabels::merge::SubstitutionField f6( "${6:%+5.2e}" );
glabels::merge::SubstitutionField f7( "${7:%+5.2e}" );
glabels::merge::SubstitutionField f8( "${8:%+5.2e}" );
glabels::merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "0";
record1[ "6" ] = "100";
record1[ "7" ] = "-100";
record1[ "8" ] = "3.14";
QCOMPARE( f1.evaluate( record1 ), QString( "+0.00" ) );
QCOMPARE( f2.evaluate( record1 ), QString( "+1.00" ) );
QCOMPARE( f3.evaluate( record1 ), QString( "-1.00" ) );
QCOMPARE( f4.evaluate( record1 ), QString( "+3.14" ) );
QCOMPARE( f5.evaluate( record1 ), QString( "+0.00e+00" ) );
QCOMPARE( f6.evaluate( record1 ), QString( "+1.00e+02" ) );
QCOMPARE( f7.evaluate( record1 ), QString( "-1.00e+02" ) );
QCOMPARE( f8.evaluate( record1 ), QString( "+3.14e+00" ) );
}
void TestSubstitutionField::formattedIntEvaluation()
{
glabels::merge::SubstitutionField f1( "${1:%08d}" );
glabels::merge::SubstitutionField f2( "${2:%08d}" );
glabels::merge::SubstitutionField f3( "${3:%08d}" );
glabels::merge::SubstitutionField f4( "${4:%08d}" );
glabels::merge::SubstitutionField f5( "${5:%08x}" );
glabels::merge::SubstitutionField f6( "${6:%08x}" );
glabels::merge::SubstitutionField f7( "${7:%08x}" );
glabels::merge::SubstitutionField f8( "${8:%08x}" );
glabels::merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "100";
record1[ "6" ] = "0x100";
record1[ "7" ] = "-1";
record1[ "8" ] = "314";
QCOMPARE( f1.evaluate( record1 ), QString( "00000000" ) );
QCOMPARE( f2.evaluate( record1 ), QString( "00000001" ) );
QCOMPARE( f3.evaluate( record1 ), QString( "-0000001" ) );
QCOMPARE( f4.evaluate( record1 ), QString( "00000000" ) ); // Invalid integer value
QCOMPARE( f5.evaluate( record1 ), QString( "00000064" ) ); // 100(decimal) == 64(hex)
QCOMPARE( f6.evaluate( record1 ), QString( "00000100" ) );
QCOMPARE( f7.evaluate( record1 ), QString( "00000000" ) ); // Invalid unsigned integer
QCOMPARE( f8.evaluate( record1 ), QString( "0000013a" ) ); // 314(decimal) == 13a(hex)
}
+53 -14
View File
@@ -30,6 +30,7 @@ namespace glabels
/// ///
RawText::RawText( const QString& string ) : mString(string) RawText::RawText( const QString& string ) : mString(string)
{ {
tokenize();
} }
@@ -38,6 +39,7 @@ namespace glabels
/// ///
RawText::RawText( const char* cString ) : mString(QString(cString)) RawText::RawText( const char* cString ) : mString(QString(cString))
{ {
tokenize();
} }
@@ -64,24 +66,17 @@ namespace glabels
/// ///
QString RawText::expand( merge::Record* record ) const QString RawText::expand( merge::Record* record ) const
{ {
QString text = mString; QString text;
if ( record ) foreach ( const Token& token, mTokens )
{ {
foreach ( QString key, record->keys() ) if ( token.isField )
{ {
// Special case: remove line when it contains only an empty field. text += token.field.evaluate( record );
// e.g. an optional ${ADDR2} line. To bypass this case, include
// whitespace at end of line.
if ( record->value(key).isEmpty() )
{
QStringList v = text.split( '\n' );
v.removeAll( "${"+key+"}" );
text = v.join( '\n' );
} }
else
// Nominal case: simple replacement {
text.replace( "${"+key+"}", record->value(key) ); text += token.text;
} }
} }
@@ -107,4 +102,48 @@ namespace glabels
return mString.isEmpty(); return mString.isEmpty();
} }
///
/// Tokenize string
///
void RawText::tokenize()
{
Token token;
QStringRef s = &mString;
while ( s.size() )
{
SubstitutionField field;
if ( SubstitutionField::parse( s, field ) )
{
// Finalize current text token, if apropos
if ( !token.text.isEmpty() )
{
token.isField = false;
mTokens.append( token );
}
// Create and finalize field token
token.isField = true;
token.text = "";
token.field = field;
mTokens.append( token );
}
else
{
token.text += s[0];
s = s.mid(1);
}
}
// Finalize last text token, if apropos
if ( !token.text.isEmpty() )
{
token.isField = false;
mTokens.append( token );
}
}
} // namespace glabels } // namespace glabels
+16 -1
View File
@@ -22,7 +22,7 @@
#define RawText_h #define RawText_h
#include "Merge/Record.h" #include "SubstitutionField.h"
#include <QString> #include <QString>
@@ -55,12 +55,27 @@ namespace glabels
bool isEmpty() const; bool isEmpty() const;
/////////////////////////////////
// Private Methods
/////////////////////////////////
private:
void tokenize();
///////////////////////////////// /////////////////////////////////
// Private Data // Private Data
///////////////////////////////// /////////////////////////////////
private: private:
QString mString; QString mString;
struct Token
{
bool isField;
QString text;
SubstitutionField field;
};
QList<Token> mTokens;
}; };
} }
+331
View File
@@ -0,0 +1,331 @@
/* SubstitutionField.cpp
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SubstitutionField.h"
#include <QTextStream>
namespace glabels
{
SubstitutionField::SubstitutionField()
: mNewLine(false), mFormatType(0)
{
}
SubstitutionField::SubstitutionField( const QString& string )
: mNewLine(false), mFormatType(0)
{
QStringRef s(&string);
parse( s, *this );
}
QString SubstitutionField::evaluate( const merge::Record* record ) const
{
QString value = mDefaultValue;
if ( record && record->contains(mFieldName) && !record->value(mFieldName).isEmpty() )
{
value = record->value(mFieldName);
}
if ( !mFormatType.isNull() )
{
value = formatValue( value );
}
if ( record && record->contains(mFieldName) && !record->value(mFieldName).isEmpty() && mNewLine )
{
value = "\n" + value;
}
return value;
}
QString SubstitutionField::fieldName() const
{
return mFieldName;
}
QString SubstitutionField::defaultValue() const
{
return mDefaultValue;
}
QString SubstitutionField::format() const
{
return mFormat;
}
QChar SubstitutionField::formatType() const
{
return mFormatType;
}
bool SubstitutionField::newLine() const
{
return mNewLine;
}
bool SubstitutionField::parse( QStringRef& s, SubstitutionField& field )
{
QStringRef sTmp = s;
if ( sTmp.startsWith( "${" ) )
{
sTmp = sTmp.mid(2);
if ( parseFieldName( sTmp, field ) )
{
while ( sTmp.size() && sTmp[0] == ':' )
{
sTmp = sTmp.mid(1);
if ( !parseModifier( sTmp, field ) )
{
return false;
}
}
if ( sTmp.size() && sTmp[0] == '}' )
{
sTmp = sTmp.mid(1);
// Success. Update s.
s = sTmp;
return true;
}
}
}
return false;
}
bool SubstitutionField::parseFieldName( QStringRef& s, SubstitutionField& field )
{
bool success = false;
while ( s.size() && (s[0].isDigit() || s[0].isLetter() || s[0] == '_' || s[0] == '-') )
{
field.mFieldName.append( s[0] );
s = s.mid(1);
success = true;
}
return success;
}
bool SubstitutionField::parseModifier( QStringRef& s, SubstitutionField& field )
{
bool success = false;
if ( s.size() && s[0] == '%' )
{
s = s.mid(1);
success = parseFormatModifier( s, field );
}
else if ( s.size() && s[0] == '=' )
{
s = s.mid(1);
success = parseDefaultValueModifier( s, field );
}
else if ( s.size() && s[0] == 'n' )
{
s = s.mid(1);
success = parseNewLineModifier( s, field );
}
return success;
}
bool SubstitutionField::parseDefaultValueModifier( QStringRef& s, SubstitutionField& field )
{
field.mDefaultValue.clear();
while ( s.size() && !QString( ":}" ).contains( s[0] ) )
{
if ( s[0] == '\\' )
{
s = s.mid(1); // Skip escape
if ( s.size() )
{
field.mDefaultValue.append( s[0] );
s = s.mid(1);
}
}
else
{
field.mDefaultValue.append( s[0] );
s = s.mid(1);
}
}
return true;
}
bool SubstitutionField::parseFormatModifier( QStringRef& s, SubstitutionField& field )
{
bool success = false;
field.mFormat = "%";
parseFormatFlags( s, field );
parseFormatWidth( s, field );
if ( s.size() && s[0] == '.' )
{
field.mFormat += ".";
s = s.mid(1);
parseFormatPrecision( s, field );
}
parseFormatType( s, field );
return true; // Don't let invalid formats kill entire SubstitutionField
}
bool SubstitutionField::parseFormatFlags( QStringRef& s, SubstitutionField& field )
{
while ( s.size() && QString( "-+ 0" ).contains( s[0] ) )
{
field.mFormat += s[0];
s = s.mid(1);
}
return true;
}
bool SubstitutionField::parseFormatWidth( QStringRef& s, SubstitutionField& field )
{
return parseNaturalInteger( s, field );
}
bool SubstitutionField::parseFormatPrecision( QStringRef& s, SubstitutionField& field )
{
return parseNaturalInteger( s, field );
}
bool SubstitutionField::parseFormatType( QStringRef& s, SubstitutionField& field )
{
bool success = false;
if ( s.size() && QString( "diufFeEgGxXos" ).contains( s[0] ) )
{
field.mFormatType = s[0];
field.mFormat += s[0];
s = s.mid(1);
success = true;
}
return success;
}
bool SubstitutionField::parseNaturalInteger( QStringRef& s, SubstitutionField& field )
{
bool success = false;
if ( s.size() && s[0] >= '1' && s[0] <= '9' )
{
field.mFormat += s[0];
s = s.mid(1);
while ( s.size() && s[0].isDigit() )
{
field.mFormat += s[0];
s = s.mid(1);
}
success = true;
}
return success;
}
bool SubstitutionField::parseNewLineModifier( QStringRef& s, SubstitutionField& field )
{
field.mNewLine = true;
return true;
}
QString SubstitutionField::formatValue( const QString& value ) const
{
switch (mFormatType.unicode())
{
case 'd':
case 'i':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toLongLong(nullptr,0) );
break;
case 'u':
case 'x':
case 'X':
case 'o':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toULongLong(nullptr,0) );
break;
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toDouble() );
break;
case 's':
return QString::asprintf( mFormat.toStdString().c_str(),
value.toStdString().c_str() );
break;
default:
// Invalid format
return "";
break;
}
}
} // namespace glabels
+77
View File
@@ -0,0 +1,77 @@
/* SubstitutionField.h
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef glabels_SubstitutionField_h
#define glabels_SubstitutionField_h
#include "Merge/Record.h"
#include <QString>
#include <QStringRef>
namespace glabels
{
class SubstitutionField
{
public:
SubstitutionField();
SubstitutionField( const QString& string );
QString evaluate( const merge::Record* record ) const;
QString fieldName() const;
QString defaultValue() const;
QString format() const;
QChar formatType() const;
bool newLine() const;
static bool parse( QStringRef& s, SubstitutionField& field );
private:
static bool parseFieldName( QStringRef& s, SubstitutionField& field );
static bool parseModifier( QStringRef& s, SubstitutionField& field );
static bool parseDefaultValueModifier( QStringRef& s, SubstitutionField& field );
static bool parseFormatModifier( QStringRef& s, SubstitutionField& field );
static bool parseFormatFlags( QStringRef& s, SubstitutionField& field );
static bool parseFormatWidth( QStringRef& s, SubstitutionField& field );
static bool parseFormatPrecision( QStringRef& s, SubstitutionField& field );
static bool parseFormatType( QStringRef& s, SubstitutionField& field );
static bool parseNaturalInteger( QStringRef& s, SubstitutionField& field );
static bool parseNewLineModifier( QStringRef& s, SubstitutionField& field );
QString formatValue( const QString& value ) const;
QString mFieldName;
QString mDefaultValue;
QString mFormat;
QChar mFormatType;
bool mNewLine;
};
}
#endif // glabels_SubstitutionField_h
@@ -6,7 +6,7 @@ if (Qt5Test_FOUND)
# Test SubstitutionField class # Test SubstitutionField class
#======================================= #=======================================
qt5_wrap_cpp (TestSubstitutionField_moc_sources TestSubstitutionField.h) qt5_wrap_cpp (TestSubstitutionField_moc_sources TestSubstitutionField.h)
add_executable (TestSubstitutionField TestSubstitutionField.cpp ${TestSubstitutionField_moc_sources}) add_executable (TestSubstitutionField TestSubstitutionField.cpp ../SubstitutionField.cpp ${TestSubstitutionField_moc_sources})
target_link_libraries (TestSubstitutionField Merge ${Qt5Core_LIBRARIES} ${Qt5Test_LIBRARIES} ) target_link_libraries (TestSubstitutionField Merge ${Qt5Core_LIBRARIES} ${Qt5Test_LIBRARIES} )
add_test (NAME SubstitutionField COMMAND TestSubstitutionField) add_test (NAME SubstitutionField COMMAND TestSubstitutionField)
@@ -0,0 +1,342 @@
/* TestSubstitutionField.cpp
*
* Copyright (C) 2017 Jim Evins <evins@snaught.com>
*
* This file is part of gLabels-qt.
*
* gLabels-qt is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* gLabels-qt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gLabels-qt. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TestSubstitutionField.h"
#include "SubstitutionField.h"
QTEST_MAIN(TestSubstitutionField)
void TestSubstitutionField::parseValid()
{
using namespace glabels;
//
// Valid substitution fields (concatenated in single input string)
//
QString input = "${1234}${abc:=ABC}${x:%08.2f}${y:%08.2f:=12.34}${ADDR2:n}";
QStringRef s = &input;
SubstitutionField f1;
QCOMPARE( SubstitutionField::parse( s, f1 ), true );
QCOMPARE( f1.fieldName(), QString( "1234" ) );
QCOMPARE( f1.newLine(), false );
SubstitutionField f2;
QCOMPARE( SubstitutionField::parse( s, f2 ), true );
QCOMPARE( f2.fieldName(), QString( "abc" ) );
QCOMPARE( f2.defaultValue(), QString( "ABC" ) );
QCOMPARE( f2.newLine(), false );
SubstitutionField f3;
QCOMPARE( SubstitutionField::parse( s, f3 ), true );
QCOMPARE( f3.fieldName(), QString( "x" ) );
QCOMPARE( f3.format(), QString( "%08.2f" ) );
QCOMPARE( f3.formatType(), QChar('f') );
QCOMPARE( f3.newLine(), false );
SubstitutionField f4;
QCOMPARE( SubstitutionField::parse( s, f4 ), true );
QCOMPARE( f4.fieldName(), QString( "y" ) );
QCOMPARE( f4.defaultValue(), QString( "12.34" ) );
QCOMPARE( f4.format(), QString( "%08.2f" ) );
QCOMPARE( f4.formatType(), QChar('f') );
QCOMPARE( f4.newLine(), false );
SubstitutionField f5;
QCOMPARE( SubstitutionField::parse( s, f5 ), true );
QCOMPARE( f5.fieldName(), QString( "ADDR2" ) );
QCOMPARE( f5.newLine(), true );
}
void TestSubstitutionField::parseInvalid()
{
using namespace glabels;
//
// Ordinary text
//
QString input5 = "Abcdefg";
QStringRef s5 = &input5;
SubstitutionField f5;
QCOMPARE( SubstitutionField::parse( s5, f5 ), false );
QCOMPARE( s5, QStringRef( &input5 ) ); // Should not advance string reference
//
// Invalid substitution fields (which are treated as ordinary text)
//
QString input6 = "$abc";
QStringRef s6 = &input6;
SubstitutionField f6;
QCOMPARE( SubstitutionField::parse( s6, f6 ), false );
QCOMPARE( s6, QStringRef( &input6 ) ); // Should not advance string reference
QString input7 = "${abc";
QStringRef s7 = &input7;
SubstitutionField f7;
QCOMPARE( SubstitutionField::parse( s7, f7 ), false );
QCOMPARE( s7, QStringRef( &input7 ) ); // Should not advance string reference
QString input8 = "${abc:}";
QStringRef s8 = &input8;
SubstitutionField f8;
QCOMPARE( SubstitutionField::parse( s8, f8 ), false );
QCOMPARE( s8, QStringRef( &input8 ) ); // Should not advance string reference
// Even though format is invalid, let it slide. Overall structure still good. Format will be ignored.
QString input9 = "${abc:%3.2}";
QStringRef s9 = &input9;
SubstitutionField f9;
QCOMPARE( SubstitutionField::parse( s9, f9 ), true );
}
void TestSubstitutionField::construction()
{
using namespace glabels;
SubstitutionField f1( "${1234}" );
QCOMPARE( f1.fieldName(), QString( "1234" ) );
SubstitutionField f2( "${abc:=ABC}" );
QCOMPARE( f2.fieldName(), QString( "abc" ) );
QCOMPARE( f2.defaultValue(), QString( "ABC" ) );
SubstitutionField f3( "${x:%08.2f}" );
QCOMPARE( f3.fieldName(), QString( "x" ) );
QCOMPARE( f3.format(), QString( "%08.2f" ) );
QCOMPARE( f3.formatType(), QChar('f') );
SubstitutionField f4( "${y:%08.2f:=12.34}" );
QCOMPARE( f4.fieldName(), QString( "y" ) );
QCOMPARE( f4.defaultValue(), QString( "12.34" ) );
QCOMPARE( f4.format(), QString( "%08.2f" ) );
QCOMPARE( f4.formatType(), QChar('f') );
}
void TestSubstitutionField::simpleEvaluation()
{
using namespace glabels;
SubstitutionField f1( "${1}" );
SubstitutionField f2( "${2}" );
SubstitutionField f3( "${3}" );
SubstitutionField f4( "${4}" );
merge::Record record1;
record1[ "1" ] = "Abcdefg";
record1[ "2" ] = "Hijklmn";
record1[ "3" ] = "Opqrstu";
record1[ "4" ] = "Vwxyz!@";
QCOMPARE( f1.evaluate( &record1 ), QString( "Abcdefg" ) );
QCOMPARE( f2.evaluate( &record1 ), QString( "Hijklmn" ) );
QCOMPARE( f3.evaluate( &record1 ), QString( "Opqrstu" ) );
QCOMPARE( f4.evaluate( &record1 ), QString( "Vwxyz!@" ) );
merge::Record record2;
record2[ "1" ] = "1234567";
record2[ "2" ] = "FooBar";
record2[ "3" ] = "8901234";
record2[ "4" ] = "#$%^&*";
QCOMPARE( f1.evaluate( &record2 ), QString( "1234567" ) );
QCOMPARE( f2.evaluate( &record2 ), QString( "FooBar" ) );
QCOMPARE( f3.evaluate( &record2 ), QString( "8901234" ) );
QCOMPARE( f4.evaluate( &record2 ), QString( "#$%^&*" ) );
}
void TestSubstitutionField::defaultValueEvaluation()
{
using namespace glabels;
SubstitutionField f1( "${1:=foo1}" );
SubstitutionField f2( "${2:=foo2}" );
SubstitutionField f3( "${3:=foo3}" );
SubstitutionField f4( "${4:=foo4}" );
merge::Record record1;
record1[ "1" ] = "Abcdefg";
record1[ "2" ] = "Hijklmn";
record1[ "3" ] = "Opqrstu";
record1[ "4" ] = "Vwxyz!@";
QCOMPARE( f1.evaluate( &record1 ), QString( "Abcdefg" ) );
QCOMPARE( f2.evaluate( &record1 ), QString( "Hijklmn" ) );
QCOMPARE( f3.evaluate( &record1 ), QString( "Opqrstu" ) );
QCOMPARE( f4.evaluate( &record1 ), QString( "Vwxyz!@" ) );
merge::Record record2; // All fields empty
QCOMPARE( f1.evaluate( &record2 ), QString( "foo1" ) );
QCOMPARE( f2.evaluate( &record2 ), QString( "foo2" ) );
QCOMPARE( f3.evaluate( &record2 ), QString( "foo3" ) );
QCOMPARE( f4.evaluate( &record2 ), QString( "foo4" ) );
merge::Record record3;
record3[ "1" ] = "xyzzy";
// Field "2" empty
// Field "3" empty
record3[ "4" ] = "plugh";
QCOMPARE( f1.evaluate( &record3 ), QString( "xyzzy" ) );
QCOMPARE( f2.evaluate( &record3 ), QString( "foo2" ) );
QCOMPARE( f3.evaluate( &record3 ), QString( "foo3" ) );
QCOMPARE( f4.evaluate( &record3 ), QString( "plugh" ) );
}
void TestSubstitutionField::formattedStringEvaluation()
{
using namespace glabels;
SubstitutionField f1( "${1:%10s}" );
SubstitutionField f2( "${2:%10s}" );
SubstitutionField f3( "${3:%10s}" );
SubstitutionField f4( "${4:%10s}" );
SubstitutionField f5( "${5:%-10s}" );
SubstitutionField f6( "${6:%-10s}" );
SubstitutionField f7( "${7:%-10s}" );
SubstitutionField f8( "${8:%-10s}" );
merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "0";
record1[ "6" ] = "100";
record1[ "7" ] = "-100";
record1[ "8" ] = "3.14";
QCOMPARE( f1.evaluate( &record1 ), QString( " 0" ) );
QCOMPARE( f2.evaluate( &record1 ), QString( " 1" ) );
QCOMPARE( f3.evaluate( &record1 ), QString( " -1" ) );
QCOMPARE( f4.evaluate( &record1 ), QString( " 3.14" ) );
QCOMPARE( f5.evaluate( &record1 ), QString( "0 " ) );
QCOMPARE( f6.evaluate( &record1 ), QString( "100 " ) );
QCOMPARE( f7.evaluate( &record1 ), QString( "-100 " ) );
QCOMPARE( f8.evaluate( &record1 ), QString( "3.14 " ) );
}
void TestSubstitutionField::formattedFloatEvaluation()
{
using namespace glabels;
SubstitutionField f1( "${1:%+5.2f}" );
SubstitutionField f2( "${2:%+5.2f}" );
SubstitutionField f3( "${3:%+5.2f}" );
SubstitutionField f4( "${4:%+5.2f}" );
SubstitutionField f5( "${5:%+5.2e}" );
SubstitutionField f6( "${6:%+5.2e}" );
SubstitutionField f7( "${7:%+5.2e}" );
SubstitutionField f8( "${8:%+5.2e}" );
merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "0";
record1[ "6" ] = "100";
record1[ "7" ] = "-100";
record1[ "8" ] = "3.14";
QCOMPARE( f1.evaluate( &record1 ), QString( "+0.00" ) );
QCOMPARE( f2.evaluate( &record1 ), QString( "+1.00" ) );
QCOMPARE( f3.evaluate( &record1 ), QString( "-1.00" ) );
QCOMPARE( f4.evaluate( &record1 ), QString( "+3.14" ) );
QCOMPARE( f5.evaluate( &record1 ), QString( "+0.00e+00" ) );
QCOMPARE( f6.evaluate( &record1 ), QString( "+1.00e+02" ) );
QCOMPARE( f7.evaluate( &record1 ), QString( "-1.00e+02" ) );
QCOMPARE( f8.evaluate( &record1 ), QString( "+3.14e+00" ) );
}
void TestSubstitutionField::formattedIntEvaluation()
{
using namespace glabels;
SubstitutionField f1( "${1:%08d}" );
SubstitutionField f2( "${2:%08d}" );
SubstitutionField f3( "${3:%08d}" );
SubstitutionField f4( "${4:%08d}" );
SubstitutionField f5( "${5:%08x}" );
SubstitutionField f6( "${6:%08x}" );
SubstitutionField f7( "${7:%08x}" );
SubstitutionField f8( "${8:%08x}" );
merge::Record record1;
record1[ "1" ] = "0";
record1[ "2" ] = "1";
record1[ "3" ] = "-1";
record1[ "4" ] = "3.14";
record1[ "5" ] = "100";
record1[ "6" ] = "0x100";
record1[ "7" ] = "-1";
record1[ "8" ] = "314";
QCOMPARE( f1.evaluate( &record1 ), QString( "00000000" ) );
QCOMPARE( f2.evaluate( &record1 ), QString( "00000001" ) );
QCOMPARE( f3.evaluate( &record1 ), QString( "-0000001" ) );
QCOMPARE( f4.evaluate( &record1 ), QString( "00000000" ) ); // Invalid integer value
QCOMPARE( f5.evaluate( &record1 ), QString( "00000064" ) ); // 100(decimal) == 64(hex)
QCOMPARE( f6.evaluate( &record1 ), QString( "00000100" ) );
QCOMPARE( f7.evaluate( &record1 ), QString( "00000000" ) ); // Invalid unsigned integer
QCOMPARE( f8.evaluate( &record1 ), QString( "0000013a" ) ); // 314(decimal) == 13a(hex)
}
void TestSubstitutionField::newLineEvaluation()
{
using namespace glabels;
SubstitutionField addr2( "${ADDR2:n}" );
QCOMPARE( addr2.fieldName(), QString( "ADDR2" ) );
QCOMPARE( addr2.newLine(), true );
merge::Record record1;
record1[ "ADDR2" ] = "Apt. 5B";
merge::Record record2;
record2[ "ADDR2" ] = ""; // ADDR2 Empty
merge::Record record3;
// ADDR2 not defined
QCOMPARE( addr2.evaluate( &record1 ), QString( "\nApt. 5B" ) ); // Prepends a newline
QCOMPARE( addr2.evaluate( &record2 ), QString( "" ) ); // Evaluates empty
QCOMPARE( addr2.evaluate( &record3 ), QString( "" ) ); // Evaluates empty
}
@@ -26,12 +26,15 @@ class TestSubstitutionField : public QObject
Q_OBJECT Q_OBJECT
private slots: private slots:
void parseValid();
void parseInvalid();
void construction(); void construction();
void simpleEvaluation(); void simpleEvaluation();
void defaultValueEvaluation(); void defaultValueEvaluation();
void formattedStringEvaluation(); void formattedStringEvaluation();
void formattedFloatEvaluation(); void formattedFloatEvaluation();
void formattedIntEvaluation(); void formattedIntEvaluation();
void newLineEvaluation();
}; };