代码之家  ›  专栏  ›  技术社区  ›  Arkaik zx81

使用可选/空格式说明符分析框架

  •  3
  • Arkaik zx81  · 技术社区  · 6 年前

    我正在尝试解析以下方案中格式化的帧:

    $[number],[number],[number],<string>;[string]~<string>
    

    用“[]”包围的参数是可选的,而用“<>”包围的参数始终是定义的:

    因此,以下框架都是正确的:

    $0,0,0,thisIsFirstString;secondString~thirdOne
    $0,,0,firstString;~thirdOne
    $,,,firstString;~thirdString
    

    当前,当所有元素都包含以下代码时,我能够分析框架

    int main() {
        char frame[100] = "$1,2,3,string1;string2~string3";
        char num1[10], num2[10], num3[10], str1[100], str2[100], str3[100];
    
        printf("frame : %s\n", frame);
    
        sscanf(frame,"$%[^,],%[^,],%[^,],%[^;];%[^~]~%s", num1, num2, num3, str1, str2, str3);
    
        printf("Number 1 : %s\n", num1);
        printf("Number 2 : %s\n", num2);
        printf("Number 3 : %s\n", num3);
        printf("String 1 : %s\n", str1);
        printf("String 2 : %s\n", str2);
        printf("String 3 : %s\n", str3);
    
        return 0;
    }
    

    结果如下

    frame : $1,2,3,string1;string2~string3
    Number 1 : 1
    Number 2 : 2
    Number 3 : 3
    String 1 : string1
    String 2 : string2
    String 3 : string3
    

    但是,如果缺少某个参数,则会很好地分析前面的参数,但不会分析后面的参数。

    frame : $1,,3,string1;string2~string3
    Number 1 : 1
    Number 2 : 
    Number 3 : 
    String 1 :��/�
    String 2 : �\<��
    String 3 : $[<��
    
    frame : $1,2,3,string1;~string3
    Number 1 : 1
    Number 2 : 2
    Number 3 : 3
    String 1 : string1
    String 2 : h�v��
    String 3 : ��v��
    

    我如何指定 sscanf 帧中可能缺少某些参数,因此在这种情况下,这些参数将被丢弃?

    3 回复  |  直到 6 年前
        1
  •  1
  •   J...S    6 年前

    正如其他人所说 scanf() 系列函数可能不适合这样做,因为如果输入字符串不是预期格式,则不可能进行适当的错误处理。

    但是,如果您确定输入字符串将始终采用这种格式,则可以使用指向输入字符串相关部分的指针,然后使用 sscanf() .

    首先将所有字符数组初始化为空字符串,这样它们在打印时不会显示垃圾。像

    char num1[10]="";
    

    为了所有的 char 要将提取的参数写入其中的数组。

    声明一个字符指针并使其指向 frame 这是输入字符串。

    char *ptr=frame;
    

    现在检查第一个可选参数,如

    if(sscanf(ptr, "$%[^,],", num1)==1)
    {
        //parameter 1 is present.
        ptr+=strlen(num1);  
    }
    ptr+=2;
    

    如果参数存在,则递增 ptr 通过参数字符串的长度和 2 为“$”和逗号完成。

    接下来的两个参数也是可选的。

    if(sscanf(ptr, "%[^,]", num2)==1)
    {
        //Parameter 2 is present
        ptr+=strlen(num2);  
    }
    ptr+=1;
    
    if(sscanf(ptr, "%[^,]", num3)==1)
    {
        //Parameter 3 is present
        ptr+=strlen(num3);  
    }
    ptr+=1;
    

    下一个参数,参数4,不是可选的。

    sscanf(ptr, "%[^;]", str1);
    ptr+=strlen(str1)+1;
    

    现在,对于可选参数5,

    if(sscanf(ptr, "%[^~]", str2)==1)
    {
        //Parameter 5 is present
        ptr+=strlen(str2);  
    }
    ptr+=1; //for ~
    

    最后,对于非可选参数6,

    sscanf(ptr, "%s", str3);
    

    为简洁起见,省略了可能的错误检查。为了防止溢出,请在 扫描因子() 字符串格式

    sscanf(ptr, "%9[^,],", num2);
    

    在哪里? 9 是一个小于 num2 字符数组。

    在你的计划中, 苏桑夫(S) 如果 %[^,] 部分对应于空字符串。

        2
  •  2
  •   KamilCuk    6 年前

    最好还是编写自己的解析器函数:

    #define _GNU_SOURCE 1
    #define _POSIX_C_SOURCE 1
    #include <stdio.h>
    #include <stddef.h>
    #include <assert.h>
    #include <string.h>
    #include <stdlib.h>
    
    #define __arraycount(x) (sizeof(x)/sizeof(x[0]))
    
    // from https://stackoverflow.com/a/3418673/9072753
    static char *mystrtok(char **m,char *s,char c)
    {
      char *p = s ? s : *m;
      if (!*p)
        return NULL;
      *m = strchr(p, c);
      if (*m)
        *(*m)++ = '\0';
      else
        *m = p + strlen(p);
      return p;
    }
    
    static char getseparator(size_t i)
    {
        return i <= 2 ? ',' : i == 3 ? ';' : i == 4 ? '~' : 0;
    }
    
    int main()
    {
        char ***output = NULL;
        size_t outputlen = 0;
        const size_t outputstrings = 6;
    
        char *line = NULL;
        size_t linelen = 0;
        size_t linecnt;
        for (linecnt = 0; getline(&line, &linelen, stdin) > 0; ++linecnt) {
            if (line[0] != '$') {
                printf("Lines not starting with $ are ignored\n");
                continue;
            }
    
            // alloc memory for new set of 6 strings
            output = realloc(output, sizeof(*output) * outputlen++);
            if (output == NULL) {
                fprintf(stderr, "%d Error allocating memory", __LINE__);
                return -1;
            }
            output[outputlen - 1] = malloc(sizeof(*output[outputlen - 1]) * outputstrings);
            if (output[outputlen - 1] == NULL) {
                fprintf(stderr, "%d Error allocating memory", __LINE__);
                return -1;
            }
    
            // remove closing newline
            line[strlen(line)-1] = '\0';
    
            //printf("Read line `%s`\n", line);
    
            char *token;
            char *rest = &line[1];
            char *state;
            size_t i;
            for (i = 0, token = mystrtok(&state, &line[1], getseparator(i)); 
                    i < outputstrings && token != NULL;
                    ++i, token = mystrtok(&state, NULL, getseparator(i))) {
                output[outputlen - 1][i] = strdup(token);
                if (output[outputlen - 1][i] == NULL) {
                    fprintf(stderr, "%d Error allocating memory", __LINE__);
                    return -1;
                }
                //printf("Read %d string: `%s`\n", i, output[outputlen - 1][i]);
            }
            if (i != outputstrings) {
                printf("Malformed line: %s %d %p \n", line, i, token);
                continue;
            }
        }
        free(line);
    
        for (size_t i = 0; i < outputlen; ++i) {
            for (size_t j = 0; j < outputstrings; ++j) {
                printf("From line %d the string num %d: `%s`\n", i, j, output[i][j]);
            }
        }
    
        for (size_t i = 0; i < outputlen; ++i) {
            for (size_t j = 0; j < outputstrings; ++j) {
                free(output[i][j]);
            }
            free(output[i]);
        }
        free(output);
    
        return 0;
    }
    

    输入内容:

    $0,0,0,thisIsFirstString;secondString~thirdOne
    $0,,0,firstString;~thirdOne
    $,,,firstString;~thirdString
    

    产生结果:

    From line 0 the string num 0: `0`
    From line 0 the string num 1: `0`
    From line 0 the string num 2: `0`
    From line 0 the string num 3: `thisIsFirstString`
    From line 0 the string num 4: `secondString`
    From line 0 the string num 5: `thirdOne`
    From line 1 the string num 0: `0`
    From line 1 the string num 1: ``
    From line 1 the string num 2: `0`
    From line 1 the string num 3: `firstString`
    From line 1 the string num 4: ``
    From line 1 the string num 5: `thirdOne`
    From line 2 the string num 0: ``
    From line 2 the string num 1: ``
    From line 2 the string num 2: ``
    From line 2 the string num 3: `firstString`
    From line 2 the string num 4: ``
    From line 2 the string num 5: `thirdStrin`
    
        3
  •  1
  •   chqrlie    6 年前

    scanf() 无法转换句柄空字符类,并且 strtok() 将每个分隔符序列都视为单个分隔符,这实际上只适用于空白区域。

    下面是一个简单的类似scanf的非贪婪解析器:

    #include <ctype.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    
    int my_sscanf(const char *s, const char *fmt, ...) {
        int res = 0;
        va_list ap;
    
        va_start(ap, fmt);
        for (; *fmt; fmt++) {
            if (*fmt == '%') {
                fmt++;
                if (*fmt == 's') {
                    size_t i = 0, size = va_arg(ap, size_t);
                    char *dest = va_arg(ap, char *);
    
                    while (*s && *s != fmt[1]) {
                        if (i + 1 < size)
                            dest[i++] = *s;
                        s++;
                    }
                    if (size)
                        dest[i] = '\0';
                    res++;
                    continue;
                }
                if (*fmt == 'd') {
                    *va_arg(ap, int *) = strtol(s, (char **)&s, 10);
                    res++;
                    continue;
                }
                if (*fmt == 'i') {
                    *va_arg(ap, int *) = strtol(s, (char **)&s, 0);
                    res++;
                    continue;
                }
                /* add support for other conversions as you wish */
                if (*fmt != '%')
                    return -1;
            }
            if (*fmt == ' ') {
                while (isspace((unsigned char)*s))
                    s++;
                continue;
            }
            if (*s == *fmt) {
                s++;
            } else {
                break;
            }
        }
        va_end(ap);
        return res;
    }
    
    int main() {
        char frame[100] = "$1,,3,string1;~string3";
        char str1[100], str2[100], str3[100];
        int res, num1, num2, num3;
    
        printf("frame : %s\n", frame);
    
        res = my_sscanf(frame, "$%d,%d,%d,%s;%s~%s", &num1, &num2, &num3,
                        sizeof str1, str1, sizeof str2, str2, sizeof str3, str3);
    
        if (res == 6) {
            printf("Number 1 : %d\n", num1);
            printf("Number 2 : %d\n", num2);
            printf("Number 3 : %d\n", num3);
            printf("String 1 : %s\n", str1);
            printf("String 2 : %s\n", str2);
            printf("String 3 : %s\n", str3);
        } else {
            printf("my_scanf returned %d\n", res);
        }
        return 0;
    }