From 8bec3594ecd55220252cc4f13209cace0da8522e Mon Sep 17 00:00:00 2001 From: Jim Evins Date: Wed, 22 Nov 2017 13:33:30 -0500 Subject: [PATCH] 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 --- .gitignore | 1 + docs/SUBSTITUTION-FIELD-SPEC.md | 49 ++- glabels/CMakeLists.txt | 3 + glabels/Merge/CMakeLists.txt | 2 - glabels/Merge/SubstitutionField.cpp | 291 --------------- glabels/Merge/SubstitutionField.h | 77 ---- .../unit_tests/TestSubstitutionField.cpp | 222 ------------ glabels/RawText.cpp | 69 +++- glabels/RawText.h | 17 +- glabels/SubstitutionField.cpp | 331 +++++++++++++++++ glabels/SubstitutionField.h | 77 ++++ glabels/{Merge => }/unit_tests/CMakeLists.txt | 2 +- glabels/unit_tests/TestSubstitutionField.cpp | 342 ++++++++++++++++++ .../unit_tests/TestSubstitutionField.h | 3 + 14 files changed, 864 insertions(+), 622 deletions(-) delete mode 100644 glabels/Merge/SubstitutionField.cpp delete mode 100644 glabels/Merge/SubstitutionField.h delete mode 100644 glabels/Merge/unit_tests/TestSubstitutionField.cpp create mode 100644 glabels/SubstitutionField.cpp create mode 100644 glabels/SubstitutionField.h rename glabels/{Merge => }/unit_tests/CMakeLists.txt (88%) create mode 100644 glabels/unit_tests/TestSubstitutionField.cpp rename glabels/{Merge => }/unit_tests/TestSubstitutionField.h (93%) diff --git a/.gitignore b/.gitignore index c526c9a..cfadfca 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ core *.safe *.sav* .directory +TEST-DATA diff --git a/docs/SUBSTITUTION-FIELD-SPEC.md b/docs/SUBSTITUTION-FIELD-SPEC.md index 7e65d39..417a88d 100644 --- a/docs/SUBSTITUTION-FIELD-SPEC.md +++ b/docs/SUBSTITUTION-FIELD-SPEC.md @@ -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. -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 ------ The general syntax of a substitution field is ```ebnf -substitution-field = "${", field-name, [ ":" modifiers ], "}" ; -modifiers = modifier, [ ":", modifiers ] ; -modifier = format-modifier | default-value-modifier ; +substitution-field = "${" field-name [ ":" modifiers ] "}" ; +modifiers = modifier [ ":" modifiers ] ; +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 --------- ### 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 ```ebnf -format-modifier = "%", [ flags ], [ width ], [ ".", precision ], type ; +format-modifier = "%" [ flags ] [ width ] [ ".", precision ] type ; ``` #### Flags @@ -57,7 +57,7 @@ Character | Description `s` | string value. ### 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 default-value-modifier = "=" value ; @@ -71,15 +71,38 @@ Escape sequence | actual character `\}` | right bracket `}` `\\` | 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 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 ------------------ -### LABEL_NUMBER -### PAGE_NUMBER -### DATE -### TIME -### FILE_NAME +Potentially, merge fields may also refer to built-in variables. Candidates include: + - LABEL_NUMBER + - PAGE_NUMBER + - DATE + - TIME + - FILE_NAME diff --git a/glabels/CMakeLists.txt b/glabels/CMakeLists.txt index 938f359..27f08b5 100644 --- a/glabels/CMakeLists.txt +++ b/glabels/CMakeLists.txt @@ -112,6 +112,7 @@ set (glabels_sources Size.cpp StartupView.cpp StrUtil.cpp + SubstitutionField.cpp Template.cpp TemplatePicker.cpp TemplatePickerItem.cpp @@ -256,6 +257,7 @@ set (glabels-batch_sources Settings.cpp Size.cpp StrUtil.cpp + SubstitutionField.cpp Template.cpp TextNode.cpp Units.cpp @@ -336,6 +338,7 @@ link_directories ( #======================================= add_subdirectory (BarcodeBackends) add_subdirectory (Merge) +add_subdirectory (unit_tests) #======================================= diff --git a/glabels/Merge/CMakeLists.txt b/glabels/Merge/CMakeLists.txt index 93dee75..c06693a 100644 --- a/glabels/Merge/CMakeLists.txt +++ b/glabels/Merge/CMakeLists.txt @@ -15,7 +15,6 @@ set (merge_sources TextColonKeys.cpp TextSemicolon.cpp TextSemicolonKeys.cpp - SubstitutionField.cpp ) set (merge_qobject_headers @@ -44,7 +43,6 @@ link_directories ( #======================================= # Subdirectories #======================================= -add_subdirectory (unit_tests) #======================================= diff --git a/glabels/Merge/SubstitutionField.cpp b/glabels/Merge/SubstitutionField.cpp deleted file mode 100644 index ebbc4f2..0000000 --- a/glabels/Merge/SubstitutionField.cpp +++ /dev/null @@ -1,291 +0,0 @@ -/* SubstitutionField.cpp - * - * Copyright (C) 2017 Jim Evins - * - * 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 . - */ - -#include "SubstitutionField.h" - -#include - - -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 diff --git a/glabels/Merge/SubstitutionField.h b/glabels/Merge/SubstitutionField.h deleted file mode 100644 index 8836b54..0000000 --- a/glabels/Merge/SubstitutionField.h +++ /dev/null @@ -1,77 +0,0 @@ -/* SubstitutionField.h - * - * Copyright (C) 2017 Jim Evins - * - * 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 . - */ - -#ifndef glabels_SubstitutionField_h -#define glabels_SubstitutionField_h - - -#include "Record.h" - -#include -#include - - -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 diff --git a/glabels/Merge/unit_tests/TestSubstitutionField.cpp b/glabels/Merge/unit_tests/TestSubstitutionField.cpp deleted file mode 100644 index 9c4c6a6..0000000 --- a/glabels/Merge/unit_tests/TestSubstitutionField.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* TestSubstitutionField.cpp - * - * Copyright (C) 2017 Jim Evins - * - * 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 . - */ - -#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) -} diff --git a/glabels/RawText.cpp b/glabels/RawText.cpp index e93fd68..5e31371 100644 --- a/glabels/RawText.cpp +++ b/glabels/RawText.cpp @@ -30,6 +30,7 @@ namespace glabels /// RawText::RawText( const QString& string ) : mString(string) { + tokenize(); } @@ -38,6 +39,7 @@ namespace glabels /// RawText::RawText( const char* cString ) : mString(QString(cString)) { + tokenize(); } @@ -64,24 +66,17 @@ namespace glabels /// 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. - // 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' ); - } - - // Nominal case: simple replacement - text.replace( "${"+key+"}", record->value(key) ); + text += token.field.evaluate( record ); + } + else + { + text += token.text; } } @@ -107,4 +102,48 @@ namespace glabels 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 diff --git a/glabels/RawText.h b/glabels/RawText.h index e0b2f68..baac1ec 100644 --- a/glabels/RawText.h +++ b/glabels/RawText.h @@ -22,7 +22,7 @@ #define RawText_h -#include "Merge/Record.h" +#include "SubstitutionField.h" #include @@ -55,12 +55,27 @@ namespace glabels bool isEmpty() const; + ///////////////////////////////// + // Private Methods + ///////////////////////////////// + private: + void tokenize(); + ///////////////////////////////// // Private Data ///////////////////////////////// private: QString mString; + struct Token + { + bool isField; + QString text; + SubstitutionField field; + }; + + QList mTokens; + }; } diff --git a/glabels/SubstitutionField.cpp b/glabels/SubstitutionField.cpp new file mode 100644 index 0000000..5ccd03f --- /dev/null +++ b/glabels/SubstitutionField.cpp @@ -0,0 +1,331 @@ +/* SubstitutionField.cpp + * + * Copyright (C) 2017 Jim Evins + * + * 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 . + */ + +#include "SubstitutionField.h" + +#include + + +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 diff --git a/glabels/SubstitutionField.h b/glabels/SubstitutionField.h new file mode 100644 index 0000000..dbd49e4 --- /dev/null +++ b/glabels/SubstitutionField.h @@ -0,0 +1,77 @@ +/* SubstitutionField.h + * + * Copyright (C) 2017 Jim Evins + * + * 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 . + */ + +#ifndef glabels_SubstitutionField_h +#define glabels_SubstitutionField_h + + +#include "Merge/Record.h" + +#include +#include + + +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 diff --git a/glabels/Merge/unit_tests/CMakeLists.txt b/glabels/unit_tests/CMakeLists.txt similarity index 88% rename from glabels/Merge/unit_tests/CMakeLists.txt rename to glabels/unit_tests/CMakeLists.txt index 07066d2..e3934cd 100644 --- a/glabels/Merge/unit_tests/CMakeLists.txt +++ b/glabels/unit_tests/CMakeLists.txt @@ -6,7 +6,7 @@ if (Qt5Test_FOUND) # Test SubstitutionField class #======================================= 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} ) add_test (NAME SubstitutionField COMMAND TestSubstitutionField) diff --git a/glabels/unit_tests/TestSubstitutionField.cpp b/glabels/unit_tests/TestSubstitutionField.cpp new file mode 100644 index 0000000..a6706d5 --- /dev/null +++ b/glabels/unit_tests/TestSubstitutionField.cpp @@ -0,0 +1,342 @@ +/* TestSubstitutionField.cpp + * + * Copyright (C) 2017 Jim Evins + * + * 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 . + */ + +#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 +} diff --git a/glabels/Merge/unit_tests/TestSubstitutionField.h b/glabels/unit_tests/TestSubstitutionField.h similarity index 93% rename from glabels/Merge/unit_tests/TestSubstitutionField.h rename to glabels/unit_tests/TestSubstitutionField.h index 735d402..a8aaf66 100644 --- a/glabels/Merge/unit_tests/TestSubstitutionField.h +++ b/glabels/unit_tests/TestSubstitutionField.h @@ -26,12 +26,15 @@ class TestSubstitutionField : public QObject Q_OBJECT private slots: + void parseValid(); + void parseInvalid(); void construction(); void simpleEvaluation(); void defaultValueEvaluation(); void formattedStringEvaluation(); void formattedFloatEvaluation(); void formattedIntEvaluation(); + void newLineEvaluation(); };