Преобразование книги из *.txt в *.fb2

Здесь обсуждается всё, что касается макросов.
Post Reply
buniak_a_h
Posts: 609
Joined: Sat 20 Apr, 2013 00:17
Location: Питер
Has thanked: 60 times
Been thanked: 15 times
Contact:

Преобразование книги из *.txt в *.fb2

Post by buniak_a_h » Mon 12 Mar, 2018 10:39

Сделал простенький макрос для преобразования электронной книги, записанной в текстовом виде, в формат fb2.
  1. -- txt2fb2.lua, Версия 3
  2. local dtLast, bQuit=0, false
  3. --local CR_LF='\r\n'
  4. local CR_LF='\n'
  5. local tin=table.insert
  6. local F=far.Flags
  7.  
  8. local blank={
  9. [[<?xml version="1.0" encoding="utf-8"?>]];
  10. [[<FictionBook>]];
  11. [[<description>]];
  12. [[<title-info>]];
  13. [[<genre>#Genre#</genre>]];
  14. [[<author>]];
  15. [[<first-name>#AuName#</first-name>]];
  16. [[<last-name>#AuFam#</last-name>]];
  17. [[</author>]];
  18. [[<book-title>#Title#</book-title>]];
  19. [[<annotation>#Annotation#</annotation>]];
  20. [[<date>#Year#</date>]];
  21. [[<lang>ru</lang>]];
  22. [[<sequence name="#Seq_Name#" number="#Seq_Number#"/>]];
  23. [[</title-info>]];
  24. [[</description>]];
  25. [[<body>]];
  26. [[<title>]];
  27. [[</title>]];
  28. [[<section>]];
  29. [[</section>]];
  30. [[</body>]];
  31. [[</FictionBook>]];
  32. }
  33.  
  34. local GenreList = {
  35.                    {Text='Фантастика, Фэнтези';
  36.                     Sub={
  37.                      {Code='sf_history'; Text='Альтернативная история'};
  38.                      {Code='sf_action'; Text='Боевая фантастика'};
  39.                      {Code='sf_epic'; Text='Эпическая фантастика'};
  40.                      {Code='sf_heroic'; Text='Героическая фантастика'};
  41.                      {Code='sf_detective'; Text='Детективная фантастика'};
  42.                      {Code='sf_cyberpunk'; Text='Киберпанк'};
  43.                      {Code='sf_space'; Text='Космическая фантастика'};
  44.                      {Code='sf_social'; Text='Социально-философская фантастика'};
  45.                      {Code='sf_horror'; Text='Ужасы и Мистика'};
  46.                      {Code='sf_humor'; Text='Юмористическая фантастика'};
  47.                      {Code='sf_fantasy'; Text='Фэнтези'};
  48.                      {Code='sf'; Text='Научная фантастика'};
  49.                      {Code='child_sf'; Text='Детская фантастика'};
  50.                      {Code='love_sf'; Text='Любовная фантастика'};
  51.                     };
  52.                    };
  53.                    {Text='Детективы и Боевики';
  54.                     Sub={
  55.                      {Code='det_classic'; Text='Классические детективы'};
  56.                      {Code='det_police'; Text='Полицейские детективы'};
  57.                      {Code='det_action'; Text='Боевики'};
  58.                      {Code='det_irony'; Text='Иронические детективы'};
  59.                      {Code='det_history'; Text='Исторические детективы'};
  60.                      {Code='det_espionage'; Text='Шпионские детективы'};
  61.                      {Code='det_crime'; Text='Криминальные детективы'};
  62.                      {Code='det_political'; Text='Политические детективы'};
  63.                      {Code='det_maniac'; Text='Маньяки'};
  64.                      {Code='det_hard'; Text='Крутой детектив'};
  65.                      {Code='thriller'; Text='Триллеры'};
  66.                      {Code='detective'; Text='Детективы'};
  67.                      {Code='sf_detective'; Text='НФ детективы'};
  68.                      {Code='child_det'; Text='Детские детективы'};
  69.                      {Code='love_detective'; Text='Остросюжетные любовные романы'};
  70.                     };
  71.                    };
  72.                    {Text='Проза';
  73.                     Sub={
  74.                      {Code='prose_classic'; Text='Классическая проза'};
  75.                      {Code='prose_history'; Text='Историческая проза'};
  76.                      {Code='prose_contemporary'; Text='Современная проза'};
  77.                      {Code='prose_counter'; Text='Контркультура'};
  78.                      {Code='prose_rus_classic'; Text='Русская классика'};
  79.                      {Code='prose_su_classics'; Text='Советская классика'};
  80.                      {Code='humor_prose'; Text='Юмористическая проза'};
  81.                      {Code='prose_military'; Text='Военная проза'};
  82.                      {Code='child_prose'; Text='Детская проза'}
  83.                     }
  84.                    };
  85.                    {Text='Любовные романы';
  86.                     Sub={
  87.                      {Code='love_contemporary'; Text='Современные любовные романы'};
  88.                      {Code='love_history'; Text='Исторические любовные романы'};
  89.                      {Code='love_detective'; Text='Остросюжетные любовные романы'};
  90.                      {Code='love_short'; Text='Короткие любовные романы'};
  91.                      {Code='love_erotica'; Text='Эротика'};
  92.                      {Code='love_sf'; Text='Любовная фантастика'}
  93.                     }
  94.                    };
  95.                    {Text='Приключения';
  96.                     Sub={
  97.                      {Code='adv_western'; Text='Вестерны'};
  98.                      {Code='adv_history'; Text='Исторические приключения'};
  99.                      {Code='adv_indian'; Text=' Приключения: Индейцы'};
  100.                      {Code='adv_maritime'; Text=' Морские приключения'};
  101.                      {Code='adv_geo'; Text=' Путешествия и География'};
  102.                      {Code='adv_animal'; Text=' Природа и Животные'};
  103.                      {Code='adventure'; Text=' Приключения: Прочее'};
  104.                      {Code='child_adv'; Text=' Детские приключения'};
  105.                     }
  106.                    };
  107.                    {Text='Детские';
  108.                     Sub={
  109.                      {Code='child_tale'; Text='Сказки'};
  110.                      {Code='child_verse'; Text='Детские стихи'};
  111.                      {Code='child_prose'; Text='Детская проза'};
  112.                      {Code='child_sf'; Text='Детская фантастика'};
  113.                      {Code='child_det'; Text='Детские остросюжетные'};
  114.                      {Code='child_adv'; Text='Детские приключения'};
  115.                      {Code='child_education'; Text='Детская образовательная'};
  116.                      {Code='children'; Text='Детские: Прочее'}
  117.                     }
  118.                    };
  119.                    {Text='Поэзия и драматургия';
  120.                     Sub={
  121.                      {Code='dramaturgy'; Text='Драматургия'};
  122.                      {Code='poetry'; Text='Поэзия'};
  123.                      {Code='humor_verse'; Text='Юмористические стихи'};
  124.                      {Code='child_verse'; Text='Детские стихи'}
  125.                     }
  126.                    };
  127.                    {Text='Старинная литература';
  128.                     Sub={
  129.                      {Code='antique_ant'; Text='Античная литература'};
  130.                      {Code='antique_european'; Text='Европейская старинная литература'};
  131.                      {Code='antique_russian'; Text='Древнерусская литература'};
  132.                      {Code='antique_east'; Text='Древневосточная литература'};
  133.                      {Code='antique_myths'; Text='Мифы. Легенды. Эпос'};
  134.                      {Code='antique'; Text='Старинная литература: Прочее'}
  135.                     }
  136.                    };
  137.                    {Text='Научно-образовательная';
  138.                     Sub={
  139.                      {Code='sci_history'; Text='История'};
  140.                      {Code='sci_psychology'; Text='Психология'};
  141.                      {Code='sci_culture'; Text='Культурология'};
  142.                      {Code='sci_religion'; Text='Религиоведение'};
  143.                      {Code='sci_philosophy'; Text='Философия'};
  144.                      {Code='sci_politics'; Text='Политика'};
  145.                      {Code='sci_juris'; Text='Юриспруденция'};
  146.                      {Code='sci_linguistic'; Text='Языкознание'};
  147.                      {Code='sci_medicine'; Text='Медицина'};
  148.                      {Code='sci_phys'; Text='Физика'};
  149.                      {Code='sci_math'; Text='Математика'};
  150.                      {Code='sci_chem'; Text='Химия'};
  151.                      {Code='sci_biology'; Text='Биология'};
  152.                      {Code='sci_tech'; Text='Технические'};
  153.                      {Code='science'; Text='Научно-образовательная: Прочее'};
  154.                      {Code='adv_animal'; Text='Природа и Животные'};
  155.                     }
  156.                    };
  157.                    {Text='Компьютеры и Интернет';
  158.                     Sub={
  159.                      {Code='comp_www'; Text='Интернет'};
  160.                      {Code='comp_programming'; Text='Программирование'};
  161.                      {Code='comp_hard'; Text='Компьютерное Железо'};
  162.                      {Code='comp_soft'; Text='Программы'};
  163.                      {Code='comp_db'; Text='Базы данных'};
  164.                      {Code='comp_osnet'; Text='ОС и Сети'};
  165.                      {Code='computers'; Text='Компьютеры и Интернет: Прочее'}
  166.                     }
  167.                    };
  168.                    {Text='Справочная литература';
  169.                     Sub={
  170.                      {Code='ref_encyc'; Text='Энциклопедии'};
  171.                      {Code='ref_dict'; Text='Словари'};
  172.                      {Code='ref_ref'; Text='Справочники'};
  173.                      {Code='ref_guide'; Text='Путеводители'};
  174.                      {Code='reference'; Text='Справочная литература: Прочее'}
  175.                     }
  176.                    };
  177.                    {Text='Документальное';
  178.                     Sub={
  179.                      {Code='nonf_biography'; Text='Биографии и Мемуары'};
  180.                      {Code='nonf_publicism'; Text='Публицистика'};
  181.                      {Code='nonf_criticism'; Text='Критика'};
  182.                      {Code='nonfiction'; Text='Документальное: Прочее'};
  183.                      {Code='design'; Text='Искусство'};
  184.                      {Code='adv_animal'; Text='Природа и Животные'}
  185.                     }
  186.                    };
  187.                    {Text='Религия и Духовность';
  188.                     Sub={
  189.                      {Code='religion_rel'; Text='Религия'};
  190.                      {Code='religion_esoterics'; Text='Эзотерика'};
  191.                      {Code='religion_self'; Text='Самосовершенствование'};
  192.                      {Code='religion'; Text='Религия и духовность: Прочее'};
  193.                      {Code='sci_religion'; Text='Религиозное образование'}
  194.                     }
  195.                    };
  196.                    {Text='Юмор';
  197.                     Sub={
  198.                      {Code='humor_anecdote'; Text='Анекдоты'};
  199.                      {Code='humor_prose'; Text='Юмористическая проза'};
  200.                      {Code='humor_verse'; Text='Юмористические стихи'};
  201.                      {Code='humor'; Text='Юмор: Прочее'}
  202.                     }
  203.                    };
  204.                    {Text='Дом и Семья';
  205.                     Sub={
  206.                      {Code='home_cooking'; Text='Кулинария'};
  207.                      {Code='home_pets'; Text='Домашние животные'};
  208.                      {Code='home_crafts'; Text='Хобби и Ремесла'};
  209.                      {Code='home_entertain'; Text='Развлечения'};
  210.                      {Code='home_health'; Text='Здоровье'};
  211.                      {Code='home_garden'; Text='Сад и Огород'};
  212.                      {Code='home_diy'; Text='Самоделки'};
  213.                      {Code='home_sport'; Text='Спорт'};
  214.                      {Code='home_sex'; Text='Эротика и Секс'};
  215.                      {Code='home'; Text='Дом и Семья: Прочее'};
  216.                      {Code='geo_guides'; Text='ГеоПутеводитель'}
  217.                     }
  218.                    };
  219.                    {Text='Экономика, Бизнес';
  220.                     Sub={
  221.                      {Code='job_hunting'; Text='Поиск работы, карьера'};
  222.                      {Code='management'; Text='Управление, подбор персонала'};
  223.                      {Code='marketing'; Text='Маркетинг, PR, реклама'};
  224.                      {Code='banking'; Text='Банковское дело'};
  225.                      {Code='stock'; Text='Ценные бумаги, инвестиции'};
  226.                      {Code='accounting'; Text='Бухучет, налогообложение, аудит'};
  227.                      {Code='global_economy'; Text='Внешнеэкономическая деятельность'};
  228.                      {Code='economics'; Text='Экономика'};
  229.                      {Code='industries'; Text='Отраслевые издания'};
  230.                      {Code='org_behavior'; Text='Корпоративная культура'};
  231.                      {Code='personal_finance'; Text='Личные финансы'};
  232.                      {Code='real_estate'; Text='Real Недвижимость'};
  233.                      {Code='popular_business'; Text='О бизнесе популярно'};
  234.                      {Code='small_business'; Text='Малый бизнес'};
  235.                      {Code='paper_work'; Text='Paper Делопроизводство'};
  236.                      {Code='economics_ref Справочники'}
  237.                     }
  238.                    };
  239.                   }
  240. local function OEMtoUTF_8(str)
  241.  return win.WideCharToMultiByte(win.MultiByteToWideChar(str, win.GetOEMCP()), 65001)
  242. end
  243.  
  244. local function AddLine(Line, hOut)
  245.  --editor.InsertString()
  246.  --editor.SetString(nil, Editor.Lines-1, Line, CR_LF)
  247.  hOut:write(( Line:match('^(.-)%s*$') or Line)..CR_LF)
  248. end
  249.  
  250. local ampsims={
  251.       ['\'']='&apos;';
  252.       ['"']='&quot;';
  253.       ['&']='&amp;';
  254.       ['<']='&lt;';
  255.       ['>']='&gt;';
  256.       ['©']='&copy;';
  257.       ['®']='&reg;';
  258.       }
  259. local function ToAmp(Str)
  260.  -- Замена символов на &-последовательности
  261.  local r=''
  262.  local iPos
  263.  repeat
  264.   --iPos = unicode.utf8.cfind(Str, '[&\'"$<>©®]')
  265.   iPos = Str:cfind('[&\'"$<>©®]')
  266.   if iPos then
  267.    r = r .. Str:sub(1,iPos-1) .. ampsims[Str:sub(iPos,iPos)]
  268.    Str = Str:sub(iPos+1)
  269.   end
  270.  until not iPos
  271.  if Str ~= '' then
  272.   r = r .. Str
  273.  end
  274.  return r
  275. end
  276.  
  277.  
  278. local function _txt2fb2(fn, CanQuit)
  279.  Far.DisableHistory(1)
  280.  local DlgItems, ID, y = {}, {}
  281.  local DlgGUID = win.Uuid('8071F9E9-8E86-415E-B2F8-6901B187E374')
  282.  local DlgResult
  283.  tin(DlgItems, { "DI_DOUBLEBOX", 0, 0, 73, 15, 0, 0, 0, 0, 'Преобразование '..fn..' в FB2' })
  284.  y = 1
  285.  tin(DlgItems, {'DI_TEXT',3,y,0,y,0,0,0,0,"Жанр"})
  286.  y= y + 1
  287.  tin(DlgItems, {'DI_COMBOBOX', 3,y,27,y, GenreList,0,0,{DIF_DROPDOWNLIST=1;DIF_CENTERGROUP=1}})
  288.  ID.Genre=#DlgItems
  289.  tin(DlgItems, {'DI_COMBOBOX', 30,y,68,y, GenreList[1].Sub,0,0,{DIF_DROPDOWNLIST=1;DIF_CENTERGROUP=1}})
  290.  ID.SubGenre=#DlgItems
  291.  y = y + 2
  292.  tin(DlgItems, {"DI_TEXT", 3,y,0,y,0,0,0,0,"Данные автора:"})
  293.  y = y + 1
  294.  tin(DlgItems, {"DI_TEXT", 3,y,0,y,0,0,0,0,"имя"})
  295.  tin(DlgItems, {"DI_EDIT", 7,y,33,y,0,'Txt2FB.AuName',0,{DIF_SELECTONENTRY=1; DIF_HISTORY=1;DIF_USELASTHISTORY=1}})
  296.  ID.AuName=#DlgItems
  297.  tin(DlgItems, {"DI_TEXT", 35,y,0,y,0,0,0,0,"фамилия"})
  298.  tin(DlgItems, {"DI_EDIT",  43,y,68,y,0,'Txt2FB.AuFam',0,{DIF_SELECTONENTRY=1; DIF_HISTORY=1;DIF_USELASTHISTORY=1}})
  299.  ID.AuFam=#DlgItems
  300.  y = y + 2
  301.  tin(DlgItems, {"DI_TEXT", 3,y,0,y,0,0,0,0,"Серия"})
  302.  tin(DlgItems, {"DI_EDIT", 9,y,60,y,0,'Txt2FB.Seq_Name',0,{DIF_SELECTONENTRY=1;DIF_HISTORY=1;DIF_USELASTHISTORY=1}})
  303.  ID.Seq_Name=#DlgItems
  304.  tin(DlgItems, {"DI_TEXT", 63,y,0,y,0,0,0,0,"№"})
  305.  tin(DlgItems, {"DI_EDIT", 65,y,68,y,0,0,0,{DIF_SELECTONENTRY=1}})
  306.  ID.Seq_Number=#DlgItems
  307.  y = y + 1
  308.  tin(DlgItems, {"DI_TEXT", -1,y,0,y,0,0,0,{DIF_SEPARATOR=1}})
  309.  y = y + 1
  310.  tin(DlgItems, {"DI_TEXT", 3,y,0,y,0,0,0,0, "Название книги"})
  311.  y = y + 1
  312.  tin(DlgItems, {"DI_EDIT", 3,y,65,y,0,0,0,{DIF_SELECTONENTRY=1}})
  313.  ID.Title=#DlgItems
  314.  y = y + 1
  315.  tin(DlgItems, {"DI_TEXT", 3,y,0,y,0,0,0,0, "Аннотация"})
  316.  y = y + 1
  317.  tin(DlgItems, {"DI_FIXEDIT", 3,y,68,y,0,0,0,{DIF_EDITOR=1}})
  318.  ID.Ann_1=#DlgItems
  319.  y = y + 1
  320.  tin(DlgItems, {"DI_FIXEDIT", 3,y,68,y,0,0,0,{DIF_EDITOR=1}})
  321.  y = y + 1
  322.  tin(DlgItems, {"DI_FIXEDIT", 3,y,68,y,0,0,0,{DIF_EDITOR=1}})
  323.  y = y + 1
  324.  tin(DlgItems, {"DI_EDIT", 3,y,68,y,0,0,0,{DIF_EDITOR=1}})
  325.  ID.Ann_2=#DlgItems
  326.  y = y + 2
  327.  tin(DlgItems, {"DI_BUTTON", 0,y,0,y,0,0,0,{DIF_CENTERGROUP=1; DIF_DEFAULTBUTTON=1},"Ок"})
  328.  ID.OkBtn=#DlgItems
  329.  tin(DlgItems, {"DI_BUTTON", 0,y,0,y,0,0,0,{DIF_CENTERGROUP=1},"Cancel"})
  330.  ID.CancelBtn=#DlgItems
  331.  if CanQuit then
  332.   tin(DlgItems, {"DI_BUTTON", 0,y,0,y,0,0,0,{DIF_CENTERGROUP=1},"&Quit"})
  333.   ID.QuitBtn=#DlgItems
  334.  end
  335.  
  336.  local function _DlgProc(hDlg, Msg, ItemId, Param2)
  337.   if Msg == F.DN_INITDIALOG then
  338.    hDlg:send("DM_LISTSETCURPOS", ID.Genre, {SelectPos=1; TopPos=1})
  339.    hDlg:send("DM_LISTSETCURPOS", ID.SubGenre, {SelectPos=1})
  340.   elseif Msg == F.DN_DROPDOWNOPENED and
  341.          ItemId == ID.Genre and
  342.          Param2 == 0 then
  343.    hDlg:send( "DM_LISTSET", ID.SubGenre, GenreList[hDlg:send("DM_LISTGETCURPOS", ID.Genre).SelectPos].Sub )
  344.    hDlg:send("DM_LISTSETCURPOS", ID.SubGenre, {SelectPos=1})
  345.   elseif Msg == F.DN_CLOSE then
  346.    if ItemId < 0 or ItemId == ID.CancelBtn or ItemId == ID.QuitBtn then
  347.     DlgResult = nil
  348.     bQuit = ItemId == ID.QuitBtn
  349.    else
  350.     DlgResult = {}
  351.     local i1, i2 = hDlg:send("DM_LISTGETCURPOS", ID.Genre).SelectPos,
  352.                    hDlg:send("DM_LISTGETCURPOS", ID.SubGenre).SelectPos
  353.     DlgResult.Genre = GenreList[i1].Sub[i2].Code
  354.     DlgResult.AuName = hDlg:send("DM_GETTEXT", ID.AuName)
  355.     DlgResult.AuFam = hDlg:send("DM_GETTEXT", ID.AuFam)
  356.     DlgResult.Title = hDlg:send("DM_GETTEXT", ID.Title)
  357.     DlgResult.Seq_Name =  hDlg:send("DM_GETTEXT", ID.Seq_Name)
  358.     DlgResult.Seq_Number =  hDlg:send("DM_GETTEXT", ID.Seq_Number)
  359.     local s=''
  360.     for i=ID.Ann_1,ID.Ann_2 do
  361.      s = s .. mf.trim(hDlg:send("DM_GETTEXT", i)) .. ' '
  362.     end
  363.     DlgResult.Annotation =  mf.trim(s)
  364.     if mf.trim(DlgResult.AuName or '') == '' or
  365.        mf.trim(DlgResult.AuFam or '') == '' then
  366.      far.Message('Необходимо указать имя и фамилию автора!','Не заполнены обязательные поля',';Ok','w')
  367.      hDlg:send("DM_SETFOCUS", mf.trim(DlgResult.AuName or '') == '' and ID.AuName or ID.AuFam)
  368.      return false
  369.     end
  370.    end
  371.   end
  372.  end -- _DlgProc
  373.  DlgItems[1][5] = y+1
  374.  far.Dialog(DlgGUID,-1,5,73,6+y, nil, DlgItems, "FDLG_SMALLDIALOG", _DlgProc)
  375.  if not DlgResult then return end
  376.  
  377.  local fnOut= (Area.Shell and APanel.Path .. '\\' or '') .. mf.fsplit(fn, 7)..'.fb2'
  378.  if mf.fexist(fnOut) then
  379.   if far.Message('Файл '..fnOut..' существует.\nПереписать?',
  380.                  'Создание файла fb2', ';YesNo', 'w') == 1 then
  381.     win.DeleteFile(fnOut)
  382.   else
  383.    return false
  384.   end
  385.  end
  386.  local hOut, msg=io.open(fnOut, 'w')
  387.  if not hOut then
  388.   far.Message('Неудача создания файла\n'..fnOut..'\n'..msg,'txt -> fb2', nil, 'w' )
  389.   return false
  390.  end
  391.  for _,Line in ipairs(blank) do
  392.   for w in Line:gmatch('#([%w_]+)#') do
  393.    Line = Line:gsub('#'..w..'#', ToAmp(DlgResult[w] or '') )
  394.   end
  395.   AddLine(Line, hOut)
  396.   if mf.trim(Line) == '<section>' then
  397.    local iEmpL, sOut=0, ''
  398.    local function AddLine1(Line1, ii)
  399.     if Line1:match('%S') then -- прочитана непустая строка
  400.      if Line1:match('^%s+') then -- строка начинается с пробелов (начало абзаца)
  401.       if iEmpL > 1 then -- был разделитель - выталкиваю
  402.        AddLine('<p><subtitle>* * *</subtitle></p>', hOut)
  403.       end
  404.       iEmpL = 0
  405.       if sOut:match('%S') then -- Уже была сформирована непустая строка - выталкиваю
  406.        AddLine('<p>'..ToAmp(sOut:match('^%s*(.*%S)%s*$'):gsub('%s+',' '))..'</p>', hOut)
  407.        sOut = ''
  408.       end
  409.      elseif iEmpL > 0 then
  410.       -- Строка начинается не с пробелов, но ей предшествует пустая - тоже абзац
  411.       if sOut:match('%S') then
  412.        AddLine('<p>     '..ToAmp(sOut:match('^%s*(.*%S)%s*$'):gsub('%s+',' '))..'</p>', hOut)
  413.        sOut = ''
  414.       end
  415.       iEmpL = 0
  416.      end -- Line1 - непустая
  417.      local function _ItIsHdr(L)
  418.       local l1,l2, l3=L:match('^(%s+)(.-)(%s+)$')
  419.       return l1 and l2 and l3 and l2:match('^[%d%u][%d%u.%s -/]+$') and (math.abs(l1:len()-l3:len()) <= 2)
  420.      end
  421.      if _ItIsHdr(Line1) then
  422.       if sOut:match('%S') then
  423.        AddLine(sOut, hOut)
  424.        sOut = ''
  425.       end
  426.       AddLine('</section><section><title><p>'..Line1..'</p></title>', hOut)
  427.      else
  428.       sOut = sOut .. Line1 .. ' '
  429.      end
  430.     else -- пустая строка; подсчитываю колиичество
  431.      iEmpL = iEmpL + 1
  432.     end
  433.    end -- function AddLine1
  434.    far.Message('Создание\n'..mf.fsplit(fnOut, 12),'','')
  435.    local aLines={}
  436.    if Area.Editor then
  437.     for i=1,Editor.Lines do
  438.      aLines[i] = Editor.GetStr(i)
  439.     end
  440.    else
  441.     for ll in io.lines(fn) do
  442.      aLines[#aLines+1] = OEMtoUTF_8(ll):gsub('[\1-\31]+', ' ')
  443.     end
  444.    end
  445.    -- Вычисление правой границы
  446.    local rb=0
  447.    for _, Line1 in ipairs(aLines) do
  448.     rb = math.max(rb,Line1:len())
  449.    end
  450.    for ii, Line1 in ipairs(aLines) do
  451.     AddLine1(mf.strpad(Line1, rb), ii )
  452.    end -- for Line1 in io.lines(...)
  453.    if sOut:match('%S') then
  454.     AddLine('<p>'..sOut:match('^%s*(.*%S)%s*$'):gsub('%s+',' ')..'</p>', hOut)
  455.    end
  456.    far.AdvControl("ACTL_REDRAWALL")
  457.   end
  458.  end
  459.  hOut:close()
  460. end -- _txt2fb2
  461.  
  462. local function txt2fb2()
  463.  local PanItem
  464.  local aFn={}
  465.  bQuit = false
  466.  if APanel.Selected then
  467.   for i=1,APanel.SelCount do
  468.    PanItem = panel.GetSelectedPanelItem(nil, 1, i)
  469.    if not PanItem.FileAttributes:match('d') and
  470.       far.ProcessName("PN_CMPNAME", '*.txt', PanItem.FileName) then
  471.     tin(aFn, PanItem.FileName)
  472.    end
  473.   end
  474.   for i,v in ipairs(aFn) do
  475.     _txt2fb2(v, i<#aFn)
  476.     if bQuit then break end
  477.   end
  478.  else
  479.   _txt2fb2(APanel.Current, false)
  480.  end
  481. end
  482.  
  483. --------------------------------------------------
  484. Macro{
  485. --  id="";
  486.   area="Shell";
  487.   key="CtrlShiftB-F";
  488.   description="F - Преобразование txt в fb2";
  489. --  filemask="";
  490.   flags="";
  491. --  priority=50;
  492. --  sortpriority=50;
  493.   condition=function(_)
  494.    local bRes=false
  495.    if APanel.Selected then
  496.     local PanItem
  497.     for i=1,APanel.SelCount do
  498.      PanItem = panel.GetSelectedPanelItem(nil, 1, i)
  499.      if not PanItem.FileAttributes:match('d') and
  500.         far.ProcessName("PN_CMPNAME", '*.txt', PanItem.FileName) then
  501.       bRes = true
  502.       break
  503.      end
  504.     end
  505.    else
  506.     bRes = (not APanel.Folder and far.ProcessName("PN_CMPNAME", '*.txt', APanel.Current) )
  507.    end
  508.    if bRes then
  509.     dtLast = 0
  510.    else
  511.     if dtLast == 0 then
  512.      dtLast = os.time()
  513.     elseif os.time()-dtLast < 10 then
  514.      dtLast = 0 --os.time()
  515.      far.Message('Необходимо либо установить указатель на файл *.txt,\nлибо отметить хотя бы один такой файл',
  516.                  'Ошибка вызова процедуры', ';Ok', 'w')
  517.     end
  518.    end
  519.    return bRes
  520.   end;
  521. action=function()
  522.  txt2fb2()
  523. end;
  524. }
  525.  
  526. Macro{
  527. --  id="";
  528.   area="Editor";
  529.   key="CtrlShiftB-F";
  530.   description="F - Преобразование txt в fb2";
  531.   filemask="*.txt";
  532.   flags="";
  533. --  priority=50;
  534. --  sortpriority=50;
  535. --  condition=function(key) return true end;
  536. action=function()
  537.  _txt2fb2(Editor.FileName, false)
  538. end;
  539. }

Т.к. несколько человек скачали (и парочка даже поблагодарили :) ), решил подправить некоторые недочёты.


"Защита от дурака" по некоторым моментам (например, файл fb2 существует; запись в папку запрещена и т.п.) не реализована. Если кто-нибудь заинтересуется - пишите, доделаю...
В макросе создание "повешено" на сочетание CtrlShiftB-F, что предполагает наличие макроса, позволяющего вводить "сложные" сочетания клавиш. Настройка не предусмотрена.
Работоспособность макроса проверена чтением результата программой Cool Reader 2
txt2fb2.lua.7z
txt2fb2.lua, Версия 3
(5.85 KiB) Downloaded 17 times
Last edited by buniak_a_h on Wed 26 Sep, 2018 03:28, edited 2 times in total.

User avatar
HaRT
Moderator
Posts: 8528
Joined: Tue 30 Aug, 2005 17:21
Location: Санкт-Петербург
Has thanked: 75 times
Been thanked: 138 times

Преобразование книги из *.txt в *.fb2

Post by HaRT » Mon 12 Mar, 2018 12:10

buniak_a_h wrote:
Mon 12 Mar, 2018 10:39
Разбивает форматированный текстовый файл на параграфы (началом параграфа считается строка, начинающаяся с пробелов
Судя по описанию, имеются в виду всё-таки абзацы. Слово «параграф» в русском языке имеет другой смысл.
Фар есть инструмент, а не нянька. © 2009 DrKnS

buniak_a_h
Posts: 609
Joined: Sat 20 Apr, 2013 00:17
Location: Питер
Has thanked: 60 times
Been thanked: 15 times
Contact:

Преобразование книги из *.txt в *.fb2

Post by buniak_a_h » Mon 12 Mar, 2018 12:30

HaRT wrote:
Mon 12 Mar, 2018 12:10
buniak_a_h wrote:
Mon 12 Mar, 2018 10:39
Разбивает форматированный текстовый файл на параграфы (началом параграфа считается строка, начинающаяся с пробелов
Судя по описанию, имеются в виду всё-таки абзацы. Слово «параграф» в русском языке имеет другой смысл.
Параграфы в терминологии MS Word. Т.е. текст, отделённые от впереди идущего жёстким переносом строки.
Впрочем, Вы, кажется, поняли...
Ну и цитата из описания html (тег <p>):
<p> Создает новый параграф
Впрочем, в другом документе
Тег <p> Определяет текстовый абзац
Видимо, терминология не устоялась. Хотя абзац - действительно логичнее
Last edited by buniak_a_h on Mon 12 Mar, 2018 12:38, edited 1 time in total.

User avatar
HaRT
Moderator
Posts: 8528
Joined: Tue 30 Aug, 2005 17:21
Location: Санкт-Петербург
Has thanked: 75 times
Been thanked: 138 times

Преобразование книги из *.txt в *.fb2

Post by HaRT » Mon 12 Mar, 2018 14:11

buniak_a_h wrote:
Mon 12 Mar, 2018 12:30
Видимо, терминология не устоялась. Хотя абзац - действительно логичнее
Терминология уже сто лет как устоялась. Английское paragraph в типографском значении переводится как абзац. Без вариантов.
Фар есть инструмент, а не нянька. © 2009 DrKnS

buniak_a_h
Posts: 609
Joined: Sat 20 Apr, 2013 00:17
Location: Питер
Has thanked: 60 times
Been thanked: 15 times
Contact:

Преобразование книги из *.txt в *.fb2

Post by buniak_a_h » Wed 26 Sep, 2018 03:38

Вышла 3-я версия макроса. Он теперь умеет распознавать заголовки (отцентрированные, напечатанные в верхнем регистре, но - увы - однострочные). Таким образом они попадают в оглавление, распознаваемое программами Cool Reader, OReader и NeoReader (две последние поставляются с электронной читалкой Monte Cristo)

Post Reply

Return to “Обсуждение макросов”