-3

There are similar questions but my problem is a little more specific. I have a C code that takes file.txt and returns file.txt.rle when I am encoding by using RLE algorithm. Same way I decode that and want to write from file.txt.rle and return file.txt. The following code is what I am using as I go from file.txt to file.txt.rle:

char name[NAME_SIZE];
if(sprintf(name, "%s.rle", argv[1]) >= sizeof(name)){
    fprintf(stderr, "Destination file name is too long\n");
}
while((o_fp = fopen(name, "wb")) == NULL){
    fprintf(stderr, "Can't create the file to be written\n");
      exit(1);
}

How can I accomplish to change the extension from file.txt.rle to file.txt when I decode? A full code won't help because I will use this in a code that decodes an encoded file.

Note: The given will always be in .txt.rle format and the returning file should always convert it to .txt.

C.J
  • 169
  • 1
  • 12
  • The `ÿ` means you're probably using `getchar()` or `getc()` and a `while (!feof(fp))` loop — but [`while (!feof(file))` is always wrong](https://stackoverflow.com/questions/5431941/), as your erroneous output ably demonstrates. If you think you've got the loop handling correct, show us the code — we can't reliably debug code we can't see. We can make guesses (that's a common cause of trouble) but we can only guess. – Jonathan Leffler Jun 28 '18 at 03:52
  • @JonathanLeffler I am using `getc()` in a for loop. Conditiion is `variable != EOF` – C.J Jun 28 '18 at 03:52
  • Is your question saying that you type `file.txt.rle` as the input name, and you want `file.txt` as the output name? – Jonathan Leffler Jun 28 '18 at 03:54
  • 1
    On posix, you can use [`rename()`](http://man7.org/linux/man-pages/man2/rename.2.html) – Stargateur Jun 28 '18 at 03:55
  • @JonathanLeffler Yes. It takes `file.txt.rle` as an input and I want the output to be `file.txt` – C.J Jun 28 '18 at 03:55
  • @Stargateur Would it keep the file name same without hardcoding? `file.txt.rle` is an example if input is `x.txt.rle` I want output to be `x.txt` – C.J Jun 28 '18 at 03:57
  • So, how do you know what the output name should be? Do you remove all the characters after the last dot? If so, which standard library function(s) help with that? You may have to search through the functions declared by `` but there's one that does the finding for you. You then need to think about whether you modify the original string or a copy of it, or what. – Jonathan Leffler Jun 28 '18 at 03:57
  • @JonathanLeffler the name should be defined by the input `x.txt.rle` gets in `x.txt` gets out. In my encoding `x.txt` gets in `x.txt.rle` gets out and I achieved that using the code above with one difference `name, "%s.rle", argv[1]` – C.J Jun 28 '18 at 03:59
  • Given that your `for` loop condition is `variable != EOF`, it means that the body of your loop is probably `int c = getc(i_fp); putc(c, o_fp);` so you output the character before testing whether you read EOF or not. The `ÿ` character has code 0xFF in 8859-1 and 8859-15 and some other code sets — and EOF also maps to 0xFF in some contexts. Hence the trouble. – Jonathan Leffler Jun 28 '18 at 04:00
  • @JonathanLeffler Accurate `int c = getc(i_fp) and then putc(c, o_fp)`. and it return ff. How can I solve it? – C.J Jun 28 '18 at 04:02
  • So, please show the code you're trying to use — both for the file name mapping, and for the actual I/O operations. We're not here to play 20 questions. After you read the file name, you need to make provision for a copy of it to hold the name with the `.rle` extension. What will you do if the user types `file.txt` instead of `file.txt.rle`, or they type `abstemious` with no dots in the name, or `fullstop.` with nothing after the dot, or `.profile` with nothing before the dot, or …? Your `while` loop could be an `if` since you exit from the loop body unconditionally. – Jonathan Leffler Jun 28 '18 at 04:02
  • `int c; while ((c = getc(i_fp)) != EOF) putc(o_fp);` is sufficient. If you can't stand assignments in conditions, then you have to use something more long-winded and repetitious instead: `int c = getc(i_fp); while (c != EOF) { putc(c, o_fp); c = getc(i_fp); }`. – Jonathan Leffler Jun 28 '18 at 04:04
  • @JonathanLeffler Updated the question with the code. For the name I really don't have any solutions I tried a lot of things but couldn't accomplish the result. Whatever gets in with whatever extension I want to keep the name same but want a .txt output – C.J Jun 28 '18 at 04:05
  • @JonathanLeffler well there is count that I need to take care of since it is decoding something like 01 A 02 B 03 C to ABBCCC – C.J Jun 28 '18 at 04:20
  • 2
    `if(sprintf(name, "%s", argv[1]) >= sizeof(name)){ fprintf(stderr, "Destination file name is too long\n"); }` You know this is superfluous? If you overwrite the array then you have undefined behaviour and even if you test for it the compiler isn't obligated to print that message or do anything else. – Jerry Jeremiah Jun 28 '18 at 04:21
  • @JerryJeremiah Yes, I know it is unnecessary, I just wanted to put it to the question because that's how I kind of accomplish from `.txt to .txt.rle`. I really don't know how to accomplish what I want in the sense of changing the extension – C.J Jun 28 '18 at 04:23
  • Please make an [mcve] instead of describing in comments what the code around the quoted code looks like. – Yunnosch Jun 28 '18 at 06:02
  • Please add sample input and show desired and actual output. – Yunnosch Jun 28 '18 at 06:02
  • 1
    If the commandline parameter says "file.name.txt.rle", what filename and extension should the output file have? Please try to make a rule how to derive the output name from the input name. Supporting that rule with a few examples is helpful, but ONLY giving an example is not sufficient. – Yunnosch Jun 28 '18 at 06:10
  • 1
    To begin with: `while((o_fp = fopen(name, "wb")) == NULL)` -> `if ((o_fp = fopen(name, "wb")) == NULL)`. However it works also with `while` but it's somewhat unusual to use `while` here. – Jabberwocky Jun 28 '18 at 06:11

3 Answers3

0

You can simply do this with:

  • strrchr to find where is the last period in string,
  • strlen / malloc to allocate memory to store the new name,
  • sprintf to create the new name.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* this function will create a new name, replacing the existing extension
   by the given one.
   returned value should be `free()` after usage

   /!\ warning: 
        * validity of parameters is not tested
        * return of strdup and malloc are not tested.
   */
char *replace_ext(const char *org, const char *new_ext)
{
    char *ext;

    /* copy the original file */
    char *tmp = strdup(org);

    /* find last period in name */
    ext = strrchr(tmp , '.');

    /* if found, replace period with '\0', thus, we have a shorter string */
    if (ext) { *ext = '\0'; }

    /* compute the new name size: size of name w/o ext + size of ext + 1 
       for the final '\0' */
    size_t new_size = strlen(tmp) + strlen(new_ext) + 1;

    /* allocate memory for new name*/
    char *new_name = malloc(new_size);

    /* concatenate the two string */
    sprintf(new_name, "%s%s", tmp, new_ext);

    /* free tmp memory */
    free(tmp);

    /* return the new name */
    return new_name;
}

int main(void)
{
    int i;
    char *tests[] = { "test.ext", "test.two.ext", "test_no_ext", NULL};

    for (i = 0; tests[i]; ++i)
    {
        char *new_name = replace_ext(tests[i], ".foo");
        printf("%s --> %s\n", tests[i], new_name);
        free(new_name);
    }

    return 0;
}
Mathieu
  • 8,840
  • 7
  • 32
  • 45
  • Borrowing from a [comment](https://stackoverflow.com/questions/51074432/#comment89138356_51074432) I made to the question: _What will you do if the user types `file.txt` instead of `file.txt.rle`, or they type `abstemious` with no dots in the name, or `fullstop.` with nothing after the dot, or `.profile` with nothing before the dot, or …?_ The objective in the question is to remove the `.rle` extension, not to add an alternative. And it is not clear what is expected if the extensions isn't `.rle` (or even `.txt.rle`) — nor what should be done about the input `file.rle` either. – Jonathan Leffler Jun 28 '18 at 14:39
  • @JonathanLeffler It is always going to be .txt.rle and it should always return .txt – C.J Jun 28 '18 at 15:42
0

Here is an implementation. The magic here is carried out by the change_file_name(org, dest, size, ext), that checks whether the name org ends with ext, and in that case copies the name up to that point.

Hope this helps.

 /* Changes the name of the sys input file. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>


bool change_file_name(const char * org, char * dest, size_t max_length, const char * file_ext)
{
    bool toret = false;
    const size_t name_length = strlen( org );
    const size_t ext_length = strlen( file_ext );
    const size_t new_name_size = name_length - ext_length;

    if ( name_length > ext_length
      && name_length < max_length
      && strcmp( org + new_name_size, file_ext ) == 0 )
    {
        strncpy( dest, org, name_length - ext_length );
        *( dest + new_name_size ) = 0;
        toret = true;
    }

    return toret;
}

void convert_file(const char * org, const char * dest)
{
    printf( "Processing file '%s' into '%s'\n", org, dest );
}

int main(int argc, char *argv[])
{
    const int NAME_SIZE = 1024;
    const char * rle_ext = ".rle";
    char new_name[NAME_SIZE];
    int toret = EXIT_SUCCESS;

    if ( argc == 2 ) {
        if ( change_file_name( argv[ 1 ], new_name, NAME_SIZE, rle_ext ) ) {
            printf( "The new name is: '%s'\n", new_name );
            convert_file( argv[ 1 ], new_name );
        } else {
            toret = EXIT_FAILURE;
            fprintf( stderr,
                     "Name results empty, is not ending in '%s' or is too large: '%s'\n",
                     rle_ext,
                     argv[ 1 ] );
        }
    } else {
        toret = EXIT_FAILURE;
        fprintf( stderr, "Usage: %s <file name>.txt.rle\n", argv[ 0 ] );
    }

    return toret;
}
Baltasarq
  • 12,014
  • 3
  • 38
  • 57
  • 1
    You've not null terminated the modified name of the file. Add the line `memset(new_name, 'X', sizeof(new_name));` before the first `if` in `main()` and try running the code to see what I mean. If you use `strncpy()`, always consider null termination. Here, you're deliberately copying up to a non-null character, so you must add the null terminator. Consider the benefits of `memmove()` (or `memcpy()`) instead of `strncpy()` — but you still have to null terminate the output string,. – Jonathan Leffler Jun 28 '18 at 14:32
  • Thanks for the heads up. – Baltasarq Jun 28 '18 at 17:52
0

You can use strsep (successor to strtok) to tokenize your input filename and copy the parts which you are interested in and discarding the rest.

If your input filename is always of the form file.txt.rle, you can use the below code.

char *name = malloc(sizeof(char) * NAME_SIZE);
if(sprintf(name, "%s.rle", argv[1]) >= sizeof(name)){
    fprintf(stderr, "Destination file name is too long\n");
}

char *token = NULL;
char *newfilename = malloc(sizeof(char) * (NAME_SIZE-4)); //strlen(".rle") = 4
uint8_t offset = 0;
memset(newfilename, 0, (NAME_SIZE-4));

while ((token = strsep(&name, ".")) != NULL) {
    if(strcmp(token, "rle") == 0) {
        break;
    }
    strncpy(newfilename+offset, token, strlen(token));
    offset += strlen(token);
    newfilename[offset] = '.';
    offset += 1;
}
newfilename[strlen(newfilename)-1] = '\0';