首页 > 解决方案 > 使用宽度和边距输出格式化文本

问题描述

我正在尝试编写一个程序,该程序将接受以下输入并将其格式化并将其输出到文本文件。

这是它应该如何工作的图片

?mrgn left:命令后面的每一行将从左边距开始缩进左边的空格。请注意,此缩进必须包含在页面宽度中。如果此命令未出现在输入文件中,则 left 的值为 0(零)。

到目前为止,我做了以下事情:

 while (fgets(line, MAX_LINE_LEN-1, infile) != NULL){/*Read the first line if it is not empty*/
    char last[MAX_WORD_LEN] = {0};
    char *p;
    for (p = strtok(line, " "); p; p = strtok(NULL, " ")){

        if(*last && strcmp(last, width)==0){
            iwidth = atoi(p);
            printf("width = %d\n", iwidth);
        }
        if(*last && strcmp(last, margin)==0){
            imargin = atoi(p);
            printf("margin = %d\n", imargin);
        }
        strncpy (last, p, MAX_WORD_LEN);
        if(iwidth != 0 || imargin != 0){
            printf("%s ", p);
        }else{
            printf("%s", line);
        }

    }
}

我能够将宽度和边距的值存储到变量中。我现在被困在如何指定所需的格式上。我做了一些研究,但找不到我想要的。请帮忙!谢谢!干杯!

标签: c

解决方案


在你有几个小时来解决这个问题之后,让我给你一些建议(不是双关语),这可能有助于简化你解决问题的方法。虽然您当然可以使用从输入文件strtok中解析您的选项,width但当您知道包含该选项的行格式将为"?name value".

