Qual é a função de substituir uma substring de uma string em C?


94

Dada uma char *string ( ), quero encontrar todas as ocorrências de uma substring e substituí-las por uma string alternativa. Não vejo nenhuma função simples que alcance isso em <string.h>.


4
duvido que você possa fazer isso de forma mutável
user44511

Respostas:


87

O otimizador deve eliminar a maioria das variáveis ​​locais. O ponteiro tmp está lá para garantir que strcpy não precise percorrer a string para encontrar o nulo. tmp aponta para o final do resultado após cada chamada. (Veja Shlemiel, o algoritmo do pintor, para saber por que strcpy pode ser irritante.)

// You must free the result if result is non-NULL.
char *str_replace(char *orig, char *rep, char *with) {
    char *result; // the return string
    char *ins;    // the next insert point
    char *tmp;    // varies
    int len_rep;  // length of rep (the string to remove)
    int len_with; // length of with (the string to replace rep with)
    int len_front; // distance between rep and end of last rep
    int count;    // number of replacements

    // sanity checks and initialization
    if (!orig || !rep)
        return NULL;
    len_rep = strlen(rep);
    if (len_rep == 0)
        return NULL; // empty rep causes infinite loop during count
    if (!with)
        with = "";
    len_with = strlen(with);

    // count the number of replacements needed
    ins = orig;
    for (count = 0; tmp = strstr(ins, rep); ++count) {
        ins = tmp + len_rep;
    }

    tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);

    if (!result)
        return NULL;

    // first time through the loop, all the variable are set correctly
    // from here on,
    //    tmp points to the end of the result string
    //    ins points to the next occurrence of rep in orig
    //    orig points to the remainder of orig after "end of rep"
    while (count--) {
        ins = strstr(orig, rep);
        len_front = ins - orig;
        tmp = strncpy(tmp, orig, len_front) + len_front;
        tmp = strcpy(tmp, with) + len_with;
        orig += len_front + len_rep; // move to next "end of rep"
    }
    strcpy(tmp, orig);
    return result;
}

@jmucchiello: use em size_tvez de intpara tamanhos arbitrários de objetos / strings e índices neles. Além disso, qual é o propósito de strcpy(tmp, orig);no final? Parece errado.
Alexey Frunze

@Alex, o último strcpy (tmp, orig) copia a última parte da string para o destino. Ex: substituir ("abab", "a", "c") no final do loop, resultado contém, "cbc" e orig aponta para o último "b" em "abab". O último strcpy anexa o "b" para que a string retornada seja "cbcb". Se não houver mais nada para copiar, orig deve apontar para o ASCIIZ da string de entrada.
jmucchiello

simplificação: você pode substituir aquele primeiro forloop por for (count = 1; ins = strstr(ins + rep_len, rep); ++count) {}, então tmpé usado apenas para escrever.
aumento em

1
char * done = replace ("abcdefghijkl", "bc", "yz"); Fazer coisas(); grátis (feito);
jmucchiello

2
Esteja avisado que esta função retorna NULL se não houver ocorrências para substituir (if (! (Ins = strstr (orig, rep))) return NULL;). Você não pode apenas usar a saída, você precisa verificar se a saída é NULL e, se for, usar a string original (não copie apenas o ponteiro para a string de resultado porque free (result) então libera a string original). O uso é mais direto se a string de entrada for apenas copiada para a string de saída se não houver nada para substituir.
Adversus de

18

Isso não é fornecido na biblioteca C padrão porque, dado apenas um char *, você não pode aumentar a memória alocada para a string se a string de substituição for maior do que a string que está sendo substituída.

Você pode fazer isso usando std :: string mais facilmente, mas mesmo assim, nenhuma função única fará isso por você.


12
Esta pergunta é sobre C, não C ++.
Geremia

1 / strlen (char *) + 1 não é necessariamente igual ao tamanho do armazenamento. 2 / Existem muitas N versões de funções de string que recebem um parâmetro de tamanho de buffer adicional, então não há razão para não haver um snreplace (). 3 / pode haver uma função de substituição no local e não de substituição no local. 4 / como você acha que funciona o sprintf? É dado um argumento char * e não é necessário aumentar a alocação de memória dele, então não há razão para uma substituição não funcionar também ... (embora C tenha um design de "string" ruim e o tamanho do buffer sempre deva ser passado com o ponteiro => snprintf)
Steven Spark

