"#Include" текстовий файл у програмі C як char []


130

Чи є спосіб включити весь текстовий файл як рядок у програму C під час компіляції?

щось на зразок:

  • file.txt:

    This is
    a little
    text file
  • main.c:

    #include <stdio.h>
    int main(void) {
       #blackmagicinclude("file.txt", content)
       /*
       equiv: char[] content = "This is\na little\ntext file";
       */
       printf("%s", content);
    }

отримання невеликої програми, яка друкує на stdout "Це невеликий текстовий файл"

На даний момент я використовував хакізський скрипт python, але він непоганий і обмежений лише одним ім'ям змінної, чи можете ви сказати мені інший спосіб це зробити?


Подивіться тут, щоб прочитати файл у char []. /programming/410943/reading-a-text-file-into-an-array-in-c Ось кілька порад щодо використання макросів препроцесора C. http://gcc.gnu.org/onlinedocs/cpp/Macros.html
Даніель А. Білий

3
Чому ти хочеш це робити? Чому б не прочитати файл під час виконання? (Відповідь: можливо, тому, що важко знати, де знаходиться файл під час виконання, а може тому, що для встановлення повинен бути лише один файл.)
Джонатан Леффлер,

або, можливо, текстовий файл доступний лише під час компіляції, наприклад, вихідний код.
TMS

1
Іноді ви хочете отримати доступ до даних як окремі файли під час розробки, але вміст збирається у ваш двійковий файл. Прикладом є веб-сервер на Arduino, який не має доступу до локального сховища. Ви хочете, щоб ваші HTML-файли були окремими, щоб редагувати їх, але під час компіляції вони повинні існувати як рядки у вашому джерелі.
Джорді

Відповіді:


134

Я б запропонував використовувати для цього (unix util) xxd . Ви можете використовувати його так

$ echo hello world > a
$ xxd -i a

Виходи:

unsigned char a[] = {
  0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a
};
unsigned int a_len = 12;

18
Лише зауваження: char [], створений xxd, не припиняється NULL! тому я роблю $ xxd -i <file.txt> file.xxd $ echo ', 0' >> file.xxd і в main.c char file_content [] = {#include "file.xxd"};

2
Я ніколи не знав про xxd. Це приголомшливо!

1
@eSKay: це надходить безпосередньо з результатів xxd, як йдеться у відповіді. ім'я масиву - це ім'я вхідного файлу. якщо ви завантажуєте дані замість файлу введення, ви отримаєте список шістнадцяткових значень (без оголошення масиву або змінної len).
Hasturkun

4
Це надзвичайно корисно при вставці шейдерів GLSL.
linello

5
Ще один спосіб додати закінчення 0x00 до xxd, що виробляється кодом C:xxd -i file.txt | sed 's/\([0-9a-f]\)$/\0, 0x00/' > file.h
vleo

104

Питання стосувалося C, але якщо хтось намагається це зробити з C ++ 11, то це можна зробити лише з невеликими змінами у текстовому файлі, що додається, завдяки новим необробленим рядковим літералам :

В C ++ зробіть це:

const char *s =
#include "test.txt"
;

У текстовому файлі зробіть це:

R"(Line 1
Line 2
Line 3
Line 4
Line 5
Line 6)"

Отже, у верхній частині файлу має бути лише префікс, а в кінці - суфікс. Між цим ви можете робити все, що завгодно, ніяких спеціальних втеч не потрібно, доки вам не потрібна послідовність символів )". Але навіть це може працювати, якщо ви вкажете власний спеціальний роздільник:

R"=====(Line 1
Line 2
Line 3
Now you can use "( and )" in the text file, too.
Line 5
Line 6)====="

5
Дякую, я вибрав запропонований тут метод, щоб вставити довгі фрагменти sql у свій код C ++ 11. Це дозволяє мені тримати SQL чітко розділеним на власні файли та редагувати їх за допомогою відповідної перевірки синтаксису, виділення тощо.
YitzikC

