From 88e32d9738f6db35de73a7b194a65fb1947b6d03 Mon Sep 17 00:00:00 2001 From: Jim Evins Date: Sat, 7 May 2016 16:17:29 -0400 Subject: [PATCH] More fleshing out of Merge framework and backends. --- glabels/CMakeLists.txt | 3 +- glabels/MergeFactory.cpp | 84 +++++ glabels/MergeFactory.h | 72 +++++ glabels/MergeNone.cpp | 9 + glabels/MergeNone.h | 7 + glabels/MergeRecord.cpp | 31 +- glabels/MergeRecord.h | 19 +- glabels/MergeText.cpp | 311 ++++++++++++++++++- glabels/MergeText.h | 13 +- glabels/{MergeField.cpp => MergeTextCsv.cpp} | 40 +-- glabels/{MergeField.h => MergeTextCsv.h} | 62 ++-- 11 files changed, 539 insertions(+), 112 deletions(-) create mode 100644 glabels/MergeFactory.cpp create mode 100644 glabels/MergeFactory.h rename glabels/{MergeField.cpp => MergeTextCsv.cpp} (58%) rename glabels/{MergeField.h => MergeTextCsv.h} (64%) diff --git a/glabels/CMakeLists.txt b/glabels/CMakeLists.txt index 810f839..723371d 100644 --- a/glabels/CMakeLists.txt +++ b/glabels/CMakeLists.txt @@ -33,11 +33,12 @@ set (glabels_sources LabelRegion.cpp MainWindow.cpp Merge.cpp - MergeField.cpp + MergeFactory.cpp MergeView.cpp MergeRecord.cpp MergeNone.cpp MergeText.cpp + MergeTextCsv.cpp ObjectEditor.cpp Outline.cpp PageRenderer.cpp diff --git a/glabels/MergeFactory.cpp b/glabels/MergeFactory.cpp new file mode 100644 index 0000000..030e55b --- /dev/null +++ b/glabels/MergeFactory.cpp @@ -0,0 +1,84 @@ +/* MergeFactory.cpp + * + * Copyright (C) 2016 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 "MergeFactory.h" + +#include "MergeNone.h" +#include "MergeTextCsv.h" + + +/// +/// Static data +/// +QMap MergeFactory::mBackendMap; + + +/// +/// Constructor +/// +MergeFactory::MergeFactory() +{ + registerBackend( "None", &MergeNone::create ); + registerBackend( "Text/CSV", &MergeTextCsv::create ); +} + + +/// +/// Initialize +/// +void MergeFactory::init() +{ + static MergeFactory* singletonInstance = 0; + if ( !singletonInstance ) + { + singletonInstance = new MergeFactory(); + } +} + + +/// +/// Create Merge object +/// +Merge* MergeFactory::createMerge( const QString& id ) +{ + QMap::iterator iBackend = mBackendMap.find( id ); + if ( iBackend != mBackendMap.end() ) + { + return iBackend->create(); + } + + return MergeNone::create(); +} + + +/// +/// Register backend +/// +void MergeFactory::registerBackend( const QString& id, CreateFct create ) +{ + BackendEntry backend; + + backend.id = id; + backend.create = create; + + mBackendMap[ id ] = backend; +} + + diff --git a/glabels/MergeFactory.h b/glabels/MergeFactory.h new file mode 100644 index 0000000..0837d0a --- /dev/null +++ b/glabels/MergeFactory.h @@ -0,0 +1,72 @@ +/* MergeFactory.h + * + * Copyright (C) 2016 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 MergeFactory_h +#define MergeFactory_h + +#include "Merge.h" + + +/// +/// MergeFactory +/// +struct MergeFactory +{ + + ///////////////////////////////// + // Life Cycle + ///////////////////////////////// +protected: + MergeFactory(); + + + ///////////////////////////////// + // Static methods + ///////////////////////////////// +public: + static void init(); + static Merge* createMerge( const QString& id ); + + + ///////////////////////////////// + // private methods + ///////////////////////////////// +private: + typedef Merge* (*CreateFct)(); + + static void registerBackend( const QString& id, CreateFct create ); + + + ///////////////////////////////// + // private data + ///////////////////////////////// + class BackendEntry + { + public: + QString id; + CreateFct create; + }; + + static QMap mBackendMap; + +}; + + +#endif // MergeFactory_h diff --git a/glabels/MergeNone.cpp b/glabels/MergeNone.cpp index f67e9e6..76938d3 100644 --- a/glabels/MergeNone.cpp +++ b/glabels/MergeNone.cpp @@ -54,6 +54,15 @@ MergeNone* MergeNone::clone() const } +/// +/// Create +/// +Merge* MergeNone::create() +{ + return new MergeNone(); +} + + /// /// Get key list /// diff --git a/glabels/MergeNone.h b/glabels/MergeNone.h index 4ae4cb2..d568eeb 100644 --- a/glabels/MergeNone.h +++ b/glabels/MergeNone.h @@ -45,6 +45,13 @@ protected: MergeNone* clone() const; + ///////////////////////////////// + // Static methods + ///////////////////////////////// +public: + static Merge* create(); + + ///////////////////////////////// // Implementation of virtual methods ///////////////////////////////// diff --git a/glabels/MergeRecord.cpp b/glabels/MergeRecord.cpp index 9276fa4..6fcb1d1 100644 --- a/glabels/MergeRecord.cpp +++ b/glabels/MergeRecord.cpp @@ -1,6 +1,6 @@ /* MergeRecord.cpp * - * Copyright (C) 2013 Jim Evins + * Copyright (C) 2013-2016 Jim Evins * * This file is part of gLabels-qt. * @@ -33,7 +33,7 @@ MergeRecord::MergeRecord() : mSelected( false ) /// Constructor /// MergeRecord::MergeRecord( const MergeRecord* record ) - : mSelected(record->mSelected), mFieldList(record->mFieldList) + : QMap(*record), mSelected(record->mSelected) { } @@ -63,30 +63,3 @@ void MergeRecord::setSelected( bool value ) { mSelected = value; } - - -/// -/// Is record empty? -/// -bool MergeRecord::isEmpty() const -{ - return mFieldList.size() == 0; -} - - -/// -/// Get field list -/// -const QList& MergeRecord::fieldList() const -{ - return mFieldList; -} - - -/// -/// Set field list -/// -void MergeRecord::setFieldList( QList& value ) -{ - mFieldList = value; -} diff --git a/glabels/MergeRecord.h b/glabels/MergeRecord.h index c514f15..5323476 100644 --- a/glabels/MergeRecord.h +++ b/glabels/MergeRecord.h @@ -1,6 +1,6 @@ /* MergeRecord.h * - * Copyright (C) 2013 Jim Evins + * Copyright (C) 2013-2016 Jim Evins * * This file is part of gLabels-qt. * @@ -22,16 +22,15 @@ #define MergeRecord_h #include -#include - -#include "MergeField.h" +#include /// -/// Merge Record Structure +/// Merge Record /// -struct MergeRecord +struct MergeRecord : public QMap { + ///////////////////////////////// // Life Cycle ///////////////////////////////// @@ -52,18 +51,14 @@ public: public: bool isSelected() const; void setSelected( bool value ); - bool isEmpty() const; - - const QList& fieldList() const; - void setFieldList( QList& value ); ///////////////////////////////// // Private data ///////////////////////////////// private: - bool mSelected; - QList mFieldList; + bool mSelected; + }; diff --git a/glabels/MergeText.cpp b/glabels/MergeText.cpp index 7cbcfd8..b20a47e 100644 --- a/glabels/MergeText.cpp +++ b/glabels/MergeText.cpp @@ -1,6 +1,6 @@ /* MergeText.cpp * - * Copyright (C) 2015 Jim Evins + * Copyright (C) 2016 Jim Evins * * This file is part of gLabels-qt. * @@ -24,7 +24,7 @@ /// /// Constructor /// -MergeText::MergeText( QChar delimiter, bool line1HasKeys ) : Merge( Merge::FILE ) +MergeText::MergeText( QChar delimiter, bool line1HasKeys ) : Merge( Merge::FILE ), mNFieldsMax(0) { } @@ -51,8 +51,12 @@ MergeText::~MergeText() /// QList MergeText::keyList() const { - QList emptyList; - return emptyList; + QList keys; + for ( int iField = 0; iField < mNFieldsMax; iField++ ) + { + keys << keyFromIndex(iField); + } + return keys; } @@ -61,7 +65,7 @@ QList MergeText::keyList() const /// QString MergeText::primaryKey() const { - return ""; + keyFromIndex(0); } @@ -73,9 +77,15 @@ void MergeText::open() mFile.setFileName( source() ); mFile.open( QIODevice::ReadOnly|QIODevice::Text ); + mKeys.clear(); + if ( mLine1HasKeys && mFile.isOpen() ) { - // Todo parse line #1, create key list from string list + mKeys = parseLine(); + if ( (mKeys.size() == 1) && (mKeys[0] == "") ) + { + mKeys.clear(); + } } } @@ -97,5 +107,294 @@ void MergeText::close() /// MergeRecord* MergeText::readNextRecord() { + QList values = parseLine(); + if ( !values.isEmpty() ) + { + MergeRecord* record = new MergeRecord(); + + int iField = 0; + foreach ( QString value, values ) + { + (*record)[ keyFromIndex(iField) ] = value; + iField++; + } + + return record; + } return 0; } + + +/// +/// Key from field index +/// +QString MergeText::keyFromIndex( int iField ) const +{ + if ( mLine1HasKeys && ( iField < mKeys.size() ) ) + { + return mKeys[iField]; + } + else + { + return QString::number( iField+1 ); + } +} + + +/// +/// Parse line. +/// +/// Attempt to be a robust parser of various CSV (and similar) formats. +/// +/// Based on CSV format described in RFC 4180 section 2. +/// +/// Additions to RFC 4180 rules: +/// - delimeters and other special characters may be "escaped" by a leading +/// backslash (\) +/// - C escape sequences for newline (\n) and tab (\t) are also translated. +/// - if quoted text is not followed by a delimeter, any additional text is +/// concatenated with quoted portion. +/// +/// Returns a list of fields. A blank line is considered a line with one +/// empty field. Returns an empty list when done. +/// +QList MergeText::parseLine() +{ + QList fields; + + enum State + { + DELIM, QUOTED, QUOTED_QUOTE1, QUOTED_ESCAPED, SIMPLE, SIMPLE_ESCAPED, DONE + } state = DELIM; + + QByteArray field; + + while ( state != DONE ) + { + char c; + if ( mFile.getChar( &c ) ) + { + switch (state) + { + + case DELIM: + switch (c) + { + case '\n': + /* last field is empty. */ + fields << ""; + state = DONE; + break; + case '\r': + /* ignore */ + state = DELIM; + break; + case '"': + /* start a quoted field. */ + state = QUOTED; + break; + case '\\': + /* simple field, but 1st character is an escape. */ + state = SIMPLE_ESCAPED; + break; + default: + if ( c == mDelimeter ) + { + /* field is empty. */ + fields << ""; + state = DELIM; + } + else + { + /* begining of a simple field. */ + field.append( c ); + state = SIMPLE; + } + break; + } + break; + + case QUOTED: + switch (c) + { + case '"': + /* Possible end of field, but could be 1st of a pair. */ + state = QUOTED_QUOTE1; + break; + case '\\': + /* Escape next character, or special escape, e.g. \n. */ + state = QUOTED_ESCAPED; + break; + default: + /* Use character literally. */ + field.append( c ); + break; + } + break; + + case QUOTED_QUOTE1: + switch (c) + { + case '\n': + /* line ended after quoted item */ + fields << QString( field ); + state = DONE; + break; + case '"': + /* second quote, insert and stay quoted. */ + field.append( c ); + state = QUOTED; + break; + case '\r': + /* ignore and go to fallback */ + state = SIMPLE; + break; + default: + if ( c == mDelimeter ) + { + /* end of field. */ + fields << QString( field ); + field.clear(); + state = DELIM; + } + else + { + /* fallback if not a delim or another quote. */ + field.append( c ); + state = SIMPLE; + } + break; + } + break; + + case QUOTED_ESCAPED: + switch (c) + { + case 'n': + /* Decode "\n" as newline. */ + field.append( '\n' ); + state = QUOTED; + break; + case 't': + /* Decode "\t" as tab. */ + field.append( '\t' ); + state = QUOTED; + break; + default: + /* Use character literally. */ + field.append( c ); + state = QUOTED; + break; + } + break; + + case SIMPLE: + switch (c) + { + case '\n': + /* line ended */ + fields << QString( field ); + state = DONE; + break; + case '\r': + /* ignore */ + state = SIMPLE; + break; + case '\\': + /* Escape next character, or special escape, e.g. \n. */ + state = SIMPLE_ESCAPED; + break; + default: + if ( c == mDelimeter ) + { + /* end of field. */ + fields << QString( field ); + field.clear(); + state = DELIM; + } + else + { + /* Use character literally. */ + field.append( c ); + state = SIMPLE; + } + break; + } + break; + + case SIMPLE_ESCAPED: + switch (c) + { + case 'n': + /* Decode "\n" as newline. */ + field.append( '\n' ); + state = SIMPLE; + break; + case 't': + /* Decode "\t" as tab. */ + field.append( '\t' ); + state = SIMPLE; + break; + default: + /* Use character literally. */ + field.append( (char)c ); + state = SIMPLE; + break; + } + break; + + default: + qWarning( "MergeText::parseLine()::Should not be reached! #1" ); + break; + } + + } + else + { + /* Handle EOF (could also be an error while reading). */ + switch (state) + { + + case DELIM: + /* EOF, no more lines. */ + break; + + case QUOTED: + /* File ended midway through quoted item. Truncate field. */ + fields << QString( field ); + break; + + case QUOTED_QUOTE1: + /* File ended after quoted item. */ + fields << QString( field ); + break; + + case QUOTED_ESCAPED: + /* File ended midway through quoted item. Truncate field. */ + fields << QString( field ); + break; + + case SIMPLE: + /* File ended after simple item. */ + fields << QString( field ); + break; + + case SIMPLE_ESCAPED: + /* File ended midway through escaped item. */ + fields << QString( field ); + break; + + default: + qWarning( "MergeText::parseLine()::Should not be reached! #2" ); + break; + } + + state = DONE; + } + } + + + return fields; +} + + diff --git a/glabels/MergeText.h b/glabels/MergeText.h index 5d2e406..35f2311 100644 --- a/glabels/MergeText.h +++ b/glabels/MergeText.h @@ -1,6 +1,6 @@ /* MergeText.h * - * Copyright (C) 2015 Jim Evins + * Copyright (C) 2016 Jim Evins * * This file is part of gLabels-qt. * @@ -53,6 +53,13 @@ protected: MergeRecord* readNextRecord(); + ///////////////////////////////// + // Private methods + ///////////////////////////////// + QString keyFromIndex( int iField ) const; + QList parseLine(); + + ///////////////////////////////// // Private data ///////////////////////////////// @@ -60,7 +67,9 @@ private: QChar mDelimeter; bool mLine1HasKeys; - QFile mFile; + QFile mFile; + QList mKeys; + int mNFieldsMax; }; diff --git a/glabels/MergeField.cpp b/glabels/MergeTextCsv.cpp similarity index 58% rename from glabels/MergeField.cpp rename to glabels/MergeTextCsv.cpp index a5409d6..0b512eb 100644 --- a/glabels/MergeField.cpp +++ b/glabels/MergeTextCsv.cpp @@ -1,6 +1,6 @@ -/* MergeField.cpp +/* MergeTextCsv.cpp * - * Copyright (C) 2013 Jim Evins + * Copyright (C) 2016 Jim Evins * * This file is part of gLabels-qt. * @@ -18,13 +18,13 @@ * along with gLabels-qt. If not, see . */ -#include "MergeField.h" +#include "MergeTextCsv.h" /// -/// Default constructor +/// Constructor /// -MergeField::MergeField() +MergeTextCsv::MergeTextCsv() : MergeText(',',false) { } @@ -32,44 +32,32 @@ MergeField::MergeField() /// /// Constructor /// -MergeField::MergeField( const QString& key, const QString& value ) +MergeTextCsv::MergeTextCsv( const MergeTextCsv* merge ) : MergeText( merge ) { - mKey = key; - mValue = value; } /// -/// Get key +/// Destructor /// -const QString MergeField::key( void ) const +MergeTextCsv::~MergeTextCsv() { - return mKey; } /// -/// Set key +/// Clone /// -void MergeField::setKey( const QString& value ) +MergeTextCsv* MergeTextCsv::clone() const { - mKey = value; + return new MergeTextCsv( this ); } /// -/// Get value +/// Create /// -const QString MergeField::value( void ) const +Merge* MergeTextCsv::create() { - return mValue; -} - - -/// -/// Set value -/// -void MergeField::setValue( const QString& value ) -{ - mValue = value; + return new MergeTextCsv(); } diff --git a/glabels/MergeField.h b/glabels/MergeTextCsv.h similarity index 64% rename from glabels/MergeField.h rename to glabels/MergeTextCsv.h index 34fc648..1f63933 100644 --- a/glabels/MergeField.h +++ b/glabels/MergeTextCsv.h @@ -1,6 +1,6 @@ -/* MergeField.h +/* MergeTextCsv.h * - * Copyright (C) 2013 Jim Evins + * Copyright (C) 2016 Jim Evins * * This file is part of gLabels-qt. * @@ -18,51 +18,41 @@ * along with gLabels-qt. If not, see . */ -#ifndef MergeField_h -#define MergeField_h +#ifndef MergeTextCsv_h +#define MergeTextCsv_h -#include +#include "MergeText.h" /// -/// Merge Field Structure +/// MergeTextCsv Backend /// -struct MergeField +struct MergeTextCsv : public MergeText { + ///////////////////////////////// // Life Cycle ///////////////////////////////// -public: - MergeField(); - MergeField( const QString& key, const QString& value ); - - - ///////////////////////////////// - // Properties - ///////////////////////////////// -public: - // - // Key Property - // - const QString key( void ) const; - void setKey( const QString& value ); - - - // - // Value Property - // - const QString value( void ) const; - void setValue( const QString& value ); - - - ///////////////////////////////// - // Private data - ///////////////////////////////// private: - QString mKey; - QString mValue; + MergeTextCsv(); + MergeTextCsv( const MergeTextCsv* merge ); + virtual ~MergeTextCsv(); + + + ///////////////////////////////// + // Object duplication + ///////////////////////////////// +public: + MergeTextCsv* clone() const; + + + ///////////////////////////////// + // Static methods + ///////////////////////////////// +public: + static Merge* create(); }; -#endif // MergeField_h +#endif // MergeTextCsv_h