12

Não existe um.

Você precisaria criar o seu próprio usando algo como strstr e strcat ou strcpy.


7
Onde as coleções de fãs de funções usadas com frequência são armazenadas? Certamente já existe uma biblioteca para isso ....
Pacerier

1
strcat()é uma sugestão ruim.
Iharob Al Asimi,

11

Você pode construir sua própria função de substituição usando strstr para encontrar as substrings e strncpy para copiar em partes para um novo buffer.

A menos que você queira ter replace_witho mesmo comprimento que você deseja replace, então provavelmente é melhor usar um novo buffer para copiar a nova string.


9

Como as strings em C não podem crescer dinamicamente, a substituição no local geralmente não funciona. Portanto, você precisa alocar espaço para uma nova string que tenha espaço suficiente para sua substituição e, em seguida, copiar as partes do original mais a substituição para a nova string. Para copiar as partes você usaria o strncpy .


O tamanho do buffer pode ser maior que o strlen, a string de substituição pode ser menor que a string substituída ... portanto, você não precisa alocar memória para realizar a substituição. (Também em microcontroladores, você pode não ter memória infinita e pode precisar realizar a substituição no local. Copiar tudo para um novo buffer pode não ser a solução certa para todos ...)
Steven Spark

8

Aqui está um exemplo de código que faz isso.

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

char * replace(
    char const * const original, 
    char const * const pattern, 
    char const * const replacement
) {
  size_t const replen = strlen(replacement);
  size_t const patlen = strlen(pattern);
  size_t const orilen = strlen(original);

  size_t patcnt = 0;
  const char * oriptr;
  const char * patloc;

  // find how many times the pattern occurs in the original string
  for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen)
  {
    patcnt++;
  }

  {
    // allocate memory for the new string
    size_t const retlen = orilen + patcnt * (replen - patlen);
    char * const returned = (char *) malloc( sizeof(char) * (retlen + 1) );

    if (returned != NULL)
    {
      // copy the original string, 
      // replacing all the instances of the pattern
      char * retptr = returned;
      for (oriptr = original; patloc = strstr(oriptr, pattern); oriptr = patloc + patlen)
      {
        size_t const skplen = patloc - oriptr;
        // copy the section until the occurence of the pattern
        strncpy(retptr, oriptr, skplen);
        retptr += skplen;
        // copy the replacement 
        strncpy(retptr, replacement, replen);
        retptr += replen;
      }
      // copy the rest of the string.
      strcpy(retptr, oriptr);
    }
    return returned;
  }
}

#include <stdio.h>
int main(int argc, char * argv[])
{
  if (argc != 4)
  {
    fprintf(stderr,"usage: %s <original text> <pattern> <replacement>\n", argv[0]);
    exit(-1);
  }
  else
  {
    char * const newstr = replace(argv[1], argv[2], argv[3]);
    if (newstr)
    {
      printf("%s\n", newstr);
      free(newstr);
    }
    else
    {
      fprintf(stderr,"allocation error\n");
      exit(-2);
    }
  }
  return 0;
}

Funciona, mas tem alguns bugs, mas obrigado mesmo assim! : D aqui está um que eu achei que funciona muito bem, coding.debuntu.org/… cheers! :)
Joe DF

4
// Here is the code for unicode strings!


int mystrstr(wchar_t *txt1,wchar_t *txt2)
{
    wchar_t *posstr=wcsstr(txt1,txt2);
    if(posstr!=NULL)
    {
        return (posstr-txt1);
    }else
    {
        return -1;
    }
}

// assume: supplied buff is enough to hold generated text
void StringReplace(wchar_t *buff,wchar_t *txt1,wchar_t *txt2)
{
    wchar_t *tmp;
    wchar_t *nextStr;
    int pos;

    tmp=wcsdup(buff);

    pos=mystrstr(tmp,txt1);
    if(pos!=-1)
    {
        buff[0]=0;
        wcsncpy(buff,tmp,pos);
        buff[pos]=0;

        wcscat(buff,txt2);

        nextStr=tmp+pos+wcslen(txt1);

        while(wcslen(nextStr)!=0)
        {
            pos=mystrstr(nextStr,txt1);

            if(pos==-1)
            {
                wcscat(buff,nextStr);
                break;
            }

            wcsncat(buff,nextStr,pos);
            wcscat(buff,txt2);

            nextStr=nextStr+pos+wcslen(txt1);   
        }
    }

    free(tmp);
}

