W Cat : другие произведения.

Python в библиотеке

Самиздат: [Регистрация] [Найти] [Рейтинги] [Обсуждения] [Новинки] [Обзоры] [Помощь|Техвопросы]
Ссылки:


 Ваша оценка:
  • Аннотация:
    Читаю я много, стал-быть и скачиваю много. Но порядок в библиотеке быть должон. Ну, как: разложить по авторам, по сериалам, по темам; удалить двойников, да мало ли что еще захочется. Да поможет нам Питон.

Python в библиотеке

 []

1. Начало

     = Привет. О чем будем говорить сегодня?
     - Здравствуй, ну конечно о программировании.
     = А конкретнее.
     - Должен тебе сказать что я сменил операционку. Теперь развлекаюсь с Ubuntu. Соответственно, возникла проблема с программированием.
     = А в чем проблема? Ubuntu это же Linux, а значит там есть С
     - Да, но я не хочу возится с С.
     = Почему! Ведь на С работают все профессионалы?
     - Не хочу быть профессионалом, вспомни: «Узкий специалист подобен флюсу»
     = Пока не понял.
     - Любые аналогии ущербны, но попробуем:
     1. все процедурные языки программирования аналогичны, достаточно знать любой один и доступны к пониманию все остальные ( «Я трохи розумию украйиньску мову, але ж балакаты не вмию» - думаю смысл предложения внятен, - славянские языки родственны)
     2. чем различаются процедурные языки программирования? Думаю, временем создания. Если просмотреть с-образные языки, идет развитие по повышению удобств, а другая ветвь это освоение новых устройств да и просто следование моде.
     3. сравниваю программирование со строительством. Можно строить из кирпичей, можно из шлакоблоков можно из ж-б панелей, а можно замесить песочек со связующим и лепить все что угодно. Т.е. выбор за вами, чем крупнее блок, тем быстрее стройка, но вам навязывают чужие решения. Хотите индивидуальности? Лепите сами. Чаще оптимален кирпич.
     = И каков твой кирпич?
     - Сегодня это Python.
     = Почему?
     - Ну, во-первых в Ubuntu он также предоставляется как и С, а во-вторых... пора переходить к теме сегодняшней встречи...
     = ...?
     - Как ты знаешь, я запойный читатель.
     = Еще и запойный...
     - Моя читалка, лучше всего понимает fb2 кои я качаю десятками и более. В процессе возникают проблемы, которые ранее я решал с помощью Delphi, а теперь те программы, хочу перевести на Python.
     А теперь скажи, что ты знаешь о Питоне.
     = Ну, проходили... ничего особенного: те-же циклы, списки, да есть еще словари, но это тоже не уникальность, а! еще пробелы.
     - Не раз уже говорил, что прочитать даже десять книг о языке, не достаточно, нужна практика, следует писать прогы...
     = Так, давай к делу...
      

Приложения?

     - Читал я как-то книгу, уже не помню какую, но по программированию. Хорошо была написана, живенько. Прочитал, а затем захотел опробовать приведенные в ней примеры.
     Вот тут-то я и помучился, ну не помню, где среди забавных высказываний автора скрываются эти самые примеры.
     - Наученный таким опытом, я решил выкладывать готовые скрипты в приложениях к главам.
     Скрипты будут написаны в Python3.
     В конце текста я планирую сделать «Скрипто-содержание»

2. Сортировка

Предпосылки задачи
     Обычно я скачиваю 50 — 80 книг. Часто, часть из них одного автора.
     Кроме того, экран моей читалки в режиме «библиотека» показывает 11 строк с возможностью перелистывания на следующие 11 (но лень листать).
      
     Задача 1.
     Написать программу раскладывающую файлы по авторам и остающиеся файлы () разложить по 11 штук в разные папки.
      
     Т.е в результате:
     Иванов — 5 книг
     Петров — 2 книги
     Сидоров — 25 книг
     ...
     и т.д.
     ...
     f1 — 11 книг
     f2 — 11 книг
     f3 — 6 книг - остаток
      
     = А че это у Сидорова так много
     - Ну, плодовитый товарищ. Но, ты прав. В дальнейшем можно сделать разбивку по циклам внутри папки писателя или просто Сидоров1, Сидоров2... Кроме того, можно и получившиеся папки разложить по внешним папкам, допустим «Ы1», «Ы2» ... «Ыn» по одиннадцать штук в папке.
     = Понял, чтобы никто не догадался. А задачка, то не простая.
     - Более того! Скачиваю файлы, как правило в архивах (zip). Моя читалка отлично их воспринимает. Значит программа должна открывать архив, анализировать fb2 файл, и далее поступать как должно.
     = Ага, еще проще.
     - Вот поэтому, я перехожу в описании на Питон. В Delphi для работы с zip я пользовался сторонним кодом, это как раз та, упомянутая, вторая причина. А в Питоне с этим проблем нет.
     - Еще два замечания:
     1. Я, ни разу не являюсь гуру в Питоне, в который раз, я «только учусь» и настоящий змееуст напишет код намного элегантнее (я в курсе — что Python назван не в часть змеи). Стыдно мне было бы показывать весь путь с сотнями глупейших ошибок, но эти промахи полезны, и я рекомендую тебе совершить их тоже (правда свои собственные). Т.е. не желательно копировать приведенный код, лучше работать ручками и тогда обнаружатся коварные двоеточия, отступы и прочие радости.
      
     2. В развитие сказанного, не желательно «подсматривать ответы» давай делать так, я даю задание, ты старательно выполняешь, и лишь потом убеждаешься, что твой кунг-фу лучше.
      
     3. В Питоне громадное значение имеют правильные отступы. К сожалению fb2 «съедает» лишние пробелы и т.п., да, имеется тег <code>, но не все читалки его правильно отображают (имеется печальный опыт). Посему предлагаю в последующем коде табуляции и группы по 4 пробела я заменю на группу « . .». При необходимости, любой текстовый редактор легко произведет обратную замену.
     = Ну, хватит вступлений. За дело.
      