1
Це дійсно близько до того, що я хочу. Особливо визначений користувачем роздільник. Дуже корисний. Я хочу піти на крок далі: чи є спосіб повністю видалити префікс R "(і суфікс)" з файлу, який ви хочете включити? Я спробував визначити два файли під назвою bra.in та ket.in з префіксом та суфіксом, включаючи bra.in, file.txt та ket.in один за одним. Але компілятор оцінить вміст bra.in (який просто R "() перед тим, як включити наступний файл. Щоб він поскаржився. Будь ласка, дайте мені знати, якщо хтось знає, як отримати пробірку та суфікс з file.txt. Дякую.
TMS

Я здогадуюсь, що C ++ не дозволить R "(<newline> #include ...)"? Було б добре, щоб файл, який перебуває в процесі компіляції, не зажадав кодування ... тобто прямий json або xml або csv або що ні ..
Brian Chrisman

Ви можете зробити текст необробленого прямого тексту трохи легше для читання, якщо 1+R"...замість цього використовуєте як початковий роздільник R"..., а потім перед цим додаєте новий рядок Line 1. Це перетворить вираз з масиву в покажчик, але це насправді не проблема, оскільки ви ініціалізуєте вказівник, а не масив.
Руслан

14

У вас є дві можливості:

  1. Скористайтеся розширеннями компілятора / лінкера, щоб перетворити файл у двійковий файл, при цьому належні символи вказують на початок і кінець бінарних даних. Дивіться цю відповідь: Додайте двійковий файл із скриптом зв’язку ld GNU .
  2. Перетворіть свій файл у послідовність символьних констант, які можуть ініціалізувати масив. Зверніть увагу, що ви не можете "" виконати кілька рядків. Для цього вам знадобиться символ продовження рядка ( \), "символи втечі та інші. Простіше просто написати невелику програму для перетворення байтів у таку послідовність, як '\xFF', '\xAB', ...., '\0'(або скористайтеся інструментом unix, xxdописаним іншою відповіддю, якщо у вас є доступність!):

Код:

#include <stdio.h>

int main() {
    int c;
    while((c = fgetc(stdin)) != EOF) {
        printf("'\\x%X',", (unsigned)c);
    }
    printf("'\\0'"); // put terminating zero
}

(не перевірено). Потім зробіть:

char my_file[] = {
#include "data.h"
};

Де data.h породжується

cat file.bin | ./bin2c > data.h

1
останній рядок, ймовірно, повинен читати "cat file.bin | ./bin2c> data.h" або "./bin2c <file.bin> data.h"
Hasturkun

Я використовував codeproject.com/Tips/845393/…, щоб створити шістнадцятковий файл (у Windows) з бінарного файлу, а потім використав вашу пропозицію char my_file[] = { #include my_large_file.h };подяки!
Хтось десь

bin2cце НЕ те ж саме , як bin2c від проекту Debian hxtools, остерігайтеся
ThorSummoner

або якщо так, зараз виклик набагато bin2c -H myoutput.h myinput1.txt myinputN.txt
дивніший

9

добре, надихнувшись постами Daemin, я перевірив наступний простий приклад:

a.data:

"this is test\n file\n"

test.c:

int main(void)
{
    char *test = 
#include "a.data"
    ;
    return 0;
}

Вихід gcc -E test.c:

# 1 "test.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "test.c"

int main(void)
{
    char *test =
# 1 "a.data" 1
"this is test\n file\n"
# 6 "test.c" 2
    ;
    return 0;
}

Тому він працює, але вимагає даних, оточених лапками.


На це я натякав в останньому фрагменті своєї відповіді.
Daemin

цитата, або як вона ще називається, пробачте мою англійську
Ілля

Для цього потрібно зберегти дані C. Я не думаю, що саме це шукає посада. Якби до цього було якесь включення макросу, який C-уникнув вмісту файлу, це було б добре.
Брайан Крісман

8

Мені подобається відповідь каяра. Якщо ви не хочете торкатися вхідних файлів , і якщо ви використовуєте CMake , можете додати послідовності символів деліметра у файл. Наприклад, наступний код CMake, наприклад, копіює вхідні файли та відповідний вміст.

function(make_includable input_file output_file)
    file(READ ${input_file} content)
    set(delim "for_c++_include")
    set(content "R\"${delim}(\n${content})${delim}\"")
    file(WRITE ${output_file} "${content}")
endfunction(make_includable)

# Use like
make_includable(external/shaders/cool.frag generated/cool.frag)

Потім включіть в c ++ так:

constexpr char *test =
#include "generated/cool.frag"
;

5

Це можна зробити за допомогою objcopy:

objcopy --input binary --output elf64-x86-64 myfile.txt myfile.o

Тепер у вас є об’єктний файл, з якого ви можете зв’язати свій виконуваний файл, який містить символи для початку, кінця та розміру вмісту myfile.txt.


1
чи можете ви сказати нам, якими будуть назви символів?
Марк Ч.

@MarkCh: Відповідно до документів, імена символів генеруються з імені вхідного файлу.
Джон Цвінк

Я здогадуюсь, що ця робота не працюватиме на машинах, що не є x86-64, чи не так?
ThorSummoner


2

Вам потрібна моя xtrутиліта, але ви можете це зробити за допомогою bash script. Це сценарій, який я закликаю bin2inc. Перший параметр - назва отриманого результату char[] variable. Другий параметр - це ім'я file. Вихідні дані є C include fileіз вмістом файлу, кодованим (малі регістри hex), як вказане ім'я змінної. Значення char arrayє zero terminated, і довжина даних зберігається в$variableName_length

#!/bin/bash

fileSize ()

{

    [ -e "$1" ]  && {

        set -- `ls -l "$1"`;

        echo $5;

    }

}

echo unsigned char $1'[] = {'
./xtr -fhex -p 0x -s ', ' < "$2";
echo '0x00'
echo '};';
echo '';
echo unsigned long int ${1}_length = $(fileSize "$2")';'

Ви можете отримати XTR ТУТ xtr (символ eXTRapolator) - GPLV3


2

Якщо ви готові вдатися до деяких брудних хитрощів, ви можете проявити творчість із необробленими рядковими літералами та #includeпевними типами файлів.

Наприклад, скажіть, що я хочу включити деякі сценарії SQL для SQLite у свій проект, і я хочу отримати підсвічування синтаксису, але не хочу спеціальної інфраструктури для побудови. У мене може бути цей файл, test.sqlякий є дійсним SQL для SQLite, де --починається коментар:

--x, R"(--
SELECT * from TestTable
WHERE field = 5
--)"

І тоді у своєму C ++ коді я можу мати:

int main()
{
    auto x = 0;
    const char* mysql = (
#include "test.sql"
    );

    cout << mysql << endl;
}

Вихід:

--
SELECT * from TestTable
WHERE field = 5
--

Або включити якийсь код Python з файлу, test.pyякий є дійсним сценарієм Python (тому що #запускає коментар у Python і passне відповідає):

#define pass R"(
pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass )"
pass

А потім у коді C ++:

int main()
{
    const char* mypython = (
#include "test.py"
    );

    cout << mypython << endl;
}

Що виведе:

pass
def myfunc():
    print("Some Python code")

myfunc()
#undef pass
#define pass

Повинно бути можливим відтворення подібних трюків для різних інших типів коду, який ви можете включити як рядок. Це гарна ідея чи ні, я не впевнений. Це свого роду акуратний злом, але, мабуть, не те, що ви хотіли б у реальному виробничому коді. Можливо, буде добре для проекту злому вихідного дня.


Я використовував такий підхід для розміщення OpenGL Shaders і в текстових файлах!
яно

1

Я повторно доповнив xxd в python3, виправляючи всі роздратування xxd:

  • Правильність Const
  • Тип даних про довжину рядка: int → size_t
  • Нульове припинення (якщо ви цього хочете)
  • Сумісний з рядком C: опустити unsignedмасив.
  • Менший, читабельний вихід, як ви це написали: Ascii для друку виводиться як є; інші байти кодуються шістнадцять

Ось сценарій, відфільтрований сам по собі, так що ви можете побачити, що він робить:

pyxxd.c

#include <stddef.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

const char pyxxd[] =
"#!/usr/bin/env python3\n"
"\n"
"import sys\n"
"import re\n"
"\n"
"def is_printable_ascii(byte):\n"
"    return byte >= ord(' ') and byte <= ord('~')\n"
"\n"
"def needs_escaping(byte):\n"
"    return byte == ord('\\\"') or byte == ord('\\\\')\n"
"\n"
"def stringify_nibble(nibble):\n"
"    if nibble < 10:\n"
"        return chr(nibble + ord('0'))\n"
"    return chr(nibble - 10 + ord('a'))\n"
"\n"
"def write_byte(of, byte):\n"
"    if is_printable_ascii(byte):\n"
"        if needs_escaping(byte):\n"
"            of.write('\\\\')\n"
"        of.write(chr(byte))\n"
"    elif byte == ord('\\n'):\n"
"        of.write('\\\\n\"\\n\"')\n"
"    else:\n"
"        of.write('\\\\x')\n"
"        of.write(stringify_nibble(byte >> 4))\n"
"        of.write(stringify_nibble(byte & 0xf))\n"
"\n"
"def mk_valid_identifier(s):\n"
"    s = re.sub('^[^_a-z]', '_', s)\n"
"    s = re.sub('[^_a-z0-9]', '_', s)\n"
"    return s\n"
"\n"
"def main():\n"
"    # `xxd -i` compatibility\n"
"    if len(sys.argv) != 4 or sys.argv[1] != \"-i\":\n"
"        print(\"Usage: xxd -i infile outfile\")\n"
"        exit(2)\n"
"\n"
"    with open(sys.argv[2], \"rb\") as infile:\n"
"        with open(sys.argv[3], \"w\") as outfile:\n"
"\n"
"            identifier = mk_valid_identifier(sys.argv[2]);\n"
"            outfile.write('#include <stddef.h>\\n\\n');\n"
"            outfile.write('extern const char {}[];\\n'.format(identifier));\n"
"            outfile.write('extern const size_t {}_len;\\n\\n'.format(identifier));\n"
"            outfile.write('const char {}[] =\\n\"'.format(identifier));\n"
"\n"
"            while True:\n"
"                byte = infile.read(1)\n"
"                if byte == b\"\":\n"
"                    break\n"
"                write_byte(outfile, ord(byte))\n"
"\n"
"            outfile.write('\";\\n\\n');\n"
"            outfile.write('const size_t {}_len = sizeof({}) - 1;\\n'.format(identifier, identifier));\n"
"\n"
"if __name__ == '__main__':\n"
"    main()\n"
"";

const size_t pyxxd_len = sizeof(pyxxd) - 1;

Використання (це витягує сценарій):

#include <stdio.h>

extern const char pyxxd[];
extern const size_t pyxxd_len;

int main()
{
    fwrite(pyxxd, 1, pyxxd_len, stdout);
}

1

Що може спрацювати, якщо ви зробите щось на кшталт:

int main()
{
    const char* text = "
#include "file.txt"
";
    printf("%s", text);
    return 0;
}

Звичайно, вам доведеться бути обережними з тим, що є насправді у файлі , упевнившись, що немає подвійних лапок, що всі відповідні символи будуть уникнуті тощо.

Тому це може бути простіше, якщо ви просто завантажуєте текст з файлу під час виконання програми або вставляєте текст прямо в код.

Якщо ви все-таки хотіли текст у іншому файлі, ви могли б мати його там, але він повинен був бути представлений як рядок. Ви б використовували код як вище, але без подвійних лапок у ньому. Наприклад:

file.txt

"Something evil\n"\
"this way comes!"

main.cpp

int main()
{
    const char* text =
#include "file.txt"
;
    printf("%s", text);
    return 0;
}

Отже, в текстовому файлі, який ви включаєте, є рядок стилю C або C ++. Це зробить код охайнішим, оскільки на початку файлу немає такої великої кількості тексту.


3
Хороша ідея, але вона не спрацює, або у вас є помилка, оскільки літерал включає новий рядок, або частина #include буде прочитана як рядок і не буде виконуватися, проклята, якщо ви це зробите, і проклята, якщо ви цього не зробите .. .
Мотті

1
@Motti: погоджено - як написано, синтаксично недійсна C. Ідея цікава - Попередній процесор C логічно є окремою фазою - але практика полягає в тому, що він не збивається з основи, оскільки кожен рядок включеного файлу повинен мати закінчити зворотним нахилом тощо.
Джонатан Леффлер

2
Гумм. Мені здається, що вам не знадобиться зворотна косої риски, оскільки більшість компіляторів поєднають суміжні рядки разом
EvilTeach

справа з цією відповіддю ... якби це було так просто, я не думаю, що ОП ніколи не поставило б це питання! -1 тому, що наявність цієї відповіді злегка спонукає людей витрачати свій час на пробування чогось, що не працює. Я думаю, що ми могли б зняти голосування, якщо ви змінили "Що може працювати" на "Для довідки, це не працює"
Марк Ч.

@JonathanLeffler Після запуску препроцесора він повинен бути дійсним C або C ++, залежно від форматування файлу file.txt.
Daemin

0

Навіть якщо це можна зробити під час компіляції (я не думаю, що це взагалі може), текст, швидше за все, буде попередньо обробленим заголовком, а не вмістом файлів дослівно. Я думаю, що вам доведеться завантажити текст з файлу під час виконання або виконати неприємну роботу з вирізанням.


0

Відповідь Гастуркуна за допомогою параметра xxd -i відмінна. Якщо ви хочете включити процес перетворення (текст -> шістнадцятковий файл включити) безпосередньо у свій збірник, інструмент / бібліотека hexdump.c нещодавно додав можливість, подібну до параметра xxd's -i (вона не дає вам повного заголовка - вам потрібно надати визначення масиву char - але це має перевагу, щоб ви могли вибрати ім'я масиву char):