3

A função repl_str () em creativeandcritical.net é rápida e confiável. Também incluída nessa página está uma variante de string ampla, repl_wcs () , que pode ser usada com strings Unicode, incluindo aquelas codificadas em UTF-8, por meio de funções auxiliares - o código de demonstração está vinculado à página. Divulgação completa tardia: eu sou o autor dessa página e das funções nela contidas.


3
rápido e confiável, mas tem um grande vazamento de memória.
MightyPork de

3
Não vejo como poderia. Há apenas um malloc e o chamador é instruído a liberar a memória quando ela não for mais necessária. Você poderia ser mais específico?
Laird

@Lairdpos_cache = realloc(pos_cache
PSkocik

@PSkocik A função foi atualizada desde a reclamação por @MightyPork, mas embora agora tenha aquele malloc / realloc adicional para pos_cache, não consigo ver um caminho de código que evite o free(pos_cache);fim da função at.
Laird

@Laird reallocpode falhar. Em caso afirmativo, ele retorna NULLe deixa o ponteiro antigo intacto. p = realloc(p, x)irá, em caso de falha, reescrever um ponteiro de heap válido pcom NULL, e se essa pfor sua única referência a esse objeto de heap, agora você o vazou. É um erro clássico de novato.
PSkocik

3

Acho que a maioria das funções propostas é difícil de entender - então eu vim com isto:

static char *dull_replace(const char *in, const char *pattern, const char *by)
{
    size_t outsize = strlen(in) + 1;
    // TODO maybe avoid reallocing by counting the non-overlapping occurences of pattern
    char *res = malloc(outsize);
    // use this to iterate over the output
    size_t resoffset = 0;

    char *needle;
    while (needle = strstr(in, pattern)) {
        // copy everything up to the pattern
        memcpy(res + resoffset, in, needle - in);
        resoffset += needle - in;

        // skip the pattern in the input-string
        in = needle + strlen(pattern);

        // adjust space for replacement
        outsize = outsize - strlen(pattern) + strlen(by);
        res = realloc(res, outsize);

        // copy the pattern
        memcpy(res + resoffset, by, strlen(by));
        resoffset += strlen(by);
    }

    // copy the remaining input
    strcpy(res + resoffset, in);

    return res;
}

a saída deve ser liberada


2

Você pode usar esta função (os comentários explicam como funciona):

void strreplace(char *string, const char *find, const char *replaceWith){
    if(strstr(string, replaceWith) != NULL){
        char *temporaryString = malloc(strlen(strstr(string, find) + strlen(find)) + 1);
        strcpy(temporaryString, strstr(string, find) + strlen(find));    //Create a string with what's after the replaced part
        *strstr(string, find) = '\0';    //Take away the part to replace and the part after it in the initial string
        strcat(string, replaceWith);    //Concat the first part of the string with the part to replace with
        strcat(string, temporaryString);    //Concat the first part of the string with the part after the replaced part
        free(temporaryString);    //Free the memory to avoid memory leaks
    }
}

1

Aqui está o que criei com base nestes requisitos:

  1. Substitua o padrão, independentemente de ele ser longo ou mais curto.

  2. Não use nenhum malloc (explícito ou implícito) para evitar intrinsecamente vazamentos de memória.

  3. Substitua qualquer número de ocorrências de padrão.

  4. Tolera a string de substituição com uma substring igual à string de pesquisa.

  5. Não é necessário verificar se a matriz Line é de tamanho suficiente para conter a substituição. por exemplo, isso não funciona a menos que o chamador saiba que a linha é de tamanho suficiente para conter a nova string.

/* returns number of strings replaced.
*/
int replacestr(char *line, const char *search, const char *replace)
{
   int count;
   char *sp; // start of pattern

   //printf("replacestr(%s, %s, %s)\n", line, search, replace);
   if ((sp = strstr(line, search)) == NULL) {
      return(0);
   }
   count = 1;
   int sLen = strlen(search);
   int rLen = strlen(replace);
   if (sLen > rLen) {
      // move from right to left
      char *src = sp + sLen;
      char *dst = sp + rLen;
      while((*dst = *src) != '\0') { dst++; src++; }
   } else if (sLen < rLen) {
      // move from left to right
      int tLen = strlen(sp) - sLen;
      char *stop = sp + rLen;
      char *src = sp + sLen + tLen;
      char *dst = sp + rLen + tLen;
      while(dst >= stop) { *dst = *src; dst--; src--; }
   }
   memcpy(sp, replace, rLen);

   count += replacestr(sp + rLen, search, replace);

   return(count);
}

Todas as sugestões para melhorar este código são aceitas com alegria. Basta postar o comentário e vou testá-lo.


1

Você pode usar strrep ()

char * strrep (const char * cadena, const char * strf, const char * strr)

strrep (substituir string). Substitui 'strf' por 'strr' em 'cadena' e retorna a nova string. Você precisa liberar a string retornada em seu código após usar strrep.

Parâmetros cadena A string com o texto. strf O texto a ser encontrado. strr O texto de substituição.

Retorna O texto atualizado com a substituição.

O projeto pode ser encontrado em https://github.com/ipserc/strrep


0

uma correção para a resposta de fann95, usando modificação no local da string e assumindo que o buffer apontado pela linha é grande o suficiente para conter a string resultante.

static void replacestr(char *line, const char *search, const char *replace)
{
     char *sp;

     if ((sp = strstr(line, search)) == NULL) {
         return;
     }
     int search_len = strlen(search);
     int replace_len = strlen(replace);
     int tail_len = strlen(sp+search_len);

     memmove(sp+replace_len,sp+search_len,tail_len+1);
     memcpy(sp, replace, replace_len);
}

0

Lá vai você .... esta é a função de substituir cada ocorrência de char xcom char ydentro de cadeia de caracteresstr

char *zStrrep(char *str, char x, char y){
    char *tmp=str;
    while(*tmp)
        if(*tmp == x)
            *tmp++ = y; /* assign first, then incement */
        else
            *tmp++;

    *tmp='\0';
    return str;
}

Um exemplo de uso poderia ser

  Exmaple Usage
        char s[]="this is a trial string to test the function.";
        char x=' ', y='_';
        printf("%s\n",zStrrep(s,x,y));

  Example Output
        this_is_a_trial_string_to_test_the_function.

A função é de uma biblioteca de strings que mantenho no Github , você é mais que bem-vindo para dar uma olhada em outras funções disponíveis ou até mesmo contribuir para o código :)