3. Поехали

     Задание 1.
     Прочитать, изучить, ознакомится с Питоном по учебникам. Книг можно найти море (главное не утонуть) в них с разной глубиной говорится об одном и том же...
      
     Повторю старую истину, при обучении весьма полезно делать конспекты, ну не хотите пользоваться шариковой ручкой — пожалуйста — перепечатайте какой либо pdf или djvu учебник (шутка, в которой только доля шутки).
      
     = Ну, у тебя и задания! Это же пол года му... чатся.
     - Жаль, эх молодежь все вам подавай мигом. Ну хорошо, давай первое задание будет продолжаться во время всего нашего курса. А пока:
      
     Задание 2.
     Открываем терминал и выполняем первые примеры из нашего «любого учебника» (в дальнейшем ЛУ)
      
     = Ну, попробовал я тут немного, получается, но как-то это все...
     - Убого? И был терминал безвиден и гол. Ты же понимаешь все далеко не сразу строится. Хорошо поехали дальше.
     В терминале работать очень не удобно. Можно редактировать только последнюю строку, а если хочется изменить до того, придется перебивать все строки?(я не совсем прав) Нет, такой футбол нам не нужен.
     Для себя я нашел решение в Geany (напоминаю, я работаю в Ubuntu) — отличный инструмент — текстовый редактор с подсветкой синтаксиса с автоматической поддержкой отступов, но главное: нажимаешь F5 и вылезает терминал со всеми твоими ошибками, исправляешь F5, снова ошибки, правка F5 и так до посинения или победы.
     = Слушай! Ничего не получается, пишет чего-то, никак не пойму.
     - Давай посмотрим. !!! Сколько раз говорено! Не тяни в рот всякую гадость! Сказал же! Забудь о копи-паст - заклей эти клавиши!
     Во-первых удали все русские буковки, даже в комментариях, с языком будем разбираться позже.
     Второе, для питончика важна не «видимость» одинаковых отступов они должны быть структурно одинаковы, т.е. если в одной строке блока отступ состоит из двух табуляций, а в другой, табуляция и несколько пробелов (т.е. на экране они выглядят на одном уровне), то Pyhton воспримет эти строки как разные блоки, и будет или ошибка или не предвиденные действия.
     = Во как загнул
     - Как это по русски? А, нет, лучше один раз увидеть... в меню Geany «Вид» активируй галочки «Отображать пробелы» и «Отображать линии отступов» и будет тебе счастье — теперь видны пробелы «точками» и табуляции «стрелками».
     = Ура!!! Заработало!!!
     - Поздравляю, кот Матроскин, теперь пошли дальше.
      
     Задание 3.
     Я надеюсь, ты не соришь в компьютере, и завел для работы спец. папку. В этой рабочей папке помести текстовый файл 'days.txt' в котором помести 7 строк с именами...
     = Знакомых девочек...
     - Я имел в виду дни недели, но можешь и девочек. Скрипт должен прочитать файл и распечатать его на экране.
      
     = Пум, пурум пум
     Не бе да
     Пум, пурум пум
     Да, да, да
      
     Так, оказалось, что я не только кот Матроскин но еще и Доцент, - тут помню, а тут не помню.
     Не помню как делать цикл.
     - Почитай в ЛУ. У меня это тоже часто бывает, что делать — повторение мать наша.
     = Все получилось, смотри.
     - Хорошо, ну в трех строчках сложно ошибиться.
     = Я не страшусь сложностей, и ошибиться удалось.
     - И что это было.
     = Двоеточие, теперь буду помнить.
      
     Задание 4.
     - Будем считать, что начальное знакомство с Python мы сделали. Для дальнейшей работы надо ознакомится с форматом fb2 и с идеологией XML.
      
     = Как! Опять? На мне еще первое задание висит! Опять вести конспект?
     - Не помешало бы, но не обязательно, достаточно будет понять принципы...
     fb2 это простой текстовый файл оснащенный командными тегами. Открой любой fb2 файл в любом текстовом редакторе.
     = А где его взять?
     - В интернете масса библиотек, скачать книжку не составит труда.
     = Что-ж качаем. Входит и выходит...
     Выходит что это HTML!
     - Не правильно! Fb2 это XML. Тогда созрело задание 4а, т.к. мне лень повторять прописное:
     Задание 4а.
     Выяснить в чем разница между XML и HTML(вопрос не корректен). И главное ЗАЧЕМ (у моих среднеазиатских знакомых это был любимый вопрос. Ему говоришь «Рядовой Исамбаев — два наряда без очереди», а он в ответ - «Зачем?»). Работа может вылиться в довольно интересное эссе о природе и истории Интернета.
      
     - Но, давай вернемся к нашим Питонам. Для работы с XML в Python имеется масса средств следовательно:
      
     Задание 5.
     Ознакомится с возможностями Python для работы с XML и написать программу для извлечения из fb2 файла названия книги.
      
     = Ну, у тебя и темп. Я еще не понял предыдущее задание, а ты...
     - А ты, не спеши. Построй мысленный цикл:
     Пока я не выполню задания 4 и 4а к дальнейшему чтению не переходить.
      
     = ...
     ...
     ...
     ...
      
     через два дня
     ...
     цикл выполнен.
      
     - Сейчас, настал хороший случай, показать тебе крутизну. Как уже сказано в Python масса способов работы с XML я выбрал простейший.
      
     | import xml.dom.minidom
     |
     | dom = xml.dom.minidom.parse ("proba.fb2");
     | dom.normalize()
     |
     | name = dom.getElementsByTagName('book-title')
     |
     | for node in name:
     | . . print node.childNodes[0].nodeValue
      
      
      
     = Слушай. Вопрос не по теме. Ясно, что точки и черточки в текст скрипта ты вводишь не вручную.
     - Несомненно.
     = Тогда... ты можешь дать скрипт с помощью которого ты обрабатываешь скрипты…… ой запутался в скр.
     - Конечно, получай. Но получишь скрипт в приложении к главе.
       
     - Тут есть забавный момент. Текст этой утилитки подвергся обработке ею же. И в тексте все пробелы заменились на блоки с точками. НО так можно совершенно запутаться. Посему в ДАННОМ скрипте последовательность из четырех пробелов заменена на четыре подчеркивания.
     = Т.е. '____' заменить на 4 пробела.
     - Начиная со строки «| . . . . s = s.replace('\t','____')»
     - Ну ничего, разберешься.
     = Погоди, а, что делают первые две строчки.
     - Это очень нужные строки. Они позволят выводить на экран надпись на русском языке. Ежели, с русским не заладится, ну просто замени надпись на латино-выглядящий текст.
      
     Пишите с нами, пишите лучше нас.
      
     Задание 6.
     Извлечь из архива (zip) файл средствами Python
      
     - Надо сказать, что сделать это довольно просто т.к. в нашем случае в архиве должен содержаться только один файл.
     = Да, решение нашел очень быстро.
     - Давай сравним.
      
     | import zipfile
     |
     | z = zipfile.ZipFile('proba.zip', 'r')
     |
     | z.extractall()
     (( Давай в приложение я приложу еще один скрипт "безвоздмездно - то есть даром". Я им довольно часто пользуюсь, когда скачаю на стороне голые fb2 и желательно засунуть их в zip ))
     - Давай, чуть уклонимся от прямого пути, напишем фрагментик, который в дальнейшем пригодится.
      
     Задание 7.
     Вывести на экран абсолютный адрес папки из которой запускалась программа.
      
     В Delphi не было проблем в функцию ExtractFilePath подавалась строк с полным адресом запуска программы (а это первый параметр в массиве атрибутов вызова программы) и она просто удаляла имя программы и вуаля — готово.
     = Пока не совсем понял термины...
     - Разбирайся, а пока скажу для чего это надо.
     Для выполнения основного задания будет дана папка в которой буду находится файлы nnn.fb2.zip и mmm.fb2 (под nnn и mmm я подразумеваю некоторое количество файлов).
     Как легко и просто было жить в Delphi вызываешь SelectDirectory и вот оно диалоговое окно выбора папки. Со временем, ежели оно найдется, сделаем это и в Python, а пока придется поступать максимально просто. В обрабатываемую папку поместим файл с программой и при запуске, папка должна просканироваться, а для сканирования программа должна узнать где она находится.
     Ну, как результаты работы, что-то у тебя затягивается. Ладно давай я все таки выложу свой вариант, хотя мне он активно не нравится, уверен, что ты найдешь решение получше.
      
     | import sys, os
     |
     | path = os.path.abspath(sys.argv[0])
     | lh = len(path) - len(sys.argv[0])-1
     | path = path[:lh]
     | print path
      
     - Подведем промежуточные итоги. Мы умеем открыть архив, умеем прочитать информацию из fb2 файла. Пусть в целевой папке буде 100 архивных файлов, теперь давай подумаем как будем действовать.
     1 вариант. Распаковать все архивы а затем их удалить и далее упорядочивать файлы fb2.
     . . . . можно, но мне более подходят как раз архивы, меньше места займут.
     2 вариант. Распаковывать архивы по одному в некий промежуточных temp.fb2 и хранить всю нужную информацию в какой-то структуре.
     . . . . . и так можно, но во второй части этого опуса «Двойники», будут обрабатываться тысячи архивов — это же сколько дней будет работать программа?
      
     Т.е. извлечение архива в файл — не нужный этап.
      
     Задание 8.
     Извлечь содержимое архива (напр. proba.fb2.zip) и передать его на обработку. Из полученного fb2 извлечь информацию о названии книги (ясное дело, без промежуточной записи в файл).
      
     - Интересно, читатели справятся с заданием. Оно довольно сложное.
     = А ты уверен, что кто-то это читает?
     - Думаю, хотя бы один читатель да есть. И т.к. show must go on даже для одного зрителя продолжаем...
      
     В Delphi...
     = О, опять осчастливимся осведомленнием об оракуле.
     - Игнорируем инсинуации.
     В Дельфи есть такая штука как файловый поток, в том числе файловый поток в памяти. В исходной программе я читаю архив, распаковываю его в поток в памяти и затем этот поток читаю с анализом тегов (в данном случае достаточно обработать только заголовок fb2).
     Ну, как? Получается?
     = Нет, пролистал все учебники. Такого дела не нашел.
     - Аналогично, я в свое время тоже на нашел. Ладно, ... есть такое дело как интернет, кроме мусора там можно найти и стоящие вещи. Попалась статья с таким названием «Самый быстрый SAX-парсер для python» честно сказать, задача, которую решает автор, мне показалась странной, а может я не врубился. Но именно здесь нашлось решение и выяснилось, что Дельфийскими потоками я только за... компосировал себе мозги. Вот хороший пример, - в чужом монастыре не сыграют не родные порядки.
      
     Для начала покажу найденную в этой статье возможность извлекать рисунки из fb2. Такая функция может быть полезна.
      
     | import sys, os
     | import base64
     | import libxml2
     |
     | def parseBinaryContent(filename):
     |
     | . . doc = libxml2.parseFile(filename)
     | . .
     | # filename = sys.argv[1]
     | . . if filename[-4:] == '.fb2':
     | . . . . . dirname = filename[:-4]+'_html'
     | . . . . . new_filename = filename[:-4]+'.html'
     | . . else:
     | . . . . . dirname = filename+'_html'
     | . . . . . new_filename = filename+'.html'
     | . . os.mkdir(dirname)
     |
     | . . current = doc.children
     |
     | . . # skip comment
     | . . while current:
     | . . . . if current.name == 'FictionBook':
     | . . . . . . break
     | . . . . current = current.next
     |
     | . . current = current.children
     |
     | . . while current:
     |
     | . . . . # search <binary> tag
     | . . . . if current.name == 'binary':
     |
     | . . . . . . # search "id" property
     | . . . . . . id_name = None
     | . . . . . . prop = current.properties
     | . . . . . . while prop:
     | . . . . . . . . name = prop.get_name()
     | . . . . . . . . if name == 'id':
     | . . . . . . . . . . id_name = prop.content
     | . . . . . . . . . . break
     | . . . . . . . . prop = prop.next
     |
     | . . . . . . # save binary content
     | . . . . . . if id_name:
     | . . . . . . . . data = base64.decodestring(current.content)
     | . . . . . . . . open(os.path.join(dirname, id_name), 'w').write(data)
     |
     | . . . . current = current.next
     |
     | . . doc.freeDoc()
     |
     | parseBinaryContent('proba.fb2')
      
     - Советую тщательно изучить данный пример, для нас, начинающих, здесь масса вкусненького. Например, теперь я изменю скрипт для определения пути из задания 7
      
     | import sys, os
     |
     | path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     | print path
      
     = Век живи век...
     - Моя проблема в том, что я привык к приемам работы со строками в Delphi, а здесь совсем другие подходы, возможно не менее эффективные, но надо привыкнуть.
     Ты не забыл о выполнении задания.
     = Все нормально. Не можем писать сами, позаимствуем...
     - Ну, а как еще учится!
      
     | #!/bin/env python
     | # -*- coding: utf-8 -*-
     |
     | import sys, os, zipfile
     | import xml.dom.minidom
     |
     | def parse_zip(fn):
     |
     | . .# print >> sys.stderr, 'Zip:', os.path.basename(fn)
     |
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .filelist = z.namelist()
     | . .filelist.sort()
     |
     | . .for n in filelist:
     | . . . .try:
     | . . . . . .parse_fb2(z.open(n))
     | . . . . . .# print >> sys.stderr, n
     | . . . .except:
     | . . . . . .print >> sys.stderr, 'X:', n
     |
     | def parse_fb2(fn):
     | . .if isinstance(fn, str):
     | . . . .fn = open(fn)
     | . .dom = xml.dom.minidom.parseString (fn.read());
     | . .dom.normalize()
     | . .name = dom.getElementsByTagName('book-title')
     |
     | . .for node in name:
     | . . . .print node.childNodes[0].nodeValue
     |
     |
     | parse_zip('Hobb.fb2.zip')
      
     - Обрати внимание на строчку «parse_fb2(z.open(n))» в ней содержится то самое перенаправление итогов разархивирования на анализ fb2, т.е. то что в Delphi делается явно Python маскирует легким применением, в каждом подходе свои достоинства.
     = Выносим благодарность Евгению Пивневу и идем дальше.
      
     - Нам предстоит сортировать файлы с книгами по авторам. Тут два ключевых слова «сортировка» и «авторы». В пятом задании мы извлекали из книги ее название, это было проще сделать — название книги всегда одно. С авторами сложнее, их может быть насколько и может совсем не быть автора (например: «Тысяча и одна ночь») и еще... в fb2 есть еще один автор — изготовитель fb2 файла — он на сейчас не нужен, может упоминаться имя и фамилия переводчика
      
     Задание 9.
     Извлечь из fb2 фамилию автора книги. Извлекать только первого автора, в случае отсутствия автора вывести «noname»
      
     - Пока ты обдумываешь решение вернусь к сортировке. Думаю, ты изучал не один метод сортировки и думаю не раз удивлялся заумности и сложности алгоритмов, но все становится на свои места если учесть историю вопроса.
     Потребность в сортировке массивов информации возникла с появлением первых ЭВМ. Современному человеку трудно себе представить насколько слабыми были тогда машины. 90% современных программистов не смогут работать на таком оборудовании. И чтобы решать задачи (те же, что и сегодня) приходилось изыскивать массу хитрых уловок.
      
     - Так, ты готов?
     = Смотри, что получилось.
      
     | import xml.dom.minidom
     |
     | dom = xml.dom.minidom.parse('proba.fb2')
     | dom.normalize()
     |
     | node1=dom.getElementsByTagName("description")[0]
     | node1=node1.getElementsByTagName("title-info")[0]
     |
     | try:
     |. . .node1=node1.getElementsByTagName("author")[0]
     |. . .node1=node1.getElementsByTagName("last-name")[0]
     |. . .print node1.childNodes[0].nodeValue
     | except:
     |. . .print 'noname'
      
     - Хорошо, но потом надо это оформить в виде функции, и еще fb2 файлы делают разные люди, фамилию могут написать «Иванов», «иванов», «ИВАНОВ» т.е. надо воспользоваться функцией S.capitalize().
      
     - И снова о сортировке. Повторюсь, т.к. ресурсы техники были очень не велики и массивы информации не умещались в памяти приходилось извращаться, сейчас... надо соотносить потребности задачи с возможностями компьютера. Последнее время я применяю метод двоичной сортировки при вводе информации, на этот раз, я не буду давать задание, а выдам свой код. Далеко не уверен, что мой код (да и мой ли он? простая математика) лучше стандартных сортировок Python.
      
     | # -*- coding: utf-8 -*-
     | Books = []
     |
     | def FindValue(s1, s2):
     | global Books
     |
     | maxW = len(Books) # верхняя граница диапазона поиска
     |
     | L1 = [s1, s2]
     | if maxW == 0: # если запись первая
     | . .Books.append(L1)
     | else: # если запись не первая начинаем двоичный поиск
     | . .minW = -1 # нижняя граница диапазона поиска
     | . .cur = maxW // 2 # текущее значение — на половине диапазона
     | . .wList = Books[cur][0] # чтение сортируемого значения (при необходимости сортировки по другому параметру, изменить именно эту строку
     |
     | . .while s1 <> wList: # цикл продолжается, пока не найдено совпадение значений
     | . . . .if s1 < wList: # изменение диапазона поиска, в зависимости ...
     | . . . . . .maxW = cur # диапазон сдвигается вниз
     | . . . .else:
     | . . . . . .minW = cur # диапазон сдвигается вверх
     | . . . .if maxW - minW == 1: # если выполняется данное условие, то совпадение не найдено
     | . . . . . .if s1 > wList: # выбираем куда запишем новое значение ниже
     | . . . . . . . .cur += 1 # или выше текущего (найденного)
     | . . . . . .Books.insert(cur, L1) # запись нового значения
     | . . . . . .return # выход, новое значение записали, и тут нам делать нечего
     | . . . .cur = ((maxW-minW) //2) + minW # определение текущего значения в середине нового диапазона
     | . . . .wList = Books[cur][0] # чтение сортируемого значения (при необходимости сортировки по другому параметру, изменить именно эту строку, как видите таких мест два, т.е. при необходимости оформить ее в виде функции
     |
     | . .Books.insert(cur+1, L1) # в случае если найдено значение, записываем новое рядом.
     | # конец функции FindValue
      
     | def PrintList(): # функция нужная только для отладки
     | . .for i in Books: # печать содержимого списка
     | . . . .print i[0]+ ' ' + i[1] # возможно удобнее разнести на две строки?
      
     Приведенный код используется как модуль сохраненный в файле m_list.py
     Пример использования:
      
     | from m_list import FindValue, PrintList
     |
     | FindValue('55','proba1') # отладочная процедура:
     | FindValue('33','proba2') # записи символизируют информацию о книгах
     | FindValue('66','proba3') # первая строчка (66) символизирует фамилию автора
     | FindValue('11','proba4') # просто по цифрам легче проверить правильность сортировки
     | FindValue('33','proba5') # вторая строка ( proba5) символизирует путь к файлу
     | FindValue('22','proba6') # сортировке подвержены не только строки
     | FindValue('8','proba7') # а все, что можно проверить «равно» «больше» «меньше»
     | FindValue('7','proba8')
     |
     | PrintList()
      
     = Так, надо разобраться.
     - Разбираться в чужом коде всегда трудно, посему, давай помогу.
     В списке Books, ясное дело, будет хранится информация о книгах. Каждая книга — ма-аленький списочек (L1), всего из двух строк, фамилия автора и путь к книге.
     В начале, когда список Books пуст — просто добавляем туда введенные данные в противном случае начинается двоичный поиск такой же фамилии.
     Переменные maxW и minW ограничиваю поле поиска (в начале это весь список Books) в переменную cur записываем адрес половины поля поиска. Читаем фамилию по адресу cur если они совпадают — выходим из цикла и записываем новую книгу рядом с найденной.
     Если фамилии данной и найденной не совпадают, сравниваем их и в зависимости от результата в два раза уменьшаем поле поиска (не охота скрупулезно описывать — прикинь сам). Цикл продолжается до тех пор, пока поле поиска не уменьшится до 1, тогда становится ясно, что совпадения не найдено и именно в это место следует записать новую книгу.
     Ну где-то так.
     = Буду разбираться. Но есть одна не то чтобы ошибка, а неточность. В одну папку будут собираться книги однофамильцев.
     - Я анализировал такую ситуацию. Не припомню, чтобы в одну сессию я скачивал книги однофамильцев, но с этим можно было бы смириться, тут другое рассуждение, как уже сказано, разные люди делают fb2 файлы и ФИО может выгладить так: «Иванов Петр Васильевич», «Иванов Петр», «Иванов П. В.», мы с вами понимаем, что это наверно один человек, но машине объяснить это трудно, легче брать в рассмотрение только фамилию.
     А теперь, довольно легкое задание.
      
     Задание 10.
     Совместить вышеизложенное. Программа, должна будет просканировать текущую папку и все найденные fb2 и fb2.zip файлы открыть и зарегистрировать (с сортировкой по автору) в списке Books. Содержимое полученного списка сохранить в файл.
      
     = Все ясно.
     - Ну, пока ты работаешь... Разговор о сортировке разбередил воспоминания.
     Работали мы по три смены, это притом, что цеха завода работали по две. В машинном зале шум стоял... как опять же в заводском цехе: гудели многочисленные (очень многочисленные) вентиляторы, трещал ввода карточный, со свистом вводились перфоленты в соответствующее устройство, гудели накопители на магнитной ленте, и пулеметно строчили АЦПУ, которые выдавали километры распечаток и к которым, девочки-операторы боялись подходить, знатно било статикой.
     У нас были 3 машины «Минск 32» и одна «ЕС 1033»
     «Минск 32» отличная машины, шикарный центральный пульт, мечта фантаста тех лет. Одни кнопки с подсветкой чего стоили. Чтобы понять принципы работы... да голову поломать приходилось «транзисторная и диодно-трансформаторная потенциально-импульсная логика». Я убежден, что чем дальше уходила вычислительная техника тем проще она становилась, сложность уходила внутрь микросхем и поиск неисправностей... ну например сейчас,... да чего там искать, заменить звуковую или материнку, да и все дела.
     И очень жаль, что множество инженерных решений найденных в тех условиях похоронены. Недавно, рассуждая о нестабильности электропитания КИП нашего производства (из-за чего случаются постоянные сбои, сбросы настроек приборов и не шуточные материальные потери) я рассказал как решались эти проблемы в те годы (а оборудование было намного более чувствительно к импульсным помехам). Рядом с ВЦ стояло помещение где находились «двух-машинные агрегаты» - т.е. пара мотор-генератор киловатт на 100(не помню точно, могу и соврать) Вот такая гудящая парочка и вырабатывала ток для наших машин.
     (Минск 32
     Питание от сети трехфазного переменного тока напряжением 380/220 В +10, минус 15%, частотой 50 Гц. Потребляемая мощность не более 21 кВ А
     http://www.bemzbrest.by/en/about/history/353-minsk-32.html)
     Прикиньте, через такой электро-механический фильтр импульсная помеха никогда не пройдет, а кпд у электрооборудования с ростом мощности только растет, т.е. современные UPS чепуха, но как вы думаете, что ответило начальство...?
     = У меня все готово.
     Я тоже решил оформить основной блок в виде модуля m_dir.py
      
     | # -*- coding: utf-8 -*-
     | import sys, os
     | import xml.dom.minidom # модуль для работы с xml
     | import zipfile # модуль для работы с архивами
     | from m_list import FindValue # а это наш собственный модуль для сортировки
     |
     | Capture = '' # глобальная переменная для хранения фамилии автора книги
     | FileName = '' # глобальная переменная для хранения пути к книге
     |
     | def parse_zip(fn): # функция для анализа архива
     | . . global FileName
     |
     | . . FileName = fn # т.е. путем к книге будет путь к архиву
     |
     | . . z = zipfile.ZipFile(fn, 'r') # читаем архив
     | . . filelist = z.namelist() # запрашиваем список файлов содержащихся в архиве
     | . . filelist.sort() # на всякий случай сортируем
     |
     | . . for n in filelist:
     | . . . . try:
     | . . . . . . if n[-4:] == '.fb2': # если файл в архиве является книгой
     | . . . . . . . . parse_fb2(z.open(n)) # то анализируем книгу
     | . . . . except: # иначе — сообщение об ошибке
     | . . . . . . print >> sys.stderr, 'X15:', n
     |
     | def parse_fb2(fn): # анализ книги
     | . . global Capture
     | . . dom = xml.dom.minidom.parse(fn) # открываем xml для анализа
     | . . dom.normalize() #
     | . . node1=dom .getElementsByTagName("title-info")[0]
     | . . try: # а вот эти разделы в принципе могут и отсутствовать
     | . . . . node1=node1.getElementsByTagName("author")[0]
     | . . . . node1=node1.getElementsByTagName("last-name")[0]
     | . . . . s = node1.childNodes[0].nodeValue
     | . . . . s = s.encode('utf-8') # а с этой строчкой еще работать и работать
     | . . . . Capture = s.capitalize() # запись релультата
     | . . except:
     | . . . . Capture = 'noname'
     | . . FindValue(Capture, FileName)
     |
     | def parse_file(fn): # анализ файла
     | . . global FileName
     | . . FileName = fn
     | . . m = fn.split('.')[-1] # проверка типа файла
     | . . if (m == 'zip'): # в зависимости от типа, соответствующее действие
     | . . . . parse_zip(fn)
     | . . elif (m == 'fb2'):
     | . . . . parse_fb2(fn)
     | . . . .
     | def parse_dir(fn): # анализ папки
     | . . dirlist = os.listdir(fn) # получение списка файлов содержащихся в папке
     | . . dirlist.sort()
     | . . for i in dirlist: # просмотр списка файлов
     | . . . . parse_file(os.path.join(fn, i))
      
     и исполняемый скрипт main.py
      
     | import sys, os
     | from m_list import SaveList
     | from m_dir import parse_dir
     | # получение адреса текущей папки
     | path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |
     | parse_dir(path) # анализ папки
     | SaveList() # сохранение результата в файл
      
     - Проверил? Работает?
     = Да, в файл пишется, читаются и архивы и fb2.
      
     - Сам понимаешь, так сходу проверить нельзя, особенно если все работает...
     Но одна неточность все же есть.
     О пользователе нужно заботится. Пользователь должен видеть, хоть что-то по окончании работы. Поэтому в скрипт main.py добавь строчку:
     print 'Done!'
      
     - Погоди, а что там в SaveList
     = О, совсем забыл. Но это же просто, в файл m_list добавил функцию:
      
     | def SaveList(): # сохранение содержимого списка в файл
     | . . f = open('lib.txt', 'w')
     | . . for i in Books:
     | . . . . f.write(i[0]+'\n') # был и такой вариант f.write(i[0].encode('cp1251')+'\n')
     | . . . . f.write(i[1]+'\n\n')
     | . . f.close()
      
     - Поздравляю, ты выполнил уже 10 заданий и мы хорошо продвинулись к решению задачи 1. Еще немного, еще чуть-чуть.
     В нашем распоряжении есть список отсортированный по фамилиям авторов. Требуется:
     1. если у автора более одной книги создать папку (по имени автора) и переместить туда файлы с книгами данного автора
     2. оставшиеся книги поместить в папки (f1, f2, ...) по 10 штук (если надо, другое число) в папке.
      
     Задание 11.
     В принципе, разобраться. Как извлечь из списка сведения о книгах одного автора***...
      
     = Кажется понял, что имеется в виду. За работу!
     - Погоди, небольшая подсказка. Предстоит просматривать список и удалять какие-то записи (неважно по каким принципам), так вот, если просматривать, как обычно, сверху, то при удалении изменится нумерация в последующих элементах списка, короче, будет большой бардак. Поэтому просматривать и изменять надо снизу.
     = Это дело, надо разжевать. Приступаю.
      
     - Прозорливый читатель уже понял, что после выдачи задания, я начинаю «вешать лапшу на уши» дабы затянуть время, для выполнения задания.
     Перед вами НЕ художественная книга, читать ее непрерывно, не имеет смысла. Нет смысла — без выполнения заданий.
      
     Мне попадались книги для изучения (английского) языка с параллельным переводом. Не раз я убедился, что такие книги, не имеют смысла.
     Язык без приложения усилий, не выучить, да — рекомендуется прикрыть русский текст листочком, и заглядывать туда только для консультаций в сложных случаях.
     Но! Но это бесполезно, рано или поздно учащийся говорит себе «что-то я устал, да и интересно, что там дальше. Почитаю-ка я русскую страничку — ну немножко...» и все! Обучение закончилось. Да, если у учащегося железо-бетонное упорство... наверняка находятся и такие, но я думаю это 1 на 10000.
      
     Вот именно по этим соображениям, дабы код с ответом не бы в обозримом пространстве «вешается лапша». Хочется надеяться, что и «лапша» в хозяйстве пригодится.
     Но вот и наш мученик науки.
     = Сделано, но попотеть пришлось изрядно.
     Как как было задание «в принципе» найти алгоритм, то я решил «тренироваться на кошках».
     Для начала я заполнил список некими значениями, и некоторые из них имеют одинаковую первую строку (первая строка с циферками — символизирует фамилию автора, а вторая адрес где лежит книга)
     В закомментированных областях лежат первый и второй этапы написания алгоритма.
     Каких только глупейших ошибок я не сделал при написании, опять забывал двоеточия, опять приходилось выравнивать отступы, да и в логике выполнения работы далеко не сразу разобрался.
      
     | from m_list import FindValue, PrintList, Books
     |
     | FindValue('66','proba 0')
     | FindValue('55','proba 1')
     | FindValue('33','proba 2')
     | FindValue('66','proba 3')
     | FindValue('11','proba 4')
     | FindValue('33','proba 5')
     | FindValue('22','proba 6')
     | FindValue('66','proba 7')
     | FindValue('8','proba 8')
     | FindValue('7','proba 9')
     | FindValue('66','proba 10')
     | FindValue('11','proba -1')
     |
     | # PrintList()
     | """
     | #1
     | for i in range(len(Books), 0, -1): # Печать списка в обратном порядке
     | . . print Books[i-1][0] + ' ' + Books[i-1][1]
     |
     | """
     |
     | """
     | #2 # Т.к. предстоит сравнивать автора с предыдущим, то
     | old_name = Books[len(Books)-1][0] # Запоминаем самую нижнюю запись
     |
     | print old_name
     | print '----------'
     |
     | for i in range(len(Books)-1, 0, -1): # просматриваем список не с нижнего,
     | . . print Books[i-1][0] # а со следующего за ним
     |
     | """
     |
     | #3
     |
     | old_name = Books[len(Books)-1][0]
     | flag = False # логическая переменная — говорящая, что читается серия книг одного автора
     |
     | def mPrintList(author, M): # Распечатка серии книг одного автора
     | . . print author # Печать фамилии автора
     | . . for k in M: # печать списка книг этого автора
     | . . . . print k
     |
     | for i in range(len(Books)-1, 0, -1):
     | . . if not flag and Books[i-1][0] == old_name: # начало серии книг одного автора
     | . . . . flag = True
     | . . . . L = [] # создаем временный список
     | . . . . L.append(Books[i][1])
     | . . . . Books.pop(i) # удаляем первую книгу серии из списка Books
     | . . . .
     | . . if flag :
     | . . . . if Books[i-1][0] == old_name:
     | . . . . . . L.append(Books[i-1][1]) # записываем серию книг во временный список
     | . . . . . . Books.pop(i-1) # удаляем книгу серии из списка Books
     | . . . . else:
     | . . . . . . mPrintList(old_name, L) # Отображение серии книг одного автора
     | . . . . . . old_name = Books[i-1][0] # запоминаем имя автора
     | . . . . . . flag = False
     | . . else:
     | . . . . old_name = Books[i-1][0]
     |
     | if flag : # Вот тут очень важно, если последняя запись входила в серию,
     | . . mPrintList(old_name, L) # то эту последнюю надо Отобразить
     |
     | print '**************'
     | PrintList() # Печать оставшегося списка
      
     - Ну, вот собственно все и сделано.
     = Как все?! А совместить все, это же была только тренировка на кошках.
     - Совместить совершенно не трудно — оформить отдельным модулем, изменить функцию mPrintList. Вместо печати фамилии автора создать папку под его именем, вместо печати списка книг этого автора выполнить перемещение этих книг в созданную папку. Вот тебе в помощь небольшой фрагментик.
      
     | import os
     | import shutil
     |
     | os.mkdir('Иванов') # создание папки
     |
     | shutil.move(r'1.fb2.zip', r'Иванов') # перемещение файла
      
     - Кроме всего прочего, не хочется давать халявы, читателям, которые просто пролистают этот текст.
     = Классика! «Сделал гадость, сердцу радость».
     Но, постой! А как же разбивка по циклам внутри папки писателя?
     - Посмотри, ведь надо сделать то-же что и ранее, только в fb2 надо искать тег <sequence name="Название цикла" number="номер книги в цикле"/>. Подумай, как выполнить работу максимально используя наш готовый код (это не совсем рекурсия, но...). Уже вижу, надо использовать кортежи, изменить входные параметры функций...
     Как видишь, запас работы есть, программу можно шлифовать до...
     А сейчас, давай разбежимся и опробуем полученный код.
     . . .
     = Все, заработало!
     - Задача 1 выполнена.
      
      

Приложение; 2 скрипта

     1|#!/bin/env python
      2|# -*- coding: utf-8 -*-
     # скрипт замены отступов точками
      3|# replacing spaces with dots
      4|import sys, os
      5|
      6|path = os.getcwd()
      7|
      8|def work():
      9| . .new_List = []  # объявляем 2 списка
     10| . .old_List = []
     11| . .new_List.clear() #
     12| . .old_List.clear()
     13| . .print('')
     14| . .FN = input('Введите имя файла:')
     15|
     16| . .fn1=os.path.join(path, FN) # формируем полный путь к файлу
     17| . .base=os.path.splitext(FN)[0] # отрезаем от имени файла — расширение (.py).
     18| . .fb2_file=open(fn1,'r') # открываем файл
     19| . .old_List=fb2_file.readlines() # читаем его содержимое в список
     20| . .fb2_file.close() # больше этот файл нам не нужен
     21|
     22| . .n = 0 # счетчик строк
     23| . .for item in old_List:  # цикл обработки содержимого
     24| . . . .n += 1
     25| . . . .s='|'+item # рисуем линию слева
     !!!! Замените в нижеследующем тексте строки  ____ на 4 пробела !!!!
     (написал так потому, что пробелы, в данном тексте, будут совершенно не различимы и не исчеслимы)
     26| . . . .s=s.replace('\t','____') # заменяем табуляции на 4 пробела
     27| . . . .s=s.replace('|____','| . .')   # заменяем пробелы в начале строки на блоки точек
     28| . . . .s=s.replace('____',' . .') # заменяем все оставшиеся блоки из 4 пробелов на блоки
     29| . . . .s = "{0:2d}{1:s}".format(n,s) #  вставляем номер строки перед текстом
     30| . . . .new_List.append(s) # записываем полученную строку в новый список
     31|
     32| . .fn2=os.path.join(path, base)+".txt" # формируем полное имя результирующего файла.
     # маленькая неточность…. Не проверенно существование одноименного файла!!!
     33| . .new_file=open(fn2,'w') # записываем результат обработки
     35| . .for item in new_List:
     36| . . . .new_file.write(item)
     37|
     38| . .new_file.close()
     39|
     40| . .print('Done')
     41|
     42|while True: # Бесконечный цикл обработки файлов
     43| . .work() # Exit => Ctrl + C
     --------------------------------------------------------------------------------
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# Упаковка файлов fb2 в архивы zip
     |import sys, os
     |import zipfile
     |
     |co = 0
     |
     |def parse_file(FileName):
     | . .global co
     | . .fn = os.path.basename(FileName)
     |
     | . .z = zipfile.ZipFile(fn+'.zip', 'w', zipfile.ZIP_DEFLATED)  # Создание нового архива
     | . .z.write(fn)
     | . .z.close()
     | . .os.remove(FileName)
     | . .co += 1
     |
     |def parse_dir(fn):
     | . . . .m = fn.split('.')[-1]
     | . . . .if (m == 'fb2'):
     | . . . . . .parse_file(fn)
     |
     |path = os.getcwd()
     |files = os.listdir(path)
     |for file in files:
     | . . . .parse_dir(os.path.join(path, file))
     |
     |print ('Zip => ' + str(co))
     |print ('Done!')
      

4. Двойники

Постановка задачи
     Затрудняюсь точно сказать, сколько fb2 на флешке моей читалки, но много, несомненно, часто я ошибочно скачиваю уже имеющуюся книгу, иногда я делаю даже осознанно, просто хочется ее перечитать и легче скачать чем искать в заросли папок. Кроме того бывают книги в процессе написания, и у меня хранятся разные версии текста, или бывает, книги объединяются в сборники. Т.е. возможны самые разные версии «двойников».
      
     Задача 2.
     Написать программу для поиска и удаления «двойников»
      
     = Как всегда, один ду... думающий человек, может поставить задачу, которую не одолеют и десять мудрецов.
     - Да, задача не проста, но давай есть слона по кусочкам. И для начала:
      
     Задание 1.
     Получить из файла fb2 расширенную информацию:
     * Вид кодировки
     * Название книги
     * Полное имя автора (авторов)
     * Аннотация книги
      
     - Для чего необходимы такие данные? Ну, это ясно. Мы будем сравнивать книги, мы будем выбирать в какой папке (в каком окружении) оставить книгу.
     Это далеко не все данные которые мы можем получить из fb2, сейчас еще рано, а в дальнейшем нам потребуется посмотреть картинку обложки, возможно потребуется оценить оглавление книги, допустим сравнить в какой версии одной книги больше глав, или, если это сборник рассказов — оглавление поможет принять решение.
     Ну, где же наш работник. Задание ведь не сложное.
     = Задание может и не сложное, но объемистое. Извини за мой французский, но какой муд...рец придумал так читать XML, нет к самому формату претензий нет, все просто и логично, но неужели нельзя сделать как-то проще, это же полная жо... - слов просто не подобрать.
     - Видимо ты первый раз столкнулся с подобной ситуацией. Работая с компьютером мы видим перед собой «железку», а на самом деле мы общаемся со множеством (в большинстве своем) умных людей, но часто они мыслят по своему, они выросли в другой культуре, на других книгах и фильмах (как правило получили очень хорошее, но другое образование), сейчас модно говорить - они живут в другой парадигме. Мне, в моей практике уже много раз приходилось менять эту самую парадигму.
     Со временем, ты или привыкнешь к такому инструментарию и найдешь в нем интересную логику, или найдешь другие методы работы, более тебе подходящие.
     Ну, показывай, что получилось.
      
     | from xml.dom.minidom import parse
     |
     | def PrArr(childList):
     | . . s = ''
     | . . for child in childList:
     | . . . . if child.nodeType==child.TEXT_NODE:
     | . . . . . . s = s + ' ' +child.nodeValue
     | . . . . . . s = s.strip() # обрезание ведущих и заключительных пробелов, табуляций и т.п.
     | . . . . else:
     | . . . . . . s = s + ' ' + child.childNodes[0].nodeValue
     | . . . . . . s = s.strip()
     | . . print s
     |
     | def PrArr1(childList):
     | . . s = ''
     | . . for child in childList:
     | . . . . if child.nodeType==child.TEXT_NODE:
     | . . . . . . s = s + ' ' +child.nodeValue
     | . . . . else:
     | . . . . . . s = s + ' ' + child.childNodes[0].nodeValue
     | . . print s
     |
     | dom = parse('Hobb.fb2')
     | #dom = parse('Abvov.fb2') # опробовал код на двух файлах
     | dom.normalize()
     |
     | print "coding:"
     | print dom._get_encoding() # получение кодировки файла
     |
     | title=dom.getElementsByTagName("title-info")[0] # находим тег title-info
     |
     | print "book title:"
     | book = title.getElementsByTagName('book-title')
     | print book[0].childNodes[0].nodeValue # печать названия книги
     |
     |
     | print "author:" # печать списка авторов
     | nodeA=title.getElementsByTagName("author")
     | for n in nodeA:
     | . . childList=n.childNodes
     | . . PrArr(childList)
     |
     | print "annotation:" # печать аннотации
     | anno=title.getElementsByTagName("annotation")
     | for n in anno:
     | . . childList=n.childNodes
     | . . PrArr1(childList)
      
     - Так, что тут у нас......
     Ну, во-первых функции PrArr() и PrArr1() можно было бы объединить в одну, например так в параметрах ввести Flag и написать:
      
     | if Flag:
     | . . s = s.strip()
      
     И соответственно в вызовах PrArr(childList, True) и PrArr(childList, False)
     Следующие претензии к авторам.
     Дело в том у тега «author» подчиненными может быть не только ФИО, но и ник автора, и адрес его web страницы и кое-что еще. Т.е строка может неожиданно разрастись.
     На последующую придирку, ты ответишь «как сказано, так сделано», но я хотел использовать сведения об авторе для дальнейших поисков, а в таком виде код не сработает, и дело не только в вышесказанном. Представь ситуацию в теге «author» — сначала будет написана "last-name" а затем "first-name" с точки зрения XML да и с точки зрения русского языка «Сергей Иванов» и «Иванов Сергей» - вполне допустимо, но если в качестве фамилии у нас окажется имя .....
      
     Хорошо, потихоньку идем дальше:
      
     Задание 2.
     Написать скрипт, просматривающий содержимое заданной папки, а также содержимое и всех вложенных папок.
      
     Давайте обсудим варианты «двойников»:
     1. Самый простой вариант, когда совпадают название книги, автор (авторы), длина файла и в довершение название физического файла — тут сомнений нет, но остается вопрос, какой из файлов удалять?
      
     = У меня все готово.
     - Что то, очень быстро.
     = А интернет на что...
      
     | import sys, os
     | list = []
     |
     | def GetListFiles(PathForWork):
     | . . global list
     | . . for file in os.listdir(PathForWork):
     | . . . . path = os.path.join(PathForWork, file)
     | . . . . if not os.path.isdir(path): # если это не папка
     | . . . . . . list.append(path) # добавляем адрес файла в список
     | . . . . else: # если все же папка
     | . . . . . . GetListFiles(path) # рекурсивный вызов функции, для анализа вложенной папки
     | # -------------------------------
     | Mpath = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |
     | GetListFiles(Mpath)
     | for i in list: # распечатка списка
     | . . print i
      
     - Так, но нам надо чтобы отбирались только fb2 и fb2.zip файлы.
     = Ну, как сказано, так и сделано, а насчет фильтрации, недавно это мы делали, повторить нетрудно.
     - Тогда еще вопросик. Выяснить величину файла.
     Возвращаясь к нашим...
     = Все сделано:
      
     | os.path.getsize(path2file)
      
      
     - Молодец, но сделай паузу, нам надо обсудить дальнейшие действия.
     = Извини, но я тут сделал открытие: «Модуль bisect - обеспечивает поддержку списка в отсортированном порядке с помощью алгоритма деления пополам.»
     - Хорошее дело! Тогда:
      
     вне очередное задание.
     Напиши два скрипта т.е. с bisect и FindValue включи туда измерение времени и протестируй их на одном и том же материале.
      
     - Ну пока ОЧ-умелые ручки заняты, продолжим.
     Я уже упоминал о возможных вариантах одной книги, возможны даже разные названия, как самой книги так и автора (псевдоним, ФИО, ФИ)
     Эту речь я веду к тому, что для поиска двойников нужно применять разные подходы и автоматическое удаление, здесь не подходит. А это значит...
     = А вот и я.
     Значит, так.
     Отчет о проделанной работе.
     Во-первЫх я переделал функцию FindValue(s1, s2) она стала FindValue(s) т.е. для ввода только одной строки.
     Потом нашел подходящий пример определяющий скорость работы программы.
     Потом сделал ошибку — стал сравнивать работу функций по чтению имен файлов в довольно объемной папке. Но получил значительный разброс при работе одной и той-же функции. Потом сообразил, что операционная система обращения к файлам кеширует т.е. последующее обращение происходит много быстрее.
     Потом сравнил работу подопытных на одинаковых массивах случайных чисел.
     Убедился (ну почти убедился), что FindValue работает медленнее почти на 10%
     - Ну этого следовало ожидать.
     = Но почему? Там же все вроде правильно.
     - Правильный вопрос. На эту тему я мог бы разродится громадной лекцией — чем выше уровень языка программирования, чем более удобна в нем работа программиста, тем (РАСПЛАТА) не эффективнее получаемый код, но с фантастическим ростом вместимости носителей и скорости вычислительных систем — теряется потребность в таковой эффективности /кроме отдельных случаев/, важнее становится скорость разработки. /ну где-то так, вкратце/
     - Но в процессе работы убедился, что отказаться от FindValue пока не могу. Т.к. не знаю как в bisect сортировать сложные структуры данных (как у нас список из списков по 2 строки).
     = Как много нам открытий чудных... подарит изучение Python.
     - И никогда не знаешь, какой сюрприз ждет тебя за углом, например сегодня я узнал о приятной особенности Geany для выделенного блока в контекстном меню можно выбрать пункт «Форматирование» «Увеличить отступ» блок сдвигается табуляциями и теперь его можно превратить в функцию.
     = За углом может ждать не только это...
     - Я имел ввиду неизвестность ценности не известной информации.
     = Ну и это тоже может ждать.
     - Да, я окончательно сбился с предыдущей мысли...
     Ага, вспомнил.
      
     Задача
     Создать упорядоченный список имен файлов книг („.fb2“ и „.fb2.zip“) /рекурсивно проверять входящие папки/ из списка отобрать повторяющиеся файлы. Результаты записать в текстовый файл.
      
     = Будет сделано!!!
      
     | #!/bin/env python
     | # -*- coding: utf-8 -*-
     |
     | import sys, os
     | from m_list import FindValue, PrintList, Books
     | list = []
     |
     | def fb2zip(pa):
     | . . p = pa.split('/')
     | . . FindValue(p[-1],pa)
     | # -----------------
     |
     | def GetListFiles(PathForWork):
     | . . global list
     | . . for file in os.listdir(PathForWork):
     | . . . . path = os.path.join(PathForWork, file)
     | . . . . ex = path.split('.')
     | . . . . if not os.path.isdir(path): #
     | . . . . . . if ex[-1].lower() == 'fb2':
     | . . . . . . . . fb2zip(path)
     | . . . . . . else:
     | . . . . . . . . e = ex[-2]+ex[-1]
     | . . . . . . . . if e.lower() == 'fb2zip':
     | . . . . . . . . . . fb2zip(path)
     | . . . . else: #
     | . . . . . . GetListFiles(path) #
     | # -----------------
     |
     | def CompareList():
     | . . OldFile = ''
     | . . OldPath = ''
     | . . flag = False
     | . .
     | . . for it in Books:
     | . . . . if (it[0] == OldFile):
     | . . . . . . if not flag :
     | . . . . . . . . flag = True
     | . . . . . . . . list.append(OldPath)
     | . . . . . . list.append(it[1])
     | . . . . else:
     | . . . . . . if flag:
     | . . . . . . . . flag = False
     | . . . . . . . . list.append('------------')
     | . . . . . . OldFile = it[0]
     | . . . . . . OldPath = it[1]
     | # -----------------
     | # Main:
     | Mpath = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |
     | GetListFiles(Mpath)
     |
     | CompareList()
     |
     | if len(list) == 0:
     | . . print('Дубликатов нет.')
     | else:
     | . . new_file = open(fn2,'w')
     | . . for i in list: #
     | . . . . new_file.write(i)
     | . . . . print i
     | . . print 'Done!'
     | . . new_file.close
      
      
      
      
      
      
      
      
     - Прежде чем битва началась, надо составить план.
     = А давай, ввяжемся в сражение, а там, как получится.
     - Нет, план нужен. И план долгосрочный, не раз замечал, если работа базируется на хорошо продуманном плане... возьмем Фандорина, начальные (запланированные) книги, подкупают продуманностью деталей, потом, когда на Акунина насели издатели.. . пошло, «как получится». Точно так, у меня бывало в программировании, пока можно держаться за четкий план, все логично, но рано или поздно, задача выходит за рамки прогнозируемого, ну в общем ясно...
     * * *
     Вот на этом закончился, более менее связный текст, написанный мною четыре-пять лет назад. Валялся он валялся, а потом я решил выложить его на этот сайт.
     Далее Вы можете ознакомится с более современными продолжениями.
     (ниже, до конца главы мусор из моей записной книжки копий я планировал вставить в текст)
     20 августа 2021.
     * * *
      
     - Вот тут и надо продумать уловки. Во-первых в Питоне есть возможность запустить дополнительные программные потоки. Вот в такой поток и можно поместить модуль сканирования по адресам из списка Books.
     = Я правильно понял, такое сканирование будет вестись в фоновом режиме.
     - Совершенно верно, как я уже сказал, сканирование путей к файлам произойдет мгновенно, поиск полностью идентичных файлов — еще быстрее, и пока наш пользователь выбирает, какой файл удалить — процесс идет помнишь:
      
 Где-то в космосе
Летит
Голубой метеорит.
Ты идёшь,
А он летит.
Ты лежишь,
А он летит.
Ты заснул,
Но всё летит
В космосе
Метеорит.

     ...
(Роман Сеф, «Голубой метеорит»)
      
     - Не беспокойся, все учтено могучим ураганом.
      
     Сделаем сортированный список ссылок на книги с сортировкой по заголовку книги.
     Несомненно, возможны книги разных авторов с одинаковым названием и такие из рассмотрения исключаем.
     = Стой! Я не понял предыдущую фразу.
     - Такая операция называется «индексирование». В данном списке мы будем хранить только номера (ссылки) записей списка Books. Но упорядочены эти номера по какому-то своему признаку (в данном случае по названию книги). Таким образом не изменяя исходный список мы получаем множество списков, с самыми разнообразными принципами сортировки исходного списка. Но, обращаю твое внимание! При изменении базового списка, необходимо переписать и все индексные.
     = Гора вопросов растет! Так, что-же для каждого вида сортировки писать свою функцию Find~~~
     - Не совсем, основная функция двоичного поиска останется базовой, но в параметрах она будет получать дополнительно: список L - который упорядочивается, функцию читающую текущее значение из списка L, функцию записывающую новое значение в нужное место списка L. Т.е. дописывать потребуется только эти две функции.
      
      
     Да, чем больше узнаю тем больше мне нравится Питон.
     = Я люблю Пэ-же. Я очень люблю ПЖ! ..... А я его ещё больше ку!
     - Еще я хочу обсудить ситуацию с авторами.
      
      
     Не поддавайся соблазну простых решений, наверняка они где-то обшибочны.

5. Пасьянс из fb2

     - В приложение выкладываю скрипт, который раскладывает файлы по папкам. Если книг данного автора больше одной то создается соответствующая папка, ежели у книги нет одноавторцев то она укладывается в папку «f» (подразумевалось название «файлы»)
     = Расскажи подробнее.
     - Скачанные на просторах… файлы скапливаются в одной папке. Задача скрипта разложить файлы по авторам. Вложенные папки не анализируются.
     - Порядок действия:
     если это fb2, то в файле(xml) ищем автора и результат записываем в список «Книги», где каждая запись содержит фамилию автора и имя файла, если-же это zip открываем его и с полученным fb2 производим те-же действия.
     В список «Книги» каждую новую запись производим с применением двоичного поиска, в результате данный список получается упорядоченным по авторам (естественно, это можно быть сделать по другому).
     Полученный список «Books» просматриваем и ищем совпадения по авторам.
      
     Обрати внимание! Список «Книги» в данном случае просматриваем «снизу» т. е. от последнего элемента до нулевого.
     = Для чего это?
     - Это стандартный прием, для случая когда список в результате действий будет изменятся (не разжевываю продумай сам), что и происходит у нас, найденные книги одного автора извлекаются из списка «Книги» и помещаются в отдельную папку.
      
     Обрати внимание и на конструкцию «try: except:» это называется обработка исключительных ситуаций, например, что будет если zip поврежден, или fb2 неисправен.
      
     Ну, в общем комменты будут внутри.

Приложение; раскладка файлов fb2 по папкам

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     spider.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# раскладка файлов fb2 по папкам
     |import sys, os
     |import xml.dom.minidom
     |import zipfile
     |import shutil
     |# 21.05.2021
     |#--------------------------------------------------
     |Books = []
     |
     |def FindValue(s1, s2):
     | . global Books
     | . maxW = len(Books)
     |
     | . L1 = [s1, s2]
     | . if maxW == 0:
     | . . Books.append(L1)
     | . else:
     | . . minW = -1
     | . . cur = maxW // 2
     | . . wList = Books[cur][0]
     | . . while not(s1 == wList):
     | . . . .if s1 < wList:
     | . . . . . .maxW = cur
     | . . . .else:
     | . . . . . .minW = cur
     | . . . .if maxW - minW == 1:
     | . . . . . .if s1 > wList:
     | . . . . . . . .cur += 1
     | . . . . . .Books.insert(cur, L1)
     | . . . . . .return
     | . . . .cur = ((maxW-minW) //2) + minW
     | . . . .wList = Books[cur][0]
     |
     | . . Books.insert(cur+1, L1)
     |
     |def PrintList():
     | . .for i in Books:
     | . . . .print (i[0] + ' ' + i[1])
     |
     |def SaveList():
     | . .f = open('lib.txt', 'w')
     | . .for i in Books:
     | . . . .f.write(i[0].encode('cp1251')+'\n')
     | . . . .f.write(i[1]+'\n\n')
     | . .f.close()
     |#--------------------------------------------------
     |Capture = ''
     |FileName = ''
     |
     |def parse_zip(fn):
     | . .global FileName
     | . .FileName = fn
     |
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .filelist = z.namelist()
     | . .filelist.sort()
     |
     | . .for n in filelist:
     | . . . .try:
     | . . . . . .if n[-4:] == ".fb2":
     | . . . . . . . .parse_fb2(z.open(n))
     | . . . .except:
     | . . . . . .print( "X15:", n )
     |
     |def parse_fb2(fn): # анализ fb2 файла
     | . .global Capture
     | . .if isinstance(fn, str):
     | . . . .fn = open(fn)
     | . .try:
     | . . . .dom = xml.dom.minidom.parse(fn)
     | . .except:
     | . . . .print('Error:')
     | . . . .print(FileName)
     | . .else:
     | . . . .dom.normalize()
     | . . . .node1=dom.getElementsByTagName("description")[0]
     | . . . .node1=node1.getElementsByTagName("title-info")[0]
     | . . . .try:
     | . . . . . .node1=node1.getElementsByTagName("author")[0]
     | . . . . . .node1=node1.getElementsByTagName("last-name")[0]
     | . . . . . .s = node1.childNodes[0].nodeValue
     | . . . . . .s = s.encode("utf-8")
     | . . . . . .Capture = s.capitalize()
     | . . . .except:
     | . . . . . .Capture = "noname"
     | . . . .FindValue(Capture, FileName)
     |
     |def parse_file(fn):
     | . .global FileName
     | . .FileName = fn
     | . .m = fn.split(".")[-1]
     | . .if (m == "zip"):
     | . . . .parse_zip(fn)
     | . .elif (m == "fb2"):
     | . . . .parse_fb2(fn)
     |
     |def parse_dir(fn):
     | . .dirlist = os.listdir(fn)
     | . .dirlist.sort()
     | . .for i in dirlist:
     | . . . .if os.path.getsize(i) > 0:
     | . . . . . .parse_file(os.path.join(fn, i))
     |#? . . . .else:
     |#? . . . . . .print("bad zip "+ i)
     |#--------------------------------------------------
     |
     |path = os.getcwd()
     |def mPrintList(oldn, M): # укладка книг в папку автора
     | . .global path
     | . .ss = str(oldn, encoding='utf-8')
     | . .if (not os.path.isdir(ss)): # проверка существования папки
     |# . . . .print(ss)
     | . . . .os.mkdir(ss) # вставляем папку
     # т.е. если папка автора уже существует то книги будут туда добавлятся
     | . .for k in M: # цикл перемещения книг в папку автора
     | . . . .if (not os.path.isfile(ss)):
     | . . . . . .shutil.move(k, ss)
     | . . . . # при желании добавь праскладку в текущей авторской книге по сериям
     |def mySorting(): # раскладка по авторам
     | . .global Books
     | . .old_name = Books[len(Books)-1][0]
     | . .flag = 0 # начальное состояние
     | . .for i in range(len(Books)-1, 0, -1): # запускаем цикл "снизу - вверх"
     | . . . . if flag == 0 and Books[i-1][0] == old_name: # если "выше расположенная"=текущей
     | . . . . . . flag = 1 # начало авторской папки
     | . . . . . . L = [] # новый список для автора
     | . . . . . . L.append(Books[i][1]) # добавляем в авторский сборник
     | . . . . . . Books.pop(i) # удаляем пункт из списка книг
     | . . . . . .
     | . . . . if flag == 1: #
     | . . . . . . if Books[i-1][0] == old_name: # если "выше" книга того-же автора
     | . . . . . . . .L.append(Books[i-1][1]) # добавляем
     | . . . . . . . .Books.pop(i-1) # удаляем
     | . . . . . . else:
     | . . . . . . . . mPrintList(old_name, L) # создание папки автора и перемещение файлов
     | . . . . . . . . old_name = Books[i-1][0] # подготовка следующей проверки
     | . . . . . . . . flag = 0
     | . . . . else: # т.е. мы находимсля в начальном положении
     | . . . . . . old_name = Books[i-1][0]
     | . .
     | . .if flag == 1: # забавный код. т.е. если цикл просмотра книг закончился,
     # но последний авторский список, не был обработан
     | . . . .mPrintList(old_name, L)
     | . . . .
     |#----------------------------------------
     |def mMain(): # раскладка книг
     |  mySorting() # раскладка книг по авторам
     |
     |  fbName = "f"
     |  fNum = 1
     |  fName = fbName + str(fNum) #'f1'
     |  num = 0 # число книг в папке f
     |  if len(Books) > 0: # если в списке остались не распределенные книги их запишим в f..
     | . .  if (not os.path.isdir(fName)): # если папка fn не существует то она создается
     | . . . .  os.mkdir(fName) # т.е. если папка fn сущестсвует файлы в нее будут дописываться
     | . .  for i in Books:
     | . . . .  if (not os.path.isfile(path+"/"+fName)):
     | . . . . . .  shutil.move(i[1], path+"/"+fName) # перемещние файла
     | . . . .  num += 1
     | . . . .  if num == 9: # number in folder !!!
     | . . . . . .  fNum += 1
     | . . . . . .  fName = fbName + str(fNum)
     | . . . . . .  if (not os.path.isdir(fName)):
     | . . . . . . . .  os.mkdir(fName)
     | . . . . . .  num = 0
     |
     |  print( 'Done!')
     |
     |#--------------------------------------------------
     |parse_dir(path) # сканирование текущей папки
     |if len(Books) == 0: # если книг не найдено
     | . .print('Empty dir')
     |else:
     | . .mMain()
      

6. Ремонт fb2

     От автора:
     - К моему изумлению данный текст более популярен чем другие мои произведения.
     = Возможно потому, что перед читателями стоят те-же задачи?
     - Давай я тебе изложу мою арифметику на этот счет:
     На сегодняшний день (22.05.21) с текстом ознакомились 192 читателя.
     * из них десятая часть заинтересовалась содержимым (19.2)
     * из них десятая часть прочитала текст (1.92)
     * из них десятая часть не разочаровалась и желает продолжения (0.19)
     - Как отлично знают читатели, у авторов есть старая болезнь — вспыхивают новые интересы, и вот опять незавершенка.
     Но для моего «две десятых» читателя я выложу немного продолжения.
     = Почему бы и нет? В мультфильме были полтора землекопа, в фильме платформа 9 3/4.
     -----------------------------------------------------------------------
     - Надеюсь читатель немного разобрался в структуре fb2.
     И кроме того убедился, что изготовляют такие книги все кому не лень, и иногда такие файлы содержат ошибки, что очень плохо. т.к. формат XML ошибки не допускает.
     Наша цель написать скрипт, который будет:
     * анализировать файл
     * находить и показывать ошибки
     * или сообщать, что ошибок нет.
      
     = Ну и какие проблемы «открывающий тег» «закрывающий тег».
     - Небольшие проблемы есть. Открывающий и закрывающий теги могут находится в разных строках. В XML знак окончания строки — никакого значения не имеет.
     = И что делать? Будем стрелять через дымоход?
     - Открываем файл как бинарный т. е. анализировать его будем побайтно.
     * выделяем из текста теги
     * если это начальный тег — записываем его в стек
     * если закрывающий тег — сравниваем его с последним в стеке и при не совпадении сообщаем об ошибке
      
     = Элементарно!
     (дальнейший код писался для Python3)
     ( см. приложение)
     -------------------------------------------------------
     = Что там со строкой?
     |f = open('xxxxx.fb2', 'rb')
     = Почему бы не написать: FN = input('Введите имя файла:')
     - К великому сожалению, как правило, ошибка в fb2 файле не одинока и проверочный скрипт надо будет запускать многократно, посему лучше записать имя проверяемого файла в скрипте, а не вбивать его каждый раз при запуске скрипта.
     = Ну пусть скрипт найдет сразу все ошибки.
     - Избаловали тебя компьютеры. Пойми! Ошибки будут на по одному шаблону. Иногда, даже с помощью такого скрипта приходится поломать голову, а что же там не так?
     - Посему! Дабы ремонтировать fb2 надобно хорошо понимать его структуру!
     = И Питон нам в помощь.
     - Но я хочу дать тебе домашнее задание. Дело в том, что ошибки не ограничиваются структурными бывает еще нарушение юникода.
     = Че-то я такое слышал.
     - Юникод позволяет отображать в книге греческие, математические, арабские и прочие символы (насчет арабских - погорячился. наши читалки отображают далеко не все символы Unicode)
     - Например для отображения в книге буквы "О с двумя точками сверху" надо вставить код: &#214; для строчной греческой "бета" &#946; для отображения знака "больше" (>) &#62;
     - Как видишь коды начинаются со знака амперсанд (&) потом решетка (#).
     = Потом циферки.
     - Не только. Упомянутый знак "больше" может отображаться как &gt; символ "Пи" как &Pi; символ "градуса" &deg; "дробь 3/4" как &frac34;
     - Закономерности ты уловил. Так вот! если по какой-то причине структура юникода будет нарушена это может быть воспринято как ошибка. (одна читалка у меня не любила код &nbsp; - это "неразрывный пробел", приходилось заменять на пробел).
     - Задание написать скрипт (или добавить к рассмотренному ) анализ правильности символов юникода.
     = Ну ты даешь! Даже не знаю!
     - А кому сейчас легко.
     -----------------------------------------------------
     -----------------------------------------------------
     = Справился! Применил тот-же подход как у тебя к чтению тега.
     = Но! Хи-хи-хи! Нашел у тебя ошибку.
     = Ты забыл закрыть файл.
     - Посмотрим. Бывает. Ты заметил, что закрывать файл надо будет в двух местах?
     - Ремонт fb2 большая и интересная работа. Например попадаются книги сделанные программами распознавания, помнится работал с «Общей химией» Глинки там все химические формулы были картинками, перевел в текстовый вид, потрудится пришлось, но «вес» файла уменьшил. Сложные математические формулы лучше оставлять картинками, но не jpg с цветом и полутонами, а жесткими черно-белыми png или gif.
     Хотя? Все фломастеры разные. И ситуации бывают…
     - Еще встречаются распознанные pdf. В этом случае каждая экранная строка оформляется как абзац, что на читалке выглядит дико. Такую книгу лучше обработать текстовым редактором поддерживающим регулярные выражения (или поищи "регулярные выражения в питоне"). В таких случаях приходится вручную делать разбивку по частям и главам (дело в том, что по идиотским правилам русского языка, названия глав и частей не должны заканчиваться точкой!!! и при тупом использовании регулярки будут неправильные слияния строк), и после таких попыток проверить структуру fb2 нашим скриптом очень даже полезно.
     = Да, о наших баранах. Что такое одиночный тег?
     - Это просто. <empty-line/> - пустой абзац; <image l:href="#cover.jpg"/> - рисунок. Может и еще кто-то есть, но сходу не вспомнить.
     - Разбередила меня тема ремонта, что только не было. Например, безумно раздражает если в тексте используются «чтото гдето когдато…) вместо (что-то где-то когда-то). Слабо написать скрипт коий исправит такие глупости???
     - Еще убивает применение слова «итак». Для меня безсомненно, что есть два случая « дело шло и так и сяк» «Итак начнем заседание». Авторы или пишут «итак» в любом случае или даже применяют это слово строго наоборот.
     = Мне кажется мы все дальше уходим от Питона.
     - Но ближе к книгам.
     - Хорошо. Прекратим дозволенные речи.
      
     7.12.21
     Скачал с некого сайта несколько книг, практика показала, что большая часть их поврежденная. Возникла задача отделить мух от…
     Написал скрипт для составления списка поврежденных книг, а следом еще три. Возник целый комплекс ремонтника:
     1. запускаю скрипт поиска больных книг.
     2. перехожу в рабочую папку, в ее помещается полученный список и необходимые скрипты.
     3. запускаю скрипт moveIn.py, который, по заданному списку извлекает больных и помещает их в инфекционное отделение.
     4. запускаю скрипт по извлечению fb2 из архивов.
     5. ремонтирую книги.
     6. запускаю скрипт moveOut.py, коий по тому же списку рассылает бывших больных по их домам.
     10.01.22
     Выложил новую версию проверки fb2 (fb2err4.py).
     Теперь это бесконечный цикл (при необходимости выход Ctrl + C)
     При нажатии Enter повторно проверяется старый файл, при вводе нового имени проверяется новичок.
     Функция printStack() распечатывает на экран или в файл stack.txt проверенные теги до места ошибки. При желании функцию можно закомментировать.
     В строках с 15 по 22 – вариант с печатью структуры глав  файла fb2. Пока этот вариант заморозил.
     Надеюсь, обновленный интерфейс  будет удобнее.
      
     Всё.

Приложение; проверка и ремонт структуры файла fb2

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     fb2err4.py
     1|#!/usr/bin/env python
      2|# -*- codning: utf-8 -*-
      3|import sys, os
      4|
      5|# проверка структуры файла fb2
      6|#----------------------------------------------
      7|file  = 'lib.fb2' # имя проверяемого файла
      8|
      9|flag = False # признак анализа тега
     10|S = '' # место для обработанного тега
     11|Tag = '' # место для тега
     12|L = [] # стек
     13|Tagss = []
     14|'''
     15|with open(file, encoding="utf8") as f:
     16| . .LList = f.readlines()
     17|for i in LList:
     18| . .count += 1
     19| . .if i.find('section') > -1:
     20| . . . .print(count)
     21| . . . .print(i)
     22|f.close()
     23|'''
     24|def printStack():
     25| . .st = open("stack.txt", "w")
     26| . .for n in Tagss:
     27| . . . .#print(n) # вывод на экран
     28| . . . .st.write(n+'\n') # или вывод в файл
     29| . .st.close()
     30|
     31|def run_analiz(fn):
     32| . .count = 1 #  счетчик строк
     33| . .if not os.path.isfile(fn): # проверка существования файла
     34| . . . .print('"'+fn+'" file does not exist.')
     35| . . . .return
     36|
     37| . .global Tagss
     38| . .f = open(fn, 'rb') #
     39| . .d = f.read()
     40| . .for n in d:
     41| . . . .if n == 10: # символ завершения строки
     42| . . . . . .count += 1 # + в счетчик строк
     43| . . . .elif chr(n) == '<': # начало тега
     44| . . . . . .flag = True
     45| . . . . . .Tag = '' # подготовка места для тега
     46| . . . .else:
     47| . . . . . .if flag: # читаем тег
     48| . . . . . . . .if chr(n) == '>': # провека на конец тега, если да:
     49| . . . . . . . . . .S = Tag.split(' ')[0] # отбрасывание возможных параметров
     50| . . . . . . . . . .flag = False
     51| . . . . . . . . . .if S[0] == '/': # проверка на закрывающий тег
     52| . . . . . . . . . . . .Tagss.append(str(count)+' <'+S)
     53| . . . . . . . . . . . .S = S[1:] # удаление слеша
     54| . . . . . . . . . . . .Lo = L.pop() # чтение из стека
     55| . . . . . . . . . . . .if Lo[0] != S: # сообщение об ошибке
     56| . . . . . . . . . . . . . .printStack() # распечатка стека
     57| . . . . . . . . . . . . . . # ежели такая распечатка не нужна - закомментируйте
     58| . . . . . . . . . . . . . .print('Teg = "'+Lo[0]+'" begin in str = '+ str(Lo[1]))
     59| . . . . . . . . . . . . . .print('Не соответствует')
     60| . . . . . . . . . . . . . .print('Teg ="' +S+ '"end in str = '+ str(count))
     61|
     62| . . . . . . . . . . . . . .f.close()
     63| . . . . . . . . . . . . . .return
     64| . . . . . . . . . .else:
     65| . . . . . . . . . . . .if Tag[-1] != '/': # обработка одинарных тегов
     66| . . . . . . . . . . . . . .T = Tag.split(' ')
     67| . . . . . . . . . . . . . .Tagss.append(str(count)+' <'+T[0])
     68| . . . . . . . . . . . . . .Lo = [S, count] # подготовка для записи в стек
     69| . . . . . . . . . . . . . .L.append(Lo) # запись в стек
     70| . . . . . . . .else:
     71| . . . . . . . . . .Tag = Tag + chr(n) # добавление символа к тегу
     72| . .f.close()
     73| . .print('OK!!!') # сообщение об удачной проверке.
     74|while True: # бесконечный цикл проверок
     75| . .FN = input('Введите имя файла: ['+file+']')
     76| . .if  FN == '': # если сразу нажат "Enter"
     77| . . . .run_analiz(file) # повторяем проверку файла
     78| . .else:
     79| . . . .file = FN # запоминаем новое имя
     80| . . . .run_analiz(file) # проверяем новый файл
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     fb2errors.py
     |#!/usr/bin/env python
     |# -*- codning: utf-8 -*-
     |import sys, os
     |import zipfile
     |
     |# создание списка файлов fb2 с повреждением структуры
     |#----------------------------------------------
     |OList = []
     |Count = 0
     |def fb2err(fn): # проверка структуры файла
     | . .global Count
     | . .flag = False # признак анализа тега
     | . .S = '' # место для обработанного тега
     | . .Tag = '' # место для тега
     | . .L = [] # стек
     |
     | . .if isinstance(fn, str):
     | . . . .f = open(fn, 'rb') # открыт .fb2
     | . . . .d = f.read()
     | . .else:
     | . . . .d = fn.read() #  открыт .zip
     | . .for n in d:
     | . . . .if chr(n) == '<': # начало тега
     | . . . . . .flag = True
     | . . . . . .Tag = '' # подготовка места для тега
     | . . . .else:
     | . . . . . .if flag: # читаем тег
     | . . . . . . . .if chr(n) == '>': # провека на конец тега, если да:
     | . . . . . . . . . .S = Tag.split(' ')[0] # отбрасывание возможных параметров
     | . . . . . . . . . .flag = False
     | . . . . . . . . . .if S[0] == '/': # проверка на закрывающий тег
     | . . . . . . . . . . . .S = S[1:] # удаление слеша
     | . . . . . . . . . . . .Lo = L.pop() # чтение из стека
     | . . . . . . . . . . . .if Lo != S: # ОШИБКА
     | . . . . . . . . . . . . . .Count += 1
     | . . . . . . . . . . . . . .return True
     | . . . . . . . . . .else:
     | . . . . . . . . . . . .if Tag[-1] != '/': # обработка одинарных тегов
     | . . . . . . . . . . . . . . L.append(S) # запись в стек
     | . . . . . . . .else:
     | . . . . . . . . . .Tag = Tag + chr(n) # добавление символа к тегу
     |
     | . .return False # удачная проверка.
     |
     |def . .parse_zip(fn): # обработка zip
     | . .global OList
     |
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .filelist = z.namelist()
     | . .filelist.sort()
     |
     | . .for n in filelist:
     | . . . .try:
     | . . . . . .if n[-4:] == ".fb2":
     | . . . . . . . .if fb2err(z.open(n, 'r')):
     | . . . . . . . . . .OList.append(fn)
     | . . . .except:
     | . . . . . .print( "X15:", n )
     |
     |def parse_file(fn): # обработка файла
     | . .global OList
     |
     | . .m = fn.split(".")[-1]
     | . .if (m == "zip"): #  если zip
     | . . . .parse_zip(fn)
     | . .elif (m == "fb2"):  # если fb2
     | . . . .if fb2err(fn):
     | . . . . . .OList.append(fn)
     | . . . .
     |def parse_dir(fn): # сканирование папки
     | . .dirlist = os.listdir(fn)
     | . .dirlist.sort()
     | . .for a in dirlist:
     | . . . .b = os.path.join(fn, a)
     | . . . .if os.path.isdir(b):
     | . . . . . .parse_dir(b) # сканирование подпапки
     | . . . .else:
     | . . . . . .if os.path.getsize(b) > 0:
     | . . . . . . . .parse_file(b)
     |#-------------------------
     |path = os.getcwd()
     |parse_dir(path) # сканирование текущей папки
     |
     |if len(OList) > 0: # сохранение результатов
     | . .fn = 'fb2Error.txt'
     |''' #  при необходимости сохранения старых файлов fb2Error.txt - снять комментарии
     | . .nu = 0
     | . .while os.path.isfile(fn):
     | . . . .nu += 1
     | . . . .fn = 'fb2Error'+str(nu)+'.txt'
     |'''
     | . .f = open(fn, 'w')
     | . .for i in OList:
     | . . . .f.write(i+'\n')
     | . .f.close()
     |print('Файлов с ошибкой '+str(Count))
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     moveIn.py
     |#!/usr/bin/env python
     |# -*- codning: utf-8 -*-
     |import sys, os
     |import shutil
     |
     |# перемещение поврежденных файлов fb2 для последующего ремонта
     |#----------------------------------------------
     |path = os.getcwd()
     |
     |Count = 0
     |
     |fn = 'fb2Error.txt' # !!! проследить за правильностью имени файла !!!
     |if os.path.isfile(fn): # проверяем существование
     | . .with open(fn, 'r') as text: # открываем файл
     | . . . .mylist = text.readlines() # и читаем
     | . .fName = 'infiles' # подготавливаем имя папки адресата
     | . .if (not os.path.isdir(fName)): # если адресата нет
     | . . . .os.mkdir(fName) # то создаем его
     | . .mydir = os.path.join(path, fName)
     | . .for i in mylist: # просматриваем список
     | . . . .i = i.strip() # отрубаем пробелы
     | . . . .name = os.path.basename(i) # выделяем имя файла
     | . . . .dst = os.path.join(mydir, name) # определяем куда его сунуть
     | . . . .if os.path.isfile(i) and (not os.path.isfile(dst)):
     | . . . . . .shutil.move(i, dst) # перемещаем файл
     | . . . . . .Count += 1 # обновляем статистику
     ||
     |print('Файлов перемещено '+str(Count))
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     moveOut.py
     |#!/usr/bin/env python
     |# -*- codning: utf-8 -*-
     |import sys, os
     |import shutil
     |
     |# перемещение файлов fb2 после ремонта на места дислокации
     |#----------------------------------------------
     |path = os.getcwd()
     |
     |Count = 0
     |
     |fn = 'fb2Error.txt' # !!! проследить за правильностью имени файла !!!
     |if os.path.isfile(fn): # проверяем существование
     | . .with open(fn, 'r') as text: # открываем файл
     | . . . .mylist = text.readlines() # и читаем
     | . .fName = 'infiles' # подготавливаем имя ремонтной папки
     | . .if (not os.path.isdir(fName)): # если адресата нет
     | . . . .print('???')
     | . . . .sys.exit() # то делать нечего...
     | . .mydir = os.path.join(path, fName) # ремонтная папка
     | . .for i in mylist: # просматриваем список
     | . . . .i = i.strip() # отрубаем пробелы
     | . . . .name = os.path.basename(i) # выделяем имя файла
     | . . . .dst = os.path.join(mydir, name) # определяем откуда его высунуть
     | . . . .if os.path.isfile(dst): # если файл на месте
     | . . . . . .shutil.move(dst,i) # перемещаем файл
     | . . . . . .Count += 1 # обновляем статистику
     |
     |print('Файлов перемещено '+str(Count))
     |
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     un_zip.py
     |#!/usr/bin/env python
     |# -*- codning: utf-8 -*-
     |import sys, os
     |import zipfile
     |
     |# Извлечение из архивов в папке
     |#----------------------------------------------
     |path = os.getcwd()
     |Count = 0
     |
     |def parse_zip(fn): # обработка zip
     | . .global path
     | . .global Count
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .z.extractall(path)
     | . .Count += 1
     |
     |def parse_file(fn): #  обработка файла
     | . .m = fn.split(".")[-1]
     | . .if (m == "zip"): # если zip
     | . . . .parse_zip(fn)
     | . . . .
     |def parse_dir(fn): #
     | . .dirlist = os.listdir(fn)
     | . .dirlist.sort()
     | . .for a in dirlist:
     | . . . .if os.path.getsize(a) > 0:
     | . . . . . . . .parse_file(a)
     |#-------------------------
     |
     |parse_dir(path) # сканирование текущей папки
     |print('Файлов извлечено '+str(Count))
      

7. Вне плана

     - Здравствуй. И не просто «здравствуй», а будь здоров…
     = Привет. Прости, что перебиваю, но у меня возникла интересная задача.
     = Мне очень понравится твой прием с бинарным файлом, и я хочу применить его для объединения строк после PDF.
     Смотри, сканируем файл, находим символ «10» проверяем символы до и после, если это строчные символы, то «10» заменяем на пробел и вуа-ля. Правда, пока у меня не получается.
      
     - Забавная идея. Надо обдумать.
      
     . . .
     . . .
     // прошла неделя //
     . . .
     . . .
      
     = Привет! Как дела? Что, получилось?
     - Ну да, у меня других дел нет кроме твоих хотелок. Кошка не кормлена, помидоры не политы.
     Ага, твою кошку попробуй не покорми, а помидоров у тебя нет. Рассказывай!
      
     - Как и следовало ожидать задача не столь проста.
     - Pthon — заточен под юникод. А юникод это два — три // = килограмма диетического сала // хм-м байта.
     Поэтому с проверкой байтов «до и после» будет проблема, но решаемая.
     - Кстати, тебе задание, изучить «усваиваемость» философии кодировки unicode и сравнить ее с «кодовой таблицей Windows 1251» именно эта система на пригодится, т. к. в ней под каждую букву русского языка отводится по одному байту.
      
     - Дабы не было неясностей объясню:
     * выполненная мною работа не является окончательным решением задачи по превращению распознанного текста в конфетку. Скорее это инструмент который может помочь в работе.
     * подразумевается работа с «простым текстом». Т.е. если необходимо поработать с fb2 то потребуется «врезать» содержательную часть из исходного файла; удалить теги <p> и </p> (если есть теги внутри… то они не помешают) переделать скрипт, дабы на выходе добавились <p> и </p> в обрамлении абзацев, и вперед с песней.
     * в некоторых случаях при распознавании в начале и/или в конце строк появляются пробелы. Нам, для работы скрипта они очень помешают. Вычистить эти лишние пробелы ты сможешь самостоятельно, для этого есть стандартные функции Питона (измени последующий скрипт).
      
     - Поехали.
      
     Запускаем следующий скрипт для изменения кодировки текста с юникода на 1251.
      
     |
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |
     |fin = open('u1.txt', 'r') # открываем исходный файл
     |out = open('text.txt', 'w', encoding='cp1251') # подготавливаем промежуточный файл
     |
     |for s in fin: # переписываем из файла в файл
     | . .out.write(s)
     |
     |fin.close()
     |out.close()
     |
     |print('Done')
     |
      
     - Как видишь тут все просто.
     - Далее немного справочных данных:
     Из таблицы кодов символов Winodws-1251 и из таблицы юникода
      
     | А | 192 | 1040
     | Я | 223 | 1071
     | a | 224 | 1072
     | я | 255 | 1103
     | , | 44
     | - | 45
     | ё | 184 | 1105
      
     - Теперь сам скрипт:
     (см. в приложении)
      
     - Дело в том, что существует два стиля завершения строки:
     Unix-подобных символ #10
     в Windows символы #13#10 (#13 - конец строки, #10 - новая строка)
     Поэтому в скрипте есть маленький цикл для определения наличия символа «13».
     Ну, тебе ясно, что если есть «13» то «до» проверять надо не предыдущий «10» символ, а еще на один раньше.
     - В функции RusBukva1 кроме проверки на строчную букву еще есть проверка на запятую и дефис. При необходимости вставь туда «:», «;» и латинские прописные.
     - Проверку на латинские прописные можешь вставить и в RusBukva2, тогда будут объединятся и латинские строки.
      
     m += 848 — преобразование кодов букв из 1251 у юникод
     дальнейшие строки — при тестировании скрипта у меня вылезли символы «тире» которые пришлось переводить в юникод принудительно.
      
     = К чему такие сложности? Я же хотел просто перезаписать «10»!
     - Учи матчасть! В Питоне запрещено изменять строки. Низя!
     Можно только создать новую строку на основе старой.
     Сразу скажу про i
     (= да, расставь точки)
     В языке С цикл for выглядит так:
     for(int i =1; i < 10; i++)
     { std::cout << i * j << "\t"; }
     Т.е. счетчик цикла указывается явно, и с ним можно выполнять разные фокусы. В Питоне — языке высокого уровня, счетчик итераций прячется от пользователя, и мне пришлось его имитировать. (повторюсь, я не считаю себя Питон-гуру, возможно все можно сделать элегантнее).
     - * -
     - После выполнения скрипта получится файл с которым еще работать и работать. - Абзацы будут разорваны именами героев, названиями стран и городов, скобками, апострофами и т. д. и т. п. Устранять это вручную и писать скрипты — дело твое.
      
     29.05.21
     ***************
     10.10.21
     = Привет.
     - Здравствуй. Лень правит миром, но жизнь подкидывает задачи и заставляет работать.
     = Интересная задача? И не лень правит, а сохранение энергии.
     - Заинтересовала книга (pdf), но результат распознавания, несколько неожиданный:
     «
     ОТ ИЗДАТЕЛЬСТВА
     Мы продолжаем нашу серию «Популярная мате-
     математика» публикацией перевода сборника, которым
     открылась новая серия «Математические миниатю-
     миниатюры», выпускаемая издательством Биркхойзер. В этом
     сборнике представлены пять лекций для широкой
     публики, прочитанных при вступлении в должность
     пятью доцентами Боннского университета. Подроб-
     Подробнее об этом ритуале говорится в открывающем сбор-
     сборник предисловии известного немецкого математика
     профессора Боннского университета Ф. Хирцебруха.
     Представленный в сборнике материал практически
     отсутствует в научно-популярной литературе на рус-
     русском языке.
     »
     - Я уже встречался с подобной ситуацией и тогда я обработал файл вручную, но в данном случае… 200 килобойт?
     = Понял. Прежний скрипт с работой не справится.
     - Пришла идея, как решить задачу. Но будем тренироваться на кошках. Для начала выложу новую версию скрипта объединения абзацев. Выкладываю в «отладочном» варианте, как всегда, скрипт «сырой» его стоит погонять по разным текстам, и отладочная информация поможет исправить багги.
     - Следующим выкладываю скрипт для объединения хитрых  переносов. Как видишь, структурно скрипты одинаковы. Для промежуточного хранения файла используется список new_List он нужен для последующего объединения этих двух скриптов (дабы обработка файла выполнялась одним скриптом)
     - Таковое объединение выполнишь сам.
     = Да одной левой!
     - Левачь. А у меня тут дальше работа не вытанцовывается.
      
      

Приложение; сборка абзацев из разрозненных строк

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |#-- скрипт для сборки абзацев из разрозненных строк ---
     |
     |s = '' # строка коию будем записывать в список
     |L = [] # список для хранения выходного файла
     |i = 0 # указатель положения в файле
     |
     |def RusBukva1(n): # обработка буквы «до»
     | . .return ((n > 223) and (n < 256)) or (n==44) or (n==45)
     |def RusBukva2(n): # обработка буквы «после»
     | . .return ((n > 223) and (n < 256))
     |
     |f = open('text.txt', 'rb') # открываем и читаем промежуточный файл
     |d = f.read()
     |f.close()
     |
     |c13 = 2 # константа индицирующая наличие символа «13»
     |for n in d: # проверка промежуточного файла на наличие символа «13»
     | . .if (n == 13):
     | . . . .c13 = 3
     | . . . .break
     |
     |for n in d: # основной цикл проверки файла
     | . .i +=1 # инкримент указателя положения в файле
     | . .if (n == 10): # если конец строки
     | . . . .if RusBukva1(d[i-c13]) and RusBukva2(d[i]): # проверяем «до» и «после»
     | . . . . s += ' ' # в строку пробел
     | . . . .else:
     | . . . . L.append(s) # добавляем строку в список
     | . . . . s='' # подготовка пустой строки
     | . .else:
     | . . . .if n != 13:
     | . . . . . .m = d[i-1] # эта строка и 6 строк ниже объясню еще ниже
     | . . . . . .if (m > 191) and (m < 256):
     | . . . . . . . .m += 848
     | . . . . . .elif(m==184): # буква ё
     | . . . . . . . .m = 1105
     | . . . . . .elif (m==151):
     | . . . . . . . .m = 8212
     | . . . . . .s += chr(m) # запись символа в строку
     |
     |
     |f = open('outtext.txt', 'w') # выходной файл
     |for etem in L: # просматриваем список и записываем в файл
     | . .f.write(etem+'\n')
     |f.close()
     |
     |print('OK!!!')
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
      
     |#!/bin/env python
     |# -*- coding: utf-8 -*-
     |# Объединение абзацев разде-
     |# разделенных переносами
     |# 19.10.21
     |import sys, os
     |#--------------------------------------
     |def EndStr(s):
     | . .SQ = ''
     | . .for a in reversed(s):
     | . . . .if a == ' ':
     | . . . . . .return SQ[:-1]
     | . . . .else:
     | . . . . . .SQ = a+SQ
     |#--------------------------------------
     |old_List = [] # Сюда читается
     |new_List = [] # Промеждуточный список
     |#--------------------------------------
     |#FN = input('Введите имя файла:')
     |#fn1=path+FN
     |FN = '1.txt'
     |fb2_file=open(FN,'r')
     |#fb2_file=open(FN,'r', encoding='utf-8')
     |old_List=fb2_file.readlines()
     |fb2_file.close()
     |#--------------------------------------
     |SS = '' # Промежуточное хранение строки
     |SQ = '' # Начало слова "разор-
     "разорванного" "странным" переносом
     |FlagQ = False
     |
     |i = 0
     Первый проход. Поиск "странных" переносов
     |for item in old_List:
     | . .s = item.strip()# Обрезание пробелов
     | . .if s == '':
     | . . . .new_List.append(s)
     | . . . .i += 1
     | . . . .if i > len(old_List):
     | . . . . . .new_List.append(item[i+1])
     | . . . . . .break
     | . . . .continue
     | . .
     | . .if (s[-1] == '-'): # последний символ строки
     | . . . .SQ = EndStr(s) # следующая строка начинается с SQ
     | . . . .if (SQ != None) and (SQ != '') and (old_List[i+1].find(SQ)==0):
     | . . . . . .m = -(len(SQ)+1)
     | . . . . . .new_List.append(s[:m]+'@') # помечаем строку для последующего объединения
     | . . . .else:
     | . . . . . .new_List.append(s)
     | . .else:
     | . . . .new_List.append(s)
     | . .i += 1
     | . .if i > len(old_List):
     | . . . .new_List.append(item[i+1])
     | . . . .break
     |
     |old_List.clear()
     |
     |# второй проход. соединение строк
     |#for item in reversed(new_List):
     |for item in new_List:
     | . .if item == '':
     | . . . .if SS != '':
     | . . . . . .old_List.append(SS)
     | . . . . . .SS = ''
     | . . . .old_List.append(item)
     | . . . .continue
     |# . .print(item)
     |
     | . .if FlagQ:
     | . . . .SS = SS[:-1] + item
     | . . . .FlagQ = item[-1] == '@'
     | . .else:
     | . . . .if SS != '':
     | . . . . . .old_List.append(SS)
     | . . . . . .SS = ''
     | . . . .if item[-1] == '@': # последний символ строки
     | . . . . . .FlagQ = True
     | . . . . . .SS = item
     | . . . .else:
     | . . . . . .old_List.append(item) 
     | . . . . . . . .
     |if SS != '':# запись последней строки
     | . .old_List.append(SS)
     |
     |#--------------------------------------
     |
     |fn2="2_.txt"
     |new_file=open(fn2,'w')
     |for i in old_List:
     | . .  new_file.write(i+'\n')
     |new_file.close()
     |print('Done')
     |
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     |#!/bin/env python
     |# -*- coding: utf-8 -*-
     |# Объединение абзацев (версия 19.10.21)
     |import sys, os
     |#--------------------------------------
     |def EndStr(s):
     | . .SQ = ''
     | . .for a in reversed(s):
     | . . . .if a == ' ':
     | . . . . . .return SQ[:-1]
     | . . . .else:
     | . . . . . .SQ = a+SQ
     |#--------------------------------------
     |old_List = [] # Сюда читается
     |new_List = [] # Промеждуточный список
     |#--------------------------------------
     |#FN = input('Введите имя файла:')
     |#fn1=path+FN
     |FN = '2_.txt'
     |fb2_file=open(FN,'r')
     |#fb2_file=open(FN,'r', encoding='utf-8')
     |old_List=fb2_file.readlines()
     |fb2_file.close()
     |#--------------------------------------
     |SS = '' # Промежуточное хранение строки
     |FlagQ = False
     |
     |i = -1
     |for item in old_List: # первый проход
     поиск разорванных строк
     | . .i += 1
     | . .if i >= len(old_List):
     |# . . . .new_List.append(item[i+1]) ?
     | . . . .break
     |
     | . .s = item.strip()# Обрезание пробелов
     | . .if s == '':
     | . . . .new_List.append(s)
     | . . . .continue
     |
     | . .m = ord(s[-1]) # последний символ строки
     |# . .print(s)
     | . .if (m > 1071 and m < 1104) or m == 44: # от "а" до "я" + ","
     | . . . .if i+1 >= len(old_List):
     | . . . . . .break
     | . . . .d = old_List[i+1].strip()
     | . . . .if (d != ''):
     | . . . . . .m = ord(d[0]) # первый символ следующей строки
     | . . . . . .if (m > 1071 and m < 1104): # от "а" до "я"):
     | . . . . . . . .new_List.append(s+' @')# помечаем строку для объединения с последующей
     | . . . . . .else:# в обычном тексте вероятность, что строка
     завершится "собачкой" очено мала, но ... тогда это будет ошибочное объединение
     | . . . . . . . .new_List.append(s)
     | . . . .else:
     | . . . . . .new_List.append(s)
     | . .else:
     | . . . .new_List.append(s)
     |
     |
     |old_List.clear()
     |
     |#for item in reversed(new_List): # второй проход
     объединение помеченных строк
     |for item in new_List:
     | . .if item == '':
     | . . . .if SS != '':
     | . . . . . .old_List.append(SS)
     | . . . . . .SS = ''
     | . . . .old_List.append(item)
     | . . . .continue
     |
     | . .if FlagQ:
     | . . . .SS = SS[:-1] + item
     | . . . .FlagQ = item[-1] == '@'
     | . .else:
     | . . . .if SS != '':
     | . . . . . .old_List.append(SS)
     | . . . . . .SS = ''
     | . . . .if item[-1] == '@': # последний символ строки
     | . . . . . .FlagQ = True
     | . . . . . .SS = item
     | . . . .else:
     | . . . . . .old_List.append(item) 
     |
     |if SS != '':
     | . .old_List.append(SS)
     |
     |#--------------------------------------
     |
     |fn2="3_x.txt"
     |new_file=open(fn2,'w')
     |for i in old_List:
     | . .  new_file.write(i+'\n')
     |new_file.close()
     |print('Done')
     |
      

8. Image

     - Давай сегодня займемся рисунками. Для затравки выложу в приложение скрипт «извлечение рисунков из fb2» (вариант кода приведенного в 3 главе).
     = А для чего это нужно?
     - Ну, как тебе сказать. Иногда хочется получить картинки содержащиеся в книге. Ладно, не кривись, признаюсь: это часть проекта конвертации файла fb2 в html.
     - Обрати внимание на закомментированую строку «filename = sys.argv[1]» - это еще один способ передачи информации скрипту (через параметр командной строки).
     Сначала создаем папку для рисунков.
     В xml структуре находим контейнер "FictionBook" и среди его «детей» находим 'binary'
     и его в атрибутах находим имя файла/источника.
     Декодируем и сохраняем рисунок в папку.
     А сейчас тебе задание.
     Задача номер 25. Удалить рисунки из файла fb2.
     - Часто попадаются книги добросовестно просканированные, но содержащие (по моему мнению) совершенно не нужные рисунки. Посуди сам "1001 ночь" в архиве весит 18 мегабайт, а после обрезания 1.1MB. Я не покушаюсь ни на чьи права, и такую обработку делаю только в своей библиотеке, что уменьшаем «вес» книги и иногда улучшает читабельность. Повторюсь, такую операцию я делаю очень выборочно.
     = Щас, подумаем.
     / через 2 часа/
      
     = Вот. Смотри.
      
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# Удаление секций binary из файла fb2
     |#
     |import sys, os
     |
     |print("")
     |print(" Удаление секций binary из файла fb2.")
     |#print(" Введите имя файла:")
     |print("")
     |#nameFile = input()
     |nameFile = 'Terskoy-front.fb2'
     |
     |my_text = open(nameFile, "r")
     |my_out = open('t'+nameFile, "w")
     |
     |notbinary = True
     |# содержимое
     |for i in my_text:
     | . .if notbinary:
     | . . . .if i.find('<binary')>=0:
     | . . . . . .notbinary = False
     | . . . .else:
     | . . . . . .my_out.write(i)
     | . .else:
     | . . . .if i.find('</binary')>=0:
     | . . . . . .notbinary = True
     | . .
     |my_text.close()
     |my_out.close()
     |
     |print("Done.")
     Посмотрим.
     . . .
     Так, интересно. Ты решил обойтись простыми средствами, без заумного XML. Должен сказать, что такой подход сработает в 99.99%, но если в одной строке с 'binary' окажется другой тег, то он будет «съеден», хотя мне такого не встречалось.
     Но встречалась другая ситуация, когда теги '<binary … >' и '</binary>' находились в одной строке. В таком случае твой алгоритм создаст ошибку (срежет весь остаток файла). Вылечить это просто… да и еще одно… чаще всего хочется удалить «лишние» рисунки и оставить обложку книги. Поэтому предлагаю:
      
     |for i in my_text:
     | . .if notbinary:
     | . . . .if i.find('<binary')>=0:
     | . . . . . .if i.find('cover.')>=0: # проверяется обложка т. к. она традиционно называется «cover»
     | . . . . . . . .my_out.write(i) # если традиция не соблюлась, останемся без обложки
     | . . . . . .else:
     | . . . . . . . .notbinary = False
     | . . . .else:
     | . . . . . .my_out.write(i)
     | . .if i.find('</binary')>=0: # проверяется та-же строка
     | . . . . notbinary = True
     -------------------------------------------------------------------------
     Но есть еще одна проблема. Как уже было сказано, книги у меня хранятся в виде xxxx.fb2.zip, и поэтому хотелось-бы чтобы скрипт получал на входе zip и результат был-бы в том же формате.
     // на следующий день //
     - Выкладываю полученный результат. Озаглавленный «Удаление секций binary из файла xxxx.fb2.zip».
     = Посмотрим, пощупаем, понюхаем, а где же…
     = А что это за строчка «vi = io.StringIO()»
     -
     Очень интересная штуковина — виртуальный файл, но давай по порядку.
     - После запуска скрипта появится минимальный интерфейс:
     1. сообщение, что собственно запущено, а вдруг пользователь запустил это по ошибке.
     2. запрос на имя файла для последующей обработки.
     3. запрос об удалении обложки. Для удаления введите «y»; при любом другом ответе, включая пустую строку обложка сохранится.
      
     - Далее пытаемся открыть архив (эх забыл одну мелочь) архив открывается в список.
     Список анализируем на бинарные блоки и результат записываем в виртуальный файл. Вышеназванную структуру записываем во временный файл, а затем удаляем книгу источник и переименуем временное хранилище в первоначальное имя.
     - Ух, ах вспотел.
     = А что за мелочь была?
     - Надо-бы вставить проверку и если задан файл fb2, а не fb2.zip, то исключить распаковку архива (такую штуковину я уже делал).
     = Посмотрю, попробую, возможно появятся вопросы.
     - Welcome.
     * * *
     Дополнение от 11.11.21
     - Должен сказать, что данный опус – не первая моя книга про программирование.
     Было дело… и вспомнилось по ассоциации с имеджами (а память у меня сплошь ассоциативная).
     - Дело в том, что формат «Base64 — стандарт кодирования двоичных данных при помощи только 64 символов ASCII (ВикипедиЯ)» позволят хранить в текстовом виде не только рисунки, а ЛЮБУЮ ИНФОРМАЦИЮ.
     И в той… давно забытой книге, я сделал такую шутку: в конце файла fb2 поместил секцию <binary id="архив.zip" content-type="arc/zip"> ( вместо привычного <binary id="cover.jpg" content-type="image/jpeg">) где, вполне очевидно, содержался архив zip в коем находились исходники примеров к той книге. В самой книге, кроме глупых рассуждений содержался и код программы для извлечения из формата base64 этих самых файлов.
     - Так вот! Я и думаю. Когда тема «Библиотеки» будет исчерпана, сделаю-ка я fb2 с дополнением архива в binary в который сгружу все готовые скрипты, и размещу-ка, пожалуй, на флибусте (данный сайт таких вольностей не позволяет).
     = Скажи когда будет готово.

Приложение. Скрипты для работы с рисунками

     Собранные в кучку скрипты для работы с рисунками.
     * получение рисунка из fb2;
     * удаление из fb2 всех рисунков
     * кодирование рисунка для вставки в fb2
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# извлечение рисунков из fb2
     |import sys, os
     |import base64
     |import xml.dom.minidom
     |
     |def parseBinaryContent(filename):
     | . .
     |  #  filename = sys.argv[1]
     | . .if filename[-4:] == '.fb2':
     | . . . .  dirname = filename[:-4]+'_pic'
     | . .else:
     | . . . .  exit()
     | . .os.mkdir(dirname)
     | . .#------------------------
     | . .if isinstance(filename, str):
     | . . . .fn = open(filename)
     | . .try:
     | . . . .dom = xml.dom.minidom.parse(fn)
     | . .except:
     | . . . .print('Error:')
     | . . . .print(FileName)
     | . .else:
     | . . . .dom.normalize()
     | . . . .node1=dom.getElementsByTagName("FictionBook")[0]
     | . . . .for no in node1.childNodes:
     | . . . . . .print(no.nodeName)
     | . . . . . .if no.nodeName == 'binary':
     | . . . . . . . . id_name = no.getAttribute("id")
     | . . . . . . . . s= no.toprettyxml()
     | . . . . . . . . s = s.split('>')[1]
     | . . . . . . . . s = s.split('<')[0]
     |# . . . . . . . . print(s)
     | . . . . . . . . dat = base64.b64decode(s)
     | . . . . . . . . open(os.path.join(dirname, id_name), 'wb').write(dat)
     |
     |parseBinaryContent('Terskoy-front.fb2')
     -----------------------------------------------------------------------------------------
     -----------------------------------------------------------------------------------------
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# Удаление секций binary из файла xxxx.fb2.zip
     |import sys, os, io, zipfile
     |
     |cover = ''
     |NameFb2 = ''
     |L=[]
     |#---------------------------------------------
     |def Del_Image():
     | . .global NameFb2
     | . .global L
     | . .vi = io.StringIO()
     |
     | . .notbinary = True
     | . .for i in L:
     | . . . .if notbinary:
     | . . . . . .if (i.find('<binary')>=0):
     | . . . . . . . .if (i.find(cover)>=0):
     | . . . . . . . . . .vi.write(i)
     | . . . . . . . .else:
     | . . . . . . . . . .notbinary = False
     | . . . . . .else:
     | . . . . . . . .vi.write(i)
     | . . . .if i.find('</binary')>=0:
     | . . . . . .notbinary = True
     |
     | . .w = zipfile.ZipFile('^temp.zip', 'w', zipfile.ZIP_DEFLATED)  # Создание нового архива
     | . .w.writestr(NameFb2, vi.getvalue()) . .
     | . .w.close()
     | . .vi.close()
     |#----------------------------------------
     |def o_zip(fn):
     | . .global NameFb2
     | . .global L
     |
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .filelist = z.namelist()
     |
     | . .for n in filelist:
     | . . . .try:
     | . . . . . .if n[-4:] == ".fb2":
     | . . . . . . . .body = z.open(n)
     | . . . . . . . .NameFb2 = n
     | . . . . . . . .for line in body:
     | . . . . . . . . . .L.append(str(line, 'UTF-8'))
     | . . . . . . . .return True
     | . . . .except:
     | . . . . . .print( "error:", n )
     | . . . . . .return False
     |#--------------------  main  ------------------------
     |print('Удаление секций binary из файла xxxx.fb2.zip')
     |FileName = input('Введите имя файла:')
     |if FileName == '':
     | . . exit()
     |e = input('Удалить обложку? [y/n]')
     |if e == 'y':
     | . . cover = 'not'
     |else:
     | . . cover = 'cover.'
     |  
     |if os.path.isfile(FileName):
     | . .if o_zip(FileName):
     | . . . .Del_Image()
     | . . . .os.remove(FileName)
     | . . . .os.rename('^temp.zip', FileName)
     | . . . .print("Done.")
     | . .else:
     | . . . .print("???")
     |else:
     | . .print("File doesn't exists!")
     -----------------------------------------------------------------------------------------
     ----------------------------------------------------------------------------------------- |# подготовка рисунка для fb2
     |import base64
     |# Автор: Abhishek Amin . .Дата записи 09.04.2021
     |with open('logo.png', 'rb') as binary_file:
     | . .binary_file_data = binary_file.read()
     | . .base64_encoded_data = base64.b64encode(binary_file_data)
     | . .base64_message = base64_encoded_data.decode('utf-8')
     |
     | . .print(base64_message)
      
      

9. Без базы

     = Начнем с постановки задачи?
     - Как ты заметил, задачи я ставлю исходя из своих потребностей.
     = Да, уж.
     - Опишу потребность.
     Скачиваю заинтересовавшие книги, а память то не полупроводниковая, и часто скачиваю двойников. Я заметил, что слушая радио можно догадаться какой звукооператор сегодня на смене — вкусы выдают. Так и с книгами я скачиваю то, что мне нравится.
     = Погоди! Мы уже говорили об этой задаче.
     - Так, да не так. Хочу посмотреть на двойников с другой точки зрения.
     Еще добавление в потребность. Бывает и такая ситуация: скачиваю книгу, читаю, что это я же уже десять раз читал и удалял эту ху...дожественную литературу. Как ты понимаешь ситуация простая автор рекрутирует всех знакомых-родственников, сам регистрируется десять раз и раскручивает популярность опуса — капитализьм — за что боролись…
     = Но не все же.
     - Умоляю тебя поверьте хоть в то, что графоманы существуют!
     - Упомянутая тобой предыдущая версия – страшно разочаровала. Скрипт выполняется м-е-д-л-е-н-н-н-н-о, понять можно скрипт просматривает три-пять тысяч файлов в каждом надо просмотреть XML содержимое, взять оттуда автора(авторов?), название…
     И такие действия выполнять каждый раз. Да и в рассмотрение не попадают «с возмущением удаленные».
     = Я все уже понял, не дурак. Ты ведешь к базам данных.
     - Хи-хи-хи, вначале я то-же подумал, что делать одни у нас шаблоны и грабли одни. Но потом применил Высшую арифметику.
     Польстим себе, допустим я читаю 1 книгу в день, за год пусть 360 (выходные тоже нужны) за десять лет… … ну пусть 5000.
     = Интересная у тебя арифметика.
     - О каждой книге надо хранить следующую информацию: автор, название, имя файла, длина файла, отзыв о книге. Пусть это займет 100 байт. Так-что на все, про все: 500 кило байт.
     = (хе-хе-хе. Читатель! Надеюсь ты помнишь, что в килобайте 1024 байт).
     - Затем я прикинул. My_SQL где-то 12 Mb даже моя любимая Firebird 5-6 Mb, но все равно мы же не будем использовать все волшебные возможности SQL, всего-лишь один маленький запрос.
     - Кроме того, я сам писал СУДБ (система управления базами данных), и PHP и др.
     = Опять побежали 30 тысяч курьеров. Легкость у тебя в мыслях необыкновенная.
     - Обижаешь! Я правду говорю. СУДБ писал по просьбе одного знакомого, правда она не дописана, т. к. заказчик заскучал, а мне было интересно. Самодельные PHP и AutoCAD пришлось состряпать, т. к. начальник боялся проверок на не лицензионное п.о.
     - Но, я тебе удивляюсь. Перечисленные работы — это уровень курсовой для 3-4 курса нормального учебного заведения. Конечно, эти работы не конкурируют с оригиналами, но поставленные мелкие задачи, успешно решают. И кроме всего, такие занятия позволяют лучше понять логику профессионалов. (рекомендую заняться таким же делом, напиши свой линукс)
      
     - Возвращаясь к нашей задаче.
     * скрипт должен проверить скачанные файлы и сравнить их с текстовым файлом содержащим информацию о моей библиотеке (с выдачей результата).
     * скрипт должен уметь отвечать на запрос о книге или о авторе.
      
     Т.е. при запуске будут сканироваться 30-40 книг, что по времени вполне приемлемо.
     = Почему текстовый файл?
     - Я делаю работу для себя. Текстовый файл я могу редактировать и вне скрипта, а если нарушу его структуру, то сам себе дурак.
     = Приступаем?
     - Для начала давай определимся, что и в каком виде будет хранится в нашем «безбазовом» файле.
     = Ну, ты уже перечислил. Название книги, имя файла, автор…
     - Авторы! Значит так. Авторы должны перечисляться в алфавитном порядке и содержать фамилию автора и инициал.
     = Инициалы!?
     - К сожалению мы «западизируемся» и часто в книгах опускается отчество. А так-как компьютер тупая машина «Иванов И.И.» и «Иванов И.» будут восприниматься как разные личности.
     - Теперь разделители. В нашем файле автор будет хранится в таком виде «Иванов.И» и если имя не указано «Иванов.», а если не указан автор то «NoName.».
     Далее, авторы будут разделятся символом «^»
     Поля нашей небазы будут такими (и в таком порядке):
     Название книги, Авторы, Отзыв о книге (один байт), Имя файл, Размер файла.
     = Ты очень расщедрился для отзыва, а где жанры, а где сериалы.
     - Насчет жанра… надо подумать, давай после Отзыва введем один байт резерва.
     И один байт — совсем не мало — это 256 вариантов символа, хватит и на оценки книги и на жанры.
     Разделителями полей будет символ «|»
     ***
     - Ненавижу!!! Ненавижу всех питонов и вообще всех змей, лягушек, ящериц и улиток.
     Пусть будет проклят тот день когда я сел за клавиатуру этого пылесоса. Нет не пылесоса а кровососа. Заберите все компьютеры в Гималаи, а я отрекаюсь от трона и в монастырь.
     …
     …
     …
     = Шо это было?!
     - Рутинный процесс отладки программы.
      
     '''
     интересная подробность: при изменении и отладке измененного кода лучше отключать блоки (try: except:) иначе при малейшей ошибке, эти блоки (как и положено) будут выдавать заданное сообщение, но понять суть ошибки будут очень-очень не легко.
     Отсюда так-же следует, что сообщения в try-блоках должны быть различными, ну хотя-бы:
     Er1, Er2, ….
     '''
     - Задал ты мне задачу с этими жанрами, порушил все планы.
     = А ты чаще рассказывай нам с читателями о своих планах и мы посмеёмся.
     - Должен согласится, что жанры увеличивают функциональность нашей «небазы».
     - Вначале я пошел неверным путем и просеял исходные 467 жанров до 255. Но потом понял, что то что легким движением я делал в Delphi и в С++ с Питоном не прокатит. И поэтому…
     - Хотя постой. Я хочу отвлечься на «легкое движение».
     - Уже не один раз я попадал в глупое положение, когда полагал, что знания и умения полученные ннадцать лет назад у меня есть в наличии и неприкосновенности.
     «Сердца без практики ржавеют!!!!!!!». Так-что я уверен, что и с Delphi и с С++ сегодня у меня были-бы большие проблемы. Тут помню, а тут не помню.
     Громаднейшая ошибка человечества состоит в том, что академики, чемпионы мира, мисс-вселенные 1938 года, раскрученные писатели и прочие небожители, ныне не в состоянии повторить достижения своей молодости, по крайней мере большинство, но пользуются своим авторитетом и заслугами без всякого сомнения.
     = И забижают тебя несчастного.
     - Ладно. Когда-нибудь сам поймешь, люди всегда создают кумиров, а кумир (если не дурак) напишет законы почитания заслуг.
     - Давай вернемся к жанрам.
     - В приложение я выкладываю скрипт, который ты должен сохранить под именем "ganres.py"
     В файле находятся два словаря.
     = А что это за «условный код»?
     - Я хочу максимально ужать нашу «небазу» и в секции жанры записывать не 'thriller_archaeological' а коротенькое 'AZ'. В один байт не уместился, пусть будет два.
     = А что означает 'AZ'?
     - Просто перебор комбинаций из двух символов.
     = Интересно… а из трех букв много комбинаций.
     - Посчитай. Есть такой раздел Мать и Матики «Комбинаторика».
     = Хорошо. А второй словарь?
     - С помощью первого словаря, жанры найденные в fb2 будут записываться в «небазу» в виде «условного кода». А при показе результатов поиска книг из второго словаря будут извлекаться русский перевод жанра.
     = И как использовать словари будем.
     - Я устал я мухожук. Давай продолжим завтра.

Приложение: Файл "ganres.py"

     # Файл "ganres.py"
     # Два словаря:
     # ali1 - перевод обозначения жанра fb2 в условый код
     # ali2 - перевод условного кода в русское значение жанра
     ali1={
     'military_arts':'AA',
     'nonf_military':'AB',
     'military_history':'AC',
     'military_weapon':'AD',
     'military':'AE',
     'military_memoirs':'AF',
     'military_special':'AG',
     'banking':'AH',
     'accounting':'AI',
     'global_economy':'AJ',
     'sci_business':'AK',
     'paper_work':'AL',
     'org_behavior':'AM',
     'personal_finance':'AN',
     'small_business':'AO',
     'marketing':'AP',
     'real_estate':'AQ',
     'popular_business':'AR',
     'industries':'AS',
     'job_hunting':'AT',
     'economics':'AU',
     'economics_ref':'AV',
     'trade':'AW',
     'management':'AX',
     'stock':'AY',
     'thriller_archaeological':'AZ',
     'det_action':'BA',
     'det_cozy':'BB',
     'detective':'BC',
     'foreign_detective':'BD',
     'det_irony':'BE',
     'det_history':'BF',
     'det_classic':'BG',
     'det_crime':'BH',
     'det_hard':'BI',
     'det_maniac':'BJ',
     'thriller_medical':'BK',
     'det_political':'BL',
     'det_police':'BM',
     'thriller_psychology':'BN',
     'det_su':'BO',
     'thriller_techno':'BP',
     'thriller':'BQ',
     'det_espionage':'BR',
     'thriller_legal':'BS',
     'children':'BT',
     'child_education':'BU',
     'child_prose':'BV',
     'child_sf':'BW',
     'child_det':'BX',
     'child_adv':'BY',
     'child_verse':'BZ',
     'child_horror':'CA',
     'child_folklore':'CB',
     'child_fantasy':'CC',
     'foreign_children':'CD',
     'child_classical':'CE',
     'ya':'CF',
     'child_rus':'CG',
     'child_tale_rus':'CH', 'child_tale':'CI', 'child_su':'CJ', 'interview':'CK', 'nonf_biography':'CL', 'nonfiction':'CM', 'naturalist_notes':'CN', 'travel_notes':'CO', 'sci_popular':'CP', 'nonf_publicism':'CQ', 'home_survival':'CR', 'home_child':'CS', 'home':'CT', 'home_pets':'CU', 'home_housekeeping':'CV', 'home_health':'CW', 'home_beauty':'CX', 'home_cooking':'CY', 'home_entertain':'CZ', 'family':'DA', 'home_sex':'DB', 'drama_antique':'DC', 'vaudeville':'DD', 'drama':'DE', 'dramaturgy':'DF', 'foreign_dramaturgy':'DG', 'screenplays':'DH', 'comedy':'DI', 'mystery':'DJ', 'dramaturgy_rus':'DK', 'dramaturgy_su':'DL', 'scenarios':'DM', 'tragedy':'DN', 'sci_history':'DO', 'sci_history_4':'DP', 'sci_history_18':'DQ', 'sci_history_0':'DR', 'sci_history_15':'DS', 'sci_history_20':'DT', 'sci_history_21':'DU', 'comp_soft_cad':'DV', 'comp_www_design':'DW', 'comp_hard':'DX', 'comp_db':'DY', 'comp_design':'DZ', 'comp_www':'EA', 'comp_history':'EB', 'comp_security':'EC', 'computers':'ED', 'comp_soft_office':'EE', 'comp_soft':'EF', 'comp_osnet':'EG', 'tbg_computers':'EH', 'comp_hacking':'EI', 'comp_dsp':'EJ', 'comp_os_android':'EK', 'comp_os_freedos':'EL', 'comp_os_linux':'EM', 'comp_os_macos':'EN', 'comp_os_msdos':'EO', 'comp_os_os2':'EP', 'comp_os_unix':'EQ', 'comp_os_windows':'ER', 'comp_os_theory':'ES', 'comp_os':'ET', 'comp_soft_dev_alg':'EU', 'comp_db_exp':'EV', 'comp_dv_ai':'EW', 'comp_soft_dev_craking':'EX', 'comp_soft_dev_man':'EY', 'comp_soft_dev_oop':'EZ', 'comp_soft_dev_debug':'FA', 'comp_soft_dev_parallel':'FB', 'comp_soft_dev_graphic':'FC', 'comp_soft_dev_games':'FD', 'comp_soft_dev':'FE', 'comp_soft_dev_system':'FF', 'comp_prog_dotnet':'FG', 'comp_prog_ada':'FH', 'comp_prog_assembler':'FI', 'comp_prog_basic':'FJ', 'comp_prog_c':'FK', 'comp_prog_forth':'FL', 'comp_prog_fortran':'FM', 'comp_prog_java':'FN', 'comp_prog_lisp':'FO', 'comp_prog_lua':'FP', 'comp_prog_mfc':'FQ', 'comp_prog_oberon':'FR', 'comp_prog_pascal':'FS', 'comp_prog_php':'FT', 'comp_prog_prolog':'FU', 'comp_prog_python':'FV', 'comp_prog_qt':'FW', 'comp_prog_ror':'FX', 'comp_prog_winapi':'FY', 'comp_programming':'FZ', 'architecture_book':'GA', 'painting':'GB', 'visual_arts':'GC', 'design':'GD', 'art_criticism':'GE', 'art_history':'GF', 'cine':'GG', 'nonf_criticism':'GH', 'sci_culture':'GI', 'art_world_culture':'GJ', 'fashion_style':'GK', 'music':'GL', 'notes':'GM', 'radio_tv':'GN', 'art_dance':'GO', 'theatre':'GP', 'old_foreign_publication':'GQ', 'old_rus_publication':'GR', 'foreign_su_publication':'GS', 'rarity':'GT', 'network_literature':'GU', 'su_publication':'GV', 'foreign_publication':'GW', 'ex_su_publication':'GX', 'rus_publication':'GY', 'literature_4':'GZ', 'foreign_antique':'HA', 'literature_16':'HB', 'literature_18':'HC', 'literature_19':'HD', 'literature_20':'HE', 'literature_21':'HF', 'foreign_love':'HG', 'love_history':'HH', 'love_short':'HI', 'love_sf':'HJ', 'love_fantasy':'HK', 'love_detective':'HL', 'love':'HM', 'love_hard':'HN', 'love_rus':'HO', 'sexual_perversion':'HP', 'love_slash':'HQ', 'love_su':'HR', 'love_contemporary':'HS', 'love_femslash':'HT', 'love_erotica':'HU', 'sci_medicine_alternative':'HV', 'sci_theories':'HW', 'sci_anachem':'HX', 'sci_archeology':'HY', 'sci_cosmos':'HZ', 'sci_veterinary':'IA', 'sci_oriental':'IB', 'sci_geography':'IC', 'sci_geo':'ID', 'sci_state':'IE', 'foreign_language':'IF', 'science_history':'IG', 'local_lore_study':'IH', 'sci_philology':'II', 'sci_math':'IJ', 'sci_medicine':'IK', 'science':'IL', 'sci_orgchem':'IM', 'sci_pedagogy':'IN', 'sci_politics':'IO', 'sci_religion':'IP', 'sci_social_studies':'IQ', 'sci_phys':'IR', 'sci_physchem':'IS', 'sci_philosophy':'IT', 'sci_chem':'IU', 'sci_economy':'IV', 'sci_juris':'IW', 'sci_linguistic':'IX', 'sci_biology':'IY', 'sci_biophys':'IZ', 'sci_biochem':'JA', 'sci_botany':'JB', 'sci_zoo':'JC', 'sci_paleontology':'JD', 'sci_evolutionism':'JE', 'sci_ecology':'JF', 'fable':'JG', 'in_verse':'JH', 'vers_libre':'JI', 'poetry_for_classical':'JJ', 'poetry_classical':'JK', 'poetry_rus_classical':'JL', 'lyrics':'JM', 'palindromes':'JN', 'song_poetry':'JO', 'poetry':'JP', 'poetry_east':'JQ', 'poem':'JR', 'poetry_su':'JS', 'poetry_for_modern':'JT', 'poetry_modern':'JU', 'poetry_rus_modern':'JV', 'poetry_military':'JW', 'experimental_poetry':'JX', 'epic_poetry':'JY', 'adv_story':'JZ', 'adv_western':'KA', 'adv_military':'KB', 'adv_history':'KC', 'adv_maritime':'KD', 'adventure':'KE', 'adv_modern':'KF', 'adv_indian':'KG', 'adv_animal':'KH', 'adv_geo':'KI', 'tale_chivalry':'KJ', 'adv_su':'KK', 'aphorisms':'KL', 'in_prose':'KM', 'prose_military':'KN', 'foreign_prose':'KO', 'foreign_contemporary':'KP', 'prose_history':'KQ', 'prose_classic':'KR', 'prose_counter':'KS', 'prose_magic':'KT', 'story':'KU', 'great_story':'KV', 'prose':'KW', 'short_story':'KX', 'roman':'KY', 'prose_rus_classic':'KZ', 'russian_contemporary':'LA', 'sagas':'LB', 'prose_sentimental':'LC', 'prose_su_classics':'LD', 'prose_contemporary':'LE', 'prose_abs':'LF', 'extravaganza':'LG', 'prose_neformatny':'LH', 'epistolary_fiction':'LI', 'prose_epic':'LJ', 'essay':'LK', 'autor_collection':'LL', 'dissident':'LM', 'bestseller':'LN', 'in_retelling':'LO', 'in_reduction':'LP', 'periodic':'LQ', 'diafilm':'LR', 'adult':'LS', 'prose_game':'LT', 'comics':'LU', 'compilation':'LV', 'cats':'LW', 'literary_fairy_tale':'LX', 'fan_translation':'LY', 'postcards':'LZ', 'beginning_authors':'MA', 'unfinished':'MB', 'other':'MC', 'novelization':'MD', 'fragment':'ME', 'collection':'MF', 'fanfiction':'MG', 'sci_hypnosis':'MH', 'psy_childs':'MI', 'sci_psychiatry':'MJ', 'sci_psychology':'MK', 'psy_theraphy':'ML', 'psy_sex_and_family':'MM', 'astrology':'MN', 'atheism':'MO', 'religion_budda':'MP', 'religion_hinduism':'MQ', 'religion_islam':'MR', 'religion_judaism':'MS', 'religion_catholicism':'MT', 'religion_orthodoxy':'MU', 'religion_protestantism':'MV', 'religion_rel':'MW', 'religion':'MX', 'religion_self':'MY', 'palmistry':'MZ', 'religion_christianity':'NA', 'religion_esoterics':'NB', 'religion_paganism':'NC', 'geo_guides':'ND', 'ref_guide':'NE', 'ref_self_tutor':'NF', 'ref_dict':'NG', 'reference':'NH', 'ref_ref':'NI', 'ref_encyc':'NJ', 'antique_ant':'NK', 'antique_east':'NL', 'antique_european':'NM', 'antique_russian':'NN', 'antique':'NO', 'auto_business':'NP', 'sci_instrumentation':'NQ', 'sci_aerodynamics':'NR', 'sci_tech_hydraulics':'NS', 'equ_history':'NT', 'sci_engineering':'NU', 'sci_tech_materials':'NV', 'sci_tech_machinery':'NW', 'sci_metal':'NX', 'sci_tech_metrology':'NY', 'sci_tech_mech':'NZ', 'sci_tech_drawing':'OA', 'sci_tech_oil':'OB', 'sci_tech_standards':'OC', 'sci_radio':'OD', 'sci_tech_rockets':'OE', 'sci_tech_sopromat':'OF', 'sci_build':'OG', 'sci_tech_theormech':'OH', 'sci_thermodynamics':'OI', 'sci_tech':'OJ', 'sci_transport':'OK', 'sci_tech_chem':'OL', 'sci_electronics':'OM', 'sci_energy':'ON', 'sci_thesis':'OO', 'sci_abstract':'OP', 'sci_textbook_su':'OQ', 'tbg_higher':'OR', 'tbg_secondary':'OS', 'sci_textbook':'OT', 'tbg_school':'OU', 'sci_crib':'OV', 'sf_history':'OW', 'sf_action':'OX', 'sf_heroic':'OY', 'sf_detective':'OZ', 'foreign_sf':'PA', 'sf_irony':'PB', 'sf_cyberpunk':'PC', 'sf_space':'PD', 'sf_space_opera':'PE', 'sf_litrpg':'PF', 'sf_mystic':'PG', 'sf':'PH', 'nsf':'PI', 'sf_paleontological':'PJ', 'popadanec':'PK', 'sf_postapocalyptic':'PL', 'sf_rus':'PM', 'sf_su':'PN', 'sf_social':'PO', 'sf_stimpank':'PP', 'sf_horror':'PQ', 'sf_etc':'PR', 'hronoopera':'PS', 'sf_epic':'PT', 'sf_humor':'PU', 'epic':'PV', 'riddles':'PW', 'antique_myths':'PX', 'folk_songs':'PY', 'folk_traditions':'PZ', 'folk_tale':'QA', 'proverbs':'QB', 'folklore_rus':'QC', 'folklore':'QD', 'limerick':'QE', 'fantasy_fight':'QF', 'sf_fantasy_city':'QG', 'gothic_novel':'QH', 'sf_fantasy_irony':'QI', 'historical_fantasy':'QJ', 'magician_book':'QK', 'vampire_book':'QL', 'dragon_fantasy':'QM', 'adventure_fantasy':'QN', 'fairy_fantasy':'QO', 'russian_fantasy':'QP', 'modern_tale':'QQ', 'sf_technofantasy':'QR', 'sf_fantasy':'QS', 'humor_fantasy':'QT', 'auto_regulations':'QU', 'home_aquarium':'QV', 'home_mountain':'QW', 'home_winemaking':'QX', 'home_livestock':'QY', 'home_furniture':'QZ', 'home_inventory':'RA', 'home_building':'RB', 'home_bookmaking':'RC', 'home_collecting':'RD', 'home_marine':'RE', 'home_hunt':'RF', 'home_writing_art':'RG', 'home_beekeeping':'RH', 'home_woodwork':'RI', 'home_metalwork':'RJ', 'home_handiwork':'RK', 'home_fishing':'RL', 'home_garden':'RM', 'home_mushrooms':'RN', 'home_diy':'RO', 'home_sport':'RP', 'home_tourism':'RQ', 'home_crafts':'RR', 'home_floriculture':'RS', 'humor_anecdote':'RT', 'humor_tales':'RU', 'humor_satire':'RV', 'humor':'RW', 'humor_prose':'RX', 'humor_verse':'RY' }
     #------------------
     ali2={ 'AA':'Боевые искусства', 'AB':'Военная документалистика и аналитика', 'AC':'Военная история', 'AD':'Военная техника и вооружение', 'AE':'Военное дело', 'AF':'Военные мемуары', 'AG':'Спецслужбы', 'AH':'Банковское дело', 'AI':'Бухучет и аудит', 'AJ':'Внешняя торговля', 'AK':'Деловая литература: прочее', 'AL':'Делопроизводство', 'AM':'Корпоративная культура', 'AN':'Личные финансы', 'AO':'Малый бизнес', 'AP':'Маркетинг, PR, реклама', 'AQ':'Недвижимость', 'AR':'О бизнесе популярно', 'AS':'Отраслевые издания', 'AT':'Поиск работы, карьера', 'AU':'Практическая экономика', 'AV':'Справочная деловая литература', 'AW':'Торговля', 'AX':'Управление, подбор персонала', 'AY':'Ценные бумаги, инвестиции', 'AZ':'Археологический триллер', 'BA':'Боевик', 'BB':'Дамский детективный роман', 'BC':'Детектив', 'BD':'Зарубежный детектив', 'BE':'Иронический детектив', 'BF':'Исторический детектив', 'BG':'Классический детектив', 'BH':'Криминальный детектив', 'BI':'Крутой детектив', 'BJ':'Маньяки', 'BK':'Медицинский триллер', 'BL':'Политический детектив', 'BM':'Полицейский детектив', 'BN':'Психологический триллер', 'BO':'Советский детектив', 'BP':'Техно триллер', 'BQ':'Триллер', 'BR':'Шпионский детектив', 'BS':'Юридический триллер', 'BT':'Детская литература: прочее', 'BU':'Детская образовательная литература', 'BV':'Детская проза', 'BW':'Детская фантастика', 'BX':'Детские остросюжетные', 'BY':'Детские приключения', 'BZ':'Детские стихи', 'CA':'Детские ужастики', 'CB':'Детский фольклор', 'CC':'Детское фэнтези', 'CD':'Зарубежная литература для детей', 'CE':'Классическая детская литература', 'CF':'Подростковая литература', 'CG':'Русская детская литература', 'CH':'Русские сказки для детей', 'CI':'Сказки для детей', 'CJ':'Советская детская литература', 'CK':'Беседы и интервью', 'CL':'Биографии и Мемуары', 'CM':'Документальная литература', 'CN':'Заметки натуралиста', 'CO':'Записки путешественника', 'CP':'Научпоп', 'CQ':'Публицистика', 'CR':'Выживание и личная безопасность', 'CS':'Дети. Книги для родителей', 'CT':'Дом и семья: прочее', 'CU':'Домашние животные', 'CV':'Домоводство', 'CW':'Здоровье', 'CX':'Красота', 'CY':'Кулинария', 'CZ':'Развлечения', 'DA':'Семейные отношения', 'DB':'Эротика, Секс', 'DC':'Античная драма', 'DD':'Водевиль, буффонада', 'DE':'Драма', 'DF':'Драматургия', 'DG':'Зарубежная современная драматургия', 'DH':'Киносценарии', 'DI':'Комедия', 'DJ':'Мистерия', 'DK':'Русская современная драматургия', 'DL':'Советская драматургия', 'DM':'Сценарии', 'DN':'Трагедия', 'DO':'История', 'DP':'История древнего мира', 'DQ':'История нового времени', 'DR':'История первобытного общества', 'DS':'История средних веков', 'DT':'Новейшая история', 'DU':'Современная история', 'DV':'CAD, CAM и CAE системы', 'DW':'Web дизайн', 'DX':'Аппаратное обеспечение, компьютерное железо', 'DY':'Базы данных', 'DZ':'Графика. Дизайн. Мультимедиа', 'EA':'Интернет', 'EB':'История информатики и вычислительной техники', 'EC':'Компьютерная безопасность', 'ED':'Околокомпьютерная литература', 'EE':'Офисные приложения', 'EF':'Программы', 'EG':'Сети', 'EH':'Учебники и самоучители по компьютеру', 'EI':'Хакерство', 'EJ':'Цифровая обработка сигналов', 'EK':'Android', 'EL':'FreeDOS', 'EM':'Linux', 'EN':'MacOS', 'EO':'MS DOS', 'EP':'OS/2', 'EQ':'Unix', 'ER':'Windows', 'ES':'ОС: теоретические вопросы', 'ET':'Прочие ОС', 'EU':'Алгоритмы и структуры данных', 'EV':'Базы знаний и экспертные системы', 'EW':'Искусственный интеллект', 'EX':'Крэкинг и реверсинжиниринг', 'EY':'Менеджмент ПО', 'EZ':'Объектно-ориентированное программирование', 'FA':'Отладка и тестирование ПО', 'FB':'Параллельное и распределенное программирование', 'FC':'Программирование графики', 'FD':'Программирование игр', 'FE':'Программирование: прочее', 'FF':'Системное программирование', 'FG':'.NET Framework', 'FH':'Ada', 'FI':'Assembler', 'FJ':'Basic, Visual Basic, VB Script, VBA и т.п.', 'FK':'C, C++, C#', 'FL':'Forth', 'FM':'Fortran', 'FN':'Java, Java Script', 'FO':'Lisp, Scheme', 'FP':'Lua', 'FQ':'MFC', 'FR':'Modula-2, Modula-3, Oberon, Oberon-2', 'FS':'Pascal, Delphi, Lazarus и т.п.', 'FT':'PHP', 'FU':'Prolog', 'FV':'Python', 'FW':'Qt', 'FX':'Ruby', 'FY':'Windows API', 'FZ':'Другие языки и системы программирования', 'GA':'Архитектура и скульптура', 'GB':'Живопись, альбомы, иллюстрированные каталоги', 'GC':'Изобразительное искусство, фотография', 'GD':'Искусство и Дизайн', 'GE':'Искусствоведение', 'GF':'История искусства', 'GG':'Кино', 'GH':'Критика', 'GI':'Культурология', 'GJ':'Мировая художественная культура', 'GK':'Мода и стиль', 'GL':'Музыка', 'GM':'Партитуры', 'GN':'Радио и телевидение', 'GO':'Танцы и хореография', 'GP':'Театр', 'GQ':'Дореволюционные зарубежные издания', 'GR':'Дореволюционные российские издания', 'GS':'Зарубежные издания советского периода', 'GT':'Раритетные издания', 'GU':'Самиздат, сетевая литература', 'GV':'Советские издания', 'GW':'Современные зарубежные издания', 'GX':'Современные издания стран бывшего СССР', 'GY':'Современные российские издания', 'GZ':'Литература IV века и ранее (эпоха Древнего мира)', 'HA':'Литература V-XIII веков (эпоха Средневековья)', 'HB':'Литература XIV-XVI веков (эпоха Возрождения)', 'HC':'Литература XVII-XVIII веков (эпоха Просвящения)', 'HD':'Литература ХIX века (эпоха Промышленной революции)', 'HE':'Литература ХX века (эпоха Социальных революций)', 'HF':'Литература ХXI века (эпоха Глобализации экономики)', 'HG':'Зарубежная литература о любви', 'HH':'Исторические любовные романы', 'HI':'Короткие любовные романы', 'HJ':'Любовная фантастика', 'HK':'Любовное фэнтези', 'HL':'Любовные детективы', 'HM':'О любви', 'HN':'Порно', 'HO':'Русская литература о любви', 'HP':'Сексуальные извращения', 'HQ':'Слэш', 'HR':'Советская литература о любви', 'HS':'Современные любовные романы', 'HT':'Фемслеш', 'HU':'Эротика', 'HV':'Альтернативная медицина', 'HW':'Альтернативные науки и научные теории', 'HX':'Аналитическая химия', 'HY':'Археология', 'HZ':'Астрономия и Космос', 'IA':'Ветеринария', 'IB':'Востоковедение', 'IC':'География', 'ID':'Геология и геофизика', 'IE':'Государство и право', 'IF':'Иностранные языки', 'IG':'История науки', 'IH':'Краеведение', 'II':'Литературоведение', 'IJ':'Математика', 'IK':'Медицина', 'IL':'Научная литература', 'IM':'Органическая химия', 'IN':'Педагогика', 'IO':'Политика и дипломатия', 'IP':'Религиоведение', 'IQ':'Социология', 'IR':'Физика', 'IS':'Физическая химия', 'IT':'Философия', 'IU':'Химия', 'IV':'Экономика', 'IW':'Юриспруденция', 'IX':'Языкознание', 'IY':'Биология', 'IZ':'Биофизика', 'JA':'Биохимия', 'JB':'Ботаника', 'JC':'Зоология', 'JD':'Палеонтология', 'JE':'Эволюционизм', 'JF':'Экология', 'JG':'Басни', 'JH':'в стихах', 'JI':'Верлибры', 'JJ':'Классическая зарубежная поэзия', 'JK':'Классическая поэзия', 'JL':'Классическая русская поэзия', 'JM':'Лирика', 'JN':'Палиндромы', 'JO':'Песенная поэзия', 'JP':'Поэзия', 'JQ':'Поэзия Востока', 'JR':'Поэма', 'JS':'Советская поэзия', 'JT':'Современная зарубежная поэзия', 'JU':'Современная поэзия', 'JV':'Современная русская поэзия', 'JW':'Стихи о войне', 'JX':'Экспериментальная поэзия', 'JY':'Эпическая поэзия', 'JZ':'Авантюрный роман', 'KA':'Вестерн', 'KB':'Военные приключения', 'KC':'Исторические приключения', 'KD':'Морские приключения', 'KE':'Приключения', 'KF':'Приключения в современном мире', 'KG':'Приключения про индейцев', 'KH':'Природа и животные', 'KI':'Путешествия и география', 'KJ':'Рыцарский роман', 'KK':'Советская приключенческая литература', 'KL':'Афоризмы и цитаты', 'KM':'В прозе', 'KN':'Военная проза', 'KO':'Зарубежная классическая проза', 'KP':'Зарубежная современная проза', 'KQ':'Историческая проза', 'KR':'Классическая проза', 'KS':'Контркультура', 'KT':'Магический реализм', 'KU':'Новелла', 'KV':'Повесть', 'KW':'Проза', 'KX':'Рассказ', 'KY':'Роман', 'KZ':'Русская классическая проза', 'LA':'Русская современная проза', 'LB':'Семейный роман/Семейная сага', 'LC':'Сентиментальная проза', 'LD':'Советская классическая проза', 'LE':'Современная проза', 'LF':'Фантасмагория, абсурдистская проза', 'LG':'Феерия', 'LH':'Экспериментальная, неформатная проза', 'LI':'Эпистолярная проза', 'LJ':'Эпопея', 'LK':'Эссе, очерк, этюд, набросок', 'LL':'Авторские сборники, собрания сочинений', 'LM':'Антисоветская литература', 'LN':'Бестселлеры', 'LO':'В пересказе, в лит. обработке', 'LP':'В сокращении', 'LQ':'Газеты и журналы', 'LR':'Диафильм', 'LS':'Для взрослых', 'LT':'Книга-игра', 'LU':'Комикс', 'LV':'Компиляции', 'LW':'Кошки', 'LX':'Литературные сказки', 'LY':'Любительские переводы', 'LZ':'Наборы открыток', 'MA':'Начинающие авторы', 'MB':'Недописанное', 'MC':'Неотсортированное', 'MD':'Новеллизации', 'ME':'Отрывок, ознакомительный фрагмент', 'MF':'Сборники, альманахи, антологии', 'MG':'Фанфик', 'MH':'Гипноз, внушение и самовнушение', 'MI':'Детская психология', 'MJ':'Психиатрия и наркология', 'MK':'Психология', 'ML':'Психотерапия и консультирование', 'MM':'Секс и семейная психология', 'MN':'Астрология', 'MO':'Атеизм', 'MP':'Буддизм', 'MQ':'Индуизм', 'MR':'Ислам', 'MS':'Иудаизм', 'MT':'Католицизм', 'MU':'Православие', 'MV':'Протестантизм', 'MW':'Религия', 'MX':'Религия и духовность: прочее', 'MY':'Самосовершенствование', 'MZ':'Хиромантия', 'NA':'Христианство', 'NB':'Эзотерика', 'NC':'Язычество', 'ND':'Путеводители', 'NE':'Руководства', 'NF':'Самоучители', 'NG':'Словари', 'NH':'Справочная литература: прочее', 'NI':'Справочники', 'NJ':'Энциклопедии', 'NK':'Античная литература', 'NL':'Древневосточная литература', 'NM':'Древнеевропейская литература', 'NN':'Древнерусская литература', 'NO':'Старинная литература', 'NP':'Автодело', 'NQ':'Автоматизация, приборостроение', 'NR':'Аэро-, газо- и гидродинамика', 'NS':'Гидравлика, пневматика', 'NT':'История техники', 'NU':'Конструирование', 'NV':'Материаловедение, конструкционные и прочие материалы', 'NW':'Машиностроение', 'NX':'Металлургия', 'NY':'Метрология, стандартизация и сертификация', 'NZ':'Механика', 'OA':'Начертательная геометрия, инженерная графика, черчение', 'OB':'Нефтегазовая и угольная промышленности', 'OC':'Нормативная техническая документация', 'OD':'Радиоэлектроника, радиотехника, связь', 'OE':'Ракетостроение и космическая техника', 'OF':'Строительная механика и сопромат', 'OG':'Строительство', 'OH':'Теория механизмов и машин', 'OI':'Термодинамика, теплопередача, теплотехника', 'OJ':'Технические науки', 'OK':'Транспорт и авиация', 'OL':'Химическая и нефтехимическая промышленности', 'OM':'Электроника, микроэлектроника, схемотехника', 'ON':'Энергетика, электротехника', 'OO':'Диссертации, дипломные, курсовые и прочие работы', 'OP':'Рефераты', 'OQ':'Советские учебники и пособия', 'OR':'Учебники и пособия ВУЗов', 'OS':'Учебники и пособия для среднего и специального образования', 'OT':'Учебники и пособия: прочее', 'OU':'Школьные учебники и пособия', 'OV':'Шпаргалки', 'OW':'Альтернативная история', 'OX':'Боевая фантастика', 'OY':'Героическая фантастика', 'OZ':'Детективная фантастика', 'PA':'Зарубежная фантастика', 'PB':'Ироническая фантастика', 'PC':'Киберпанк', 'PD':'Космическая фантастика', 'PE':'Космоопера', 'PF':'ЛитРПГ', 'PG':'Мистика', 'PH':'Научная Фантастика', 'PI':'Ненаучная фантастика', 'PJ':'Палеонтологическая фантастика', 'PK':'Попаданцы', 'PL':'Постапокалипсис', 'PM':'Российская фантастика', 'PN':'Советская фантастика', 'PO':'Социальная фантастика', 'PP':'Стимпанк', 'PQ':'Ужасы', 'PR':'Фантастика: прочее', 'PS':'Хроноопера', 'PT':'Эпическая фантастика', 'PU':'Юмористическая фантастика', 'PV':'Былины', 'PW':'Загадки', 'PX':'Мифы. Легенды. Эпос', 'PY':'Народные песни', 'PZ':'Народные приметы, обряды, традиции', 'QA':'Народные сказки', 'QB':'Пословицы, поговорки', 'QC':'Русский фольклор', 'QD':'Фольклор: прочее', 'QE':'Частушки, прибаутки, потешки', 'QF':'Боевое фэнтези', 'QG':'Городское фэнтези', 'QH':'Готический роман', 'QI':'Ироническое фэнтези', 'QJ':'Историческое фэнтези', 'QK':'Магическое фэнтези', 'QL':'О вампирах', 'QM':'О драконах', 'QN':'Приключенческое фэнтези', 'QO':'Сказочная фантастика', 'QP':'Славянское фэнтези', 'QQ':'Современная сказка', 'QR':'Технофэнтези', 'QS':'Фэнтези: прочее', 'QT':'Юмористическое фэнтези', 'QU':'Авто- и мототранспорт, ПДД', 'QV':'Аквариумистика', 'QW':'Альпинизм и скалолазание', 'QX':'Виноделие, спиртные напитки', 'QY':'Животноводство и птицеводство', 'QZ':'Изготовление и ремонт мебели', 'RA':'Инвентарь, инструменты', 'RB':'Индивидуальное строительство и ремонт', 'RC':'Книгоделие', 'RD':'Коллекционирование', 'RE':'Морское дело, парусный спорт', 'RF':'Охота и охотоведение', 'RG':'Писательское искусство', 'RH':'Пчеловодство', 'RI':'Работа по дереву', 'RJ':'Работа по металлу', 'RK':'Рукоделие', 'RL':'Рыболовство и рыбоводство', 'RM':'Сад и огород', 'RN':'Сбор и выращивание грибов', 'RO':'Сделай сам', 'RP':'Спорт', 'RQ':'Туризм', 'RR':'Хобби и ремесла: прочее', 'RS':'Цветоводство и комнатное садоводство', 'RT':'Анекдоты',
     'RU':'Байки',
     'RV':'Сатира',
     'RW':'Юмор: прочее',
     'RX':'Юмористическая проза',
     'RY':'Юмористические стихи',
     'ZZ':'Жанр отсутствует в словаре'
     }
     '''
     print("\n")
     print("Проверка для жанра - 'naturalist_notes'")
     print("Условный код:")
     print(ali1['naturalist_notes'])
     print("Русское значение:")
     print(ali2[ali1['naturalist_notes']])
     print("Проверка на ошибку:")
     try:
     . . print(ali2[ali1['nat5st_notes']])
     except:
     . . print('no Ganre')
     '''
      

10. Сканирование библиотеки

     С новыми силами за работу, а работы предстоит много.
     = Но вроде все уже обсудили.
     - Нет. Еще много нюансов.
     - Для начала предлагаю скрипт (в приложении) разыскивающий полные дубликаты, т. е. файлы совпадающие по величине и по содержимому.
     - Работает он в несколько этапов, но довольно просто:
     * поиск в папках файлов с заданным расширением.
     * формирование из найденных файлов списка А; строк из размера файла и пути к оному.
     * сортировка списка А (в результате равные по размеру файлы окажутся рядом)
     * поиск в списке А файлов одинаковых по размеру.
     * из найденных файлов формируется список Б; (сигнатура файла и путь к нему-же)
     * в списке Б находим файлы с одинаковой сигнатурой и записываем в выходной список В.
     * сортировка списка В (да еще одна) и вывод результата.
     ВСЕ.
     = А, что такое «сигнатура»
     - Хе-хе. В свое время для того чтобы убедится в уникальности файла мы подсчитывали контрольную сумму.
     Одна из первых моих работ на ЭВМ, была такая: мне в руки попала колода из 15 перфокарт на которых содержался шуточный тест «Отношение к противоположному полу», где программа задавала 16 вопросов и на их основании выдавался диагноз: «синий чулок», «мачо», «заучка», НО программа не работала т. к. не хватало одной карты и выдавалось сообщение «не совпадение контрольной суммы». Потрудится пришлось, распечатал программу проанализировал алгоритм (программа была в машинных кодах) дальше… абсолютно не помню как … но я решил эту задачу, набил правильную карту, сумма сошлась, и я погордился. Развлекал тестом девчонок — операторов.
     Но, контрольная сумма — примитив. Представь в одном файле есть строка « 1 2 3 », а в другом « 3 2 1 » контрольная сумма (т. е. тупое сложение байтов) покажет, что файлы одинаковы. Вообще это интереснейшая тема «избыточная информация», «передача сообщений в искажающей среде», «код с возможностью исправления ошибок в сообщении».
     Для получения сигнатуры файла можно использовать разные методы, но я предлагаю воспользоваться алгоритмом md5.
     = Погоди! Получается, что этот скрипт может найти дубликаты любого типа?
     - Да, и довольно быстро.
     - Но к сожалению он не решает всех проблем с нашей библиотекой.
     = Возвращаемя к нашим баранам?
     - Минуточку. Я закинул в приложение еще один маленьки, но довольно полезный скрипт: "Удаление пустых папок". Пользуйся.
     - А теперь за лопату и кирку.
      
     Имена файлов fb2.zip
     Пользуясь тем, что длина имени файла сейчас ограничена 236 байт, сайтостроители пихают в имя массу информации. Ясно, что это делается для обеспечения уникальности имени файла на данном сайте. Но мне такой хоккей не нужен, будет раздуваться спамом наш «небаза» файл.
     Посему!!!
     Переименовываю файлы!
     В качестве уникального имени, применяем простейшее решение ГОД, МЕСЯЦ, ДЕНЬ, ЧАС, МИНУТА.
     = Но за минуту можно отработать несколько файлов.
     - Поэтому добавим еще счетчик просмотренных файлов.
      
     |
     |import datetime
     |#
     |count = 0
     |y = datetime.datetime.today().strftime("%y")
     |yo = chr(int(y)+44)
     |m = datetime.datetime.today().strftime("%m")
     |mo = chr(int(m)+64)
     |h = datetime.datetime.today().strftime("%H")
     |ho = chr(int(h)+65)
     |a = datetime.datetime.today().strftime("-%d%M"+yo+mo+ho)
     |print(a+str(count))
     |
     В результате получится:
     -1715AFK0
     = Хм.
     - Мне тоже не нравится этот дебильно неуклюжий код. Но нет времени на приведение в элегантность. Работает, и ладно. (домашнее задание: сделай код лучше. Заменим.)
     = Я не о том. Какая то мешанина букв и цифр.
     - Мы не ставили цель шифровать, но и понятность не нужна, главное относительная уникальность при минимальном размере.
     = Проведи разбор полетов.
     - :
     1. знак минус — показывает, что файл прошел обработку и при следующем сканировании будет пропускаться.
     2. год — принимаем нынешний 2021 г за начало отсчета и ближайшие 26 лет отображаем буквой. (я сомневаюсь, что этот скрипт будет использоваться через четверть века)
     3. месяц, час — 12 месяцев как и 24 часа — легко отобразить буквой.
     - Далее. Возможны два подхода. Сначала о неправильном: вызывать генерацию имени при обработке каждого файла. Неправильность в том, что сканирование библиотеки и так будет оооочень медленным и тормозить его не нужно.
     Поэтому выбираем второй подход. При запуске скрипта делаем шаблон имени и в дальнейшем к шаблону прибавляем только показания счетчика. (но обязательно проверяем существование файла {под новым именем} и при наличии такого или выходим из скрипта или счетчик+=1)
      
     NeBaza.txt
     - Пусть выходной файл будет временно называться так.
     = Понятно, временно.
     - Давай рассуждать. При первом сканировании результат сохраняется в NeBaza.txt — все просто и понятно.
     - При последующих сканированиях вначале читаем файл NeBaza.txt находим новые книги и пополненный список сохраняем в файл.
     = Я читал, что можно файл открыть одновременно на чтение и запись.
     - Плохая идея. С момента начала работы скрипта до потребности сохранить результат может пройти некоторое время, а файл все время будет открыт для работы. Все бы ничего, но вдруг бешеный электрик отключит наш дом. Последствия непредсказуемы, возможно повреждение файловой системы, и возможно повреждение открытого (на тот момент) файла NeBaza.txt.
     = Что-то я сомневаюсь.
     - Согласен — это маловероятно. Но если соломки подстелить просто, то почему-бы не подстелить.
     - Поступаем так. При запуске скрипта проверяем существование файла NeBaza.txt, если есть то считываем в список и закрываем его.
     = Ясно. По окончании сканирования записываем его.
     - Если твоя паранойя разыграется, можно сделать еще более безопасно. Итоговый список записываем во временный файл, и если все прошло нормально удаляем старый и переименовываем временный файл в NeBaza.txt.
      
     Теперь начинаем работать с «небазой».
     На сегодняшний день я понимаю дело так, доступ к «небазе» нам нужен в таких случаях:
     1. сканирование существующей библиотеки.
     2. сканирование скачанных файлов для выявления двойников.
     3. выполнения запроса по автору или названию книги при гулянии по библиотечным сайтам.
     4. выполнение работ по обслуживанию моей библиотеки.
     = Значит это надо 4 или более скриптов?
     - Да! Вопрос!
     Первая и вторая задачи выполнятся будут на разных носителях (комп и читалка) да и назначение у них разное.
     Вторая и третья выполняются на компе и разумно их объединить в одном скрипте.
     До четвертой нужно еще дожить, подумаем о ней «завтра».
     ------------------------------------------------
     В "Приложение №2" выкладываю скрипт сканирующий библиотеку и результат сохраняющий в "небазе".
     ........................................
     = Простота - хуже воровства!
     = С упрощенными названиями файлов работать невозможно.
     - Да, пожалуй погорячился. Надо подумать.
     ........................................
     - Во втором приложении заменяю скрипт на результат размышлений.
     Переименовывать файл все таки придется, т. к. мне нужна метка на файле о том, что он прошел сканирование. Но если уже переименовываем, то надо идти дальше; автор? (скрипач не нужен) оставляем только урезанное название книги.
     = Почему урезанное?
     - Некоторые авторы увлекаются километровыми названиями. 20 символов — вполне достаточно. Забавно, что в «небазе» не требуется писать имя файла, оно элементарно вычисляется по заданному алгоритму.
     - Просканировал свою библиотеку. В наличии оказалось почти 5000 файлов из них 35 не прочиталось вследствие ошибки в fb2 (т. е. в скрипте требования строже чем в читалке)
     - Ох — хо-хо. Опять надо напрягаться, разбираться с ошибками.
     = Ну кому ныне легко. Апчхи.
     - Не приближайся. Иди на карантин.
     Поиск двойников
     - Вот на карантине и займись.
     Все карты у тебя на руках.
     Напиши скрипт для поиска двойников.
     - Такое — вот домашнее само-изаляционное задание.
      

Приложение

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     samefiles.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |# поиск файлов одинаковых по размеру и содержимому
     |#
     |import sys, os
     |import hashlib #
     |
     |ex = 'zip'#'fb2'#'txt'#'mp3'#'mp4'#'gif'#'jpg'#'py'
     |GFiles = []
     |co = 0
     |LOut = []
     |path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |def SaveInList(L3): # запись в выходной список
     | . .if len(L3) > 0:
     | . . . .global co
     | . . . .global LOut
     | . . . .s = ''
     | . . . .for i in L3:
     | . . . . . .s = s + i[len(path):] +'\n\n'
     | . . . . . .co += 1
     | . . . .LOut.append(s+'-----\n')
     |
     |def ScanMd5(L2): # работа со списком упорядоч. по сигнатуре
     | . .if len(L2) > 0:
     | . . . .L3 = []
     | . . . .L2.sort()
     | . . . .flag = False
     | . . . .oldMd5 = ''
     | . . . .OldFile = ''
     | . . . .for i in L2:
     | . . . . . .m = i.split('%')
     | . . . . . .ZMd5= m[0]
     | . . . . . .if (not flag) and (ZMd5 == oldMd5):
     | . . . . . . . .flag = True
     | . . . . . . . .L3.append(OldFile)
     | . . . . . .if flag:
     | . . . . . . . .if ZMd5 == oldMd5:
     | . . . . . . . . . .L3.append(m[1])
     | . . . . . . . .else:
     | . . . . . . . . . .flag = False
     | . . . . . . . . . .SaveInList(L3)
     | . . . . . . . . . .L3.clear()
     | . . . . . . . . . .
     | . . . . . .oldMd5 = ZMd5
     | . . . . . .OldFile = m[1]
     | . . . .SaveInList(L3) #
      
     |def ScanSize(GFiles): # работа со списком файлов упор. по размеру
     | . .if len(GFiles) > 0:
     | . . . .L = []
     | . . . .flag = False
     | . . . .oldSize = ''
     | . . . .OldFile = ''
     | . . . .for i in GFiles:
     | . . . . . .m = i.split('%')
     | . . . . . .Zaize = m[0]
     | . . . . . .if (not flag) and (Zaize == oldSize):
     | . . . . . . . .flag = True
     | . . . . . . . .L.append(getmd5(OldFile)+'%'+ OldFile)
     | . . . . . .if flag:
     | . . . . . . . .if Zaize == oldSize:
     | . . . . . . . . . .L.append(getmd5(m[1])+'%'+ m[1])
     | . . . . . . . .else:
     | . . . . . . . . . .flag = False
     | . . . . . . . . . .ScanMd5(L)
     | . . . . . . . . . .L.clear()
     | . . . . . . . . . . . .
     | . . . . . .oldSize = Zaize
     | . . . . . .OldFile = m[1]
     | . . . .ScanMd5(L) #
     |#--------------------------------------------------
     |def getmd5(file_name): # получение сигнатуры файла
     |## try
     | with open(file_name, "rb" ) as file_to_check:
     | data = file_to_check.read()
     | return hashlib.md5(data).hexdigest()
     |
     |def parse_file(fn): # работа с файлом
     | . .global GFiles
     | . .s=str(os.path.getsize(fn))+'%'#
     | . .GFiles.append(s+fn)
     | . . . .
     |def parse_dir(sSrc): # сканирование папки
     | . .global f_list
     | . .for file in os.listdir(sSrc):
     | . . . .# full pathname
     | . . . .file=os.path.join(sSrc,file)
     | . . . .if os.path.isdir(file):
     | . . . . . .parse_dir(file)
     | . . . .else:
     | . . . . . .m = file.split('.')[-1] # извлечение расширения
     | . . . . . .m = m.lower()
     | . . . . . .if (m == ex):
     | . . . . . . . .parse_file(file)
     | . . . . . . . .
     |#--------------------------------------------------
     |
     |parse_dir(path)
     |GFiles.sort()
     |ScanSize(GFiles) #
     |
     |if co > 0:
     | f = open('merg_'+ex+'.txt', 'w')
     | LOut.sort() #
     | for i in LOut:
     | . . f.write(i+'\n')
     | f.close()
     |
     |print('Done '+ex+' -> '+str(co))
     -------------------------------------------------------------------
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     del_empty_dir.py
     |#!/bin/env python
     |# -*- coding: utf-8 -*-
     |# Удаление пустых папок
     |import sys, os
     |
     |def del_empty_dirs(path):
     | . .global co
     | . .for d in os.listdir(path):
     | . . . .a = os.path.join(path, d)
     | . . . .if os.path.isdir(a):
     | . . . . . .del_empty_dirs(a)
     | . . . . . .if not os.listdir(a):
     | . . . . . . . .co += 1
     | . . . . . . . .os.rmdir(a)
     |# ------------------------------
     |Mpath = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |co = 0
     |del_empty_dirs(Mpath)
     |print('Удалено '+str(co)+' пустых папок.')
      
      

Приложение 2

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     scan_lib.py
     Скрипт для записи библиотеки в "небазу"
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |import zipfile
     |import xml.dom.minidom
     |### import ganres # modul
     | ### согласно последним воззрениям удаляем чтение жанров
     |
     |# 16 Jul 21
     |# Запись списка книг в "небазу" (вторая версия)
     |#--------------------------------------------------
     |Books = []
     |siz = 0 # размер файла
     |countFiles = 0
     |book_title = ''
     |stroka = ''
     |
     |replace_val = [('\\', '_'),('/', '_'),(':', '_'),('*', '_'),('?', '_'),('"', '_'),('<', '_'), ('>', '_'), ('|', '_')]
     |#--------------------------------------------------
     |def SaveList2():
     | . .if countFiles > 0:
     | . . . .f = open('NeBaza.txt', 'w')
     | . . . .for i in Books:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |# . . . .print('write')
     |#--------------------------------------------------
     |#**********************************************************************
     |
     |def parse_fb2(fn):
     | . .global Books
     | . .global stroka
     | . .global book_title
     |
     | . .if isinstance(fn, str):
     |
     | . . . .fn = open(fn)
     | . .try:
     | . . . .dom = xml.dom.minidom.parse(fn)
     |
     | . .except:
     | . . . .print('Error in fb2:')
     | . . . .print(fn)
     | . . . .return False
     | . .else:
     | . . . .dom.normalize()
     | . . . .node1=dom.getElementsByTagName("description")[0]
     | . . . .titl=node1.getElementsByTagName("title-info")[0]
     | ''' ###
     | . . . .ganre = '' #
     | . . . .for ga in titl.getElementsByTagName("genre"):
     | . . . . . .try:
     | . . . . . . . ganre += ganres.ali1[ga.childNodes[0].nodeValue]
     | . . . . . .except:
     | . . . . . . . ganre += 'ZZ'
     | '''
     | ###. . . .ganre = 'K'+ganre+'|'
     | . . . .ganre = 'K'+'|'
     | . . . .book=titl.getElementsByTagName("book-title")[0]
     | . . . .book_title = book.childNodes[0].nodeValue
     | . . . .s = book_title +'|'
     |
     | . . . .au = ''
     | . . . .try:
     | . . . . . .for auto in titl.getElementsByTagName("author"):
     | . . . . . . . .no1=auto.getElementsByTagName("last-name")[0]
     | . . . . . . . .au = au + no1.childNodes[0].nodeValue
     | . . . . . . . .no1=auto.getElementsByTagName("first-name")[0]
     | . . . . . . . .fi = no1.childNodes[0].nodeValue
     | . . . . . . . .au = au + '.'+ fi[0]+'^' . . . . . .
     | . . . . . .au = au[:-1]
     | . . . .except:
     | . . . . . .au = "NoName."
     | . . . .s = s+au
     | . . . .stroka = s +'|'+ganre
     | . . . .return True
     |
     |#**********************************************************************
     |#--------------------------------------------
     |def parse_zip(adr):
     | . .z = zipfile.ZipFile(adr, 'r')
     | . .filelist = z.namelist()
     | . .filelist.sort()
     | . .for n in filelist:
     | . . . . try:
     | . . . . . .if n[-4:] == ".fb2":
     | . . . . . . . .return parse_fb2(z.open(n))
     | . . . . except:
     | . . . . . .print( "Errror:", n )
     | . . . . . .return False
     |#--------------------------------------------------
     |def replace(line, old_new_num): # функция для удаления
     из названия файла неприемлимых символов \ / : * ? " < > |
     | . .for vals in old_new_num:
     | . . . . . .# распаковываем кортеж
     | . . . . . .old, new = vals
     | . . . . . .line = line.replace(old, new)
     | . .return line
     |
     |def my_rename(adr,ph, ex):
     | . .co = 0
     | . .book_ti = book_title[:20]
     | . .book_ti = replace(book_ti, replace_val)
     |
     | . .a = '-' + book_ti + ex
     | . .while os.path.isfile(os.path.join(ph, a)):
     | . . . .co += 1
     | . . . .a = '-' + book_ti +str(co)+ ex
     |# . .print(a)
     | . .os.rename(adr,os.path.join(ph, a))
     | . .if ex == '.fb2':
     | . . . .ex = 'f'
     | . .else:
     | . . . .ex = 'z'
     | . .if co > 0:
     | . . . .ex = str(co)+ex
     |
     | . .Books.append(stroka +ex+ '|'+str(siz)+'\n')
     |
     |def parse_file(adr,ph):
     | . .global siz # размер файла
     | . .global countFiles
     | . .flag = os.path.basename(adr)[0] != '-'
     | . .siz = os.path.getsize(adr)
     | . .m = adr.split(".")
     | . .if (m[-1] == "zip") and (m[-2] == "fb2"):
     | . . . .if flag and parse_zip(adr):
     | . . . . . . countFiles += 1
     | . . . . . . my_rename(adr,ph,'.fb2.zip')
     | . .elif (m[-1] == "fb2"):
     | . . . .if flag and parse_fb2(adr):
     | . . . . . . countFiles += 1
     | . . . . . . my_rename(adr,ph,'.fb2')
     |
     |def parse_dir(ph):
     | . .dirlist = os.listdir(ph)
     | . .dirlist.sort()
     | . .for i in dirlist:
     | . . . .adr = os.path.join(ph, i)
     | . . . .if os.path.isdir(adr):
     | . . . . . .parse_dir(adr)
     | . . . .else:
     |#? . . . . . .if os.path.getsize(i) > 0:
     | . . . . . . . .parse_file(adr, ph)
     |#? . . . .else:
     |#? . . . . . .print("bad zip "+ i)
     |#----------------------------------------
     |
     |path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |
     |#----------------------------------------
     |#----------------------------------------
     |
     |if os.path.isfile('NeBaza.txt'):
     | . .f = open('NeBaza.txt','r')
     | . .for i in f:
     | . . . . . .Books.append(i)
     | . .f.close()
     |parse_dir(path)
     |
     |Books.sort()
     |SaveList2()
     |
     |print( 'Done!')
     |print( 'Add '+ str(countFiles)+' files.')
     |
      

11. Использование

     я почему раньше злой был
     потому что у меня «небазы» не было,
     а теперь я сразу добреть начну
     и новые скрипты заведу.
      
     - Начинаем использование «небазы».
     Во первых строках напишем долгожданный скрипт для проверки наличия книги в библиотеке.
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     ask.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |# Ответы на запросы о наличии в биб. автора или книги
     |# 17 Jul
     |#--------------------------------------------------
     |Books = []
     |#--------------------------------------------
     |def show_book(S):
     | . .z = S.split('|')
     | . .print(z[0]) # Название книги
     | . .a = z[1]
     | . .print(a.replace('^', ', ')) # Авторы
     | . .print('size ' + z[4].strip()) # длина файла
     | . .print('-----------------')
     |#--------------------------------------------------
     |
     |if os.path.isfile('NeBaza.txt'):
     | . .f = open('NeBaza.txt','r')
     | . .for i in f:
     | . . . . . .Books.append(i)
     | . .f.close()
     |else:
     | . .print('I need "NeBaza.txt".')
     | . .exit()
     |print('Поиск строки в каталоге библиотеки или 0 для выхода.')
     |while True:
     | . .print('')
     | . .Qu = input('Введите ')
     | . .countFiles = 0
     | . .print(':::::::::::::::::')
     | . .Qu.strip()
     | . .if Qu == '0': # выход из цикла
     | . . . .break
     | . .for i in Books: # просмотр списка
     | . . . .if i.find(Qu) != -1: # если строка найдена
     | . . . . . .show_book(i) # вывод сведений о книге
     | . . . . . .countFiles += 1
     | . .print('******************')
     | . .if countFiles == 0:
     | . . . .print(Qu + ' - не найдено.')
     | . .else:
     | . . . .print('Найдено: ' + str(countFiles))
     |print( 'Done!')
     - Как видишь, вначале загружаем в список «небазу», затем запускаем бесконечный цикл «вопрос — ответ».
     Для прерывания цикла можно ввести «0» или комбинацию «Ctrl» + «C»
     = А почему на поместишь скрипт в приложение?
     - Сырой он. Еще сто раз будет меняться и дополнятся.
     *****
     - В качестве отвлечения, предлагаю тебе небольшую функцию.
     - В процессе поиска двойников часто возникает такая ситуация: название книги и автор совпадают, но размеры файлов разные. Это может быть в двух случаях: или это разные версии книги; или это один и тот-же текст, но с разными обложками. И решение, что оставить, должен принять библиотекарь (возможно захочется оставить обе /но при этом не хочется принимать такое решение при каждом следующем поиске двойников!?!/)
     - Помочь должен рекламируемый скрипт (точнее заготовка для скрипта).
      
     |#----------------------------------------------
     |
     |def co_fb2(fn):
     | . body = False
     | . FTag = False
     | . STag = ''
     | . Count = 0
     | . SecCount = 0
     |
     | . f = open(fn, 'rb')
     | . d = f.read()
     | . for n in d:
     | . .if chr(n) == '<':
     | . . . .FTag = True
     | . . . .STag = ''
     | . .else:
     | . . . .if FTag:
     | . . . . . .if chr(n) == '>':
     | . . . . . . . .FTag = False
     | . . . . . . . .if STag == 'body':
     | . . . . . . . . . .body = True
     | . . . . . . . .elif STag == 'section':
     | . . . . . . . . . .SecCount += 1
     | . . . . . . . .elif STag == '/body': # Печать итогов
     |
     | . . . . . . . . . .print(fn)
     | . . . . . . . . . .print('section ' + str(SecCount))
     | . . . . . . . . . .print('Count ' + str(Count))
     | . . . . . . . . . .return
     | . . . . . . . .STag = ''
     | . . . . . .else:
     | . . . . . . . .STag =STag + chr(n)
     | . . . .else:
     | . . . . . .if body:
     | . . . . . . . .Count += 1
     |#----------------------------------------------
     |
     |co_fb2('файл1.fb2')
     |co_fb2('файл2.fb2')
      
     - Все достаточно просто.
     * открываем файл как бинарный
     * находим таги.
     * считаем число тегов «section»
     * считаем размер текста в теге «body»
     с рисунками не возимся.
      
     Теги «секция» используются в fb2 для создания структуры глав частей и т. п. Если их число в разный файлах совпадают то ВОЗМОЖНО текст одинаков.
     Если размер текста в «теле» отличается незначительно, то ВЕРОЯТНОСТЬ одинаковости возрастает.
     - Как уже сказано, это только заготовка для скрипта.
     Рецепт:
     * Продумай как лучше вводить адреса проверяемых файлов.
     * Для начала проверь существование файлов.
     * Обеспечь, при необходимости, возможность работы с архивированным файлом.
     * Вывод можно оставить в терминале (решай).
     * Наслаждайся.
     - Ну, а у тебя как дела с двойниками?
     = Давай я отвечу по твоему рецепту:
     * у нас есть «небаза» которая уже сортирована
     * если просмотреть список, каждую строку разбивая по «|» и рассматривая [0] + [1] (т. е. название и автор) можно выявить одинаковые книги
     * используя функцию ShowBook организовать вывод.
     = Вроде все.
     * * *
     - Мне надо подумать, что делать дальше.
     - Пока я размышляю напиши скрипт выбора книг по жанру.
     *
     * * *
     * * * *
     = С сожалением должен признать, я не справился с заданием, ну один, ну два, но четыреста жанров…
     - В свою очередь, должен извиниться, с преподавателями изредка такое бывает, даешь задание, вроде не сложное… теорему Ферма триста лет решали.
     - Давай я расскажу как я видел решение:
     Для начала скрипт сообщает пользователю, что происходит и что от него требуется.
       
     Процедура выбора жанра.
     Выберите нужную категорию из списка:
     1 Деловая литература
     2 Детективы и триллеры
     3 Документальная литература
     4 Дом семья
     5 Искусство
     6 Компьютеры, интернет
     7 Литература для детей
     8 Прочее…
     Введите номер интересующей категории ___
       
     Вот такой список появляется на терминале, и если необходимой категории нет пользователь вводит 8 и нажимает Enter.
       
     Процедура выбора жанра.
     Выберите нужную категорию из списка:
     0 Возврат к предыдущему пункту
     1 Любовные романы
     2 Наука
     3 Образование
     4 Поэзия
     5 Приключения
     6 Проза
     7 Прочее…
     Введите номер интересующей категории ___
       
     При выборе категории Наука отрывается список категорий
     Процедура выбора жанра.
     Выберите нужную категорию из списка:
     0 Возврат к предыдущему пункту
     1 А, Б
     2 В, Г, З
     3 И, Л
     4 М, Н
     5 О, Ф
     6 Х, Э
     7 Ю, Я
     Введите номер интересующей категории ___
       
     При выборе Математика нажимаем 4.
       
     Процедура выбора жанра.
     Выберите нужный жанр из списка:
     0 Возврат к предыдущему пункту
     1 Математика (sci_math)
     2 Медицина (sci_medicine)
     3 Научная литература (science)
       
     Введите номер интересующего жанра ___
       
     = То есть! Организуем своеобразное меню.
     - Точно. В каждом разделе 7 — 8 пунктов для выбора.
     - Опять-же на словах все просто, но есть «возврат к предыдущему пункту», а это уже посложнее…
     - И ныне я сам себе думаю, а оно нам надо?
     * * *
     - Есть еще один подход.
     - Если просканировать «небазу» на наличие разных жанров, то выяснится, что в скачанных книгах используются десяток — два имярек (ясно, скачиваю по своему вкусу, а он не всеобъемлющий ).
     - Еще одно соображение. Файлы fb2 изготавливают люди с разной квалификацией. Изучение «небазы» показало наличие жанров ZZ, а это жанры не содержащиеся в заданном списке, т. е. авторы файлов или пользуются другими списками или просто произвольно выдумывают жанры.
     * * *
     - Посему. Предлагаю временно удалить из «небазы» сведения о жанрах.
     = Ясно. Временно.
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |# 21 Jul
     |# Удаление из "небазы" жанров
     |#--------------------------------------------------
     |L = []
     |
     |if os.path.isfile('NeBaza.old'):
     | . .f = open('NeBaza.old','r')
     | . .for i in f:
     | . . L.append(i)
     | . .f.close()
     | . .
     |
     |f = open('NeBaza.txt', 'w')
     |for i in L:
     | . .s = i.split('|')
     | . .f.write(s[0]+'|'+s[1]+'|'+s[2][0]+'|'+s[3]+'|'+s[4])
     |f.close()
     | . .
     |print( 'Done!')
      
     - Хорошо. Но у нас еще масса проблем, к которым я даже не знаю как подойти.
     - Что нам делать с трупами?
     = !?!
      

12. Использование 2

     Мы строили, строили
     и наконец построили.
     - Выкладываю скрипт, нет, набор скриптов для поиска двойников.
     * * *
     - Для работы скрипты должны находится в корне твоей библиотеки.
     - Сначала запускаем первый скрипт, который просматривает все папки и сохраняет в файле «tree.txt» сведения о путях ко всем содержащимся в библиотеке книгам.
     - Следующим шагом, запускаем второй скрипт.
     - Пользователю выдается краткая инструкция (ах да, забыл, в рабочей зоне должен находится и файл небазы - если этого файла или tree.txt не обнаружится, то поезд дальше не пойдет).
     = И чего надо делать?
     - Делать надо то, что комп не умеет - принимать решения.
     В терминале будут выдаваться сведения о книгах с одинаковыми названиями и одинаковыми авторами.
     - Сведения о книге будут содержать:
     * Путь к книге (т.е. при необходимости ты сможешь просмотреть саму книгу)
     * Количество секций составляющих структуру книги (как правило, больше секций - лучше НО?…)
     * Количество текста в секциях (ну тут довольно ясно)
     * Размер физического файла.
      
     - Сравнивая полученные сведения пользователь должен принять решение, какую книгу оставить, какую удалить или разрешить существование дубля.
     Пользователь должен ввести команду:
     * одну цифру от 1 до 9 (номер книги в списке со сведениями) и «d» - для удаления или «x» - для разрешения дубля.
     Или для принудительного завершения программы:
     * q - выход без сохранения изменений
     * s - выход с сохранением изменений в небазе и сохранения в файле 'delfiles.txt' адресов удаляемых файлов.
     В любом случае команду завершаем «Enter».
     = Ты меня совсем за хххх держишь?
     - «ХХХХ» бывают разные... Нажатие энтера без ввода команды, вызовет переход с следующей стопке книг.
     Но помни! Не стоит нажимать кнопки «Ctrl» + «C» т.к. это вызовет нежданное прерывание программы.
     = То есть то же самое, что команда «q», а в чем тогда разница, да и зачем такая команда нужна?
     - Пригодится - хотя-бы для начального тестирования скрипта, а разница очень большая - одно дело осознанное решение, другое - потерять время и выполненную работу.
     = А для чего выход с сохранением?
     - В моем случае нашлось 90 событий дубликатов, и за один сеанс обрабатывать их скучно. Поэтому поработаю - сохраняю, поработаю — сохраняю.
     - Должен в очередной раз поплакаться, что скрипт только из печки. По хорошему программу следует протестировать пару лет и тогда возможно она достойна... но столько ждать ты не будешь.
     = Будем жевать то, что есть.
     - Надеюсь ты сам разжуешь как написать скрипт для удаления физических файлов по данным в файле 'delfiles.txt'.
     * * *
     = Задание выполнено. Результат в приложении.
      

Приложение. набор скриптов для поиска двойников.

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     tree.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |# 27 Jul
     |# изготовление файла содержащего все адреса файлов в текущей папке
     |#--------------------------------------------------
     |L = []
     |ln = 0
     |def parse_dir(ph):
     | . .global L
     | . .dirlist = os.listdir(ph)
     | . .dirlist.sort()
     | . .for i in dirlist:
     | . . . .adr = os.path.join(ph, i)
     | . . . .if os.path.isdir(adr):
     | . . . . . .parse_dir(adr)
     | . . . .else:
     | . . . . . .adr = adr[ln:]
     | . . . . . .L.append(adr+'\n')
     |#----------------------------------------
     |def SaveList(L):
     | . . . .f = open('tree.txt', 'w')
     | . . . .for i in L:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |#----------------------------------------
     |path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |ln = len(path) + 1
     |#----------------------------------------
     |
     |parse_dir(path)
     |SaveList(L)
     |print( 'Done!')
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     dubl_books.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |import xml.dom.minidom
     |import zipfile
     |
     |# 31 Jul
     |# Ручная сортировка дубликатов в "небазе" (вторая версия)
     |#--------------------------------------------------
     |Books = []
     |tree = []
     |Ltree = []
     |LOut = []
     |co = 0
     |#--------------------------------------------------
     |def SaveList(fn, Li): # запись списка в файл
     | . . . .f = open(fn, 'w')
     | . . . .for i in Li:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |def SaveListA(fn, Li): # дозапись
     | . . . .f = open(fn, 'a')
     | . . . .for i in Li:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |#--------------------------------------------------
     |def SaveInList(L3): # Перезапись из промежуточного в окончательный список дубликатов
     | . .if len(L3) > 0:
     | . . . .global co
     | . . . .global LOut
     | . . . .LOut.append('--\n')
     | . . . .for i in L3:
     | . . . . . .LOut.append(i+'\n')
     | . . . .co += 1
     |#**********************************************************************
     |def my_sort(L2): # Поиск серии дубликатов книг
     | . .L3 = [] # Промежуточное хранилище
     | . .flag = False
     | . .n = -1
     | . .OldCap = ''
     | . .Oldn = 0
     | . .Cap = ''
     | . .for i in L2:
     | . . . .m = i.split('|')
     | . . . .n += 1
     | . . . .Cap = m[0]+' '+m[1] # Название книги и автор
     | . . . .if (not flag) and (Cap == OldCap):
     | . . . . . .flag = True
     | . . . . . .L3.append(str(Oldn))
     | . . . .if flag:
     | . . . . . .if Cap == OldCap:
     | . . . . . . . .L3.append(str(n))
     | . . . . . .else:
     | . . . . . . . .flag = False
     | . . . . . . . .SaveInList(L3)
     | . . . . . . . .L3.clear()
     | . . . .OldCap = Cap
     | . . . .Oldn = n
     | . .SaveInList(L3) #
     |#+++++++++++++++++++++++++++++++++++++++++++++
     |replace_val = [('\\', '_'),('/', '_'),(':', '_'),('*', '_'),('?', '_'),('"', '_'),('<', '_'), ('>', '_'), ('|', '_')]
     |def replace(line, old_new_num):
     | . .for vals in old_new_num:
     | . . . . . .# распаковываем кортеж
     | . . . . . .old, new = vals
     | . . . . . .line = line.replace(old, new)
     | . .return line
     |#---------------------------------------
     |def my_name(book_title):
     | . .book_ti = book_title[:20] # обрезание названия книги
     | . .book = replace(book_ti, replace_val) # замена в названии запрещенных символов
     | . .return book
     |#---------------------------------------
     |def ScanTree(s): # Поиск в дереве файлов по шаблону
     | . .global Ltree
     | . .Ltree.clear
     | . .for i in tree:
     | . . . .if i.find(s) > -1:
     | . . . . . .Ltree.append(i)
     |#---------------------------------------
     |def LoadFile(fn, Li): # Загрузка из файла в список
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .Li.append(i)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |#**************-------анализ стукруты fb2----------**************
     |count_s = 0
     |count_t = 0
     |count_b = 0
     |#-------------------------------------
     |def sect(el): # Подсчет числа секций и объем текста в них
     | . .global count_s
     | . .global count_t
     | . .try:
     | . . . .childList=el.childNodes
     | . . . .for child in childList:
     | . . . . . .if child.nodeName == 'p':
     | . . . . . . . .text = child.childNodes[0].nodeValue
     | . . . . . . . .if text != None:
     | . . . . . . . . . .count_t += len(text)
     | . . . . . .if child.nodeName == 'section':
     | . . . . . . . .count_s += 1
     | . . . . . . . .sect(child)
     | . .except:
     | . . . .return
     |#---------------------------------------
     |def parse_fb2(fn):
     | . .global count_b # вычисление характеристик fb2 файла
     | . .dom = xml.dom.minidom.parse(fn);
     | . .dom.normalize()
     | . .n_body=dom.getElementsByTagName("body")[0]
     | . .sect(n_body)
     | . .fb=dom.getElementsByTagName("FictionBook")[0]
     | . .childList=fb.childNodes
     | . .for child in childList:
     | . . . . if child.nodeName == 'binary':
     | . . . . . .text = child.childNodes[0].nodeValue
     | . . . . . .if text != None:
     | . . . . . . . .count_b += len(text)
     |#---------------------------------------
     |OldStr = ''
     |#---------------------------------------
     |def MyPrint(adr, n, mm): # распечатка характеристик файла
     | . .global OldStr
     | . .print(adr)
     | . .s = 'sect '+str(count_s)+' text '+str(count_t)+' pic '+str(count_b)
     | . .if n == 1:
     | . . OldStr = s # подготовка сравнения строк
     | . . print(s)
     | . .elif OldStr == s:
     | . . print('Параметры совпадают')
     |# . . print(s)
     | . .else:
     | . . print(s)
     | . .print('size file '+ str(os.path.getsize(adr))+' [ '+mm+' ]')
     |#---------------------------------------
     |def parse_zip(adr):
     | . .z = zipfile.ZipFile(adr, 'r')
     | . .filelist = z.namelist()
     | . .filelist.sort()
     | . .for n in filelist:
     | . . . .if n[-4:] == ".fb2":
     | . . . . . .parse_fb2(z.open(n))
     |
     |def parse_file(adr):
     | . .global count_s
     | . .global count_t
     | . .global count_b
     | . .count_s = 0
     | . .count_t = 0
     | . .count_b = 0
     | . .m = adr.split(".")
     | . .if (m[-1] == "zip") and (m[-2] == "fb2"):
     | . . . . parse_zip(adr)
     | . .elif (m[-1] == "fb2"):
     | . . . . parse_fb2(adr)
     |
     |#*******-------конец анализа стукруты fb2----------**************
     |def fileInTree(aut,sz, n, mm): #
     | . .k = -1
     | . .for R in Ltree:
     | . . . .k += 1
     | . . . .adr = R.strip() #os.path.join(path, R)
     | . . . .if os.path.isfile(adr):
     | . . . . . .if os.path.getsize(adr) == int(sz):
     | . . . . . . . .parse_file(adr)
     | . . . . . . . .MyPrint(adr, n, mm)
     | . . . . . . . .Ltree.pop(k) # удаление из списка обработанного файла
     | . . . . . . . .return adr
     | . .return '' # если ничего не найдено
     |
     |#********************************************************
     |path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |LoadFile('NeBaza.txt', Books)
     |LoadFile('tree.txt', tree)
     | . .
     |Books.sort()
     |my_sort(Books)
     |LOut.reverse()
     |print('********************************')
     |print('Обнаружено дубликатов '+ str(co))
     |print('')
     |print('Ручная сортировка дубликатов в "небазе".')
     |print('Введите номер выбранной строки и команду.')
     |print('Команды:')
     |print('"d" - удаление из "небазы"')
     |print('"x" - пометка "разрешенная копия"')
     |print('Для перехода к следующим дубликатам, нажмите "Enter"')
     |print('Для принудительного завершения введите команды:')
     |print('"s" - выход из скрипта с сохранением изменений в "небазе"')
     |print('или')
     |print('"q" - Выход из скрипта без сохранения изменений.')
     |print('')
     |L = [] # содержит ссылки на книги в списке Books
     |LA = [] # список адресов файлов книг
     |DF = [] # удаляемые файлы
     |D = [] # метки на удаление
     |X = [] # метки на разрешенные копии
     |k = 0
     |for i in LOut:
     | . .i = i.strip()
     |
     | . .if i == '--':
     | . . . .n = 0 # счетчик книг в группе дубликатов
     | . . . .k += 1 # счетчик итераций
     | . . . .print('Дубликат '+str(k)+ '['+str(co)+']')
     | . . . .for e in L:
     | . . . . . .mm = Books[e].split('|')
     | . . . . . .if n == 0: #
     | . . . . . . . .ScanTree(my_name(mm[0])) # поиск файлов
     |
     | . . . . . .n += 1
     | . . . . . .print(str(n)+':')
     | . . . . . .aa = fileInTree(mm[1], mm[4].strip(), n, mm[2])
     | . . . . . .LA.append(aa)
     | . . . .print('------------')
     | . . . . . .
     | . . . .Qu = input('Введите ') # запрос на команду
     | . . . .if Qu == 'q':
     | . . . . . .exit()
     | . . . .elif Qu == 's':
     | . . . . . .break
     | . . . .elif len(Qu) != 2: # переход на следующий список дубликатов
     | . . . . . .L.clear()
     | . . . . . .LA.clear()
     | . . . . . .n = 0
     | . . . .elif Qu[-1] == 'd':
     | . . . . . .try:
     | . . . . . . . .D.append(L[int(Qu[0])-1])
     | . . . . . . . .DF.append(LA[int(Qu[0])-1]+'\n')
     | . . . . . .except:
     | . . . . . . . .print('Ошибка при вводе.')
     | . . . . . .L.clear()
     | . . . . . .LA.clear()
     | . . . .else:
     | . . . . . .try:
     | . . . . . . . .X.append(L[int(Qu[0])-1])
     | . . . . . .except:
     | . . . . . . . .print('Ошибка при вводе.')
     | . . . . . .L.clear()
     | . . . . . .LA.clear()
     | . . . . . .
     | . .else:
     | . . . .L.append(int(i))
     |print('========================')
     |
     |for u in X: # запись меток разрешенных копий
     | . .mmm = Books[u].split('|')
     | . .Books[u] = mmm[0]+'|'+mmm[1]+'|'+mmm[2]+'|X|'+mmm[4]+'\n'
     |
     |D.sort()
     |D.reverse()
     |for u in D: # удаление книги из "небазы"
     | . .Books.pop(u)
     |### всавьте цикл удаления физических файлов по списку DF
     |if (len(X)>0) or (len(D)>0):
     | . .SaveList('NeBaza.txt', Books)
     | . .SaveListA('delfiles.txt', DF) # если не хотите удалять файлы сохраните информацию о них
     | . .
     |print( 'Done!')
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     DelByList.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |# 2 авг
     |# Удаление файлов по адресам содержащимся в файле 'delfiles.txt'
     |#--------------------------------------------------
     |L = []
     |co = 0
     |#--------------------------------------- . .
     |def LoadFile(fn, Li): # Загрузка из файла в список
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .Li.append(i)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |#--------------------------------------
     |def myDelFile(L):
     | . .global co
     | . .for i in L:
     | . . . .n = i.strip()
     | . . . .if os.path.isfile(n):
     | . . . . . .os.remove(n)
     | . . . . . .co += 1
     | . . . .else:
     | . . . . . .print(n)
     |#**************************************
     |LoadFile('delfiles.txt', L)
     |myDelFile(L)
     |print( 'Delite '+ str(co))
     |
      
      
       

13. Трупы в библиотеке

     Применив этот скрипт один раз, ты захочешь повторить его.
     Это даст обманчивое чувство могущества и власти.
     Не нужно много трудов, чтобы применить его, но сила дается немалая.
     Слабые духом быстро поддаются эйфории могущества.
     Однако это дает не только удовольствие, но и отравляет душу и разум, сводя с ума и делая мага маньяком. - народ побледнел.
     Теперь желания использовать это вряд ли у кого появится.
     …. он кровожадно усмехнулся и облизнул губы.
     Директор и Министерство выдали мне разрешение научить вас…..
     - В предыдущей главе мы уже слегка касались варианта «оставить насколько двойников». Но, что делать если файл физически удален (книга скучна, неинтересна, бездарна или вызывает резкое отвращение). Удалять труп из «небазы»? Нооо! Я уже описывал ситуацию, когда многократно скачивал книгу с привлекательным названием и с брезгливостью удалял до следующего… (память то старческая).
     - Для отработки всех таких ситуаций у нас предусмотрено поле «оценка» куда пока записывается символ «К» - что означает «не прочитано, без оценки»
      
     Надо продумать что и КАК мы будем делать с оценкой.
     * * *
     - Выложил скрипт для отыскания «мертвых» книг в «небазе», технически это довольно просто, чем дальше тем больше я использую метод «лего» т.е. сборки программы из готовых блоков
     = Там получается еще файл «нет в базе».
     - Да он получается как сухой остаток при сравнении списков «небазы» и «дерева» - списка файлов в библиотеке. По этому файлу ты можешь отследить почему те или иные файлы не попали в небазу, изготовители fb2 бывают такими забавниками…
     - Но более всего меня интересует поле оценки.
     Во-первых я решил изменить начальное значение поля с |K| на |0|.
     = Какое право ты имеешь изменять написанное!!! Я заметил, что ты регулярно изменяешь код в скриптах и текст глав!!!
     - Ну право-то я имею. Пойми! Ты читаешь живую книгу. Книгу в процессе написания. В том числе мне хотелось, хоть отдаленно, показать процесс работы программиста. При чтении учебников - возникает впечатление, что гуру мудр, гуру знает все, и это оправдано.
     Любой преподаватель скажет, что труднее всего обучать взрослых. Таким ученикам, с жизненным и учебным опытом, с первых шагов надо доказать свою состоятельность, право обучать. Посему учитель должен раздувать щеки, гордо заявлять «я считаю»... точно в такой-же ситуации находятся врачи, адвокаты.... да и специалисты любой уважаемой профессии.
     - Учись, студент.
     * * *
     - Так вот.
     |0| будет означать - /без оценки/ отсюда логично от |1| до |5| моя оценка книги, хотя возможны и |!|, |?|, |*| и т.д. Для отрицательных оценок кроме |1| |2| помни о |Y| и |Z| для удаленных и удаленных с отвращением.
     = Ты не учитываешь, что иногда удаляются и хорошие книги, просто не хочется перечитывать.
     - Да... продумывать систему оценивания предстоит еще долго.
     А пока выложу маленький скрипт для присвоения статуса |Y| всем книгам перечисленным в файле «мертвые книги».
      
      

Приложение

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     deadBooks.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |import xml.dom.minidom
     |import zipfile
     |# 31 Jul
     |# поиск книг содержащихся в небазе, но не существующих физически
     |#--------------------------------------------------
     |Books = []
     |tree = []
     |Ltree = []
     |LOut = []
     |co = 0
     |#--------------------------------------------------
     |def SaveList(fn, Li): # запись списка в файл
     | . .if len(Li) > 0:
     | . . . .f = open(fn, 'w')
     | . . . .for i in Li:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |#---------------------------------------
     |replace_val = [('\\', '_'),('/', '_'),(':', '_'),('*', '_'),('?', '_'),('"', '_'),('<', '_'), ('>', '_'), ('|', '_')]
     |def replace(line, old_new_num):
     | . .for vals in old_new_num:
     | . . . . . .# распаковываем кортеж
     | . . . . . .old, new = vals
     | . . . . . .line = line.replace(old, new)
     | . .return line
     |#---------------------------------------
     |def my_name(book_title):
     | . .book_ti = book_title[:20] # обрезание названия книги
     | . .book = replace(book_ti, replace_val) # удаление из названия запрещенных символов
     | . .return book
     |#--------------------------------------- . .
     |def LoadFile(fn, Li): # Загрузка из файла в список
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .Li.append(i)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |#+++++++++++++++++++++++++++++++++++++++
     |def FindInTree(s, i):
     | . .global tree
     | . .global Books
     | . .n = -1
     | . .for k in tree:
     | . . . .n += 1
     | . . . .if k.find(s) > -1:
     | . . . . . .tree.pop(n)
     | . . . . . .Books.pop(i)
     | . . . . . .break
     |#---------------------------------------
     |L = []
     |ln = 0
     |#---------------------------------------
     |def parse_dir(ph):
     | . .global tree
     | . .dirlist = os.listdir(ph)
     | . .dirlist.sort()
     | . .for i in dirlist:
     | . . . .adr = os.path.join(ph, i)
     | . . . .if os.path.isdir(adr):
     | . . . . . .parse_dir(adr)
     | . . . .else:
     | . . . . . .hh = ph[ln:]
     | . . . . . .tree.append(hh+'|'+ str(os.path.getsize(adr))+'|'+i+ '\n')
     |#********************************************************
     |path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     |ln = len(path) + 1
     |
     |LoadFile('NeBaza.txt', Books)
     |parse_dir(path)
     |for i in range(len(Books), 0, -1): # удаление из списка
     | . .if (Books[i-1].find('|Y|')>0) or (Books[i-1].find('|Z|')>0):
     |# . . . .print(Books[i-1])
     | . . . .Books.pop(i-1) # ранее удаленных книг
     |
     |for i in Books:
     | . .m = i.split('|')
     | . .L.append(m[4].strip()+'|-'+my_name(m[0])+'\n')
     |
     |for i in range(len(L), 0, -1):
     | . .FindInTree(L[i-1].strip(), i-1)
     | . .
     |for i in range(len(tree), 0, -1):
     | . .s = tree[i-1].strip()
     | . .if (s[-1] != 'p') and (s[-1] != '2'):
     |# . . . .print(tree[i-1])
     | . . . .tree.pop(i-1)
     | . .
     |SaveList('net_v_base.txt',tree)
     |SaveList('dead_B.txt',Books)
     |
     |print( 'Done!')
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     MarkDelBook.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |# 8 avg
     |# изменение оценки на метку "удаленная книга"
     |#--------------------------------------------------
     |NeBaza = []
     |dddel = []
     |#--------------------------------------------------
     |def SaveList(fn, Li): # запись списка в файл
     | . .if len(Li) > 0:
     | . . . .f = open(fn, 'w')
     | . . . .for i in Li:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |#--------------------------------------- . .
     |def LoadFile(fn, Li): # Загрузка из файла в список
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .Li.append(i)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |########################################
     |
     |LoadFile('NeBaza.txt', NeBaza)
     |LoadFile('mark.txt', dddel)
     |
     |for i in range(len(NeBaza), 0, -1): # удаление из списка
     | . .for h in dddel:
     | . . . .if (NeBaza[i-1] == h):
     | . . . . . .NeBaza[i-1] = NeBaza[i-1].replace('|0|','|Y|') #
     | . . . . . .break
     |
     |SaveList('NeBaza2.txt',NeBaza)
     |print( 'Done!')

14. Сортировка по авторам

     Для работы нам потребуются два файла: "небаза" и "tree2.txt"
     Для получения второго дерева делаем прививку существующему
     строки:
     | . . . . . .adr = adr[ln:]
     | . . . . . .L.append(adr+'\n')
     заменяем на:
     | . . . . . .hh = ph[ln:]
     | . . . . . .L.append(hh+'|'+ str(os.path.getsize(adr))+'|'+i+ '\n')
     Ну и выходной файл сохраняем как "tree2.txt"
     В результате мы получим файл со структурой:
     Путь к файлу+'|'+Размер файла+'|'+Имя файла
     = Создаем странную структуру совершенно сомнительного содержания.
     - Далее станет ясно.
     - Должен сказать, что скриптом «sortbyauthor.py» я не доволен. Хотелось волшебно-компьютерного перемещения книг в папки по авторам... но.
     Нет, сделать это можно, но... опустеют уже сделанные папки с сортировкой по сериалам, что не есть хорошо.
     Из средств автоматизации я вставил только функцию SameFolder - которая отслеживает ситуацию, когда все книги данного автора находятся в одной папке и тогда автор не публикуется в выходном файле.
     - Посему, опять ручная работа по приведению библиотеки в порядок.
     - Кроме того:
     * из рассмотрения исключены книги с автором «NoName.»
     * не попадут в разборку книги созданные «замысловатыми» изготовителями, коие не указывают имя автора, или еще как-то изгаляются над форматом fb2.
     * остается не решенной проблема соавторов. Встречался: в одной книге «Иванов» первый, и «Сидоров» второй, а в следующей ровно наоборот.
     - Вот такие дела.
     = Но ведь можно сделать папку например «new_Lib» и в ней создавать папки авторов куда стаскивать соответствующие книги, а затем проверить их на сериалы и упорядочить их по созданным папкам. А пустые папки удалить.
     - Вот, что значит - взгляд со стороны. Приступай к работе все инструменты у тебя есть.
     - Уточнение! Как сказано: «Спешить на надо!». Давай не будем перемещать файлы, а результат работы так-же выдадим в виде файла-рекомендации, потому как у библиотекаря могут быть свои критерии по упорядочиванию книг.
     = Я начинаю понимать в чем трудность. Выяснилось, что сканирование для получения информации о сериалах оооооочень дддддддолго, во-вторых оказалось, что иногда книга может относится к нескольким сериалам???
     = Далее, книги разных авторов могут относится к одному сериалу и обратно, сериалы с одним названием могут не являться одним сборником.
     = И еще. Довольно часто автор публикуя книги объявляет ее как начало сериала, а затем по разным причинам продолжения не следует. НО одна книга не сериал!
     = И что делать?
     - Я думаю, наше дело дать в руки библиотекаря скрипт, который будет давать информацию о сериалах в конкретной папке (или для конкретного автора), а дальнейшее решение должен принимать владелец библиотеки.
     - Работаем.

Приложение

     /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\
     sortbyauthor.py
     |#!/usr/bin/env python
     |# -*- coding: utf-8 -*-
     |import sys, os
     |
     |# 19 Avg
     |# сортировка книг по авторам
     |#---------------------------------------
     |Books = []
     |tree = []
     |LOut = [] # выходной список
     |#---------------------------------------
     |def LoadFile(fn, Li): # Загрузка из файла в список
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .Li.append(i)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |#---------------------------------------
     |def LoadFileM(fn, Li): # Загрузка из файла в список (модифицированно)
     | . .if os.path.isfile(fn):
     | . . . .f = open(fn,'r')
     | . . . .for i in f:
     | . . . . . .mmm = i.split('|')
     | . . . . . .# автор[1]; название[0]; размер файла[4]
     | . . . . . .Bo = mmm[1]+'|'+mmm[0]+'|'+mmm[4]
     | . . . . . .if mmm[1] != 'NoName.':
     | . . . . . . . .Li.append(Bo)
     | . . . .f.close()
     | . .else:
     | . . . .print('I need '+ fn)
     | . . . .exit()
     |
     |#--------------------------------------------------
     |def SaveList(fn, Li): # запись списка в файл
     | . . . .f = open(fn, 'w')
     | . . . .for i in Li:
     | . . . . . .f.write(i)
     | . . . .f.close()
     |#**********************************************************************
     |def my_sort(L2): # Поиск авторов
     | . .L3 = [] # Промежуточное хранилище
     | . .flag = False
     | . .n = -1
     | . .OldAu = ''
     | . .OldSt = ''
     | . .Cap = ''
     | . .for i in L2:
     | . . . .m = i.split('|')
     | . . . .n += 1
     | . . . .Cap = m[0] # автор
     | . . . .if (not flag) and (Cap == OldAu):
     | . . . . . .flag = True
     | . . . . . .L3.append(OldSt)
     | . . . .if flag:
     | . . . . . .if Cap == OldAu:
     | . . . . . . . .L3.append(i)
     | . . . . . .else:
     | . . . . . . . .flag = False
     | . . . . . . . .SaveInList(L3, OldAu)
     | . . . . . . . .L3.clear()
     | . . . .OldAu = Cap
     | . . . .OldSt = i
     | . .SaveInList(L3, OldAu) #
     |#+++++++++++++++++++++++++++++++++++++++++++++
     |replace_val = [('\\', '_'),('/', '_'),(':', '_'),('*', '_'),('?', '_'),('"', '_'),('<', '_'), ('>', '_'), ('|', '_')]
     |def replace(line, old_new_num):
     | . .for vals in old_new_num:
     | . . . . . .# распаковываем кортеж
     | . . . . . .old, new = vals
     | . . . . . .line = line.replace(old, new)
     | . .return line
     |#---------------------------------------
     |def my_name(book_title):
     | . .book_ti = book_title.strip() # исправление ввести в остальные скрипты
     | . .book_ti = book_ti[:20] # обрезание названия книги
     | . .book = replace(book_ti, replace_val) # удаление из названия запрещенных символов
     | . .return book
     |#---------------------------------------
     |def ScanTree(s): # Поиск в дереве файлов по шаблону
     | . .global tree
     | . .c = -1
     | . .for i in tree:
     | . . . .c += 1
     |# . . . .print(i)
     |# . . . .print(s)
     | . . . .if i.find(s) > -1:
     | . . . . . .tree.pop(c)
     | . . . . . .return i
     | . .return ' '
     |
     |#--------------------------------------------------
     |def SameFolder(L3): # проверка - нахождение книг в одной папке
     | . .mmm = L3[0].split('|')
     | . .s = mmm[0]
     | . .for i in L3:
     | . . . .mmm = i.split('|')
     | . . . .if mmm[0] != s:
     | . . . . . .return False
     | . .return True
     |# ++++++++++++++++++++++++++++++++
     |def SaveInList(L3, au): # Перезапись из промежуточного в окончательный список дубликатов
     | . .if len(L3) > 0:
     | . . . .c = -1
     | . . . .global LOut
     |
     | . . . .for k in L3: #
     | . . . . . .c += 1
     | . . . . . .mmm = k.split('|')
     | . . . . . .nnn = mmm[2].strip()+'|-'+my_name(mmm[1])
     | . . . . . .s = ScanTree(nnn)
     | . . . . . .if s != ' ':
     | . . . . . . . . . .hhh = s.split('|')
     | . . . . . . . . . .L3[c] = hhh[0]+'|'+hhh[2]
     | . . . .if SameFolder(L3):
     | . . . . . .return
     | . . . .L3.sort()
     | . . . .LOut.append('-- '+au+' --\n')
     | . . . .for i in L3:
     | . . . . . .LOut.append(i)
     |#************************** main *********************
     |LoadFileM('NeBaza.txt', Books)
     |LoadFile('tree2.txt', tree)
     |
     |Books.sort()
     |my_sort(Books)
     |
     |SaveList('authors.txt', LOut)
     |
     |print( 'Done!')

15. Windows

     = Как windows? Почему windows? Ты же клялся в любви Linux!!!
     - К великому сожалению должен признать, что пользователь всегда на стороне больших батальонов(программистов).
     Операционная система – это инструмент. Как у Windows так и у Linux есть свои достоинства. Я пользуюсь всеми доступными инструментами, и использую все их достоинства (уж как умею).
     Как выяснилось, я сделал большую ошибку не проверив мои скрипты в Windows. Понадеялся на утверждение, что Питон работает во всех операционках. Работать то он работает, но есть нюансы.
     Например:
     фрагмент :  path = os.path.abspath(sys.argv[0])[:-(len(sys.argv[0])+1)]
     следует заменить на: path = os.getcwd()
     = Короче, и почти понятно.
     - Функция возвращает путь к текущей папке.
     - Windows и Linux используют разные «слешы» (\ и /) при описании пути к файлу, поэтому в скрипте недопустимо прямое использование слеш, а для формирования пути к файлу надо пользоваться такой строкой:
     file=os.path.join(path, file)
     Такой код должен работать везде.
     - Надобно проверить все скрипты.
     = Проверю, исправлю и о выполнении доложу.

16. Объединение

     Ну, за единение!
     Объединение сериалов в один файл, задача старая и давно решенная, но решена она другими, а я предпочитаю хоть плохенькое, но мое.
     При объединении файлов fb2 возникают несколько проблем.
     1. Порядок следования файлов в объединенном. Решим задачу простейшим образом, работу сборки файлов будем производить по списку.
     2. ---- Нет, нет – погодите. Дальнейшие проблемы я сейчас решать не буду, давайте я их перечислю, а потом выложу свои аргументы.
     Итак:
     2. Кодировка. Файлы могут делать разные авторы, разными инструментами и возможно с разной кодировкой?... Задача решается довольно легко, просто надо учесть такую возможность.
     3. Сноски.
     4. Рисунки. Также как и сноски в разных файлах могут иметь одинаковые идентификаторы, т.е. имярек надо будет поменять не только на объектах, но и в тексте книги в соответствующих ссылках.
     --- Как уже сказано, задачи с 2 по 4, я сейчас решать не буду. Хотя наметки есть, и жизнь может заставить.
     Объединенные файлы я будут делать ТОЛЬКО для внутреннего потребления, а значит, буду делать так, как мне хочется. Разная кодировка – мало вероятна, без сносок  - переживу, а рисунки на 99% мне не нужны.
     Комплект для работы состоит из трех скриптов:
     fb2_list.py – распаковка zip и изготовление списка (list.txt) файлов fb2.
     join_fb2.py – объединение файлов fb2 в файл lib.fb2.
     fb2err.py – проверка структуры файла lib.fb2.
     Пояснение:
     Работу желательно проводить в отдельной папке, содержащей только необходимое.
     Желательно проверить полученный список файлов list.txt. Сообразите сами, что там может быть не так.
     Полученный файл lib.fb2 – обязательно открыть в текстовом редакторе. Содержимое разных файлов будет разделено строкой «*%%%%%%%*». Введите исправления в структуру книги!!!
     Файлы fb2 производят разные люди, разными программами, я не собираюсь отслеживать все выверты вариантов в скрипте, сделать это придется вручную (это не сложно).
     Повторюсь! Без простейших знаний структуры fb2 читать этот опус бессмысленно.
     Выполненную работу желательно проверить скриптом fb2err.py. При необходимости ввести нужные изменения.
     В итоге, переименовать файл lib.fb2 в нужное имя и запаковать в архив.
     Думаю, все вышесказанное не понравится большинству читателей, что делать, я предупреждал, делаю для себя.
     Описание:
     fb2_list.py – описывать особо нечего. Сначала распаковка затем создание списка.
     Но, повторюсь – проверяйте созданное.
     Для проверки, специально я сращивал большой сериал, и получилось:
     Автор_сериал_10_заголовок.fb2
     Автор_сериал_11_заголовок.fb2
     Автор_сериал_12_заголовок.fb2
     Автор_сериал_1_заголовок.fb2
      Автор_сериал_2_заголовок.fb2
     Автор_сериал_3_заголовок.fb2
      . . . и т.д.
     Автор_сериал_9_заголовок.fb2
     Исправить не трудно.
     join_fb2.py – В итоговый файл записывается заголовочная часть ТОЛЬКО первого (по списку) файла fb2, для остальных – заголовочная часть игнорируется. Также опускается (для всех файлов) все содержимое после тега </body>.
     Для поиска мест сращивания вводится строка *%%%%%%%*.
     В качестве тестирования и удовлетворения МОИХ желаний объединено 12 сериалов.
     При ОБЯЗАТЕЛЬНОЙ проверке мест единения, как правило, находится:
     </section>
     *%%%%%%%*
     <title>
     <p>Название книги… и пр.</p>
     </title>
     <section>
     Требуется привести к такому виду:
     </section>
     *%%%%%%%*
     <section>
     <title>
     <p>Название книги… и пр.</p>
     </title>
     Как вы понимаете возможны варианты. Тут уж без знаний не обойтись.
     fb2err.py – ну я не знаю чё сказать! Иду себе, никого не трогаю….. морда красная.. см. главу «Ремонт fb2»
     ------------------------------------------------------
     17.01.22
     Течёт река времени. Все меняется, меняемся мы и наши интересы.
     Захотелось… дабы в объединенном файле, в начале каждой книги было бы её название – сделал.
     Выкладываю… /нумерация с учетом дальнейших вставок/
     Захотелось… чтобы заработали ссылки – вот тут загвоздка.
      
     Давайте я напомню вам реалии формата fb2.
     В тексте книги помещается ссылка на последующую расшифровку, например:
      
     <p>После смерти Екатерины и отстранения от власти семейства Зубовых и князя Барятинского<a l:href="#n_104" type="note">[104]</a>, влияние этих трёх человек только усилилось.</p>
      
     По завершении основного текста книги (т.е. после тега </body>), при необходимости, размещается тело примечаний, пример:
      
     <body name="notes">
       <title>
        <p>Примечания</p>
       </title>
     . . .
     Здесь находятся секции ссылок, устроенных по такому принципу:
       <section id="n_104">
        <title>
         <p>104</p>
        </title>
        <p>Князь Иван Сергеевич Барятинский (1740–1811) – российский дипломат екатерининской эпохи, посланник в Париже, генерал-поручик (1777).</p>
     <p>Брат князя Ф. С. Барятинского.</p>
       </section>
     . . .
     Тело примечаний завершается тегом:
     </body>
     Изначальная мысль была такова: значащую часть ссылки (т.е. "#n_104") в тексте и в ссылках заменить на "#x_n_104" где x – номер книги со ссылкой. Казалось бы – а чего сложного?
     Но посмотрите:
     <a l:href="#n_104" type="note">[104]</a>
     <a l:href = "#n_104" type="note">[104]</a>
     <a l:href=
     ’#n_104’
     type=’note’>[104]</a>
     Все это допустимые варианты для формата fb2. И что тогда искать?
     Хорошо, допустим я буду искать # и заменять ее на #1_ (для первой книги) и примирюсь с возможным искажением теста, в котором возможно появление не ссылочных решеток.
     Но в теле ссылок решеток нет (кстати, а вы знаете что # означает?  :-), а варианты формата – такие же.
     Есть красивое решение «пусть нам помогут другие» парсировать строку инструментарием XML.
     Короче – буду пробовать, набирать статистику, по исполнении доложу.(точка)
     19.01.22
     Решил пока обойтись без XML. И сделал версию для первого варианта ссылок (без извращений).
     Сделал, заработало, а дальше 2-3 месяца тестирования.
     W cat.
      
      
      
      
      

Приложение к объединению

     /\/\/\/\/\/\/\/\/\/\/\/\/\
     fb2_list.py
     |#!/usr/bin/env python
     |# -*- codning: utf-8 -*-
     |import sys, os
     |import zipfile
     |
     |#
     |#
     |#-----------------------------------------------
     |List = []
     |Count = 0
     |def parse_zip(fn): #  zip
     | . .global path
     | . .global Count
     | . .z = zipfile.ZipFile(fn, 'r')
     | . .z.extractall(path)
     | . .Count += 1
     |
     |def parse_file(fn): # 
     | . .m = fn.split(".")[-1]
     | . .if (m == "zip"): # если zip
     | . . . .parse_zip(fn)
     | . . . .
     |def parse_dir(fn): #
     | . .dirlist = os.listdir(fn)
     | . .dirlist.sort()
     | . .for a in dirlist:
     | . . . .if os.path.getsize(a) > 0:
     | . . . . . . . .parse_file(a)
     |#-------------------------
     |
     |def SaveList():
     | . .f = open('list.txt', 'w', encoding="utf-8")
     | . .for i in List:
     | . . . .f.write(i)#   'cp1251' '\n'
     | . .f.close()
     |
     |def ScanDir(fn): #
     | . .dirlist = os.listdir(fn)
     | . .dirlist.sort()
     | . .for a in dirlist: . . . .
     | . . . .if not os.path.isdir(a):
     | . . . . . .a = a.lower()
     | . . . . . .m = a.split(".")[-1]
     | . . . . . .if (m == "fb2"): # . . . . . or (m == "zip")
     | . . . . . . . .a  = os.path.join(fn, a)
     | . . . . . . . .List.append(a+'\n')
     |#--------------------------------------------------
     |
     |path = os.getcwd()
     |
     |parse_dir(path) #
     |print(' '+str(Count))
     |
     |ScanDir(path)
     |SaveList()
     |print('ok??')
      
     /\/\/\/\/\/\/\/\/\/\/\/\/\
     join_fb2m1.py
     10|#!/usr/bin/env python
      20|# -*- coding: utf-8 -*-
      30|import sys, os
      40| # объединение fb2 файлов перечисленных в файле list.txt
      50|#--------------------------------------------------
      60|
      70|def SaveList():
      80| . .f = open('lib.fb2', 'w', encoding="utf-8")
      90| . .for i in Books:
     100| . . . .f.write(i)#   'cp1251' '\n'
     110| . .f.close()
     120|
     130|Books = []
     140|conutBooks = 1
     150|
     160|def addFb2(fn, first):
     170| . .global Books
     180| . .global conutBooks
     190| . .LList = []
     200| . .sBook_title = ''
     210| . .bBook_title = False
     220| . .if os.path.isfile(nfile):
     230| . . . .with open(fn, encoding="utf8") as f:
     240| . . . . . .LList = f.readlines()
     250| . . . .for i in LList:
     260| . . . . . .if not first: #
     270| . . . . . . . .if bBook_title:
     280| . . . . . . . . . .n = i.find('</book-title')
     290| . . . . . . . . . .if ( n == -1):
     300| . . . . . . . . . . . .sBook_title = sBook_title + ' ' + i.strip()
     310| . . . . . . . . . . . .continue
     320| . . . . . . . . . .else:
     330| . . . . . . . . . . . .sBook_title = sBook_title + ' ' + i[:n]
     340| . . . . . . . . . . . .bBook_title = False
     350| . . . . . . . . . . . .
     360| . . . . . . . .n = i.find('<book-title')
     370| . . . . . . . .if n > -1:
     380| . . . . . . . . . .sBook_title = i[n+12:]
     390| . . . . . . . . . .n = sBook_title.find('</book-title')
     400| . . . . . . . . . .if n > -1:
     410| . . . . . . . . . . . .sBook_title = sBook_title[:n]
     420| . . . . . . . . . . . .bBook_title = False
     430| . . . . . . . . . .else:
     440| . . . . . . . . . . . .sBook_title = sBook_title + ' ' + i.strip()
     450| . . . . . . . . . . . .bBook_title = True
     460| . . . . . . . . . . . .continue
     470| . . . . . . . .n = i.find('<body')
     480| . . . . . . . .if n != -1:
     490| . . . . . . . . . .i = i[n:]
     500| . . . . . . . . . .n = i.find('>')
     510| . . . . . . . . . .i = i[n+1:]
     520| . . . . . . . . . .conutBooks += 1
     530| . . . . . . . . . .Books.append('\n*%%%%%%%* '+str(conutBooks)+'\n')
     540| . . . . . . . . . .Books.append('<p>'+sBook_title.strip()+'</p>')
     550| . . . . . . . . . .Books.append(i)
     560| . . . . . . . . . .first = True
     570| . . . . . .else:
     580| . . . . . . . .n = i.find('</body')
     590| . . . . . . . .if n != -1:
     600| . . . . . . . . . .i = i[:n]
     610| . . . . . . . . . .Books.append(i)
     620| . . . . . . . . . .break
     630| . . . . . . . .else:
     640| . . . . . . . . . .Books.append(i)
     650|#--------------------------------------------------
     660|
     670|nfile = "list.txt"
     680|first = True
     690|if os.path.isfile(nfile):   # encoding='cp1251'
     700|   with open(nfile, encoding="utf8") as f:
     710| . .   Li = f.readlines()
     720|   for i in Li:
     730| . .   addFb2(i.strip(), first)
     740| . .   first = False
     750|  
     760|Books.append('</body>\n')
     770|Books.append('</FictionBook>\n')#
     780|SaveList()  
     790|
     800|print( 'Done!')

Эта глава никогда не будет дописана

     = Почему? О чем речь?
     - Сегодня давай ПОПЫТАЕМСЯ преобразовать html-файл в книгу fb2.
     = Так в чем проблема? Форматы почти одинаковы, и тут и там теги, ну чуть разные.
     - Так – да не так. Совсем не так.
     Давай разберем пример:
     
     <H1>Как видишь – несовпадение регистра</h1>
     <p>Начинается абзац <i><b>выделенный текст </i>перемешанная вложенность</B> абзац не завершен должным образом.
     <h4>Нарушение структуры заголовков(после h1 ДОЛЖЕН быть h2)
     <div>Абзац может начинаться и таким макаром
     <br> любимый метод разбивки абзаца на части в лучшем случае так: <br /> в худшем так: <hr>
     Перечисленные bags вполне допустимы для HTML.
     
     = Какой же идиот придумал такой дебильный формат, и почему мы до сих пор им пользуемся.
     - Я с тобой, коренным образом не согласен. Гении, достойные десятка Нобелевек, придумали формат, который ни один дебил не может окончательно испохабить.
     Почему был разработан именно такой формат, я уже говорил, не буду повторяться, но еще раз:
     да, такой код xml-ридер отвергнет с отвращением, а любой браузер отобразит без запинки.
     - Думаю теперь понятно, почему эта глава бесконечна - бездонна человеческая... изобретательность в изготовлении вариантов HTML.
     
     * * *
     Читатель давно догадался, что у меня в рукаве полно тузов.
     Дело в том, что у меня довольно большой опыт в изготовлении fb2. Достаточно давно у меня написана соответствующая программа (пользуюсь ею до сих пор):

     Рис 1
     Как видите это закладка “title-info”
     А это основная рабочая закладка “Содержимое”:

     Рис 2
     Программа писалась «для себя» поэтому есть некая « непричёсанность ». Работа писалась долго – лет пять, … нет больше, (если надо, уточню) заложено было масса возможностей (возникших из потребностей) и многие из них я уже забыл (отпала потребность).
     = А почему ты не выложишь ее в общее пользование?
     - К авторским правам я отношусь резко отрицательно. Если бы первые каменные топоры авторизировались, то мы бы сейчас сидели в пещерах.
     = Зато природа чистая. Без химии и радиации.
     = Как упоительны неандертальские вечера…
     - Не выкладываю:
     1. по той же причине… прога не будет дописана никогда.
     2. к сожалению прога не для простых пользователей, которым для совершения волшебства достаточно нажать две кнопки.
     Но, вернемся к теме. Предлагаю ввести промежуточный код.
     Промежуточные патроны используют многие языки программирования.
     Посмотрите как удобно.
     Из HTML делаем промежуточный (текстовый) файл.
     Далее открываем полученный файл любым текстовым редактором, при необходимости редактируем (сохраняя условности промежуточного кода).
     А затем преобразуем в итоговый fb2 файл.
     = В чем состоит промежуточность?
     - На рисунке 2 ты увидишь, как это выглядит, т.е. в начале абзаца помещаются сведения о его типе.
     Перечислю типы:
     .| (пробел, точка, палка, пробел) – обычный текстовый абзац.
     Начиная с h1| по h7| - заголовки разного уровня / максимум, что мне встретилось в практике H5 /
     e| (пробел, E, палка, пробел) – эпиграф.
     a| - автор эпиграфа или цитаты или стихотворения.
     s| - центрированная полужирная строка.
     p| - стихи.
     c| - цитата
     = Что за безграмотность! Что это за «Jmage», что за «Kode»?
     - Порождение моего мрачного разума. Я различаю «Image» - картинки расположенные между абзацами и «Jmage» изображение находящееся внутри абзаца /в этом случае абзац принудительно разделяется на 2 или 3 части, которые при изготовлении fb2 опять объединятся {при необходимости, тип картинки легко меняется}/.
     Соответственно это типы i| и j|
     Остальные типы, перечисленные в панели «Styles» нам не потребуются, кроме «Kode» типом k| я помечаю строки html файла заключенные между тегами <pre> и </pre>. Почему «Kode»? Дело в том, что буква «с» занята типом «цитата» и для русского человека такое написание не очень режет взор.
     Кроме перечисленных, есть еще тип t| - таблица. Будем ли использовать?? Посмотрим.
     Ясное дело, при необходимости типов прибавим.
     2.02.22
     ...
     1.05.22
     Моя заинтересованность в написании этого текста изменяется по синусоидам, эллипсам и спиралям. Сейчас кривая вывела меня к такому решению:
     Не хочу я писать скрипт по изготовлению fb2 из html, меня удовлетворяет работа моей вышеописанной программки (хотел написать, уже прикинул вариант, но перегорел). Кто знает, может, я изменю свое решение, кривые они такие.
     А пока у меня есть два совета читателю.
     1. Напишите упомянутый скрипт, и для преобразования используйте файл html изготовленный Word-ом или OpenОфисом (ну их несколько видов) это дает некоторую гарантию правильной структуры исходного файла.
     2. Сделайте скрипт преобразующий файл epub в fb2. Работа довольно интересная и может даже полезная. И структура файла правильна….. хотя не гарантирую, кто знает, кто и как делал подопытный epub.
     Ну, и до кучи – обратное преобразование. Правильно сделанное fb2 стопроцентно можно превратить в epub.
     Да пребудет с вами СИЛА!!
     
     
     
     
     
     

18. Уплотнение

     Библио-практика породила следующую задачу.
     Скрипт «раскладка файлов fb2 по папкам», как известно, раскладывает книги по авторам, а если книга единична, то такие опусы раскладываются по папкам «f1», «f2» и т.д. Полученную кучу папок я незамысловато раскладываю по папкам «1», «2» и т.д. при этом придерживаясь, правила: в папке не должно содержаться более 9-11 элементов (в зависимости от читалки).
     В процессе чтения, понравившиеся книги уходят в нужные места, а остальные удаляются, часто удаляется и не понравившийся автор. В результате работы такого «решета эратосфена» в папках «f1», «f2» вместо 9 – 11 остаются по 2 – 3 книги, да и папки «1», «2» редеют своим содержимым.
     Вчера, очередной раз начал наводить порядок, и плюнул. Надоело! (наверно сказалась «Вальпургиева ночь»)
     Надо писать скрипт.
     * * *
     Итак, начинаем.
     Сканируем заданную папку.
     В содержимом нас интересуют только папки.
     При сканировании составляем две списка: fList, dList. Соответственно это списки со ссылками на папки «f1», «f2»… и «1x», «2x»… Если папка не попадает ни в один из этих списков – запускаем функцию сканирования с параметром этой папки (рекурсия).
     Далее.
     Проверяем все папки по списку fList. Если внутри папки находится «заданный максимум файлов», то исключаем эту папку из дальнейшего рассмотрения (неточность).
     Во втором проходе: если папок больше 1, запускаем цикл: в текущую папку переносим файлы из последней (дополняя до «заданного максимума») (неточность)
     Далее.
     Проверяем все папки по списку dList. Если внутри папки находится «заданный максимум файлов», то исключаем эту папку из дальнейшего рассмотрения (неточность).
     Во втором проходе: если папок больше 1, запускаем цикл: в текущую папку переносим папки из последней (дополняя до «заданного максимума») (неточность)
     И для завершения удаляем все пустые папки.
     В чем заключаются неточности? Подумайте.

Скрипто-содержание

     Я уже начал путаться, где - что находится. Посему этот текст для служебного пользования, т.е. будет многократно редактироваться.
      
     3. Поехали
     Приложение: 2 скрипта
     скрипт замены отступов точками
     Упаковка файлов fb2 в архивы zip
      
      
     упорядоченный список имен файлов книг
      
     5. Пасьянс из fb2
     Приложение: раскладка файлов fb2 по папкам
     6. Ремонт fb2
     Приложение:
     1. fb2err4.py проверка структуры файла fb2
     2. fb2errors.py составление списка книг с повреждением структуры (проверяется заданная папка и вложения)
     3. moveIn.py перемещение файлов по списку в рабочую папку
     4. moveOut.py перемещение файлов по списку из рабочей папки по местам жительства
     5. un_zip.py распаковка
      
     7. Вне плана
     Приложение:
     1. сборка абзацев из разрозненных строк
     2. Объединение абзацев (версия 10.10.21)
     3. Объединение абзацев разде-
     |# разделенных переносами
      
     8. Image
     извлечение рисунков из fb2
     Приложение
     Собранные в кучку скрипты для работы с рисунками.
     * получение рисунка из fb2;
     * удаление из fb2 всех рисунков
     * кодирование рисунка для вставки в fb2
      
     9. Без базы
     Приложение: Файл "ganres.py"
      
     10. Сканирование библиотеки
     Приложение
     поиск файлов одинаковых по размеру и содержимому
      
     del_empty_dir.py Удаление пустых папок
     scan_lib.py Скрипт для записи библиотеки в "небазу"
      
     12. Использование 2
     Приложение. набор скриптов для поиска двойников.
     tree.py изготовление файла содержащего все адреса файлов в текущей папке
     dubl_books.py Ручная сортировка дубликатов в "небазе" (вторая версия)
      
     Удаление файлов по адресам содержащимся в файле 'delfiles.txt'
      
     13. Трупы в библиотеке
     Приложение
     deadBooks.py поиск книг содержащихся в небазе, но не существующих физически
     MarkDelBook.py изменение оценки на метку "удаленная книга"
      
     14. Сортировка по авторам
     Приложение
     sortbyauthor.py сортировка книг по авторам
     16. Объединение
     Приложение
     fb2_list.py # Извлечение из архивов в папке # Изготовление списка книг в текущей папке
     join_fb2.py # объединение fb2 файлов перечисленных в файле list.txt
      

 Ваша оценка:

Связаться с программистом сайта.

Новые книги авторов СИ, вышедшие из печати:
О.Болдырева "Крадуш. Чужие души" М.Николаев "Вторжение на Землю"

Как попасть в этoт список
Сайт - "Художники" .. || .. Доска об'явлений "Книги"