diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 132524acf..db590e379 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -2,6 +2,7 @@ ADD_SUBDIRECTORY(audio_file_processor) ADD_SUBDIRECTORY(bass_booster) ADD_SUBDIRECTORY(bit_invader) ADD_SUBDIRECTORY(flp_import) +ADD_SUBDIRECTORY(HydrogenImport) ADD_SUBDIRECTORY(kicker) ADD_SUBDIRECTORY(ladspa_browser) ADD_SUBDIRECTORY(ladspa_effect) diff --git a/plugins/HydrogenImport/CMakeLists.txt b/plugins/HydrogenImport/CMakeLists.txt new file mode 100644 index 000000000..27e8d6a4d --- /dev/null +++ b/plugins/HydrogenImport/CMakeLists.txt @@ -0,0 +1,4 @@ +INCLUDE(BuildPlugin) + +BUILD_PLUGIN(hydrogenimport HydrogenImport.cpp HydrogenImport.h local_file_mgr.cpp LocalFileMng.h) + diff --git a/plugins/HydrogenImport/HydrogenImport.cpp b/plugins/HydrogenImport/HydrogenImport.cpp new file mode 100644 index 000000000..6390e4834 --- /dev/null +++ b/plugins/HydrogenImport/HydrogenImport.cpp @@ -0,0 +1,404 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "LocalFileMng.h" +#include "HydrogenImport.h" +#include "song.h" +#include "engine.h" +#include "Instrument.h" +#include "InstrumentTrack.h" +#include "note.h" +#include "pattern.h" +#include "track.h" +#include "bb_track.h" +#include "bb_track_container.h" +#include "Instrument.h" + +#define MAX_LAYERS 4 +extern "C" +{ + +Plugin::Descriptor PLUGIN_EXPORT hydrogenimport_plugin_descriptor = +{ + STRINGIFY( PLUGIN_NAME ), + "Hydrogen Import", + QT_TRANSLATE_NOOP( "pluginBrowser", + "Filter for importing Hydrogen files into LMMS" ), + "frank mather", + 0x0100, + Plugin::ImportFilter, + NULL, + NULL, + NULL +} ; + +} + +QString filename; +class NoteKey +{ +public: + enum Key { + C = 0, + Cs, + D, + Ef, + E, + F, + Fs, + G, + Af, + A, + Bf, + B, + }; + + static int stringToNoteKey( const QString& str ) + { + int m_key; + + + QString sKey = str.left( str.length() - 1 ); + QString sOct = str.mid( str.length() - 1, str.length() ); + + if ( sKey.endsWith( "-" ) ) + { + sKey.replace( "-", "" ); + sOct.insert( 0, "-" ); + } + int nOctave = sOct.toInt(); + + if ( sKey == "C" ) + { + m_key = NoteKey::C; + } + else if ( sKey == "Cs" ) + { + m_key = NoteKey::Cs; + } + else if ( sKey == "D" ) + { + m_key = NoteKey::D; + } + else if ( sKey == "Ef" ) + { + m_key = NoteKey::Ef; + } + else if ( sKey == "E" ) + { + m_key = NoteKey::E; + } + else if ( sKey == "F" ) + { + m_key = NoteKey::F; + } + else if ( sKey == "Fs" ) + { + m_key = NoteKey::Fs; + } + else if ( sKey == "G" ) + { + m_key = NoteKey::G; + } + else if ( sKey == "Af" ) + { + m_key = NoteKey::Af; + } + else if ( sKey == "A" ) + { + m_key = NoteKey::A; + } + else if ( sKey == "Bf" ) + { + m_key = NoteKey::Bf; + } + else if ( sKey == "B" ) { + m_key = NoteKey::B; + } + return m_key + (nOctave*12)+57; + } + +}; +HydrogenImport::HydrogenImport( const QString & _file ) : + ImportFilter( _file, &hydrogenimport_plugin_descriptor ) +{ + filename = _file; +} + + + + +HydrogenImport::~HydrogenImport() +{ +} +Instrument * ins; +bool HydrogenImport::readSong() +{ + QHash drum_track; + QHash pattern_length; + QHash pattern_id; + + song *s = engine::getSong(); + int song_num_tracks = s->tracks().size(); + if ( QFile( filename ).exists() == false ) + { + printf( "Song file not found \n" ); + return false; + } + QDomDocument doc = LocalFileMng::openXmlDocument( filename ); + QDomNodeList nodeList = doc.elementsByTagName( "song" ); + + if( nodeList.isEmpty() ) + { + printf( "Error reading song: song node not found\n" ); + return NULL; + } + QDomNode songNode = nodeList.at( 0 ); + + QString m_sSongVersion = LocalFileMng::readXmlString( songNode , "version", "Unknown version" ); + + + + + float fBpm = LocalFileMng::readXmlFloat( songNode, "bpm", 120 ); + float fVolume = LocalFileMng::readXmlFloat( songNode, "volume", 0.5 ); + float fMetronomeVolume = LocalFileMng::readXmlFloat( songNode, "metronomeVolume", 0.5 ); + QString sName( LocalFileMng::readXmlString( songNode, "name", "Untitled Song" ) ); + QString sAuthor( LocalFileMng::readXmlString( songNode, "author", "Unknown Author" ) ); + QString sNotes( LocalFileMng::readXmlString( songNode, "notes", "..." ) ); + QString sLicense( LocalFileMng::readXmlString( songNode, "license", "Unknown license" ) ); + bool bLoopEnabled = LocalFileMng::readXmlBool( songNode, "loopEnabled", false ); + QString sMode = LocalFileMng::readXmlString( songNode, "mode", "pattern" ); + + + float fHumanizeTimeValue = LocalFileMng::readXmlFloat( songNode, "humanize_time", 0.0 ); + float fHumanizeVelocityValue = LocalFileMng::readXmlFloat( songNode, "humanize_velocity", 0.0 ); + float fSwingFactor = LocalFileMng::readXmlFloat( songNode, "swing_factor", 0.0 ); + + + QDomNode instrumentListNode = songNode.firstChildElement( "instrumentList" ); + if ( ( ! instrumentListNode.isNull() ) ) + { + + int instrumentList_count = 0; + QDomNode instrumentNode; + instrumentNode = instrumentListNode.firstChildElement( "instrument" ); + while ( ! instrumentNode.isNull() ) + { + instrumentList_count++; + QString sId = LocalFileMng::readXmlString( instrumentNode, "id", "" ); // instrument id + QString sDrumkit = LocalFileMng::readXmlString( instrumentNode, "drumkit", "" ); // drumkit + QString sName = LocalFileMng::readXmlString( instrumentNode, "name", "" ); // name + float fVolume = LocalFileMng::readXmlFloat( instrumentNode, "volume", 1.0 ); // volume + bool bIsMuted = LocalFileMng::readXmlBool( instrumentNode, "isMuted", false ); // is muted + float fPan_L = LocalFileMng::readXmlFloat( instrumentNode, "pan_L", 0.5 ); // pan L + float fPan_R = LocalFileMng::readXmlFloat( instrumentNode, "pan_R", 0.5 ); // pan R + float fFX1Level = LocalFileMng::readXmlFloat( instrumentNode, "FX1Level", 0.0 ); // FX level + float fFX2Level = LocalFileMng::readXmlFloat( instrumentNode, "FX2Level", 0.0 ); // FX level + float fFX3Level = LocalFileMng::readXmlFloat( instrumentNode, "FX3Level", 0.0 ); // FX level + float fFX4Level = LocalFileMng::readXmlFloat( instrumentNode, "FX4Level", 0.0 ); // FX level + float fGain = LocalFileMng::readXmlFloat( instrumentNode, "gain", 1.0, false, false ); // instrument gain + + + int fAttack = LocalFileMng::readXmlInt( instrumentNode, "Attack", 0, false, false ); // Attack + int fDecay = LocalFileMng::readXmlInt( instrumentNode, "Decay", 0, false, false ); // Decay + float fSustain = LocalFileMng::readXmlFloat( instrumentNode, "Sustain", 1.0, false, false ); // Sustain + int fRelease = LocalFileMng::readXmlInt( instrumentNode, "Release", 1000, false, false ); // Release + + float fRandomPitchFactor = LocalFileMng::readXmlFloat( instrumentNode, "randomPitchFactor", 0.0f, false, false ); + + bool bFilterActive = LocalFileMng::readXmlBool( instrumentNode, "filterActive", false ); + float fFilterCutoff = LocalFileMng::readXmlFloat( instrumentNode, "filterCutoff", 1.0f, false ); + float fFilterResonance = LocalFileMng::readXmlFloat( instrumentNode, "filterResonance", 0.0f, false ); + QString sMuteGroup = LocalFileMng::readXmlString( instrumentNode, "muteGroup", "-1", false ); + QString sMidiOutChannel = LocalFileMng::readXmlString( instrumentNode, "midiOutChannel", "-1", false, false ); + QString sMidiOutNote = LocalFileMng::readXmlString( instrumentNode, "midiOutNote", "60", false, false ); + int nMuteGroup = sMuteGroup.toInt(); + bool isStopNote = LocalFileMng::readXmlBool( instrumentNode, "isStopNote", false ); + int nMidiOutChannel = sMidiOutChannel.toInt(); + int nMidiOutNote = sMidiOutNote.toInt(); + + if ( sId.isEmpty() ) { + printf( "Empty ID for instrument. skipping \n" ); + instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" ); + continue; + } + QDomNode filenameNode = instrumentNode.firstChildElement( "filename" ); + + if ( ! filenameNode.isNull() ) + { + return false; + } + else + { + unsigned nLayer = 0; + QDomNode layerNode = instrumentNode.firstChildElement( "layer" ); + while ( ! layerNode.isNull() ) + { + if ( nLayer >= MAX_LAYERS ) + { + printf( "nLayer >= MAX_LAYERS" ); + continue; + } + QString sFilename = LocalFileMng::readXmlString( layerNode, "filename", "" ); + bool sIsModified = LocalFileMng::readXmlBool( layerNode, "ismodified", false ); + QString sMode = LocalFileMng::readXmlString( layerNode, "smode", "forward" ); + unsigned sStartframe = LocalFileMng::readXmlInt( layerNode, "startframe", 0 ); + unsigned sLoopFrame = LocalFileMng::readXmlInt( layerNode, "loopframe", 0 ); + int sLoops = LocalFileMng::readXmlInt( layerNode, "loops", 0 ); + unsigned sEndframe = LocalFileMng::readXmlInt( layerNode, "endframe", 0 ); + bool sUseRubber = LocalFileMng::readXmlInt( layerNode, "userubber", 0, false ); + float sRubberDivider = LocalFileMng::readXmlFloat( layerNode, "rubberdivider", 0.0 ); + int sRubberCsettings = LocalFileMng::readXmlInt( layerNode, "rubberCsettings", 1 ); + int sRubberPitch = LocalFileMng::readXmlFloat( layerNode, "rubberPitch", 0.0 ); + + float fMin = LocalFileMng::readXmlFloat( layerNode, "min", 0.0 ); + float fMax = LocalFileMng::readXmlFloat( layerNode, "max", 1.0 ); + float fGain = LocalFileMng::readXmlFloat( layerNode, "gain", 1.0 ); + float fPitch = LocalFileMng::readXmlFloat( layerNode, "pitch", 0.0, false, false ); + if ( nLayer == 0 ) + { + drum_track[sId] = ( InstrumentTrack * ) track::create( track::InstrumentTrack,engine::getBBTrackContainer() ); + drum_track[sId]->volumeModel()->setValue( fVolume * 100 ); + drum_track[sId]->panningModel()->setValue( ( fPan_R - fPan_L ) * 100 ); + ins = drum_track[sId]->loadInstrument( "audiofileprocessor" ); + ins->loadFile( sFilename ); + } + nLayer++; + layerNode = ( QDomNode ) layerNode.nextSiblingElement( "layer" ); + } + } + + instrumentNode = (QDomNode) instrumentNode.nextSiblingElement( "instrument" ); + } + if ( instrumentList_count == 0 ) + { + return false; + } + } + else + { + return false; + } + QDomNode patterns = songNode.firstChildElement( "patternList" ); + int pattern_count = 0; + int nbb = engine::getBBTrackContainer()->numOfBBs(); + QDomNode patternNode = patterns.firstChildElement( "pattern" ); + int pn = 1; + while ( !patternNode.isNull() ) + { + if ( pn > 0 ) + { + pattern_count++; + s->addBBTrack(); + pn = 0; + } + QString sName; // name + sName = LocalFileMng::readXmlString( patternNode, "name", sName ); + + QString sCategory = ""; // category + sCategory = LocalFileMng::readXmlString( patternNode, "category", sCategory ,false ,false ); + int nSize = -1; + nSize = LocalFileMng::readXmlInt( patternNode, "size", nSize, false, false ); + pattern_length[sName] = nSize; + QDomNode pNoteListNode = patternNode.firstChildElement( "noteList" ); + if ( ! pNoteListNode.isNull() ) { + QDomNode noteNode = pNoteListNode.firstChildElement( "note" ); + while ( ! noteNode.isNull() ) { + unsigned nPosition = LocalFileMng::readXmlInt( noteNode, "position", 0 ); + float fLeadLag = LocalFileMng::readXmlFloat( noteNode, "leadlag", 0.0 , false , false ); + float fVelocity = LocalFileMng::readXmlFloat( noteNode, "velocity", 0.8f ); + float fPan_L = LocalFileMng::readXmlFloat( noteNode, "pan_L", 0.5 ); + float fPan_R = LocalFileMng::readXmlFloat( noteNode, "pan_R", 0.5 ); + int nLength = LocalFileMng::readXmlInt( noteNode, "length", -1, true ); + float nPitch = LocalFileMng::readXmlFloat( noteNode, "pitch", 0.0, false, false ); + QString sKey = LocalFileMng::readXmlString( noteNode, "key", "C0", false, false ); + QString nNoteOff = LocalFileMng::readXmlString( noteNode, "note_off", "false", false, false ); + + QString instrId = LocalFileMng::readXmlString( noteNode, "instrument", 0,false, false ); + int i = pattern_count - 1 + nbb; + pattern_id[sName] = pattern_count - 1; + pattern *p = dynamic_cast( drum_track[instrId]->getTCO( i ) ); + note n; + n.setPos( nPosition ); + if ( (nPosition + 48) <= nSize ) + { + n.setLength( 48 ); + } + else + { + n.setLength( nSize - nPosition ); + } + n.setVolume( fVelocity * 100 ); + n.setPanning( ( fPan_R - fPan_L ) * 100 ); + n.setKey( NoteKey::stringToNoteKey( sKey ) ); + p->addNote( n,false ); + pn = pn + 1; + noteNode = ( QDomNode ) noteNode.nextSiblingElement( "note" ); + } + } + patternNode = ( QDomNode ) patternNode.nextSiblingElement( "pattern" ); + } + // Pattern sequence + QDomNode patternSequenceNode = songNode.firstChildElement( "patternSequence" ); + QDomNode groupNode = patternSequenceNode.firstChildElement( "group" ); + int pos = 0; + while ( !groupNode.isNull() ) + { + int best_length = 0; + QDomNode patternId = groupNode.firstChildElement( "patternID" ); + while ( !patternId.isNull() ) + { + QString patId = patternId.firstChild().nodeValue(); + patternId = ( QDomNode ) patternId.nextSiblingElement( "patternID" ); + + int i = pattern_id[patId]+song_num_tracks; + track *t = ( bbTrack * ) s->tracks().at( i ); + trackContentObject *tco = t->createTCO( pos ); + tco->movePosition( pos ); + + + if ( pattern_length[patId] > best_length ) + { + best_length = pattern_length[patId]; + } + } + pos = pos + best_length; + groupNode = groupNode.nextSiblingElement( "group" ); + } + + if ( pattern_count == 0 ) + { + return false; + } + return true; +} +bool HydrogenImport::tryImport( trackContainer * _tc ) +{ + if( openFile() == false ) + { + return false; + } + return readSong(); +} + + + +extern "C" +{ + +// necessary for getting instance out of shared lib +Plugin * PLUGIN_EXPORT lmms_plugin_main( Model *, void * _data ) +{ + return new HydrogenImport( QString::fromUtf8( + static_cast( _data ) ) ); +} + + +} + diff --git a/plugins/HydrogenImport/HydrogenImport.h b/plugins/HydrogenImport/HydrogenImport.h new file mode 100644 index 000000000..10a1cba7c --- /dev/null +++ b/plugins/HydrogenImport/HydrogenImport.h @@ -0,0 +1,27 @@ +#ifndef _HYDROGEN_IMPORT_H +#define _HYDROGEN_IMPORT_H + +#include +#include +#include + +#include "ImportFilter.h" + + +class HydrogenImport : public ImportFilter +{ +public: + HydrogenImport( const QString & _file ); + bool readSong(); + + virtual ~HydrogenImport(); + + virtual PluginView * instantiateView( QWidget * ) + { + return( NULL ); + } +private: + virtual bool tryImport( trackContainer * _tc ); +}; +#endif + diff --git a/plugins/HydrogenImport/LocalFileMng.h b/plugins/HydrogenImport/LocalFileMng.h new file mode 100644 index 000000000..217f95f34 --- /dev/null +++ b/plugins/HydrogenImport/LocalFileMng.h @@ -0,0 +1,29 @@ +#ifndef LFILEMNG_H +#define LFILEMNG_H + +#include +#include +#include +#include +#include + +class LocalFileMng +{ +public: + LocalFileMng(); + ~LocalFileMng(); + std::vector getallPatternList(){ + return m_allPatternList; + } + + static QString readXmlString( QDomNode , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static float readXmlFloat( QDomNode , const QString& nodeName, float defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static int readXmlInt( QDomNode , const QString& nodeName, int defaultValue, bool bCanBeEmpty = false, bool bShouldExists = true , bool tinyXmlCompatMode = false); + static bool readXmlBool( QDomNode , const QString& nodeName, bool defaultValue, bool bShouldExists = true , bool tinyXmlCompatMode = false ); + static void convertFromTinyXMLString( QByteArray* str ); + static bool checkTinyXMLCompatMode( const QString& filename ); + static QDomDocument openXmlDocument( const QString& filename ); + std::vector m_allPatternList; +}; +#endif //LFILEMNG_H + diff --git a/plugins/HydrogenImport/local_file_mgr.cpp b/plugins/HydrogenImport/local_file_mgr.cpp new file mode 100644 index 000000000..78560fef9 --- /dev/null +++ b/plugins/HydrogenImport/local_file_mgr.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "LocalFileMng.h" + + +/* New QtXml based methods */ + +QString LocalFileMng::readXmlString( QDomNode node , const QString& nodeName, const QString& defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return element.text(); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + + } + return defaultValue; + } +} + +float LocalFileMng::readXmlFloat( QDomNode node , const QString& nodeName, float defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QLocale c_locale = QLocale::c(); + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return c_locale.toFloat(element.text()); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + +int LocalFileMng::readXmlInt( QDomNode node , const QString& nodeName, int defaultValue, bool bCanBeEmpty, bool bShouldExists, bool tinyXmlCompatMode) +{ + QLocale c_locale = QLocale::c(); + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + return c_locale.toInt( element.text() ); + } else { + if ( !bCanBeEmpty ) { + //_WARNINGLOG( "Using default value in " + nodeName ); + } + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + +bool LocalFileMng::readXmlBool( QDomNode node , const QString& nodeName, bool defaultValue, bool bShouldExists, bool tinyXmlCompatMode) +{ + QDomElement element = node.firstChildElement( nodeName ); + + if( !node.isNull() && !element.isNull() ){ + if( !element.text().isEmpty() ){ + if( element.text() == "true"){ + return true; + } else { + return false; + } + } else { + //_WARNINGLOG( "Using default value in " + nodeName ); + return defaultValue; + } + } else { + if( bShouldExists ){ + //_WARNINGLOG( "'" + nodeName + "' node not found" ); + } + return defaultValue; + } +} + + +/* Convert (in-place) an XML escape sequence into a literal byte, + * rather than the character it actually refers to. + */ +void LocalFileMng::convertFromTinyXMLString( QByteArray* str ) +{ + /* When TinyXML encountered a non-ASCII character, it would + * simply write the character as "&#xx;" -- where "xx" is + * the hex character code. However, this doesn't respect + * any encodings (e.g. UTF-8, UTF-16). In XML, &#xx; literally + * means "the Unicode character # xx." However, in a UTF-8 + * sequence, this could be an escape character that tells + * whether we have a 2, 3, or 4-byte UTF-8 sequence. + * + * For example, the UTF-8 sequence 0xD184 was being written + * by TinyXML as "Ñ„". However, this is the UTF-8 + * sequence for the cyrillic small letter EF (which looks + * kind of like a thorn or a greek phi). This letter, in + * XML, should be saved as ф, or even literally + * (no escaping). As a consequence, when Ñ is read + * by an XML parser, it will be interpreted as capital N + * with a tilde (~). Then „ will be interpreted as + * an unknown or control character. + * + * So, when we know that TinyXML wrote the file, we can + * simply exchange these hex sequences to literal bytes. + */ + int pos = 0; + + pos = str->indexOf("&#x"); + while( pos != -1 ) { + if( isxdigit(str->at(pos+3)) + && isxdigit(str->at(pos+4)) + && (str->at(pos+5) == ';') ) { + char w1 = str->at(pos+3); + char w2 = str->at(pos+4); + + w1 = tolower(w1) - 0x30; // '0' = 0x30 + if( w1 > 9 ) w1 -= 0x27; // '9' = 0x39, 'a' = 0x61 + w1 = (w1 & 0xF); + + w2 = tolower(w2) - 0x30; // '0' = 0x30 + if( w2 > 9 ) w2 -= 0x27; // '9' = 0x39, 'a' = 0x61 + w2 = (w2 & 0xF); + + char ch = (w1 << 4) | w2; + (*str)[pos] = ch; + ++pos; + str->remove(pos, 5); + } + pos = str->indexOf("&#x"); + } +} + +bool LocalFileMng::checkTinyXMLCompatMode( const QString& filename ) +{ + /* + Check if filename was created with TinyXml or QtXml + TinyXML: return true + QtXml: return false + */ + + QFile file( filename ); + + if ( !file.open(QIODevice::ReadOnly) ) + return false; + + QString line = file.readLine(); + file.close(); + if ( line.startsWith( "name(); + if( enc == QString("System") ) { + enc = "UTF-8"; + } + QByteArray line; + QByteArray buf = QString("\n") + .arg( enc ) + .toLocal8Bit(); + + //_INFOLOG( QString("Using '%1' encoding for TinyXML file").arg(enc) ); + + while( !file.atEnd() ) { + line = file.readLine(); + LocalFileMng::convertFromTinyXMLString( &line ); + buf += line; + } + + if( ! doc.setContent( buf ) ) { + file.close(); + return QDomDocument(); + } + + } else { + if( ! doc.setContent( &file ) ) { + file.close(); + return QDomDocument(); + } + } + file.close(); + + return doc; +} +