http://25thandclement.com/~william/projects/hexdump.c.html

Його ліцензія набагато "стандартніша", ніж xxd, і дуже ліберальна - приклад її використання для вбудовування файлу init у програму можна побачити у файлах CMakeLists.txt та sche.c тут:

https://github.com/starseeker/tinyscheme-cmake

Існують плюси і мінуси, як у тому, щоб включити генеровані файли у вихідні дерева, так і об'єднати утиліти - як це впоратися, буде залежати від конкретних цілей і потреб вашого проекту. hexdump.c відкриває опцію пакетування для цієї програми.


0

Я думаю, що це неможливо лише з компілятором і препроцесором. gcc дозволяє це:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

    printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
            STRGF(
#               define hostname my_dear_hostname
                hostname
            )
            "\n" );

Але, на жаль, не так:

#define _STRGF(x) # x
#define STRGF(x) _STRGF(x)

    printk ( MODULE_NAME " built " __DATE__ " at " __TIME__ " on host "
            STRGF(
#               include "/etc/hostname"
            )
            "\n" );

Помилка:

/etc/hostname: In function init_module’:
/etc/hostname:1:0: error: unterminated argument list invoking macro "STRGF"

Я дивився, як ви пропонуєте мені подивитися. Я не бачу жодної нової інформації у вашій відповіді (інформації, якої немає в інших відповідях), поза посиланням на /etc/hostnameспосіб вбудовування імені машини збирання в рядок, який (навіть якби він працював) не був би портативний, оскільки у Mac OS X немає файлу /etc/hostname. Зауважте, що при використанні імен макросів, які починаються з підкреслення, а потім великої літери, використовується ім'я, зарезервоване для реалізації, яке є Bad Bading ™.
Джонатан Леффлер

