{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python\n",
    "\n",
    "## Работа с файлами\n",
    "\n",
    "### Раздел 1. Общие сведения\n",
    "\n",
    "В некоторых проектах просто невозможно избежать необходимости создавать хранилище данных. В случаях, когда речь идет о большом количестве данных, которые реализуют какую-то схему (в контексте релляционных БД это озночает, что они могут быть описаны в виде таблицы или совокупности таблиц), лучше всего подходят базы данных. Но очевидно, что создавать базу данных только лишь для хранения конфигурации — не самое оптимальное решение. К тому же, часть задач требует чтения информации из файлов или записи информации в файлы. Таким образом, понимание работы с файлами необходима и может пригодиться в очень задачах из различных областей.\n",
    "\n",
    "### Раздел 2. Взаимодействие с binary-файлами\n",
    "\n",
    "Прежде всего необходимо отметить, что в Python есть два типа данных, которые позволяют оперировать над двоичными данными: bytes и bytearray. Оба из них содержат последовательность из нуля и более целых чисел от 0 до 255 включительно. Оба типа похожи на тип string и предоставляют схожий интерфейс. В дополнение, bytes является неизменяемым, а bytearray — изменяемым. В результате, для последнего доступен интерфейс, схожий со списками.\n",
    "\n",
    "Взаимодействие с двоичным представлением данных как правило позволяет добиться наибольшей компрессии данных. Самым удобным в данном случае является использование модуля Pickles, хотя ручная обработка бинарных данных может помочь сэкономить немного дискового пространства.\n",
    "\n",
    "#### Pickles с опциональным сжатием\n",
    "\n",
    "Pickles — самый удобный способ сохранять в и загружать из файлов данные, представленные в виде объектов языка Python. При этом Pickles не предоставляет никаких механизмов защиты, так что загрузка объекта, полученного извне, несет с собой потенциальные угрозы.\n",
    "\n",
    "Вот пример кода для экспорт объекта при помощи Pickles (из Саммерфилда):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_pickle(self, filename, compress=False):\n",
    "    fh = None\n",
    "    try:\n",
    "        if compress:\n",
    "            fh = gzip.open(filename, \"wb\")\n",
    "        else:\n",
    "            fh = open(filename, \"wb\")\n",
    "        pickle.dump(self, fh, pickle.HIGHEST_PROTOCOL)\n",
    "        return True\n",
    "    except (EnvironmentError, pickle.PicklingError) as err:\n",
    "        print(\"{0}: export error: {1}\".format(os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "    finally:\n",
    "        if fh is not None:\n",
    "            fh.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если был передан параметр compression со значением True, то будет выполнено сжатие при помощи модуля gzip. В противном случае, дескриптор файла будет получен при помощи функции open. И в том, и в другом случае доступ к файлу будет осуществляться в бинарном режиме для чтения (wb). Для записи объекта в файл используется вызов dump модуля pickle, с параметром pickle.HIGHEST_PROTOCOL который отвечает за использование компактного двоичного формата.\n",
    "\n",
    "В случае, если мы получили какое-то исключение, мы его обработает, выведя сообщение об ошибке и вернув False в качестве результата.\n",
    "\n",
    "Если никаких исключений не было, то возвращается значение True.\n",
    "\n",
    "В любом случае, дескриптор файла необходимо закрыть, так что в блоке finally содержится соответствующий вызов, если дескриптор файла не является None на момент вызова."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Небольшая вставка про магические числа\n",
    "\n",
    "Магические числа (magic numbers) — специальные последовательности бит, которые располагаются где-то вначале файла, которые позволяют определить, к какому типу относится данный файл. За всеми подробностями обращайтесь к `man file` и `man magic`, но если коротко, то волшебные числа определяются при помощи смещения, типа значения и самого значения. Т.е. вы можете определить собственное магическое число, которое представлено, скажем строкой \"Lambda\" со смещением 42. Тогда взяв любой файл, вы сможете определить, относится он или не относится к определенному вами типу."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Перейдем теперь к импорту файлов при помощи Pickles (код также из Саммерфилда):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "GZIP_MAGIC = b\"\\x1F\\x8B\"\n",
    "\n",
    "def import_pickle(self, filename):\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, \"rb\")\n",
    "        magic = fh.read(len(GZIP_MAGIC))\n",
    "        if magic == GZIP_MAGIC:\n",
    "            fh.close()\n",
    "            fh = gzip.open(filename, \"rb\")\n",
    "        else:\n",
    "            fh.seek(0)\n",
    "        self.clear()\n",
    "        self.update(pickle.load(fh))\n",
    "        return True\n",
    "    except (EnvironmentError, pickle.UnpicklingError) as err:\n",
    "        print(\"{0}: import error: {1}\".format(os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "    finally:\n",
    "        if fh is not None:\n",
    "            fh.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь мы просто открываем файл, проверяем, есть ли в начале него магическое число, отвечающее формату gzip. Если есть, то закрываем дескриптор файла и открываем снова при помощи вызова open модуля gzip. В противном случае возвращаемся к началу файла при помощи метода seek файлового дескриптора. В обоих случаях открытие происходит с двоичным доступом для чтения (rb). После чего очищаем объект self при помощи метода clear и заполняем его при помощи метода update результатом работы вызова load модуля pickle, которому передаем файловый дескриптор.\n",
    "\n",
    "Если в результате было получено исключение, выведем его и вернем False. В противном случае возвращаемое значение равно True. В блоке finally закрываем дескриптор файла, если он существует."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Раздел 3. Взаимодействие с тексстовыми файлами\n",
    "\n",
    "Запись текстовых файлов — очень простой процесс, но вот парсинг данных из текстового файла — занятие трудозатратное.\n",
    "\n",
    "#### Запись в текстовые файлы\n",
    "\n",
    "Мы можем записать текст в виде пар key=value, обозначив данные narrative (описание произошедшего) при помощи маркеров .NARRATIVE_START и .NARRATIVE_END\n",
    "\n",
    "Например:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[20070927022009C]\n",
    "\n",
    "date=2007-09-27\n",
    "\n",
    "aircraft_id=1675B\n",
    "\n",
    "aircraft_type=DHC-2-MK1\n",
    "\n",
    "airport=MERLE K (MUDHOLE) SMITH\n",
    "\n",
    "pilot_percent_hours_on_type=46.1538461538\n",
    "\n",
    "pilot_total_hours=13000\n",
    "\n",
    "midair=0\n",
    "\n",
    ".NARRATIVE_START.\n",
    "\n",
    "    ACCORDING TO THE PILOT, THE DRAG LINK FAILED DUE TO AN OVERSIZED\n",
    "    TAIL WHEEL TIRE LANDING ON HARD SURFACE.\n",
    "    \n",
    ".NARRATIVE_END."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Запись текста в файл будет производиться следующим образом (код из Саммерфилда):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_text(self, filename):\n",
    "    wrapper = textwrap.TextWrapper(initial_indent=\"    \", subsequent_indent=\"    \")\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, \"w\", encoding=\"utf8\")\n",
    "        for incident in self.values():\n",
    "            narrative = \"\\n\".join(wrapper.wrap(incident.narrative.strip()))\n",
    "            fh.write(\"[{0.report_id}]\\n\"\n",
    "                \"date={0.date!s}\\n\"\n",
    "                \"aircraft_id={0.aircraft_id}\\n\"\n",
    "                \"aircraft_type={0.aircraft_type}\\n\"\n",
    "                \"airport={airport}\\n\"\n",
    "                \"pilot_percent_hours_on_type=\"\n",
    "                \"{0.pilot_percent_hours_on_type}\\n\"\n",
    "                \"pilot_total_hours={0.pilot_total_hours}\\n\"\n",
    "                \"midair={0.midair:d}\\n\"\n",
    "                \".NARRATIVE_START.\\n{narrative}\\n\"\n",
    "                \".NARRATIVE_END.\\n\\n\".format(incident, airport=incident.airport.strip(), narrative=narrative))\n",
    "        if fh is not None:\n",
    "            fh.close()\n",
    "        return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь мы просто открываем файл для записи в текстовом режиме (w) в кодировке utf-8 и делаем форматный вывод нашего объекта."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Чтение текстового файла\n",
    "\n",
    "Чтение текстового файла может производиться при помощи \"ручной\" обработки текста:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def import_text_manual(self, filename):\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, encoding=\"utf8\")\n",
    "        self.clear()\n",
    "        data = {}\n",
    "        narrative = None\n",
    "        for lino, line in enumerate(fh, start=1):\n",
    "            line = line.rstrip()\n",
    "            if not line and narrative is None:\n",
    "                continue\n",
    "            if narrative is not None:\n",
    "                if line == \".NARRATIVE_END.\":\n",
    "                    data[\"narrative\"] = textwrap.dedent(\n",
    "                    narrative).strip()\n",
    "                if len(data) != 9:\n",
    "                    raise IncidentError(\"missing data on \"\n",
    "                \"line {0}\".format(lino))\n",
    "                incident = Incident(**data)\n",
    "                self[incident.report_id] = incident\n",
    "                data = {}\n",
    "                narrative = None\n",
    "                else:\n",
    "                    narrative += line + \"\\n\"\n",
    "            elif (not data and line[0] == \"[\" and line[-1] == \"]\"):\n",
    "                data[\"report_id\"] = line[1:-1]\n",
    "            elif \"=\" in line:\n",
    "                key, value = line.split(\"=\", 1)\n",
    "                if key == \"date\":\n",
    "                    data[key] = datetime.datetime.strptime(value, \"%Y-%m-%d\").date()\n",
    "                elif key == \"pilot_percent_hours_on_type\":\n",
    "                    data[key] = float(value)\n",
    "                elif key == \"pilot_total_hours\":\n",
    "                    data[key] = int(value)\n",
    "                elif key == \"midair\":\n",
    "                    data[key] = bool(int(value))\n",
    "                else:\n",
    "                    data[key] = value\n",
    "            elif line == \".NARRATIVE_START.\":\n",
    "                narrative = \"\"\n",
    "            else:\n",
    "                raise KeyError(\"parsing error on line {0}\".format(\n",
    "                lino))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как видно, парсинг файла \"вручную\" сопряжено с огромной головной болью, необходимо вручную отлавливать ключи и т.д.\n",
    "\n",
    "С другой стороны, можно подключить инструментарий регулярных выражений для парсинга текста:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def import_text_regex(self, filename):\n",
    "    incident_re = re.compile(\n",
    "    r\"\\[(?P<id>[^]]+)\\](?P<keyvalues>.+?)\"\n",
    "    r\"^\\.NARRATIVE_START\\.$(?P<narrative>.*?)\"\n",
    "    r\"^\\.NARRATIVE_END\\.$\",\n",
    "    re.DOTALL|re.MULTILINE)\n",
    "    key_value_re = re.compile(r\"^\\s*(?P<key>[^=]+?)\\s*=\\s*\"\n",
    "    r\"(?P<value>.+?)\\s*$\", re.MULTILINE)\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, encoding=\"utf8\")\n",
    "        self.clear()\n",
    "        for incident_match in incident_re.finditer(fh.read()):\n",
    "            data = {}\n",
    "            data[\"report_id\"] = incident_match.group(\"id\")\n",
    "            data[\"narrative\"] = textwrap.dedent(\n",
    "            incident_match.group(\"narrative\")).strip()\n",
    "            keyvalues = incident_match.group(\"keyvalues\")\n",
    "            for match in key_value_re.finditer(keyvalues):\n",
    "                data[match.group(\"key\")] = match.group(\"value\")\n",
    "                data[\"date\"] = datetime.datetime.strptime(\n",
    "                data[\"date\"], \"%Y-%m-%d\").date()\n",
    "                data[\"pilot_percent_hours_on_type\"] = (\n",
    "                float(data[\"pilot_percent_hours_on_type\"]))\n",
    "                data[\"pilot_total_hours\"] = int(\n",
    "                data[\"pilot_total_hours\"])\n",
    "                data[\"midair\"] = bool(int(data[\"midair\"]))\n",
    "            if len(data) != 9:\n",
    "                raise IncidentError(\"missing data\")\n",
    "        incident = Incident(**data)\n",
    "        self[incident.report_id] = incident\n",
    "        return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Регулярные выражения в значительной степени облегчают поиск паттернов (они для этого и нужны) в текстовых файлах."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Раздел 4. Работа с XML-файлами\n",
    "\n",
    "XML — специальный язык для разметки данных. Работа с ним может осуществляться при помощи модуля Element Tree, DOM и в ручном режиме, а также при помощи Simple API for XML (SAX).\n",
    "\n",
    "#### Использование Element Trees\n",
    "\n",
    "Запись XML:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_xml_etree(self, filename):\n",
    "    root = xml.etree.ElementTree.Element(\"incidents\")\n",
    "    for incident in self.values():\n",
    "        element = xml.etree.ElementTree.Element(\"incident\",\n",
    "        report_id=incident.report_id,\n",
    "        date=incident.date.isoformat(),\n",
    "        aircraft_id=incident.aircraft_id,\n",
    "        aircraft_type=incident.aircraft_type,\n",
    "        pilot_percent_hours_on_type=str(\n",
    "        incident.pilot_percent_hours_on_type),\n",
    "        pilot_total_hours=str(incident.pilot_total_hours),\n",
    "        midair=str(int(incident.midair)))\n",
    "        airport = xml.etree.ElementTree.SubElement(element,\n",
    "        \"airport\")\n",
    "        airport.text = incident.airport.strip()\n",
    "        narrative = xml.etree.ElementTree.SubElement(element,\n",
    "        \"narrative\")\n",
    "        narrative.text = incident.narrative.strip()\n",
    "        root.append(element)\n",
    "        tree = xml.etree.ElementTree.ElementTree(root)\n",
    "    try:\n",
    "        tree.write(filename, \"UTF-8\")\n",
    "    except EnvironmentError as err:\n",
    "        print(\"{0}: import error: {1}\".format(\n",
    "        os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "    return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь мы просто проходим по вершинам дерева и заполняем соответствующие данные.\n",
    "\n",
    "Чтение XML:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def import_xml_etree(self, filename):\n",
    "    try:\n",
    "        tree = xml.etree.ElementTree.parse(filename)\n",
    "    except (EnvironmentError,\n",
    "        xml.parsers.expat.ExpatError) as err:\n",
    "        print(\"{0}: import error: {1}\".format(\n",
    "        os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "    self.clear()\n",
    "    for element in tree.findall(\"incident\"):\n",
    "        try:\n",
    "            data = {}\n",
    "            for attribute in (\"report_id\", \"date\", \"aircraft_id\",\n",
    "                \"aircraft_type\",\n",
    "                \"pilot_percent_hours_on_type\",\n",
    "                \"pilot_total_hours\", \"midair\"):\n",
    "                data[attribute] = element.get(attribute)\n",
    "                data[\"date\"] = datetime.datetime.strptime(\n",
    "                data[\"date\"], \"%Y-%m-%d\").date()\n",
    "                data[\"pilot_percent_hours_on_type\"] = (\n",
    "                float(data[\"pilot_percent_hours_on_type\"]))\n",
    "                data[\"pilot_total_hours\"] = int(\n",
    "                data[\"pilot_total_hours\"])\n",
    "                data[\"midair\"] = bool(int(data[\"midair\"]))\n",
    "                data[\"airport\"] = element.find(\"airport\").text.strip()\n",
    "                narrative = element.find(\"narrative\").text\n",
    "                data[\"narrative\"] = (narrative.strip() if narrative is not None else \"\")\n",
    "                incident = Incident(**data)\n",
    "                self[incident.report_id] = incident\n",
    "        except (ValueError, LookupError, IncidentError) as err:\n",
    "            print(\"{0}: import error: {1}\".format(\n",
    "            os.path.basename(sys.argv[0]), err))\n",
    "            return False\n",
    "    return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В данном случае логика обратная, загружаем дерево, обходим дерево и заполняем объект self данными.\n",
    "\n",
    "#### Чтение и запись XML при помощи DOM\n",
    "\n",
    "Запись:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_xml_dom(self, filename):\n",
    "    dom = xml.dom.minidom.getDOMImplementation()\n",
    "    tree = dom.createDocument(None, \"incidents\", None)\n",
    "    root = tree.documentElement\n",
    "    for incident in self.values():\n",
    "        element = tree.createElement(\"incident\")\n",
    "        for attribute, value in (\n",
    "            (\"report_id\", incident.report_id),\n",
    "            (\"date\", incident.date.isoformat()),\n",
    "            (\"aircraft_id\", incident.aircraft_id),\n",
    "            (\"aircraft_type\", incident.aircraft_type),\n",
    "            (\"pilot_percent_hours_on_type\",\n",
    "            str(incident.pilot_percent_hours_on_type)),\n",
    "            (\"pilot_total_hours\",\n",
    "            str(incident.pilot_total_hours)),\n",
    "            (\"midair\", str(int(incident.midair)))):\n",
    "                element.setAttribute(attribute, value)\n",
    "                for name, text in ((\"airport\", incident.airport), (\"narrative\", incident.narrative)):\n",
    "                    text_element = tree.createTextNode(text)\n",
    "                    name_element = tree.createElement(name)\n",
    "                    name_element.appendChild(text_element)\n",
    "                    element.appendChild(name_element)\n",
    "                    root.appendChild(element)\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, \"w\", encoding=\"utf8\")\n",
    "        tree.writexml(fh, encoding=\"UTF-8\")\n",
    "        return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "\n",
    "Чтение:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def import_xml_dom(self, filename):\n",
    "    def get_text(node_list):\n",
    "        text = []\n",
    "        for node in node_list:\n",
    "            if node.nodeType == node.TEXT_NODE:\n",
    "                text.append(node.data)\n",
    "        return \"\".join(text).strip()\n",
    "    \n",
    "    try:\n",
    "        dom = xml.dom.minidom.parse(filename)\n",
    "    except (EnvironmentError,\n",
    "        xml.parsers.expat.ExpatError) as err:\n",
    "        print(\"{0}: import error: {1}\".format(\n",
    "        os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "    self.clear()\n",
    "    for element in dom.getElementsByTagName(\"incident\"):\n",
    "        try:\n",
    "            data = {}\n",
    "            for attribute in (\"report_id\", \"date\", \"aircraft_id\",\n",
    "            \"aircraft_type\",\n",
    "            \"pilot_percent_hours_on_type\",\n",
    "            \"pilot_total_hours\", \"midair\"):\n",
    "                data[attribute] = element.getAttribute(attribute)\n",
    "                data[\"date\"] = datetime.datetime.strptime(\n",
    "                data[\"date\"], \"%Y-%m-%d\").date()\n",
    "                data[\"pilot_percent_hours_on_type\"] = (\n",
    "                float(data[\"pilot_percent_hours_on_type\"]))\n",
    "                data[\"pilot_total_hours\"] = int(\n",
    "                data[\"pilot_total_hours\"])\n",
    "                data[\"midair\"] = bool(int(data[\"midair\"]))\n",
    "                airport = element.getElementsByTagName(\"airport\")[0]\n",
    "                data[\"airport\"] = get_text(airport.childNodes)\n",
    "                narrative = element.getElementsByTagName(\n",
    "                \"narrative\")[0]\n",
    "                data[\"narrative\"] = get_text(narrative.childNodes)\n",
    "                incident = Incident(**data)\n",
    "                self[incident.report_id] = incident\n",
    "        except (ValueError, LookupError, IncidentError) as err:\n",
    "            print(\"{0}: import error: {1}\".format(\n",
    "            os.path.basename(sys.argv[0]), err))\n",
    "            return False\n",
    "    return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Запись XML-файлов \"вручную\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def export_xml_manual(self, filename):\n",
    "    fh = None\n",
    "    try:\n",
    "        fh = open(filename, \"w\", encoding=\"utf8\")\n",
    "        fh.write('<?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n')\n",
    "        fh.write(\"<incidents>\\n\")\n",
    "        for incident in self.values():\n",
    "            fh.write('<incident report_id={report_id} '\n",
    "            'date=\"{0.date!s}\" '\n",
    "            'aircraft_id={aircraft_id} '\n",
    "            'aircraft_type={aircraft_type} '\n",
    "            'pilot_percent_hours_on_type='\n",
    "            '\"{0.pilot_percent_hours_on_type}\" '\n",
    "            'pilot_total_hours=\"{0.pilot_total_hours}\" '\n",
    "            'midair=\"{0.midair:d}\">\\n'\n",
    "            '<airport>{airport}</airport>\\n'\n",
    "            '<narrative>\\n{narrative}\\n</narrative>\\n'\n",
    "            '</incident>\\n'.format(incident,\n",
    "            report_id=xml.sax.saxutils.quoteattr(\n",
    "            incident.report_id),\n",
    "            aircraft_id=xml.sax.saxutils.quoteattr(\n",
    "            incident.aircraft_id),\n",
    "            aircraft_type=xml.sax.saxutils.quoteattr(\n",
    "            incident.aircraft_type),\n",
    "            airport=xml.sax.saxutils.escape(incident.airport),\n",
    "            narrative=\"\\n\".join(textwrap.wrap(\n",
    "            xml.sax.saxutils.escape(\n",
    "            incident.narrative.strip()), 70))))\n",
    "            fh.write(\"</incidents>\\n\")\n",
    "        return True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Чтение XML при помощи SAX"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def import_xml_sax(self, filename):\n",
    "    fh = None\n",
    "    try:\n",
    "        handler = IncidentSaxHandler(self)\n",
    "        parser = xml.sax.make_parser()\n",
    "        parser.setContentHandler(handler)\n",
    "        parser.parse(filename)\n",
    "        return True\n",
    "    except (EnvironmentError, ValueError, IncidentError,\n",
    "        xml.sax.SAXParseException) as err:\n",
    "        print(\"{0}: import error: {1}\".format(\n",
    "        os.path.basename(sys.argv[0]), err))\n",
    "        return False\n",
    "\n",
    "class IncidentSaxHandler(xml.sax.handler.ContentHandler):\n",
    "    def __init__(self, incidents):\n",
    "        super().__init__()\n",
    "        self.__data = {}\n",
    "        self.__text = \"\"\n",
    "        self.__incidents = incidents\n",
    "        self.__incidents.clear()\n",
    "\n",
    "    def startElement(self, name, attributes):\n",
    "        if name == \"incident\":\n",
    "            self.__data = {}\n",
    "        for key, value in attributes.items():\n",
    "            if key == \"date\":\n",
    "                self.__data[key] = datetime.datetime.strptime(\n",
    "                value, \"%Y-%m-%d\").date()\n",
    "            elif key == \"pilot_percent_hours_on_type\":\n",
    "                self.__data[key] = float(value)\n",
    "            elif key == \"pilot_total_hours\":\n",
    "                self.__data[key] = int(value)\n",
    "            elif key == \"midair\":\n",
    "                self.__data[key] = bool(int(value))\n",
    "            else:\n",
    "                self.__data[key] = value\n",
    "                self.__text = \"\"\n",
    "\n",
    "    def endElement(self, name):\n",
    "        if name == \"incident\":\n",
    "            if len(self.__data) != 9:\n",
    "                raise IncidentError(\"missing data\")\n",
    "            incident = Incident(**self.__data)\n",
    "            self.__incidents[incident.report_id] = incident\n",
    "        elif name in frozenset({\"airport\", \"narrative\"}):\n",
    "            self.__data[name] = self.__text.strip()\n",
    "            self.__text = \"\"\n",
    "\n",
    "        def characters(self, text):\n",
    "            self.__text += text"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Раздел 5. Работа с файлами, формалированными в других стандартах описания данных\n",
    "\n",
    "Зачастую конфигурации удобно хранить в форматах JSON и Yaml. Доступ осуществляется соответственно при помощи модулей JSON и PyYaml.\n",
    "\n",
    "Чтение и запись из производится при помощи методов load и dump из соответствующих классов."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Раздел 6. Домашнее задание\n",
    "\n",
    "- Выполнить все упражнения после 7 главы Саммерфилда\n",
    "- Прочитать в Саммерфилде про ручную работу с binary-файлами и Random Access binary-файлами\n",
    "- Почитать про Big Endian / Little Endian\n",
    "- Почитать про Document Object Model (DOM)\n",
    "- Выполнить субботнюю задачу\n",
    "\n",
    "Для самых настойчивых:\n",
    "\n",
    "- Написать свой reader и writer для файлов JSON и/или Yaml"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}