segunda-feira, 9 de junho de 2014

Alterando arquivo xml como array de bytes.

Olá...


Recentemente tive uma demanda de um cliente, com  uma banda de acesso muito pequena. Por tanto tive que gerar um executável muito pequeno para enviar para ele.

A principio utilizei as bibliotecas do Qt para ler um Xml e fazer as modificações. Por causa da lib do qt exigir dependências externas, e não achei como remover as dependências, meu executável ficou com quase 40Mb.
Isso mesmo! 40 Mb :-/ Imagina minha decepção.

Partir para a libxml2, biblioteca bem documentada (não conhecia mas me agradou muito). Só que o executável e as bibliotecas juntas geram um montante de 10Mb.

Sei o que vocês vão perguntar? Não poderia gerar executável estático, gerar uma nova lib com só o necessário, certificar que os usuário já tenham as principais bibliotecas na máquina.

Respondendo: Era tarde da noite e não tinha como ter essas "regalias". Tinha que gerar um executável para a manhã seguinte.

Eis minha solução... gerei um executável em C com as bibliotecas básicas: stdio.h, string.h e stdlib.h
O executável ficou minusculo... 100 kb. Isso mesmo! só que deu um trabalho danado para re-escrever algumas funções triviais das bibliotecas Qt e libxml2. E outra coisa, eu tratei o arquivo xml como um array de bytes ;-)

- Achei o texto relacionado a tag
- Alterei o texto
- Salvei em um novo arquivo

Após esta história, vem o motivo deste post... foi difícil achar na net implementações das funções em C, então segue meu código abaixo para quem quiser reaproveitar:

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

FILE *pFile_old = NULL,
         *pFile_new = NULL;
static char strTag_new[4096] = {};
int size_new = 0;
static char strTag_old[4096] = {};
int size_old = 0;

static char xmlData[4096] = {};
int size_xml = 0;

static char text[] = "Texto\\\0";
int size_text;

/**
 * @brief getPositionBegin
 * @param source
 * @param substr
 * @return position where start substr in source
 */
int getPositionBegin(char *source, char *substr)
{
    char *tmp = strstr(source, substr);
    int pos = (tmp - source);
    return pos;
}

/**
 * @brief getPositionEnd
 * @param source
 * @param substr
 * @return  position where end substr in source
 */
int getPositionEnd(char *source, char *substr)
{
    char *tmp = strstr(source, substr);
    int pos = (tmp - source) + strlen(substr);
    return pos;
}

/**
 * @brief readFileBytes
 * @param name
 */
void readFileBytes(const char *name)
{
    pFile_old = fopen(name , "rb");
    if (pFile_old == NULL) {
        fputs ("File error",stderr);
        exit(1);
    }
    // obtain file size:
    fseek (pFile_old , 0 , SEEK_END);
    size_xml = ftell (pFile_old);
    rewind (pFile_old);

    // copy the file into the buffer:
    size_t result = fread (xmlData, 1, size_xml, pFile_old);
    if (result != size_xml) {
        fputs ("Reading error",stderr);
        exit(2);
    }
    xmlData[size_xml+1] = '\0';

    fclose(pFile_old);
    pFile_old = NULL;
}

/**
 * @brief writeFileData
 * @param name
 * @param data
 * @return
 */
int writeFileData (char *name, char *data)
{
    pFile_new = fopen(name, "wb");
    size_t result = fwrite (data , sizeof(char), strlen(data), pFile_new);
//    if (result > 0)
//        exit(3);
    fclose (pFile_new);
    pFile_new = NULL;
    return result;
}

char *substring(char *string, int position, int length)
{
    char *pointer;
    int c;

    pointer = malloc(length+1);

    if( pointer == NULL )
        exit(EXIT_FAILURE);

    for( c = 0 ; c < length ; c++ )
        *(pointer+c) = *((string+position-1)+c);

    *(pointer+c) = '\0';

    return pointer;
}

/**
 * @brief Insert substring into source
 * @param source
 * @param substr
 * @param position
 * @warning array start in 1
 */
void insert_substring(char *source, char *substr, int position)
{
    char *f, *e;
    int length;

    length = strlen(source);

    // get text before 'substr'
    f = substring(source, 1, position - 1 );
    // get text after 'substr'
    e = substring(source, position, (length-position)+1);

    strcpy(source, "");
    strcat(source, f);
    free(f);
    strcat(source, substr);
    strcat(source, e);
    free(e);
}

/**
 * @brief remove_substring
 * @param source
 * @param substr
 */
void remove_substring(char *source, char *substr)
{
    int begin = getPositionBegin(source, substr);
    int end = getPositionEnd(source, substr);

    while( source = strstr(source, substr) )
        memmove(source,source+strlen(substr), 1+strlen(source+strlen(substr)));
}

void *replace_str(char *source, char *_old, char *_new)
{
    int pos = getPositionBegin(source, _old);
    remove_substring(source, _old);
    insert_substring(source, _new, pos);
}


Segue a função main:

int main(int argc, char* argv[])
{
    // reading xml file
    readFileBytes("c:\\exemplo.xml");

    // find position of tag
    int begin = 15 + getPositionBegin(xmlData, "<ReceiptFooter>");

    // find position of tag
    int end = getPositionBegin(xmlData, "</ReceiptFooter>");

    // define size of text
    size_old = (end-begin);
    int tam = (end-begin) - 1;

    // check text empty
    if (tam > 0) {

        // get the text
        strncpy(strTag_old, (xmlData+begin), size_old);
        strTag_old[size_old + 1] = '\0';

        //remove old tag text from xml
        remove_substring(xmlData, strTag_old);

        // Text split - remove lines that start with '--'
        char *pch = strtok (strTag_old,"\\");
        while (pch != NULL)
        {
            // check if string start with '--'
            int pos = getPositionBegin(pch, "--");
            if (pos != 0){
                // put in new string
                strcat(strTag_new, pch);
                size_new += strlen(pch);
                strcat(strTag_new, "\\");
                size_new++;
            }
            pch = strtok (NULL, "\\");
        }
        strTag_new[size_new+1] = '\0';
    }

    // find the text
    int pos = getPositionBegin(strTag_new, text);
    if (pos < 0) {
        //add text
        insert_substring(strTag_new, text, 1);
    }

    // remove old tag text and put new in xml
    insert_substring(xmlData, strTag_new, begin+1);
    writeFileData("c:\\exemplo2.xml", xmlData);

    // delete temporary files
    system("del c:\\exemplo.xml");
    system("del c:\\exemplo2.xml");

    return(0);
}


Utilizando esse código, com includes das bibliotecas básicas do C. Pudi criar um executável sem parâmetros para o GCC.

GCC main.c


Abraços