0

Чому б не зв’язати текст з програмою і не використовувати його як глобальну змінну! Ось приклад. Я розглядаю можливість використовувати це для включення файлів шаблону Open GL у виконуваний файл, оскільки шейдери GL повинні бути скомпільовані для GPU під час виконання.


0

У мене були подібні проблеми, і для невеликих файлів вищезгадане рішення Йоганнеса Шауба працювало як шарм для мене.

Однак для файлів, трохи більших, вони стикалися з проблемами з обмеженням масиву символів компілятора. Тому я написав невелику програму для кодування, яка перетворює вміст файлів у 2D-масив символів з однаковим розміром (та можливо нульові нулі). Він створює вихідні текстові файли з двома даними масиву таким чином:

const char main_js_file_data[8][4]= {
    {'\x69','\x73','\x20','\0'},
    {'\x69','\x73','\x20','\0'},
    {'\x61','\x20','\x74','\0'},
    {'\x65','\x73','\x74','\0'},
    {'\x20','\x66','\x6f','\0'},
    {'\x72','\x20','\x79','\0'},
    {'\x6f','\x75','\xd','\0'},
    {'\xa','\0','\0','\0'}};

де 4 насправді є змінною MAX_CHARS_PER_ARRAY в кодері. Файл із отриманим кодом C, який називається, наприклад, "main_js_file_data.h", може бути легко введений у додаток C ++, наприклад, як це:

