【问题标题】:Format Checking in CC中的格式检查
【发布时间】:2014-02-09 06:16:00
【问题描述】:

我正在尝试用 c 语言编写一个程序,以从文本中读取溜冰场活动的大型数据库。有谁知道如何检查格式不正确的东西

即文本文档会有这样的内容

   sample
---------------------------------------------
date             startT    endT           END
_______________________________________________



Ice Rink 1

1/13/2014        1:50     3:50 PM              Public Skating

1/13/2014        1:50     3:50 PM              Game

ice rink 2

1/13/2014        1:50     3:50 PM              OPEN

我已经可以成功读入一行事件、日期时间和描述 但是如何跳过或检测与我的扫描样式不匹配的行

fscanf(ifp,"%d/%d/%d\t%d:%d%s\t%d:%d%s\t\t  %20c",
&e1[i].month,&e1[i].day,&e1[i].year,&e1[i].startH,&e1[i].startM,e1[i].MER1,&e1[i].endH,&e1[i].endM,e1[i].MER2,e1[i].event);

简而言之:如何检测与此不完全匹配的案例?

提前致谢

【问题讨论】:

  • 如果您有任何选择,您可能需要考虑一种更适合格式检查/文本处理类型工作的语言。 Python 跃入脑海,当然还有其他的。
  • 如果我没记错的话,这也可以用正则表达式来完成。在 C 的限制范围内,逐行检查返回 bool 有效性的方法。这会减慢程序的速度,因为现在代码必须遍历整行以确保正确性,然后才能进行操作。

标签: c string io


【解决方案1】:

正如其他人已经说过的,您可以检查fscanf 的返回值以找出一行是否为给定格式。然而,这不是理想的方法。首先,您的数据是按行组织的,但fscanf 将换行符视为任何其他空格。您可以先阅读带有fgets 的行,然后在该行上应用sscanf,但您仍然有一个很容易忘记的大型单一格式说明符。

我想提出另一种方法。你的数据行似乎是按字段组织的,它们之间用制表符分隔。您可以使用fgets 读取这些行,然后使用strtok 拆分它们,最后使用sscanf 扫描单独的字段。如果您将自定义包装函数写入 sscanf 语句,则可以在读取数据时对其进行完整性检查。

/*
 *      Return true if str has format "hh:min AM/PM"
 */
int scan_time(const char *str, int *hh, int *mm)
{
    char buf[4] = {0};
    int n;
    char c;

    n = sscanf(str, "%d:%d%4s %c", hh, mm, buf, &c);

    if (n == 4) return 0;                  /* trailing extra chars */
    if (n < 2) return 0;                   /* missing minutes */

    if (n == 3) {
        int key = (buf[0] << 16) + (buf[1] << 8) + buf[2];

        #define KEY(a, b) ((a << 16) + (b << 8))

        switch (key) {
        case KEY('a', 'm'):
        case KEY('A', 'M'):
            break;

        case KEY('p', 'm'):
        case KEY('P', 'M'):
            *hh += 12;
            break;

        default:
            return 0;                      /* invalid am/pm spec */
        }
    }

    if (*hh < 0 || *hh >= 24) return 0;    /* invalid hours */
    if (*mm < 0 || *mm >= 60) return 0;    /* invalid minutes */

    return 1;
}

/*
 *      Return true, if str has format "mm/dd/year"
 */
int scan_date(const char *str, int *yy, int *mm, int *dd)
{
    static const int mdays[] = {
        0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
    };
    int n;
    char c;

    n = sscanf(str, "%d/%d/%d %c", mm, dd, yy, &c);

    if (n == 4) return 0;                   /* trailing extra chars */
    if (n < 2) return 0;                    /* missing day */
    if (n == 2) *yy = 2014;                 /* set default value */
    if (*yy < 100) *yy += 2000;             /* allow 1/1/14 */

    if (*mm < 1 || *mm > 12) return 0;      /* invalid month */
    if (*dd < 1 || *dd > mdays[*mm]) return 0;
    if (*mm == 2 && *dd == 29 % *yy % 4) return 0;
                                            /* invalid day */
    return 1;
}

/*
 *      Return true if line is "date \t time \t time \t text"
 */
int scan_line(char *str, struct Event *ev)
{
    char *token;

    token = strtok(str, "\t");
    if (token == NULL) return 0;
    if (!scan_date(token, &ev->year, &ev->month, &ev->day)) return 0;

    token = strtok(NULL, "\t");
    if (token == NULL) return 0;
    if (!scan_time(token, &ev->startH, &ev->startM)) return 0;

    token = strtok(NULL, "\t");
    if (token == NULL) return 0;
    if (!scan_time(token, &ev->endH, &ev->endM)) return 0;

    token = strtok(NULL, "\t");
    if (token == NULL) return 0;
    strncpy(ev->event, token, 40);

    return 1;
}