一种简化的方法,知道您的文件包含作为第一行的选项,只需读取整行(使用fgets或 POSIX getline),验证第一个字符是'?',然后从带有. (您可以在format-string中包含,或者只是从第二个字符开始解析。(我的选择)要完成此操作,您可以从类似于以下内容开始:namevaluesscanf'?'

#include <stdio.h>
#include <string.h> /* for strcmp, strlen */

#define OPT    32   /* if you need constants, #define one (or more) */
#define WDTH   78
#define MAXC 1024

void str_word_wrap (char *buf, int n);  /* wrap function prototype */

int main (void) {

    char buf[MAXC] = "",    /* buffer to hold words in file */
        *p = buf,           /* pointer to buf */
        opt[OPT] = "";      /* buffer to hold option found in file */
    int width  = WDTH,      /* variable holding width from option */
        used = 0,           /* number of chars used in buf */
        val = 0;            /* temp value to read option value from file */

    /* option on line 1, read entire line */
    if (!fgets (buf, MAXC, stdin) || *buf != '?')
        fputs ("error: unexpected file format, using default width.\n\n", 
                stderr);

    /* parse option and value, compare option is "width", use val as width */
    if (sscanf (buf + 1, "%s %d", opt, &val) == 2) {
        if (strcmp (opt, "width") == 0)
            width = val;
    }
    ...

此时在您的代码中,buf包含第一行,opt包含选项名称,并width包含文件中指定的宽度(如果第一行不包含信息,则为默认宽度WDTH( ) )。理想情况下,如果第一行不是有效的选项/值行,您只需从 中删除多余的空格,添加一个结尾并继续,但该代码留给您。78"?width val"buf' '

(注意:我只是将文件重定向到,stdin所以我从这里读取stdin而不是文件 - 但你infile也很好。你只需替换infile我阅读的地方stdin

由于您只是想从输入文件中消除所有额外的空白,留下一个正常格式化的段落,您将把它换成指定的宽度,使用fscanf格式"%s" 说明符可以自动处理空白删除。(使用scanf家庭"%s"和数字格式说明符忽略前导空格,"%c"并且"%[..]"不要)。因此,将文件的其余部分读入缓冲区只需读取文件中的每个单词,跟踪缓冲区中有多少个字符used(这样你就知道下一个单词适合),然后添加一个' '空格)将它们添加到缓冲区时,在每个单词之间。

如果有帮助,您可以使用strcat,或者您可以简单地使用指针并' '在当前缓冲区末尾写入 a ,然后在每次迭代中写入一个nul 终止字符。无论哪种方式,只需跟踪您到目前为止使用了多少个字符以及len您正在添加的内容(长度),然后在您used进行时使用每个单词的长度更新您的计数。您可以执行以下操作:

    while (scanf ("%s", p + used) == 1) {   /* read each word, ignore WS */
        size_t len = strlen (p + used);     /* get length of word */
        if (used + len + 2 >= MAXC) {       /* make sure it fits with ' ' */
            fputs ("warning: file truncated.\n", stderr);
            break;  /* note you can refine to save 1-char space at end */
        }
        *(p + used + len++) = ' ';  /* add space at end of word */
        *(p + used + len) = 0;      /* nul-termiante after space */
        used += len;                /* update used with len */
    }
    *(p + --used) = 0;  /* overwrite final ' ' with nul-character */

此时,您可以width在将行换行之前写出您的值和填充缓冲区的内容以进行检查width。我只是width在输出完成程序功能的换行之前写出用过main()的,例如

    printf ("Wrapping file at width: %d\n\n", width);
    str_word_wrap (buf, width);     /* wrap buffer at width chars/output */

    return 0;
}

剩下的就是完成将缓冲区包装为在输出缓冲区时每行不再包含该width字符的功能。我在我的原始评论中提供了上面的原型的str_word_wrap功能和详细信息,关于包装缓冲区的方法,只需使用长度的滑动窗口width处理缓冲区,每次向下移动时输出适合滑动窗口的单词缓冲区。

要完成任务,您通常使用三个指针(我命名p为指向当前字符sp的指针、窗口的开始指针和窗口ep的结束指针。方案是这样的,您从所有三个初始化为您的缓冲区,然后您遍历每个字符,p直到p指向单词之间的空格,ep = p;每次遇到空格时设置结束指针.在每次迭代中检查 if p - sp >= width,其中p - sp只是当前指针地址减去起始指针地址它告诉你从一开始你移动了多少个字符。如果等于或超过你的width,你知道你最后一组ep(您的结束指针)指向窗口中的最后一个空格,标记要输出的最后一个单词的结尾。

剩下的就是将行输出到结束指针(和 a '\n'),然后将新的开始指针设置为结束指针之后的下一个字符,您可以将结束指针设置为当前指针之后的一个(向前滑动你的窗口)然后你重复。不需要任何花哨的东西。像下面这样的东西可以正常工作:

void str_word_wrap (char *buf, int n)
{
    char *p = buf,      /* pointer to current char */
        *sp = buf,     /* pointer to start of line to print */
        *ep = buf;     /* pointer to end of line to print */

    for (; *p && *p != '\n'; p++) { /* loop over each char (omit '\n')*/
        if (*p == ' ')              /* if space, set ep */
            ep = p;
        if (p - sp >= n) {          /* if wrap length 'n' reached */
            while (sp < ep)         /* loop outputting chars sp -> ep */
                putchar (*sp++);
            putchar ('\n');         /* tidy up with '\n' */
            sp = ++ep;              /* set start to next after end */
            ep = ++p;               /* set end to next after current */
        }
    }
    while (*sp && *sp != '\n')      /* output last line of chars */
        putchar (*sp++);
    putchar ('\n');                 /* tidy up with final '\n' */
}

完全可以解决您的问题,确切地说:

示例输入文件

$ cat dat/taggedparagraph.txt
?width 30
While there    are enough characters    here to
fill
   at least one line, there is
plenty
of
           white space that needs to be
eliminated
from the original
         text file.

现在只需使用文件作为输入运行程序或将文件重定向到程序即可stdin

示例使用/输出

$ ./bin/wrapped_tagged_p < dat/taggedparagraph.txt
Wrapping file at width: 30

While there are enough
characters here to fill at
least one line, there is
plenty of white space that
needs to be eliminated from
the original text file.

看看事情,如果你有问题,请告诉我。这一切都归结为基本的指针算法并跟踪您在缓冲区中的位置,同时迭代缓冲区中的每个字符以从中提取您需要的任何特定信息。您经常会听到在缓冲区上方或下方被称为“walking-a-pointer”的说法。使用滑动窗口只不过是在跟踪您开始的固定点并将步行限制为不超过某个固定字符宽度的同时移动指针,并做任何您需要的事情。一遍一遍,直到你到达终点。

帮助“了解指针”

由于在您的问题下方的评论中您提到您“将学习指针”,因此从基础开始:

指针只是一个普通变量,它保存其他东西的地址作为它的值。换句话说,一个指针指向可以找到其他东西的地址。您通常会想到一个保存立即值的变量,例如int a = 5;,一个指针只会保存5存储在内存中的地址,例如int *b = &a;

要引用指针持有的地址处的值,您可以通过在指针名称前使用一元字符来取消引用指针。'*'例如,b保存a(例如b指向a)的地址,因此要获取 保存的地址处的值b,您只需取消引用 b,例如*b

无论指针指向什么类型的对象,它的工作方式都是相同的。它能够以这种方式工作,因为type指针的 控制指针算术,例如,使用char *指针,pointer+1指向下一个字节,对于int *指针(普通 4 字节整数),pointer+1将指向int偏移 4 字节的下一个之后pointer。(所以一个指针,只是一个指针......其中算术由 自动处理type

当您在 C 中处理字符串时,您可以从字符串的开头到结尾进行迭代,检查每个字符,并在到达每个字符串末尾的nul 终止字符时停止。空字符用作字符串结尾的标记。你会看到它表示为'\0'或只是简单0的。两者是等价的。ASCII 字符'\0'具有整数值0

一个走指针的简单例子可能有助于巩固这个概念:

#include <stdio.h>

int main (void) {

    char buf[] = "walk-a-pointer down buf",     /* buffer  */
        *p = buf;       /* initialize p to point to buffer */

    /* dereference the pointer to get the character at that address */
    while (*p) {        /* while *p != 0, or (*p != '\0') */
        putchar (*p);   /* output each character */
        p++;            /* advance pointer to next char */
    }
    putchar ('\n');     /* then tidy up with a newline */

    return 0;
}

示例使用/输出

$ ./bin/walkpointer
walk-a-pointer down buf

推荐阅读