All posts by Admin

Метод наполнения базы SQLite через выгрузку Excel данных в XML

Здесь будет описан метод, который позволяет наполнять базу данных SQLite из Excel посредством выгрузки данных в XML.

SQLite — легковесная встраиваемая реляционная база данных. Исходный код библиотеки передан в общественное достояние. В 2005 году проект получил награду Google-O’Reilly Open Source Awards. Простота и удобство встраивания SQLite привели к тому, что библиотека используется в браузерах, музыкальных плеерах и многих других программах. Многие программы поддерживают SQLite в качестве формата хранения данных (особенно в Mac OS и iPhone OS, Android).

Можно использовать Microsoft Excel 2007, на других версиях метод не тестировался. Предположим, есть заполненная данными таблица Excel 2007 с четырьмя столбцами: ID, Text, Author, Favorite.

1. Создаем XML файл в Windows – образец будущего большого XML файла. Он будет играть роль схемы в Excel. Например,

<?xml version=”1.0″ encoding=”UTF-8″ standalone=”yes”?>
<BusCits xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”>
<BusCit>
<ID>1</ID>
<Text>Т1</Text>
<Author>А1</Author>
<Favorite>N</Favorite>
</BusCit>
<BusCit>
<ID>2</ID>
<Text>Т2</Text>
<Author>А2</Author>
<Favorite>N</Favorite>
</BusCit>
</BusCits>

Здесь представлены два элемента BusCit, в каждом из которых есть параметры ID, Text, Author, Favorite. Элементы являются дочерними по отношению к главному элементу BusCits. Сохраняем этот образцовый XML. Назовем его XMLSchema.xml.

2. В Excel 2007 в Ribbon’е есть вкладка Разработчик. Открываем ее. На этой вкладке есть раздел XML. Нажимаем там на кнопку “Источик”, а затем в появившейся справа панели на кнопку “Карты XML…”. Нажимаем Добавить и добавляем ранее сохраненный XMLSchema.xml. Нажимаем Ок. Соглашаемся использовать маленький XML в качестве схемы. Дальше в той же правой панели Источник XML должна появиться ваша схема. Нажимаем правой кнопкой на ID и выбираем Сопоставить элемент. Далее выбираем первый столбец в таблице с ID, нажав на букву, которая его идентифицирует, при этом заголовки тоже выделяются(A, B, C и т.д). Аналогично поступаем с каждым другим параметром: Text, Author, Favorite.  Мы привязали XML схему к таблице, теперь во вкладке Разработчик/XML в Ribbon нажимаем Export и экспортируем XML с нужным нам названием, например BusCits.xml. Готово, получившийся XML можно удобно посмотреть в программе Notepad++ и проверить. Должно получиться что-то вроде этого:

3. Запускаем Mac Os X. Заходим в XCode и, используя сайт http://www.tbxml.co.uk и  библиотеку, описанную в нем, пишем код для парсинга XML файла. Можно использовать любую другую облегченную библиотеку XML.
Пример работающего кода:


-(void) insertBusCitsToDatabase {
// Setup the database object
sqlite3 *database;
char *sqlStatement;
NSString *insert;
//Подготовка к парсингу XML
TBXML * tbxml = [[TBXML tbxmlWithXMLFile:@"BusCits.xml"] retain];
//Получение корневого элемента
TBXMLElement * rootXMLElement = tbxml.rootXMLElement;
TBXMLElement * XMLBusCit;
TBXMLElement * XMLID;
TBXMLElement * XMLText;
TBXMLElement * XMLAuthor;
TBXMLElement * XMLFavorite;
NSString *BusCitText;
NSString *BusCitId;
NSString *BusCitAuthor;
NSString *BusCitFavorite;
char *errMsg;
int returnValue;

// Init the busCits Array
busCits = [[NSMutableArray alloc] init];

//Cycle until XML ended
//1. Read XML to busCit object
//2. Insert busCit object to DB
//End Cycle

// Open the database from the users filessytem
if(sqlite3_open([databasePath UTF8String], &database) == SQLITE_OK) {

XMLBusCit = [TBXML childElementNamed:@"BusCit" parentElement:rootXMLElement];
if (XMLBusCit==nil) {
return;
}

while (XMLBusCit) {

if (XMLBusCit==nil) {
break;
}
XMLID = [TBXML childElementNamed:@"ID" parentElement:XMLBusCit];
XMLText = [TBXML childElementNamed:@"Text" parentElement:XMLBusCit];
XMLAuthor = [TBXML childElementNamed:@"Author" parentElement:XMLBusCit];
XMLFavorite = [TBXML childElementNamed:@"Favorite" parentElement:XMLBusCit];
BusCitText = [TBXML textForElement:XMLText];
BusCitId = [TBXML textForElement:XMLID];
BusCitAuthor = [TBXML textForElement:XMLAuthor];
BusCitFavorite = [TBXML textForElement:XMLFavorite];

NSLog(BusCitId);
NSLog(BusCitText);
NSLog(BusCitAuthor);
NSLog(BusCitFavorite);
sqlite3_stmt *insertStatement;
char *st;

//Генерация и подготовка SQL выражения для вставки записи

insert = [[NSString alloc] initWithFormat:@"INSERT OR REPLACE INTO BusCits (ID, Text, Author, Favorite) VALUES ('%@', '%@', '%@', '%@');", BusCitId, BusCitText, BusCitAuthor, BusCitFavorite];
st = [insert UTF8String];
//NSLog(st);
returnValue = sqlite3_exec (database, st, NULL, NULL, &errMsg);
if (returnValue != SQLITE_OK)
{
NSLog(@"Addition failed");
//NSLog(errMsg);
}
else
{
NSLog(@"Addition Ok!");

}
XMLBusCit = XMLBusCit->nextSibling;
}

// Setup the SQL Statement and compile it for faster access
const char *sqlStatement = "SELECT * FROM BusCits";
sqlite3_stmt *compiledStatement;
if(sqlite3_prepare_v2(database, sqlStatement, -1, &compiledStatement, NULL) == SQLITE_OK)
{
// Loop through the results and add them to the feeds array
while(sqlite3_step(compiledStatement) == SQLITE_ROW)
{
// Read the data from the result row
NSString *aId = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
NSString *aText = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 2)];
NSString *aAuthor = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 3)];
NSString *aFavorite = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 4)];

