作业要求实现一个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;
}