/* ** Astrolog (Version 4.40) File: io.c ** ** IMPORTANT NOTICE: The graphics database and chart display routines ** used in this program are Copyright (C) 1991-1995 by Walter D. Pullen ** (astara@u.washington.edu). Permission is granted to freely use and ** distribute these routines provided one doesn't sell, restrict, or ** profit from them in any way. Modification is allowed provided these ** notices remain with any altered or edited versions of the program. ** ** The main planetary calculation routines used in this program have ** been Copyrighted and the core of this program is basically a ** conversion to C of the routines created by James Neely as listed in ** Michael Erlewine's 'Manual of Computer Programming for Astrologers', ** available from Matrix Software. The copyright gives us permission to ** use the routines for personal use but not to sell them or profit from ** them in any way. ** ** The PostScript code within the core graphics routines are programmed ** and Copyright (C) 1992-1993 by Brian D. Willoughby ** (brianw@sounds.wa.com). Conditions are identical to those above. ** ** The extended accurate ephemeris databases and formulas are from the ** calculation routines in the program "Placalc" and are programmed and ** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl ** (alois@azur.ch). The use of that source code is subject to ** regulations made by Astrodienst Zurich, and the code is not in the ** public domain. This copyright notice must not be changed or removed ** by any user of this program. ** ** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991. ** X Window graphics initially programmed 10/23-29/1991. ** PostScript graphics initially programmed 11/29-30/1992. ** Last code change made 1/29/1995. */ #include "astrolog.h" /* ****************************************************************************** ** File IO Routines. ****************************************************************************** */ /* Open the file indicated by the given string and return the file's stream */ /* pointer, or NULL if the file couldn't be found or opened. All parts of */ /* the program which open files to read call this routine. We look in */ /* several various locations and directories for the file before giving up. */ FILE *FileOpen(szFile, nFileMode) char *szFile; int nFileMode; { FILE *file; char name[cchSzDef], mode[3]; #ifdef ENVIRON char *env; #endif /* Some file types we want to open as binary instead of Ascii. */ sprintf(mode, "r%s", nFileMode == 2 ? "b" : ""); /* First look for the file in the current directory. */ file = fopen(szFile, mode); if (file != NULL) return file; #ifdef ENVIRON /* Next look for the file in the directory indicated by the version */ /* specific system environment variable. */ sprintf(name, "%s%s", ENVIRONVER, szVersionCore); env = getenv(name); if (env && *env) { sprintf(name, "%s%c%s", env, chDirSep, szFile); file = fopen(name, mode); if (file != NULL) return file; } /* Next look in the directory in the general environment variable. */ env = getenv(ENVIRONALL); if (env && *env) { sprintf(name, "%s%c%s", env, chDirSep, szFile); file = fopen(name, mode); if (file != NULL) return file; } /* Next look in the directory in the version prefix environment variable. */ env = getenv(ENVIRONVER); if (env && *env) { sprintf(name, "%s%c%s", env, chDirSep, szFile); file = fopen(name, mode); if (file != NULL) return file; } #endif /* Finally look in one of several directories specified at compile time. */ sprintf(name, "%s%c%s", nFileMode == 0 ? DEFAULT_DIR : (nFileMode == 1 ? CHART_DIR : EPHE_DIR), chDirSep, szFile); file = fopen(name, mode); if (file == NULL && nFileMode == 1) { /* If the file was never found, print an error (unless we were looking */ /* for a certain file type, e.g. the optional astrolog.dat file). */ sprintf(name, "File '%s' not found.", szFile); PrintError(name); } return file; } /* ##################################################################### Ajout personnel Abel Philipe 26/7/95 La fonction feof semble bugu‚e. Je la remplace par celle ci-dessous : ##################################################################### */ #ifdef ATARI int feof2(FILE *file) { int eof=getc(file); if (eof!=EOF) { ungetc(eof,file); return(0); } else return(eof); } #endif /* This is Astrolog's generic file processing routine, which handles chart */ /* info files, position files, and config files. Given a file name or a */ /* file handle, run through each line as a series of command switches. */ bool FProcessSwitchFile(szFile, file) char *szFile; FILE *file; { char szLine[cchSzMax], *argv[MAXSWITCHES], ch; int argc, i; if (file == NULL) file = FileOpen(szFile, 0); if (file == NULL) return fFalse; /* All files have to begin with the -@ switch file type identifier. */ ch = getc(file); ungetc(ch, file); if (ch != '@') { sprintf(szLine, "The command file '%s' is not in any valid format.", szFile); PrintWarning(szLine); return fFalse; } #ifdef ATARI while (!feof2(file)) { while (!feof2(file) && (ch = getc(file)) < ' ') ; for (szLine[0] = ch, i = 1; i < cchSzMax && !feof2(file) && #else while (!feof(file)) { while (!feof(file) && (ch = getc(file)) < ' ') ; for (szLine[0] = ch, i = 1; i < cchSzMax && !feof(file) && #endif (szLine[i] = getc(file)) >= ' '; i++) ; szLine[i] = chNull; argc = NParseCommandLine(szLine, argv); if (!FProcessSwitches(argc, argv)) return fFalse; } return fTrue; } /* Take the current chart information, and write it out to the file */ /* as indicated by the -o switch. This is only executed at the end of */ /* program execution if the -o switch is in effect. */ bool FOutputData() { char sz[cchSzDef]; FILE *file; int i, j; real rT; file = fopen(is.szFileOut, "w"); /* Create and open the file for output. */ if (file == NULL) { sprintf(sz, "File %s can not be created.", is.szFileOut); PrintError(sz); return fFalse; } if (!us.fWritePos) { /* Write the chart information to the file. */ if (Mon < 1) { fclose(file); PrintError("Can't output chart with no time/space to file."); return fFalse; } if (us.fWriteOld) { fprintf(file, "%d\n%d\n%d\n%.2f\n%.2f\n%.2f\n%.2f\n", Mon, Day, Yea, Tim, Zon-Dst, Lon, Lat); } else { fprintf(file, "@0102 ; %s chart info.\n", szAppName); i = us.fAnsi; us.fAnsi = fFalse; fprintf(file, "%cqb %c%c%c %d %d %s %s %s %s\n", chSwitch, chMon3(Mon), Day, Yea, SzTim(Tim), Dst == 0.0 ? "ST" : (Dst == 1.0 ? "DT" : SzZone(Dst)), SzZone(-Zon), SzLocation(Lon, Lat)); fprintf(file, "%czi \"%s\" \"%s\"\n", chSwitch, ciMain.nam, ciMain.loc); us.fAnsi = i; } } else { /* However, if the -o0 switch is in effect, then write the actual */ /* positions of the planets and houses to the file instead. */ if (us.fWriteOld) { for (i = 1; i <= oNorm; i++) { j = (int)planet[i]; fprintf(file, "%c%c%c: %2d %2d %10.7f\n", chObj3(i), j%30, j/30+1, RFract(planet[i])*60.0); /* Position */ rT = planetalt[i]; fprintf(file, "[%c]: %3d %12.8f\n", /* Altitude */ ret[i] >= 0.0 ? 'D' : chRet, (int)(RSgn(rT)* RFloor(RAbs(rT))), (rT-(real)(int)rT)*60.0); /* Retrograde? */ if (i == oNod) i = oFor-1; else if (i == oFor) i = oMC -1; else if (i == oMC) i = oAsc-1; else if (i == oAsc) i = oVtx-1; else if (i == oVtx) /* Skip minor cusps to write uranians */ i = us.fUranian ? uranLo-1 : cObj; } for (i = 1; i <= cSign/2; i++) { /* Write first six cusp positions */ j = (int)house[i]; fprintf(file, "H_%c: %2d %2d %10.7f\n", 'a'+i-1, j%30, j/30+1, RFract(house[i])*60.0); } } else { fprintf(file, "@0203 ; %s chart positions.\n", szAppName); for (i = 1; i <= cObj; i++) if (!ignore[i] || FCusp(i)) { fprintf(file, "%cYF ", chSwitch); if (i <= oNorm) fprintf(file, "%c%c%c", chObj3(i)); else fprintf(file, "%3d", i); rT = FBetween(i, cuspLo-1+4, cuspLo-1+9) ? house[i-(cuspLo-1)] : planet[i]; j = (int)rT; fprintf(file, ":%3d %c%c%c%13.9f,%4d%13.9f,", j%30, chSig3(j/30+1), RFract(rT)*60.0, (int)planetalt[i], RFract(RAbs(planetalt[i]))*60.0); rT = i > oNorm ? 999.0 : (i == oMoo && !us.fPlacalc ? 0.0026 : RSqr(spacex[i]*spacex[i]+spacey[i]*spacey[i]+spacez[i]*spacez[i])); fprintf(file, "%14.9f%14.9f\n", DFromR(ret[i]), rT); } } } /* Now write any extra strings that were on the command line after the -o */ /* specification but before the next switch, to the file as comments. */ for (i = 1; i < is.cszComment; i++) { is.rgszComment++; fprintf(file, "%s%s\n", us.fWriteOld ? "" : "; ", is.rgszComment[1]); } fclose(file); return fTrue; } /* ****************************************************************************** ** User Input Routines. ****************************************************************************** */ /* Given a string, return an index number corresponding to what the string */ /* indicates, based on a given parsing mode. In most cases this is mainly */ /* looking up a string in the appropriate array and returning the index. */ int NParseSz(szEntry, pm) char *szEntry; int pm; { char szLocal[cchSzMax], *sz, ch0, ch1, ch2; int cch, n, i; /* First strip off any leading or trailing spaces. */ for (cch = 0; szLocal[cch] = szEntry[cch]; cch++) ; while (cch && szLocal[cch-1] <= ' ') szLocal[--cch] = chNull; for (sz = szLocal; *sz && *sz <= ' '; sz++, cch--) ; if (cch >= 3) { ch0 = ChCap(sz[0]); ch1 = ChUncap(sz[1]); ch2 = ChUncap(sz[2]); switch (pm) { /* Parse months, e.g. "February" or "Feb" -> 2 for February. */ case pmMon: for (i = 1; i <= cSign; i++) { if (ch0 == szMonth[i][0] && ch1 == szMonth[i][1] && ch2 == szMonth[i][2]) return i; } break; /* Parse planets, e.g. "Jupiter" or "Jup" -> 6 for Jupiter. */ case pmObject: for (i = 1; i <= cObj; i++) { if (ch0 == szObjName[i][0] && ch1 == szObjName[i][1] && ch2 == szObjName[i][2]) return i; } if (ch0 == 'L' && ch1 == 'i' && ch2 == 'l') return oLil; if (ch0 == 'S' && ch1 == '.' && ch2 == 'n') return oSou; break; /* Parse aspects, e.g. "Conjunct" or "Con" -> 1 for the Conjunction. */ case pmAspect: for (i = 1; i <= cAspect; i++) { if (ch0 == szAspectAbbrev[i][0] && ch1 == ChUncap(szAspectAbbrev[i][1]) && ch2 == szAspectAbbrev[i][2]) return i; } break; /* Parse house systems, e.g. "Koch" or "Koc" -> 1 for Koch houses. */ case pmSystem: for (i = 1; i <= cSystem; i++) { if (ch0 == szSystem[i][0] && ch1 == szSystem[i][1] && ch2 == szSystem[i][2]) return i; } /* Parse zodiac signs, e.g. "Scorpio" or "Sco" -> 8 for Scorpio. */ case pmSign: for (i = 1; i <= cSign; i++) { if (ch0 == szSignName[i][0] && ch1 == szSignName[i][1] && ch2 == szSignName[i][2]) return i; } /* Parse colors, e.g. "White" or "Whi" -> 15 for White. */ case pmColor: for (i = 0; i < 16 ; i++) { if (ch0 == szColor[i][0] && ch1 == szColor[i][1] && ch2 == ChUncap(szColor[i][2])) return i; } } } n = atoi(sz); if (pm == pmYea) { /* For years, process any "BC" (or "B.C.", "b.c", and variations) and */ /* convert an example such as "5BC" to -4. For negative years, note the */ /* difference of one, as 1AD was preceeded by 1BC, with no year zero. */ i = Max(cch-1, 0); if (i && sz[i] == '.') i--; if (i && ChCap(sz[i]) == 'C') i--; if (i && sz[i] == '.') i--; if (i && ChCap(sz[i]) == 'B') n = 1 - n; } return n; } /* Given a string, return a floating point number corresponding to what the */ /* string indicates, based on a given parsing mode, like above for integers. */ real RParseSz(szEntry, pm) char *szEntry; int pm; { char szLocal[cchSzMax], *sz, *pch, ch; int cch, i, f = fFalse; real r; /* First strip off any leading or trailing spaces. */ for (cch = 0; szLocal[cch] = szEntry[cch]; cch++) ; while (cch && szLocal[cch-1] <= ' ') szLocal[--cch] = chNull; for (sz = szLocal; *sz && *sz <= ' '; sz++, cch--); ; /* Capitalize all letters and make colons be periods to be like numbers. */ for (pch = sz; *pch; pch++) { ch = *pch; if (ch == ':') ch = '.'; else ch = ChCap(ch); *pch = ch; } ch = sz[0]; if (pm == pmTim) { /* For times, process "Noon" and "Midnight" (or just "N" and "M"). */ if (ch == 'N') return 12.0; else if (ch == 'M') return 0.0; } else if (pm == pmDst) { /* For the Daylight time flag, "Daylight", "Yes", and "True" (or just */ /* their first characters) are all indications to be ahead one hour. */ if (ch == 'D' || ch == 'Y' || ch == 'T') return 1.0; /* "Standard", "No", and "False" mean the normal zero offset. */ else if (ch == 'S' || ch == 'N' || ch == 'F') return 0.0; } else if (pm == pmZon) { /* For time zones, see if the abbrev is in a table, e.g. "EST" -> 5. */ for (i = 0; i < cZone; i++) if (NCompareSz(sz, szZon[i]) == 0) return rZon[i]; } else if (pm == pmLon || pm == pmLat) { /* For locations, negate the value for an "E" or "S" in the middle */ /* somewhere (e.g. "105E30" or "27:40S") for eastern/southern values. */ for (i = 0; i < cch; i++) { ch = sz[i]; if (FCapCh(ch)) { if (ch == 'E' || ch == 'S') f = fTrue; sz[i] = '.'; i = cch; } } ch = sz[0]; } /* Anything still at this point should be in a numeric format. */ if (!FNumCh(ch) && ch != '+' && ch != '-' && ch != '.') return rLarge; r = (f ? -1.0 : 1.0) * atof(sz); if (pm == pmTim) { /* Backtrack over any time suffix, i.e. "AM", "p.m." and variations. */ i = Max(cch-1, 0); if (i && sz[i] == '.') i--; if (i && sz[i] == 'M') i--; if (i && sz[i] == '.') i--; if (i) { ch = sz[i]; if (ch == 'A') /* Adjust value appropriately */ r = r >= 12.0 ? r-12.0 : r; /* if AM or PM suffix. */ else if (ch == 'P') r = r >= 12.0 ? r : r+12.0; } } return r; } /* Stop and wait for the user to enter a line of text given a prompt to */ /* display and a string buffer to fill with it. */ void InputString(szPrompt, sz) char *szPrompt, *sz; { FILE *file; file = S; S = stdout; PrintSz(szPrompt); AnsiColor(kYellow); PrintSz(" > "); AnsiColor(kDefault); if (gets(sz) == NULL) /* Pressing control-D will terminate the */ Terminate(tcForce); /* program (at least on some machines.) */ S = file; is.cchCol = 0; } /* Prompt the user for a floating point value, parsing as appropriate, and */ /* make sure it conforms to the specified bounds before returning it. */ int NInputRange(szPrompt, low, high, pm) char *szPrompt; int low, high; int pm; { char szLine[cchSzDef]; int n; loop { InputString(szPrompt, szLine); n = NParseSz(szLine, pm); if (FBetween(n, low, high)) return n; sprintf(szLine, "Value %d out of range from %d to %d.", n, low, high); PrintWarning(szLine); } } /* This is identical to above except it takes/returns floating point values. */ real RInputRange(szPrompt, low, high, pm) char *szPrompt; real low, high; int pm; { char szLine[cchSzDef]; real r; loop { InputString(szPrompt, szLine); r = RParseSz(szLine, pm); if (FBetween(r, low, high)) return r; sprintf(szLine, "Value %.0f out of range from %.0f to %.0f.", r, low, high); PrintWarning(szLine); } } /* This important procedure gets all the parameters defining the chart that */ /* will be worked with later. Given a "filename", it gets from it all the */ /* pertinent chart information. This is more than just reading from a file - */ /* the procedure also takes care of the cases of prompting the user for the */ /* information and using the time functions to determine the date now - the */ /* program considers these cases "virtual" files. Furthermore, when reading */ /* from a real file, we have to check if it was written in the -o0 format. */ bool FInputData(szFile) char *szFile; { FILE *file; char sz[cchSzDef], ch; int i, fT; real k, l, m; /* If we are to read from the virtual file "nul" that means to leave the */ /* chart information alone with whatever settings it may have already. */ if (NCompareSz(szFile, szNulCore) == 0) { is.fHaveInfo = fTrue; return fTrue; } /* If we are to read from the virtual file "set" then that means use a */ /* particular set of chart information generated earlier in the program. */ if (NCompareSz(szFile, szSetCore) == 0) { is.fHaveInfo = fTrue; ciCore = ciSave; return fTrue; } #ifdef TIME /* If we are to read from the file "now" then that means use the time */ /* functions to calculate the present date and time. */ if (NCompareSz(szFile, szNowCore) == 0) { is.fHaveInfo = fTrue; SS = us.dstDef; ZZ = us.zonDef; OO = us.lonDef; AA = us.latDef; GetTimeNow(&MM, &DD, &YY, &TT, ZZ-SS); return fTrue; } #endif /* If we are to read from the file "tty" then that means prompt the user */ /* for all the chart information. */ if (NCompareSz(szFile, szTtyCore) == 0) { file = S; S = stdout; if (!us.fNoSwitches) { /* Temporarily disable an internal redirection of output to a file */ /* because we always want user headers and prompts to be displayed. */ AnsiColor(kWhite); sprintf(sz, "** %s version %s ", szAppName, szVersionCore); PrintSz(sz); sprintf(sz, "(See '%cHc' switch for copyrights and credits.) **\n", chSwitch); PrintSz(sz); AnsiColor(kDefault); sprintf(sz, " Invoke as '%s %cH' for list of command line options.\n", ProcessProgname(is.szProgName), chSwitch); PrintSz(sz); } MM = NInputRange("Enter month for chart (e.g. '8' 'Aug')", 1, 12, pmMon); DD = NInputRange("Enter day for chart (e.g. '1' '31') ", 1, DayInMonth(MM, 0), pmDay); YY = NInputRange("Enter year for chart (e.g. '1995') ", -5000, 5000, pmYea); if (FBetween(YY, 0, 99)) { sprintf(sz, "Assuming first century A.D. is really meant instead of %d.", 1900 + YY); PrintWarning(sz); } TT = RInputRange("Enter time for chart (e.g. '18:30' '6:30pm') ", -2.0, 24.0, pmTim); SS = us.fWriteOld ? 0.0 : RInputRange("Enter if Daylight time in effect (e.g. 'y' '1')", -24.0, 24.0, pmDst); ZZ = RInputRange("Enter time zone (e.g. '5' 'ET' for Eastern) ", -24.0, 24.0, pmZon); if ((int)(RFract(ZZ) * 100.0 + rRound) == 50) { PrintWarning( "Assuming unusual zone of 50 minutes after the hour instead of 30."); } OO = RInputRange("Enter Longitude of place (e.g. '122W20')", -rDegHalf, rDegHalf, pmLon); AA = RInputRange("Enter Latitude of place (e.g. '47N36') ", -rDegQuad, rDegQuad, pmLat); if (!us.fWriteOld) { InputString("Enter name or title for chart ", sz); ciCore.nam = SzPersist(sz); InputString("Enter name of city or location", sz); ciCore.loc = SzPersist(sz); } PrintL(); is.cchRow = 0; S = file; return fTrue; } /* Now that the special cases are taken care of, we can assume we are */ /* to read from a real file. */ file = FileOpen(szFile, 1); if (file == NULL) return fFalse; is.fHaveInfo = fTrue; ch = getc(file); ungetc(ch, file); /* Read the chart parameters from a standard command switch file. */ if (ch == '@') { fT = is.fSzPersist; is.fSzPersist = fFalse; if (!FProcessSwitchFile(szFile, file)) return fFalse; is.fSzPersist = fT; /* Read the chart info from an older style -o list of seven numbers. */ } else if (FNumCh(ch)) { SS = 0.0; fscanf(file, "%d%d%d", &MM, &DD, &YY); fscanf(file, "%lf%lf%lf%lf", &TT, &ZZ, &OO, &AA); if (!FValidMon(MM) || !FValidDay(DD, MM, YY) || !FValidYea(YY) || !FValidTim(TT) || !FValidZon(ZZ) || !FValidLon(OO) || !FValidLat(AA)) { PrintWarning("Values in old style chart info file are out of range."); return fFalse; } /* Read the actual chart positions from a file produced with the -o0. */ } else if (ch == 'S') { MM = -1; /* Hack: A negative month value means the chart parameters are invalid, */ /* hence -o0 is in effect and we can assume the chart positions are */ /* already in memory so we don't have to calculate them later. */ for (i = 1; i <= oNorm; i++) { fscanf(file, "%s%lf%lf%lf", sz, &k, &l, &m); planet[i] = Mod((l-1.0)*30.0+k+m/60.0); fscanf(file, "%s%lf%lf", sz, &k, &l); if ((m = k+l/60.0) > rDegHalf) m = rDegMax - m; planetalt[i] = m; ret[i] = RFromD(sz[1] == 'D' ? 1.0 : -1.0); /* -o0 files from versions 3.05 and before don't have the uranians in */ /* them. Be prepared to skip over them in old files for compatibility. */ if (i == oVtx) { while (getc(file) >= ' ') ; if ((ch = getc(file)) != 'H') i = cuspHi; else i = cObj; } if (i == oNod) i = oFor-1; else if (i == oFor) i = oLil-1; else if (i == oLil) i = oEP -1; else if (i == oEP) i = oVtx-1; } for (i = 1; i <= cSign/2; i++) { fscanf(file, "%s%lf%lf%lf", sz, &k, &l, &m); house[i+6] = Mod((house[i] = Mod((l-1.0)*30.0+k+m/60.0))+rDegHalf); } for (i = 1; i <= cSign; i++) planet[cuspLo-1+i] = house[i]; planet[oMC] = planet[oLil]; planet[oNad] = Mod(planet[oMC] + rDegHalf); planet[oAsc] = planet[oEP]; planet[oDes] = Mod(planet[oAsc] + rDegHalf); planet[oSou] = Mod(planet[oNod] + rDegHalf); ret[oSou] = ret[oNod]; } else { PrintWarning("The chart info file is not in any valid format."); return fFalse; } fclose(file); return fTrue; } /* io.c */