#include "main_js_file_data.h"

Ось вихідний код кодера:

#include <fstream>
#include <iterator>
#include <vector>
#include <algorithm>


#define MAX_CHARS_PER_ARRAY 2048


int main(int argc, char * argv[])
{
    // three parameters: input filename, output filename, variable name
    if (argc < 4)
    {
        return 1;
    }

    // buffer data, packaged into chunks
    std::vector<char> bufferedData;

    // open input file, in binary mode
    {    
        std::ifstream fStr(argv[1], std::ios::binary);
        if (!fStr.is_open())
        {
            return 1;
        }

        bufferedData.assign(std::istreambuf_iterator<char>(fStr), 
                            std::istreambuf_iterator<char>()     );
    }

    // write output text file, containing a variable declaration,
    // which will be a fixed-size two-dimensional plain array
    {
        std::ofstream fStr(argv[2]);
        if (!fStr.is_open())
        {
            return 1;
        }
        const std::size_t numChunks = std::size_t(std::ceil(double(bufferedData.size()) / (MAX_CHARS_PER_ARRAY - 1)));
        fStr << "const char " << argv[3] << "[" << numChunks           << "]"    <<
                                            "[" << MAX_CHARS_PER_ARRAY << "]= {" << std::endl;
        std::size_t count = 0;
        fStr << std::hex;
        while (count < bufferedData.size())
        {
            std::size_t n = 0;
            fStr << "{";
            for (; n < MAX_CHARS_PER_ARRAY - 1 && count < bufferedData.size(); ++n)
            {
                fStr << "'\\x" << int(unsigned char(bufferedData[count++])) << "',";
            }
            // fill missing part to reach fixed chunk size with zero entries
            for (std::size_t j = 0; j < (MAX_CHARS_PER_ARRAY - 1) - n; ++j)
            {
                fStr << "'\\0',";
            }
            fStr << "'\\0'}";
            if (count < bufferedData.size())
            {
                fStr << ",\n";
            }
        }
        fStr << "};\n";
    }

    return 0;
}