https://github.com/fnoyanisi/zString

EDITAR: @siride está certo, a função acima substitui apenas caracteres. Acabei de escrever este, que substitui cadeias de caracteres.

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

/* replace every occurance of string x with string y */
char *zstring_replace_str(char *str, const char *x, const char *y){
    char *tmp_str = str, *tmp_x = x, *dummy_ptr = tmp_x, *tmp_y = y;
    int len_str=0, len_y=0, len_x=0;

    /* string length */
    for(; *tmp_y; ++len_y, ++tmp_y)
        ;

    for(; *tmp_str; ++len_str, ++tmp_str)
        ;

    for(; *tmp_x; ++len_x, ++tmp_x)
        ;

    /* Bounds check */
    if (len_y >= len_str)
        return str;

    /* reset tmp pointers */
    tmp_y = y;
    tmp_x = x;

    for (tmp_str = str ; *tmp_str; ++tmp_str)
        if(*tmp_str == *tmp_x) {
            /* save tmp_str */
            for (dummy_ptr=tmp_str; *dummy_ptr == *tmp_x; ++tmp_x, ++dummy_ptr)
                if (*(tmp_x+1) == '\0' && ((dummy_ptr-str+len_y) < len_str)){
                /* Reached end of x, we got something to replace then!
                * Copy y only if there is enough room for it
                */
                    for(tmp_y=y; *tmp_y; ++tmp_y, ++tmp_str)
                        *tmp_str = *tmp_y;
            }
        /* reset tmp_x */
        tmp_x = x;
        }

    return str;
}