/*
 *      Remove trailing newline
 */
void chomp(char *str)
{
    int l = strlen(str);

    if (l && str[l - 1] == '\n') str[l - 1] = '\0';
}

/*
 *      Scan file with events
 */
int scan_file(const char *fn)
{
    FILE *f = fopen(fn, "r");

    if (f == NULL) return -1;
    for (;;) {
        struct Event ev;
        char line[200];

        if (fgets(line, 200, f) == NULL) break;
        chomp(line);
        if (scan_line(line, &ev)) {
            printf("%s on %d/%d/%d\n", 
                ev.event, ev.month, ev.day, ev.year);
        }
    }
    return 0;
}

这里,scan_xxx 函数扫描一条数据,检查格式,分配数据并对数据进行基本检查,这样您就永远不会在 1 月 32 日或 35 日收到事件: 00小时。

这使得扫描功能比单次调用sscanf 更复杂,但也有一些好处。首先,在读取格式时进行检查。这意味着您不必检查客户端代码中的数据,因为您可以依赖合理的值。这也意味着您不必重复代码:请注意时间检查是如何只编码一次的,即在scan_time 中,尽管对于开始时间和结束时间每行应用两次。

在封装函数中按字段处理数据允许您更改格式。例如,您可以允许“1pm”作为“1:00 pm”的有效快捷方式。当第一种格式失败时,您只需要使用第二种格式字符串重新扫描您的时间字段。您也可以使用长单行格式来做到这一点,但由于您有两个时间字段,这并不容易。

还要注意上面的代码如何接受 14 作为 2014 的快捷方式,并将缺失的年份解释为 2014。所有这些对于简单的数据扫描工具来说似乎有点过于复杂,但您可以在类似的项目中重复使用您的函数。此外,编写这些整洁的函数比纠缠冗长的scanf 格式更有趣。

【讨论】:

  • 我同意标记化是另一种好方法,具体取决于您要付出多少努力。我还认为,仅用于解析简单文件的大量代码证明 C 不是正确的语言,如果您有任何选择:) :)
  • @GreenAsJade:嗯,上面的大部分代码都是验证的东西。核心阅读归结为几个sscanfstrtok。它仍然比脚本语言中的等效行长,但如果它是更大程序的一部分,或者如果用户不了解此类语言,C 可能仍然是一个不错的选择。 (我的一些年长的同事会为此使用 Fortran - 因为他们知道这一点。)
【解决方案2】:

您可以查看 fscanf 的返回:“成功时,函数返回成功填充的参数列表的项目数。此计数可以与预期的项目数匹配或由于匹配失败而更少(甚至为零) 、读取错误或到达文件末尾。”如果您知道要匹配的项目数量,则可以检查成功匹配的项目数量。现在意识到这一点,下一个将在第一个停止的地方接起。意思是下一个 fscanf 从另一个停止的地方开始,或者在完成完整的 fscanf 时,它第一次遇到不属于格式的东西。

只是头脑风暴,但您可以做的就是使用某种形式的 fget 来获取该行,直到出现 '\n' 并且对该行不执行任何操作。

【讨论】:

  • 您可以使用fgets 从文件中读取行,然后如上所述使用sscanf 扫描这些行。这应该摆脱换行问题。
  • 我不知道我可以用 Fscanf 做到这一点,谢谢!
【解决方案3】:

请注意,无论您做什么,因为您有多个输入行的“类型”,所以您不会将数据直接扫描到程序中的变量中。在决定如何处理线路之前,您必须知道自己是否有溜冰场名称或活动条目。

因此,您将首先读入整行,然后对其进行处理(并随时转储空行)

您可以使用 sscanf 查看该行的格式是否可接受。您需要先根据活动条目的格式对其进行测试,因为您会得出结论,如果第一个元素不匹配(时间的第一个数字),那么您必须有一个 rink-name。然后看看你是否可以将结果扫描成合适的溜冰场名称(你可能想检查一下这些)。

如果活动条目的 sscanf 在除第一个条目之外的任何其他项上都失败了,您可以告诉您的用户它是哪一个,因此它是错误的(IE 如果 sscanf 返回 3,那么您知道日期没有'不能正确扫描)。

【讨论】:

    猜你喜欢
    • 2023-03-12
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 2013-10-01
    • 2011-08-13
    • 2013-02-24
    • 2013-10-13
    • 2016-05-04
    相关资源
    最近更新 更多