This is BBAlchemyFilter.m in view mode; [Download] [Up]
/* BBAlchemyFilter.m * * This format filter does handle read/write of molecules in the * Alchemy Text (.alchMol or .mol) fileformat. * * For interface-info see the header file. The comments in this file mostly * cover only the real implementation details. * * Written by: Thomas Engel * Created: 15.03.1994 (Copyleft) * Last modified: 30.10.1994 */ #import "BBAlchemyFilter.h" #import "BBFileFilterManager.h" #import "../BBAppManager.h" #import "../AtomLibrary.subproj/BBAtomLibraryManager.h" #import "../BBBeaker.h" #import "../BBMolecule.h" #import "../BBAtom.h" #import "../BBBond.h" #import <misckit/MiscString.h> #define MYBUFFERSIZE 500 #define COMMENTTOKEN "#" #define SINGLEBONDTOKEN "SINGLE" #define DOUBLEBONDTOKEN "DOUBLE" #define AROMATICBONDTOKEN "AROMATIC" // I hate statics but here they might be useful. // The other variables have been added to the intercase to allow debugging. static char lineBuffer[MYBUFFERSIZE]; static FILE *inStream; @implementation BBAlchemyFilter - (float)canReadFile:(const char*)filename { // Let's see if we can handle the file. We will check the extention. // "alchMol" will be regonized direktly where as the normal "mol" // extention has to pass so file checking. id pathString; id aString; float answer; pathString = [MiscString new]; [pathString setPath:filename]; aString = [pathString fileExtension]; if( [aString casecmp:"alchMol"] == 0 ) answer = (float)YES; else if( [aString casecmp:"mol"] == 0 ) { // The "mol" extention is not save. We have to check the first line // for the three magic tokens to see we really have a alchemy file. inStream = fopen( filename, "r" ); if( inStream == NULL ) return NO; theLine = [MiscString new]; tokenList = nil; answer = (float)NO; if( fgets( lineBuffer, MYBUFFERSIZE, inStream )) { [theLine setStringValue:lineBuffer]; tokenList = [theLine tokenize:" \t\n\r" into:nil]; if( [[tokenList objectAt:1] compareStr:"ATOMS,"] == 0 && [[tokenList objectAt:3] compareStr:"BONDS,"] == 0 && [[tokenList objectAt:5] compareStr:"CHARGES,"] == 0 ) answer = (float)YES; } [theLine free]; fclose( inStream ); } else answer = (float)NO; [pathString free]; [aString free]; return answer; } - readBeaker:aBeaker fromFile:(const char*)filename { // A simple parser for Alchemy molecules. // The tokenList is only defined as long as its data is not already // parsed or it contains the parts that have to be parsed. Freeing // the list is not enough! We have to reset the id to nil to make the // check work properly. // Right here we only read the number of data. id aString; int atomCount; int bondCount; inStream = fopen( filename, "r" ); if( inStream == NULL ) return self; // We will read the first line and extract all the info in there. // After that we will read the atoms and bonds. mol = [BBMolecule new]; theLine = [MiscString new]; tokenList = nil; if( fgets( lineBuffer, MYBUFFERSIZE, inStream )) { [theLine setStringValue:lineBuffer]; tokenList = [theLine tokenize:" \t\n\r" into:nil]; atomCount = [[tokenList objectAt:0] intValue]; bondCount = [[tokenList objectAt:2] intValue]; aString = [theLine substringFromToken:6 toToken:[tokenList count]-1 ofList:tokenList]; [(BBMolecule *)mol setName:[aString stringValue]]; [aString free]; [[tokenList freeObjects] free]; tokenList = nil; if( [self readAtomData:atomCount] ) [self readBondData:bondCount]; } [aBeaker addMolecule:mol]; [theLine free]; fclose( inStream ); return self; } - (BOOL)readAtomData:(int)count { // Reading the atom data // Right here we will continue to read lines until count atoms have been // read. In normal Alchemy files the atoms have to follow without a break. BOOL goAhead = YES; id anAtom; id basicAtom; id atomLibrary; id token; atomLibrary = [[NXApp delegate] atomLibrary]; do { // Stop after last atom or at files end.. if( count == 0 ) break; if( !fgets( lineBuffer, MYBUFFERSIZE, inStream )) { goAhead = NO; break; } [theLine setStringValue:lineBuffer]; tokenList = [theLine tokenize:" \t\n\r" into:nil]; // Check for comments and blank lines. // We won't accept less than 6 elements per line. token = [tokenList objectAt:0]; if( [tokenList count] < 6 || [token compareStr:COMMENTTOKEN] == 0 ) { [[tokenList freeObjects] free]; tokenList = nil; continue; } // Otherwise lets init a new atom from this line: // We have to find the basicAtom too. // To find the basic atom we need to convert the Alchemy atomtype // settings. Therefore we strip of any unwanted character. token = [tokenList objectAt:1]; [self checkSymbol:token]; anAtom = [BBAtom new]; basicAtom = [atomLibrary findBasicAtomWithSymbol:[token stringValue]]; [anAtom setMotherAtom:basicAtom]; [anAtom setXPos:[[tokenList objectAt:2] doubleValue]]; [anAtom setYPos:[[tokenList objectAt:3] doubleValue]]; [anAtom setZPos:[[tokenList objectAt:4] doubleValue]]; [mol addAtom:anAtom]; --count; // Lets free the list and make the line as read. We don't need it // anymore. With count = 0 we have reached the end. [[tokenList freeObjects] free]; tokenList = nil; } while( goAhead ); return goAhead; } - (BOOL)readBondData:(int)count { // Reading the bond data. // Right here we will continue to read lines until count bonds are read. BOOL goAhead = YES; BBBond * aBond; id atomList; id fromAtom; id toAtom; id token; char type; atomList = [mol atomList]; do { // It there are no bonds...let's leave this place.. if( count < 1 ) break; if( !fgets( lineBuffer, MYBUFFERSIZE, inStream )) { goAhead = NO; break; } [theLine setStringValue:lineBuffer]; tokenList = [theLine tokenize:" \t\n\r" into:nil]; // Check for comments, blank lines and new tokens. // If there are not enough elements we will skip it token = [tokenList objectAt:0]; if( [tokenList count] < 4 || [token compareStr:COMMENTTOKEN] == 0 ) { [[tokenList freeObjects] free]; tokenList = nil; continue; } // otherwise lets init a new bond from this line: // We will set the name to some usefull value. aBond = [BBBond new]; token = [tokenList objectAt:3]; if( [token compareStr:SINGLEBONDTOKEN] == 0 ) type = BOND_SINGLE; else if( [token compareStr:DOUBLEBONDTOKEN] == 0 ) type = BOND_DOUBLE; else if( [token compareStr:AROMATICBONDTOKEN] == 0 ) type = BOND_DOUBLE_DELOCATED; else type = BOND_SINGLE; fromAtom = [atomList objectAt:[[tokenList objectAt:1] intValue]-1]; toAtom = [atomList objectAt:[[tokenList objectAt:2] intValue]-1]; [aBond connect:fromAtom to:toAtom with:type]; // We will use the token string object to compose our Bond name. [token setStringValue:[fromAtom symbol]]; [token catStringValue:"±"]; [token catStringValue:[toAtom symbol]]; [aBond setName:[token stringValue]]; [mol addBond:aBond]; --count; // Lets free the list and make the line as read. We don't need it // anymore [[tokenList freeObjects] free]; tokenList = nil; } while( goAhead ); return goAhead; } - checkSymbol:symbol { // Here we try to simplify a given atom symbol. // The original symbols have additional information that we might not need // or can not cope with right now. // This method will change the original string object. if( [symbol compareStr:"C2"] == 0 || [symbol compareStr:"C3"] == 0 || [symbol compareStr:"CAR"] == 0 || [symbol compareStr:"CC"] == 0 || [symbol compareStr:"CK"] == 0 || [symbol compareStr:"CM"] == 0 || [symbol compareStr:"CN"] == 0 || [symbol compareStr:"CQ"] == 0 || [symbol compareStr:"CR"] == 0 || [symbol compareStr:"CT"] == 0 || [symbol compareStr:"CV"] == 0 || [symbol compareStr:"CW"] == 0) [symbol setStringValue:"C"]; else if( [symbol compareStr:"H2"] == 0 || [symbol compareStr:"H3"] == 0 || [symbol compareStr:"HO"] == 0 || [symbol compareStr:"HS"] == 0 ) [symbol setStringValue:"H"]; else if( [symbol compareStr:"NM"] == 0 || [symbol compareStr:"N2"] == 0 || [symbol compareStr:"N3"] == 0 || [symbol compareStr:"N3+"] == 0 || [symbol compareStr:"NPL3"] == 0 || [symbol compareStr:"NB"] == 0 || [symbol compareStr:"NC"] == 0 || [symbol compareStr:"NT"] == 0 ) [symbol setStringValue:"N"]; else if( [symbol compareStr:"O2"] == 0 || [symbol compareStr:"O3"] == 0 || [symbol compareStr:"OH"] == 0 ) [symbol setStringValue:"O"]; else if( [symbol compareStr:"SH"] == 0 ) [symbol setStringValue:"S"]; else if( [symbol compareStr:"P3D"] == 0 ) [symbol setStringValue:"P"]; return self; } - writeBeaker:aBeaker toFile:(const char *)filename { return self; } @end /* * History: 30.10.94 Switched to the MiscString. * * 14.05.94 Changes to work with new BB... objects. * * 25.03.94 Added checkSymbol. * * 15.03.94 A basic implementation. * * * Bugs: - We will watch for '#' comment even they are not defined for that * data format.. * * - Does not handle charges and strips extra data from the symbol * name. These symbolextentions may be automatically typed to our * symbol addition area..perhaps with some conversion ?? (15.03.94) */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.