int main()
{
    char s[]="Free software is a matter of liberty, not price.\n"
             "To understand the concept, you should think of 'free' \n"
             "as in 'free speech', not as in 'free beer'";

    printf("%s\n\n",s);
    printf("%s\n",zstring_replace_str(s,"ree","XYZ"));
    return 0;
}

E abaixo está o resultado

Free software is a matter of liberty, not price.
To understand the concept, you should think of 'free' 
as in 'free speech', not as in 'free beer'

FXYZ software is a matter of liberty, not price.
To understand the concept, you should think of 'fXYZ' 
as in 'fXYZ speech', not as in 'fXYZ beer'

Isso apenas substitui caracteres únicos, não substrings.
siride

0
/*замена символа в строке*/
char* replace_char(char* str, char in, char out) {
    char * p = str;

    while(p != '\0') {
        if(*p == in)
            *p == out;
        ++p;
    }

    return str;
}

segfault quando str é nulo
Code_So1dier

0
DWORD ReplaceString(__inout PCHAR source, __in DWORD dwSourceLen, __in const char* pszTextToReplace, __in const char* pszReplaceWith)
{
    DWORD dwRC = NO_ERROR;
    PCHAR foundSeq = NULL;
    PCHAR restOfString = NULL;
    PCHAR searchStart = source;
    size_t szReplStrcLen = strlen(pszReplaceWith), szRestOfStringLen = 0, sztextToReplaceLen = strlen(pszTextToReplace), remainingSpace = 0, dwSpaceRequired = 0;
    if (strcmp(pszTextToReplace, "") == 0)
        dwRC = ERROR_INVALID_PARAMETER;
    else if (strcmp(pszTextToReplace, pszReplaceWith) != 0)
    {
        do
        {
            foundSeq = strstr(searchStart, pszTextToReplace);
            if (foundSeq)
            {
                szRestOfStringLen = (strlen(foundSeq) - sztextToReplaceLen) + 1;
                remainingSpace = dwSourceLen - (foundSeq - source);
                dwSpaceRequired = szReplStrcLen + (szRestOfStringLen);
                if (dwSpaceRequired > remainingSpace)
                {
                    dwRC = ERROR_MORE_DATA;
                }

                else
                {
                    restOfString = CMNUTIL_calloc(szRestOfStringLen, sizeof(CHAR));
                    strcpy_s(restOfString, szRestOfStringLen, foundSeq + sztextToReplaceLen);

                    strcpy_s(foundSeq, remainingSpace, pszReplaceWith);
                    strcat_s(foundSeq, remainingSpace, restOfString);
                }

                CMNUTIL_free(restOfString);
                searchStart = foundSeq + szReplStrcLen; //search in the remaining str. (avoid loops when replWith contains textToRepl 
            }
        } while (foundSeq && dwRC == NO_ERROR);
    }
    return dwRC;
}

0
char *replace(const char*instring, const char *old_part, const char *new_part)
{

#ifndef EXPECTED_REPLACEMENTS
    #define EXPECTED_REPLACEMENTS 100
#endif

    if(!instring || !old_part || !new_part)
    {
        return (char*)NULL;
    }

    size_t instring_len=strlen(instring);
    size_t new_len=strlen(new_part);
    size_t old_len=strlen(old_part);
    if(instring_len<old_len || old_len==0)
    {
        return (char*)NULL;
    }

    const char *in=instring;
    const char *found=NULL;
    size_t count=0;
    size_t out=0;
    size_t ax=0;
    char *outstring=NULL;

    if(new_len> old_len )
    {
        size_t Diff=EXPECTED_REPLACEMENTS*(new_len-old_len);
        size_t outstring_len=instring_len + Diff;
        outstring =(char*) malloc(outstring_len); 
        if(!outstring){
            return (char*)NULL;
        }
        while((found = strstr(in, old_part))!=NULL)
        {
            if(count==EXPECTED_REPLACEMENTS)
            {
                outstring_len+=Diff;
                if((outstring=realloc(outstring,outstring_len))==NULL)
                {
                     return (char*)NULL;
                }
                count=0;
            }
            ax=found-in;
            strncpy(outstring+out,in,ax);
            out+=ax;
            strncpy(outstring+out,new_part,new_len);
            out+=new_len;
            in=found+old_len;
            count++;
        }
    }
    else
    {
        outstring =(char*) malloc(instring_len);
        if(!outstring){
            return (char*)NULL;
        }
        while((found = strstr(in, old_part))!=NULL)
        {
            ax=found-in;
            strncpy(outstring+out,in,ax);
            out+=ax;
            strncpy(outstring+out,new_part,new_len);
            out+=new_len;
            in=found+old_len;
        }
    }
    ax=(instring+instring_len)-in;
    strncpy(outstring+out,in,ax);
    out+=ax;
    outstring[out]='\0';

    return outstring;
}