0

Ця проблема мене дратувала, і xxd не працює для мого випадку використання, тому що вона зробила змінну, яка називається чимось на зразок __home_myname_build_prog_cmakelists_src_autogen, коли я намагався її скриптувати, тому я зробив утиліту, щоб вирішити цю точну проблему:

https://github.com/Exaeta/brcc

Він генерує вихідний і заголовковий файл і дозволяє явно встановити ім’я кожної змінної, щоб потім ви могли використовувати їх через std :: begin (ім'я масиву) та std :: end (ім'я масиву).

Я включив його у свій проект cmake так:

add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.hpp ${CMAKE_CURRENT_BINARY_DIR}/binary_resources.cpp
  COMMAND brcc ${CMAKE_CURRENT_BINARY_DIR}/binary_resources RGAME_BINARY_RESOURCES_HH txt_vertex_shader ${CMAKE_CURRENT_BINARY_DIR}/src/vertex_shader1.glsl
  DEPENDS src/vertex_shader1.glsl)

З невеликими налаштуваннями, я гадаю, це могло б змусити працювати і на C.


-1

в хх

"this is a "
"buncha text"

в main.c

#include <stdio.h>
int main(void)
{
    char *textFileContents =
#include "x.h"
    ;

    printf("%s\n", textFileContents);

    return 0
}

повинен зробити роботу.


Для кількох рядків потрібно додати \ n так: "рядок 1 \ n" "рядок 2 \ n"
Superfly Jon,

це трохи оманливо, очевидно, це вимагає певної підготовки текстового файлу для додавання лапок та \ n символів, не працює в загальному випадку
Марк Ч.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.