// Create a new animal object with the data from the database
BusCit *busCit = [[BusCit alloc] initWithID:aId Text:aText Author:aAuthor Favorite:aFavorite];
// Add the animal object to the animals Array
[busCits addObject:busCit];

[busCit release];
}
}
// Release the compiled statement from memory
sqlite3_finalize(compiledStatement);

}
sqlite3_close(database);

}

Для того, чтобы посмотреть и проверить работу используется NSLog. После отработки функции можно нажать Shift+Command+R и вызвать консоль, в которой пишется то, что в NSLog.

О runtime ошибке SIGABRT

Баг с текстом SIGABRT в NSLog может появляться при тестировании программы на iPhone Simulator. Например, при открытии нового View. То есть он возникает во время работы программы, а компилятор XCode не находит эту ошибку. На понимание причины бага или сигнала SIGABRT было потрачено 3-4 часа. Поэтому было решено написать эту статью. Посмотрим сначала, что нам пишет википедия об этой ошибке.

POSIX® (англ. Portable Operating System Interface for Unix — Переносимый интерфейс операционных систем Unix) — набор стандартов, описывающих интерфейсы между операционной системой и прикладной программой. Стандарт создан для обеспечения совместимости различных UNIX-подобных операционных систем и переносимости прикладных программ на уровне исходного кода, но может быть использован и для не-Unix систем.
В POSIX-системах, SIGABRT — сигнал, посылаемый процессом самому себе при выполнении функции abort(), для аварийного останова, в случае невозможности дальнейшего продолжения программы. Сигнал SIGABRT процесс посылает сам себе, когда вызывает функцию abort() (функция libc, с прототипом в stdlib.h). Сигнал SIGABRT может быть перехвачен или проигнорирован, но abort() принудительно завершит программу (предварительно сбросив и закрыв стандартные потоки и сделав дамп памяти программы) даже если этого не сделает обработчик сигнала. Таким образом, функция abort() гарантирует завершение программы, почему она часто используется библиотеками в аварийных ситуациях — когда выполнение не должно быть продолжено, но основная программа должна иметь возможность выполнить действия, необходимые перед аварийным завершением. Также SIGABRT вызвается утверждениями (англ. assertions; средство отладки Си — макроопределение assert() вызывает abort() если утверждение переданное в качестве аргумента ложно).

Из этого текста можно понять, что ошибка или сигнал SIGABRT посылается просто из-за вызова функции abort() при падении программы. В свою очередь эта функция может вызываться утверждением assert(), но мы не можем посмотреть и выявить точную причину бага. Поэтому SIGABRT – один из самых вредных и труднопреодолимых багов при разработке для iPhone: отладка(debugging) не помогает, так как ошибка возникает где-то в коде, который не доступен разработчику. В интернете можно прочитать много всего про этот баг приложений для iPhone. Кому-то помогла перезагрузка XCode каким-то магическим образом или “Clean all Targets”. Пишут также, что это ошибка XCode или Симулятора, а не ошибка разработчика. Я думаю, что проблема в другом.

Наверное, главная и самая распространенная причина этой ошибки(касательно iPhone, iPhone Simulator) в том, что XIB не соответствует вашим аутлетам(Outlets) или классам. Например, если написать:
@interface FavoritesTableView : UITableViewController,

когда у вас нет контроллера TableViewController, а вместо него обычный View с контролом TableView, то выскочит эта ошибка. Видимо, программа пытается обратиться к сущностям, которых нет, при загрузке View. Они либо были освобождены из памяти(deallocated) раньше времени, либо их просто нет (например, в XIB). Далее вызывается abort(), который посылает сигнал SIGABRT.

Итак, в данном случае надо было написать:
@interface FavoritesTableView : UIViewController

Также SIGABRT может быть вызван обращением к любой несуществующей сущности – к элементу за пределами массива, к переменной, объекту, XIB файлу и т.д.