0

Esta função só funciona se a sua string tiver espaço extra para um novo comprimento

void replace_str(char *str,char *org,char *rep)
{
    char *ToRep = strstr(str,org);
    char *Rest = (char*)malloc(strlen(ToRep));
    strcpy(Rest,((ToRep)+strlen(org)));

    strcpy(ToRep,rep);
    strcat(ToRep,Rest);

    free(Rest);
}

Isso apenas substitui a Primeira ocorrência


0

Aqui vai o meu, é independente e versátil, além de eficiente, aumenta ou diminui os buffers conforme necessário em cada recursão

void strreplace(char *src, char *str, char *rep)
{
    char *p = strstr(src, str);
    if (p)
    {
        int len = strlen(src)+strlen(rep)-strlen(str);
        char r[len];
        memset(r, 0, len);
        if ( p >= src ){
            strncpy(r, src, p-src);
            r[p-src]='\0';
            strncat(r, rep, strlen(rep));
            strncat(r, p+strlen(str), p+strlen(str)-src+strlen(src));
            strcpy(src, r);
            strreplace(p+strlen(rep), str, rep);
        }
    }
}

0

Aqui vai o meu, torne todos char *, o que torna mais fácil chamar ...

char *strrpc(char *str,char *oldstr,char *newstr){
    char bstr[strlen(str)];
    memset(bstr,0,sizeof(bstr));
    int i;
    for(i = 0;i < strlen(str);i++){
        if(!strncmp(str+i,oldstr,strlen(oldstr))){
            strcat(bstr,newstr);
            i += strlen(oldstr) - 1;
        }else{
                strncat(bstr,str + i,1);
            }
    }

    strcpy(str,bstr);
    return str;
}

0

Usando apenas strlen de string.h

Desculpe pelo meu Inglês

char * str_replace(char * text,char * rep, char * repw){//text -> to replace in it | rep -> replace | repw -> replace with
    int replen = strlen(rep),repwlen = strlen(repw),count;//some constant variables
    for(int i=0;i<strlen(text);i++){//search for the first character from rep in text
        if(text[i] == rep[0]){//if it found it
            count = 1;//start searching from the next character to avoid repetition
            for(int j=1;j<replen;j++){
                if(text[i+j] == rep[j]){//see if the next character in text is the same as the next in the rep if not break
                    count++;
                }else{
                    break;
                }
            }
            if(count == replen){//if count equals to the lenght of the rep then we found the word that we want to replace in the text
                if(replen < repwlen){
                    for(int l = strlen(text);l>i;l--){//cuz repwlen greater than replen we need to shift characters to the right to make space for the replacement to fit
                        text[l+repwlen-replen] = text[l];//shift by repwlen-replen
                    }
                }
                if(replen > repwlen){
                    for(int l=i+replen-repwlen;l<strlen(text);l++){//cuz replen greater than repwlen we need to shift the characters to the left
                        text[l-(replen-repwlen)] = text[l];//shift by replen-repwlen
                    }
                    text[strlen(text)-(replen-repwlen)] = '\0';//get rid of the last unwanted characters
                }
                for(int l=0;l<repwlen;l++){//replace rep with repwlen
                    text[i+l] = repw[l];
                }
                if(replen != repwlen){
                    i+=repwlen-1;//pass to the next character | try text "y" ,rep "y",repw "yy" without this line to understand
                }
            }
        }
    }
    return text;
}

se você quiser que o código strlen evite chamar string.h

int strlen(char * string){//use this code to avoid calling string.h
    int lenght = 0;
    while(string[lenght] != '\0'){
        lenght++;
    }
    return lenght;
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.