作业要求实现一个ADI和CSV数据处理功能,按照命令行参数的指示,进行对应的处理
包括-i读取,-o导出,-s查询,-l按时间查询,格式如下
-i <file name>(可以导入.csv或.adi格式的文件)
-o <file name>(只能输出.csv或.adi格式的文件)
-s <call>(按名称为call的字段查找记录,可以更改为按照其他字段查找记录)
-l <start time> <end time> (时间格式:一连串的字符,如:20230413215133)
.h头文件如下,里面解释了各个函数的功能
#ifndef PROCESS_H
#define PROCESS_H
#include <stdio.h>
//定义最小数据存储单元
#define LOGBIT 16
//一条记录包含的数据量
#define PIECE_LENGTH 7
//存储一条记录的信息的结构体
typedef struct data{
char qso_date[LOGBIT];
char time_on[LOGBIT];
char freq[LOGBIT];
char mode[LOGBIT];
char call[LOGBIT];
char rst_rcvd[LOGBIT];
char rst_sent[LOGBIT];
}OneLog;
//将一条数据log保存为二进制文件(对比qso_data和time_on字段,如果相同则考虑覆写记录)
void saveBinaryLog(OneLog *log);
//往output中以二进制的方式写入一条log
void saveOneBinaryLog(OneLog *log,FILE *output);
//读取保存记录信息的二进制文件,返回所有记录(长度信息保存在外部变量中)
OneLog *readMyBinaryLog();
//分隔csv格式的一行记录(csv格式字段间以逗号分隔),(返回的记录保存在外部变量中)
int splitLine(char *line);
//获得csv格式文件(已全部读出为data)中含有的记录条数
int getOneLogLength(char *data);
//按时间比较两个记录是否相同,同返回1,不同返回0
int compareOneLogByTime(OneLog *a,OneLog *b);
//导入adi文件并存入我的二进制数据文件中
int readAdi(char *fileName);
//导入csv文件并存入我的二进制数据文件中
int readCsv(char *fileName);
//读取csv文件,结果存储在*saveTo中,长度信息在外部变量中
void readCsvCore(OneLog **saveTo,char *fileName);
//按csv格式保存一条记录(由于用二进制形式保存,所以弃用)
//void saveOneLog(OneLog *stack);
//以CSV格式向output输出一条记录
void writeOneLog(OneLog *one,FILE *output,int withTitle);
//一次性读取文件全部内容,保存到*data中
int readALL(char **data,char *fileName);
//按name字段查询我的二进制文件中的记录,打印stdout
void findPiece(char **target,const char* data,const char *name);
//读取adi文件时,指针跳转到下一个
const char *skipTo(const char *dataptr,const char *name);
//处理命令行参数内容,输入(0)输出(1)
int judgeFileName(char *fileName,int inorout);
//以特定格式导出我的二进制文件中的数据
int writeCsv(char *fileName);
int writeAdi(char *fileName);
//按时间段查询我的二进制文件中的记录,打印stdout
int searchByTime(char *startTime,char *endTime);
#endif
以下是源码内容(内容较长,可按Ctrl+F,用浏览器查询对应名称函数的位置)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "myadif.h"
//#define fprintf(file,str) {fwrite((str),\
sizeof(char)*strlen(str),1,(file))}
//用于查询的键值表
char PIECE[PIECE_LENGTH][LOGBIT] = {"qso_date","time_on","freq",
"mode","call","rst_rcvd","rst_sent"};
char *MyLog = "mylog.bin";
//为了避免使用char ***传值,用外部变量临时保存一个记录
char tempLog[PIECE_LENGTH][LOGBIT];
//保存各文件中的记录条数
int lengOfMyLog = 0;
int lengOfOtherCsv = 0;
int lengOfOtherAdi = 0;
int lengOfcollision = 0;
//最后一条记录是否覆写,用于快捷计算当前dat有多少条记录
int isLastColli=0;
int readAdi(char *fileName){
//一次性读完所有数据内容
char *oridata;
if(readALL(&oridata,fileName) == -1) return -1;
//核心程序:进行数据处理,不再改动原始数据
const char *data = oridata;
//去头
const char *begin = skipTo(data,"EOH");
if(begin!=NULL) data=begin+4;
char *temp=NULL;
int count=0;
//提取所有的结构体,创建新结构体的标志:至少有一个<qso-data>字段
while(*data && (findPiece(&temp,data,PIECE[0]),temp!=NULL)){
OneLog oneAdif;
count++;
//提取一个结构体,按名称寻找,data不移位
for(int w=0;w<PIECE_LENGTH;w++){
findPiece(&temp,data,PIECE[w]);
if(temp == NULL){
*((char *)(&oneAdif)+LOGBIT*w) = '\0';
continue;
}
//对time_on项判断
if(w == 1 && strlen(temp)==4){
temp = strcat(temp,"00");
}
//用指针挨个赋值
strncpy( (char *)(&oneAdif) + LOGBIT*w ,temp ,LOGBIT);
//注:每一次寻找最后都要free找到的内容
free(temp);
temp=NULL;
}
saveBinaryLog(&oneAdif);
//跳到尾部
data = skipTo(data,"EOR");
data+=4;
}
lengOfOtherAdi = count;
free(oridata);
return 0;
}
//从adi格式的data中寻找名称为name的字段(不区分大小写),直接返回数据内容,找不到返回NULL(注意此处不修改data指针)
void findPiece(char **target,const char* data,const char *name){
const char *startPtr,*endPtr;
startPtr = skipTo(data,name);
if(startPtr == NULL) return;
//转到长度信息位置
while(*startPtr!=':') startPtr++;
startPtr++;
//获取字段长度
int segmentLength = atoi(startPtr);
//转到字段开始位置
while(*startPtr!='>') startPtr++;
startPtr++;
//拷贝字串.注意跳过回车和空格
*target = (char *)malloc((segmentLength+1)*sizeof(char));
for(int q=0;q<segmentLength;q++){
while(*(startPtr+q) == ' '\
|| *(startPtr+q)=='\n') startPtr++;
(*target)[q] = *(startPtr+q);
}
(*target)[segmentLength]='\0';
return;
}
//让adi格式的data指针,跳转到下一个名称标识<name:X>的开头
const char *skipTo(const char *dataptr,const char *name){
int nameLength = strlen(name);
while(*(dataptr+1)){
//找到字段名称开始的标识<
while(*(dataptr+1) && *dataptr!='<') dataptr++;
dataptr++;
//比较字段名字,若匹配,让dataptr跳转(忽略大小写)
if(strnicmp(dataptr,name,nameLength) == 0){
return dataptr;
}
}
//找不到name字段
return NULL;
}
//读取并处理外来Csv数据的函数
int readCsv(char *fileName){
OneLog *data = NULL;
readCsvCore(&data,fileName);
if(data == NULL) return -1;
for(int q=0; q<lengOfOtherCsv; q++){
saveBinaryLog(data+q);
}
free(data);
return 0;
}
//读取myadif数据的函数(私有化)
void readCsvCore(OneLog **saveTo,char *fileName){
char *database=NULL,*endOfLine=NULL;
//取巧:找到RST_SENT表头,跳过,若没有则不跳过(用于读取myadif)
int errorCode = 0;
if((errorCode = readALL(&database,fileName)) != 0) return;
char *start = database;
char *begin = strstr(database,"RST_SENT");
if(begin) database = begin+10;
//获得长度,声明用于定位的变量
int leng =getOneLogLength(database),nowLog=0;
//保存某个数据库有几条记录
if(strcmp(fileName,MyLog)==0) lengOfMyLog = leng;
else lengOfOtherCsv = leng;
//技巧:由于数据内存对齐,因此挨个赋值即可
OneLog *ret = (OneLog *)malloc(sizeof(OneLog)*leng);
*saveTo = ret;
//分解每一行
for(int e=0;e<leng;e++){
//利用字符串分隔,替换\n为\0,打成多份记录
endOfLine = database;
//Windows文本特性,\r\n
while(*endOfLine && *endOfLine != '\n') endOfLine++;
if(*(endOfLine-1) == '\r'){
*(endOfLine-1)='\0';
}
*endOfLine = '\0';
splitLine(database);
for(int r=0;r<PIECE_LENGTH;r++){
if(r == 1 && strlen(tempLog[r]) == 4)
strncpy((char *)(ret+nowLog)+LOGBIT*r, strcat(tempLog[r],"00"), LOGBIT);
else
strncpy((char *)(ret+nowLog) + LOGBIT*r ,tempLog[r],LOGBIT);
}
nowLog++;
//开启新的一行
database=endOfLine+1;
}
free(start);
return;
}
//传入以'\0'结尾的一行记录,在一行中按','分隔Csv的字段,保存到外部变量中
int splitLine(char *line){
if(*line == '\0' || *line == '\n') return -1;
int nowPiece=0;
char *start,*end;
while(*line){
//字段的开始
start=line;
//优先级更高的字段开始标志
if(*line == '"'){
line++;
//找到结束位置
while(*line &&*line!='"') line++;
}else{
while(*line &&*line!=',') line++;
}
//字段结束,赋值
if(*line == '"') line++;
strncpy(tempLog[nowPiece],start,line-start);
//使字段形成字符串
tempLog[nowPiece][line-start] = '\0';
// if(!*line && (int)tempLog[nowPiece][line-start-1] == 13){
// tempLog[nowPiece][line-start-1]='\0';
// }
nowPiece++;
//跳过分隔符
if(*line && *line == ',') line++;
}
return 0;
}
//根据时间查找记录,不需要改动data,返回该行记录行首的位置
int compareOneLogByTime(OneLog *a ,OneLog *b){
int isequal=0;
if(strcmp(a->qso_date,b->qso_date)==0 &&
strcmp(a->time_on,b->time_on) == 0){
isequal=1;
}
return isequal;
}
//(关键!)获得csv中的记录条数,不需要改动data
int getOneLogLength(char *data){
//一行一条记录,先统计\n出现次数
if(data==NULL || *data=='\0') return 0;
int lines = 1;//第一行
char *endOfFile = data+strlen(data)-1 , *endOfLine;
//去除结尾可能的换行符
while(endOfFile > data && *endOfFile == '\n') endOfFile--;
//每找到换行符号,记录条数加一
while(endOfLine = strchr(data,'\n')){
lines++;
//文件末尾出现换行,要-1
if(endOfLine >= endOfFile){
lines--;
break;
}
//进入新一行
data = endOfLine+1;
}
return lines;
}
//保存数据到csv格式的“数据库”文件,可以自己设置保存数据类型,如二进制
void saveBinaryLog(OneLog *log){
//先读取
OneLog *mylog = readMyBinaryLog(),*opelog=mylog;
//再写入
FILE *myfile = fopen(MyLog,"wb");
int isrenew = 0;
for(int q=0;q<lengOfMyLog;q++){
if(compareOneLogByTime(log,opelog)){
lengOfcollision++;
isrenew=1;
saveOneBinaryLog(log,myfile);
}else saveOneBinaryLog(opelog,myfile);
opelog++;
}
if(!isrenew){
isLastColli = 0;
saveOneBinaryLog(log,myfile);
}else isLastColli = 1;
fclose(myfile);
free(mylog);
}
void saveOneBinaryLog(OneLog *log,FILE *output){
fwrite(log,sizeof(OneLog),1,output);
return;
}
OneLog *readMyBinaryLog(){
FILE *database = fopen(MyLog,"rb");
if(!database){
printf("Database Unfined,create a new one!\n");
lengOfMyLog=0;
return NULL;
}
fseek(database,0,SEEK_END);
long leng=ftell(database);
rewind(database);
OneLog *retlog=(OneLog *)malloc(leng);
//printf("%d:%d\n",sizeof(OneLog),leng);
fread(retlog,leng,1,database);
lengOfMyLog = leng/sizeof(OneLog);
fclose(database);
return retlog;
}
void saveOneCsvLog(OneLog *stack){
//先全部读出数据;
OneLog *oldOneLog = NULL;
readCsvCore(&oldOneLog,MyLog);
FILE *output = fopen(MyLog,"wb");
int isWritten = 0;
for(int q=0;q<lengOfMyLog;q++){
//如果时间上相同,则覆写数据
if(compareOneLogByTime(stack,oldOneLog+q) == 1){
lengOfcollision++;
writeOneLog(stack,output,0);
isWritten=1;
}else{
writeOneLog(oldOneLog+q,output,0);
}
}
if(isWritten == 0)
writeOneLog(stack,output,0);
isLastColli = isWritten;
free(oldOneLog);
fclose(output);
}
//写入单个一条记录
void writeOneLog(OneLog *one,FILE *output,int withTitle){
char *operator=(char *)one;
for(int q=0;q<PIECE_LENGTH;q++){
//注意有逗号时加上"(当不保存"时,此处应该更改数据,不要在数据中加入")
//char *hascomma = strchr(operator,',');
if(withTitle == 1) fprintf(output,"%s: ",strupr(PIECE[q]));
//if(hascomma!=NULL) fprintf(output,"\"");
fprintf(output,operator);
//if(hascomma!=NULL) fprintf(output,"\"");
if(q!=PIECE_LENGTH-1){
fprintf(output,",");
if(withTitle == 1) fprintf(output," ");
}
//锁定到每一个字段
operator+=LOGBIT;
}
fprintf(output,"\n");
}
//一次性读完所有数据内容
int readALL(char **data,char *fileName){
FILE * pfile = fopen(fileName,"rb");
if(fileName == NULL){
*data = NULL;
return -1;
}
//指针移到文件末尾
fseek(pfile,0,SEEK_END);
//ftell计算偏移字节数,使flen储存文件长度
int flen = ftell(pfile);
//开辟内存,存放读取的字符串,注意为\0保留多一个字符的空间
*data = (char *)malloc((flen+1) * sizeof(char));
//文件指针返回开头
rewind(pfile);
//一次性读完
flen = fread(*data,1,flen,pfile);
(*data)[flen] = '\0';
fclose(pfile);
return 0;
}
int writeAdi(char *fileName){
FILE *output = fopen(fileName,"wb");
if(!output) return -1;
OneLog *database = readMyBinaryLog();
//写入每一条记录
for(int q=0;q<lengOfMyLog;q++){
//写入每一个字段
for(int w=0;w<PIECE_LENGTH;w++){
//不想用strcat连成一个标题了,直接多次写入。
fprintf(output,"<");
fprintf(output,strupr(PIECE[w]));
fprintf(output,":");
//数字(长度信息)转字符串
char temp[3],*onePiece=NULL;
//定位到当前字段
onePiece = (char *)(database+q) + LOGBIT*w;
sprintf(temp,"%d", strlen(onePiece));
fprintf(output,temp);
fprintf(output,">");
fprintf(output,onePiece);
}
fprintf(output,"<EOR>\n");
}
fclose(output);
free(database);
return 0;
}
int writeCsv(char *fileName){
FILE *output = fopen(fileName,"wb");
if(!output) return -1;
OneLog *database = readMyBinaryLog(MyLog);
//写入文件头
fprintf(output,"QSO_DATE,TIME_ON,FREQ,MODE,CALL,RST_RCVD,RST_SENT\n");
for(int q=0;q<lengOfMyLog;q++){
writeOneLog(database+q,output,0);
}
fclose(output);
free(database);
return 0;
}
//判断按照何种格式输入(0)输出(1)
int judgeFileName(char *fileName,int inorout){
//用于检测文件名是否合法
int errorCode = -1;
int leng = strlen(fileName);
const int *fileleng = NULL;
if(strcmp(fileName+leng-4,".adi") == 0){
//查找到以.adi结尾
fileleng= &lengOfOtherAdi;
errorCode = inorout==0 ? readAdi(fileName):writeAdi(fileName);
}
else if(strcmp(fileName+leng-4,".csv") == 0){
//查找到以.csv结尾
fileleng = &lengOfOtherCsv;
errorCode = inorout==0 ? readCsv(fileName):writeCsv(fileName);
}
else{
return -1;
}
//输出ui信息
if(inorout == 0){
printf("Added %d records from %s,overrided %d records.\nLogs number in database: %d \n",
*fileleng-lengOfcollision,fileName,lengOfcollision,lengOfMyLog+ (!isLastColli) );
}else{
printf("Write totally %d records to %s.\n",lengOfMyLog,fileName);
}
return errorCode;
}
//按照某字段名查找记录
int searchByPieceName(char *contant,int type){
if(type<0 && type>=PIECE_LENGTH) return 0;
OneLog *database = readMyBinaryLog();
int count=0;
for(int q=0;q<lengOfMyLog;q++){
if(strcmp((char *)(database+q) + type*LOGBIT ,contant) == 0){
//目前可重用性最高的函数:csv输出单条记录
writeOneLog((database+q),stdout,1);
count++;
}
}
free(database);
printf("Search %d records from database, %d found.\n",lengOfMyLog,count);
return 0;
}
int searchByTime(char *startTime,char *endTime){
if(strlen(startTime)<14 || strlen(endTime)<14) return -1;
int count=0;
//写得非常机械的地方,或许可插入\0分隔
char startDay[9],startSec[7],endDay[9],endSec[7];
strncpy(startDay,startTime,8);
startDay[8]='\0';
strncpy(startSec,startTime+8,6);
startSec[6]='\0';
strncpy(endDay,endTime,8);
endDay[8]='\0';
strncpy(endSec,endTime+8,6);
endSec[6]='\0';
//printf("%s %s %s %s\n",startDay,startSec,endDay,endSec);
OneLog *database = readMyBinaryLog(),*tempdata = NULL;
for(int q=0;q<lengOfMyLog;q++){
tempdata=database+q;
int DayMatch = strcmp((tempdata)->qso_date,startDay)
+ strcmp((tempdata)->qso_date,endDay);
int SecMatch = strcmp((tempdata)->time_on,startSec)
+ strcmp((tempdata)->time_on,endSec);
if(DayMatch == 0){
count++;
writeOneLog((database+q),stdout,1);
}else if(DayMatch == -1 || DayMatch == 1){
if(SecMatch>=-1 && SecMatch<=1){
count++;
writeOneLog((database+q),stdout,1);
}
}
}
free(database);
printf("Search %d records from database, %d found.\n",lengOfMyLog,count);
return 0;
}
//主函数
int main(int argNum,char **argValue){
//处理命令行参数指令,从1开始,跳过命令字符串argValue[0]
for(int q=1;q<argNum;q++){
//检测命令合法性
if(strlen(argValue[q])<=1\
|| argValue[q][0] !='-' || (argNum-q)<=1){
return 0;
}
//从1开始,跳过字符'-'
switch (argValue[q][1])
{
case 'i':
//导入文件,按照后缀名判别需要执行的函数
judgeFileName(argValue[++q] ,0);
break;
case 'o':
//导出文件
judgeFileName(argValue[++q], 1);
break;
case 's':
//按照字段contant搜索并打印数据
searchByPieceName(argValue[++q],4);
break;
case 'l':
//按照起止时间搜索并打印数据(此处再一次性读取两个时间参数)
if((argNum-q)<=2){
return 0;
}
searchByTime(argValue[++q],argValue[++q]);
break;
default:
//未知命令
q++;
break;
}
}
return 0;
}