#!/usr/bin/env python3 # # Copyright (C) 2023 Jonathan A. Poritz # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # import argparse import fileinput import os import re import code import sys import time import uuid import warnings if not sys.warnoptions: warnings.simplefilter("ignore") parser = argparse.ArgumentParser(description='Process a prepared html file in various ways to load into Pressbooks') parser.add_argument("input_file", help="Source html file", type=argparse.FileType('rb')) parser.add_argument("-l", "--logfile", help='Filename for logfile to which will be appended detailed progress information; default is "split.log".', default="split.log") parser.add_argument('--lol', help="Learning Objectives sections consist entirely of an
    ", action='store_true') parser.add_argument('-v', '--verbose', help="print on console all information also going in to the logfile", action='store_true') parser.add_argument("-i", "--image_file", help="Html file from PB image upload pseudo-section; default is pb_imgs.html.", default="pb_imgs.html") parser.add_argument("-m", "--manifest", help="Name to use as manifest file for later uploading with OOupload.py. If absent, will be 'manifest'", default="manifest") parser.add_argument("-p", "--preamble", help="Name manifest preamble file. If absent, there will be no preamble", default="") parser.add_argument("-o", "--output", help="Name to use as output directory. If absent, will be OOhtml", default="OOhtml") parser.add_argument("-k","--key_terms_strong", help='manually makes the terms in the Key Terms section strong, i.e., any line of the form "
  1. : " in the Key Terms section becomes "
  2. : "; default is False', action='store_true') parser.add_argument("-n","--numbered_sections", help='asserts that the sections and subsections at all levels have been pre-numbered -- however, they will be newly numbered and discrepancies will be reported; default is False', action='store_true') args = parser.parse_args() output = args.output verbose = args.verbose preamble = args.preamble imgfile = args.image_file strong_KTs = args.key_terms_strong numbered_sects = args.numbered_sections logfile_fh = open(args.logfile,"a") logfile_fh.write("------------------------------------\n") def log_and_print(s): t=time.strftime('%H:%M:%S')+" "+s logfile_fh.write(t+"\n") if verbose: print(t) when_work = "On "+time.strftime('%d/%m/%Y') log_and_print(when_work+", doing ") what_work = ' '.join(sys.argv)+" in directory "+os.getcwd() log_and_print(what_work) temp_fn="/tmp/"+str(uuid.uuid4()) tidy_command = 'tidy -g -q -o '+temp_fn+' -w 0 '+imgfile+" 2> /dev/null" os.system(tidy_command) log_and_print(f'Did {tidy_command}') img_fh = open(temp_fn, 'r') img_file_s = img_fh.read() img_fh.close() os.system('rm '+temp_fn) img_x=0 os.mkdir(output) log_and_print(f'Made directory {output}') h2pat = re.compile(r'

    ([1-9]+[0-9]*)\.([0-9]*) (.*)

    \n') h3pat = re.compile(r'

    ([1-9]+[0-9]*)\.([0-9]*)\.([0-9]*) (.*)

    \n') h4pat = re.compile(r'

    ([1-9]+[0-9]*)\.([0-9]*)\.([0-9]*)\.([0-9]*) (.*)

    \n') h5pat = re.compile(r'
    ([1-9]+[0-9]*)\.([0-9]*)\.([0-9]*)\.([0-9]*)\.([0-9]*) (.*)
    \n') preamble_lines = 0 manifest_chapter_lines = 0 manifest_part_lines = 0 manifest_fh = open(output+"/"+args.manifest,'w') manifest_fh.write("# "+when_work+", this was\n") manifest_fh.write("# "+what_work+" which resulted in this file\n") if preamble: preamble_fh=open(preamble,'r') for line in preamble_fh: preamble_lines += 1 manifest_fh.write(line) preamble_fh.close() if len(line) and line[-1]!="\n": manifest_fh.write("\n") log_and_print(f'Wrote {preamble_lines} manifest preamble lines') current_fh = None new_html_files = 0 total_html_lines = 0 in_LandA_ssec = False in_LO_ssec = False in_KT_ssec = False in_SL_ssec = False in_GD_ssec = False in_DQ_ssec = False in_LTtL_ssec = False KT_pattern=re.compile('
  3. ([^:]+):') sect_title = '' chap_no = "" line_no = 0 manifest_chapters = "" while True: line = args.input_file.readline().decode() if not line: break line_no += 1 if line[:4] == "

    ": if line[-6:-1] != "

    ": raise ValueError("malformed line with h1 tag: "+line+" on line "+str(line_no)) if in_LandA_ssec: if LandA_w_tables: raise ValueError("malformed Licenses and Attributions section (new chapter inside box): "+LandA_ssec_title+" on line "+str(line_no)) else: current_fh.write("\n") total_html_lines += 1 in_LandA_ssec = False if in_LTtL_ssec: raise ValueError("malformed 'Looking Through the Lens' section (new chapter inside box) on line "+str(line_no)) if in_SL_ssec: raise ValueError("malformed SPOTLIGHT section (new chapter inside box) on line "+str(line_no)) if in_GD_ssec: raise ValueError("malformed Going Deeper section (new chapter inside box) on line "+str(line_no)) if in_KT_ssec or (in_LO_ssec and not args.lol) or in_DQ_ssec: current_fh.write("\n\n") total_html_lines += 2 in_LO_ssec = False in_KT_ssec = False in_DQ_ssec = False if in_LO_ssec and args.lol: raise ValueError("malformed Learning Objectives section (new chapter inside list) on line "+str(line_no)) if current_fh: current_fh.close() chap_no=line[line.find(" ")+1:line.find(":")] sect_no=0 sect_title = '' sub_sect_no=0 sub_sub_sect_no=0 sub_sub_sub_sect_no=0 current_filename=output+"/"+chap_no+".0.html" current_fh = open(current_filename,"w") new_html_files += 1 current_fh.write("Click on the + in the Contents menu to see all the parts of this chapter, or go through them in order by clicking Next → below.\n") total_html_lines += 1 manifest_part_lines += 1 manifest_fh.write("Part: "+line[4:-6]+"\n"+current_filename+"\n") elif line[:4] == "

    ": if line[-6:-1] != "

    ": raise ValueError("malformed line with h2 tag: "+line+" on line "+str(line_no)) if in_LandA_ssec: if LandA_w_tables: raise ValueError("malformed Licenses and Attributions section (new chapter inside box): "+LandA_ssec_title+" on line "+str(line_no)) else: current_fh.write("\n") total_html_lines += 1 in_LandA_ssec = False if in_LTtL_ssec: raise ValueError("malformed 'Looking Through the Lens' section (new section inside box) on line "+str(line_no)) if in_SL_ssec: raise ValueError("malformed SPOTLIGHT section (new section inside box) on line "+str(line_no)) if in_GD_ssec: raise ValueError("malformed Going Deeper section (new section inside box) on line "+str(line_no)) if in_KT_ssec or (in_LO_ssec and not args.lol) or in_DQ_ssec: current_fh.write("\n\n") total_html_lines += 2 in_LO_ssec = False in_KT_ssec = False in_DQ_ssec = False if in_LO_ssec and args.lol: raise ValueError("malformed Learning Objectives section (new section inside list) on line "+str(line_no)) sect_no += 1 if numbered_sects: if not h2pat.match(line): raise ValueError("malformed line with h2 tag: "+line+" on line "+str(line_no)) declared_chap_no = h2pat.sub(r'\1',line) declared_sect_no = int(h2pat.sub(r'\2',line)) if (declared_chap_no != chap_no) or (declared_sect_no != sect_no): log_and_print(f'Level two heading "{line[4:-6]}" should be numbered {chap_no}.{str(sect_no)}; correcting!') sect_title = h2pat.sub(r'\3',line) else: sect_title = line[4:-6] sub_sect_no=0 sub_sub_sect_no=0 sub_sub_sub_sect_no=0 if current_fh: current_fh.close() current_filename = output+"/"+chap_no+"."+str(sect_no)+".html" current_fh = open(current_filename,"w") new_html_files += 1 manifest_chapter_lines += 1 manifest_chapters += "Chapter["+chap_no+"]: "+chap_no+"."+str(sect_no)+" "+sect_title+"\n"+current_filename+"\n" elif line[:4] == "

    ": if line[-6:-1] != "

    ": raise ValueError("malformed line with h3 tag: "+line+" on line "+str(line_no)) if in_KT_ssec or (in_LO_ssec and not args.lol) or in_DQ_ssec: current_fh.write("\n\n") total_html_lines += 2 in_LO_ssec = False in_KT_ssec = False in_DQ_ssec = False if in_LO_ssec and args.lol: raise ValueError("malformed Learning Objectives section (new subsection inside list) on line "+str(line_no)) if in_LandA_ssec: if LandA_w_tables: raise ValueError("malformed Licenses and Attributions section (new chapter inside box): "+LandA_ssec_title+" on line "+str(line_no)) else: current_fh.write("\n") total_html_lines += 1 in_LandA_ssec = False if in_LTtL_ssec: raise ValueError("malformed 'Looking Through the Lens' section (new subsection inside box) on line "+str(line_no)) if in_SL_ssec: raise ValueError("malformed SPOTLIGHT section (new subsection inside box) on line "+str(line_no)) if in_GD_ssec: raise ValueError("malformed Going Deeper section (new subsection inside box) on line "+str(line_no)) sub_sect_no += 1 if numbered_sects: if not h3pat.match(line): raise ValueError("malformed line with h3 tag: "+line+" on line "+str(line_no)) declared_chap_no = h3pat.sub(r'\1',line) declared_sect_no = int(h3pat.sub(r'\2',line)) declared_sub_sect_no = int(h3pat.sub(r'\3',line)) if (declared_chap_no != chap_no) or (declared_sect_no != sect_no) or (declared_sub_sect_no != sub_sect_no): log_and_print(f'Level three heading "{line[4:-6]}" should be numbered {chap_no}.{str(sect_no)}.{str(sub_sect_no)}; correcting!') ssect_title = h3pat.sub(r'\4',line) else: ssect_title = line[4:-6] sub_sub_sect_no=0 sub_sub_sub_sect_no=0 if ssect_title[:25]=="Looking Through the Lens:": s = args.input_file.readline().decode() line_no += 1 if s != "\n": raise ValueError("malformed 'Looking Through the Lens' section (missing starting
    : "+line+" on line "+str(line_no)) in_LTtL_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 elif ssect_title[:9]=="SPOTLIGHT": s = args.input_file.readline().decode() line_no += 1 if s != "
    \n": raise ValueError("malformed SPOTLIGHT section (missing starting
    : "+line+" on line "+str(line_no)) in_SL_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 elif line[:12]=="Going Deeper": s = args.input_file.readline().decode() line_no += 1 if s != "
    \n": raise ValueError("malformed Going Deeper section (missing starting
    : "+line+" on line "+str(line_no)) in_GD_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 elif "Licenses and Attributions" in line: # this for Mental Disorders book: # if ssect_title[-26:]!=" Licenses and Attributions": # print(ssect_title) # raise ValueError("malformed Licenses and Attributions section (header wrong): '"+ssect_title[-26:]+"'"+" on line "+str(line_no)) # if ssect_title[-27]==':': # sec_name = ssect_title[:-27] # else: # sec_name = ssect_title[:-26] # probably should use sect_title instead of this sec_name... # line="

    Licenses and Attributions for "+sec_name+"

    \n" # #following code works for whether or not the author has remembered to put a box inside the LandA subsections (only recognized as a
    immediately after the

    LandA

    , though!), even mixed in the same book ... but won't handle any other use of
    s in those subsections raw_s = args.input_file.readline() s = raw_s.decode() line_no += 1 if s == "
    \n": LandA_w_tables = True else: LandA_w_tables = False; args.input_file.seek(-len(raw_s),1) in_LandA_ssec = True current_fh.write('
    \n') if not sect_title: raise ValueError("orphaned Licenses and Attributions section: '"+ssect_title[-26:]+"'"+" on line "+str(line_no)) LandA_ssec_title = chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" Licenses and Attributions for "+sect_title current_fh.write("

    "+LandA_ssec_title+"

    \n") total_html_lines += 2 elif line=="

    Learning Objectives

    \n": if args.lol: raw_s = args.input_file.readline() s = raw_s.decode() line_no += 1 if s[:3] != "
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 elif line=="

    Preview of Key Terms

    \n" or line=="

    Key Terms

    \n": in_KT_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 elif line=="

    Discussion Questions

    \n": in_DQ_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 else: current_fh.write("

    "+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+" "+ssect_title+"

    \n") total_html_lines += 1 elif line[:4] == "

    ": if line[-6:-1] != "

    ": raise ValueError("malformed line with h4 tag: "+line+" on line "+str(line_no)) sub_sub_sect_no += 1 if numbered_sects: if not h4pat.match(line): raise ValueError("malformed line with h4 tag: "+line+" on line "+str(line_no)) declared_chap_no = h4pat.sub(r'\1',line) declared_sect_no = int(h4pat.sub(r'\2',line)) declared_sub_sect_no = int(h4pat.sub(r'\3',line)) declared_sub_sub_sect_no = int(h4pat.sub(r'\4',line)) if (declared_chap_no != chap_no) or (declared_sect_no != sect_no) or (declared_sub_sect_no != sub_sect_no) or (declared_sub_sub_sect_no != sub_sub_sect_no): log_and_print(f'Level four heading "{line[4:-6]}" should be numbered {chap_no}.{str(sect_no)}.{str(sub_sect_no)}.{str(sub_sub_sect_no)}; correcting!') sssect_title = h4pat.sub(r'\5',line) else: sssect_title = line[4:-6] sub_sub_sub_sect_no=0 if sssect_title[:12]=="Going Deeper": s = args.input_file.readline().decode() line_no += 1 if s != "
    \n": raise ValueError("malformed Going Deeper section (missing starting
    : "+line+" on line "+str(line_no)) in_GD_ssec = True current_fh.write('
    \n') current_fh.write('

    '+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+"."+str(sub_sub_sect_no)+" Going Deeper

    \n") current_fh.write('
    \n') current_fh.write('
    \n') total_html_lines += 4 else: current_fh.write("

    "+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+"."+str(sub_sub_sect_no)+" "+sssect_title+"

    \n") total_html_lines += 1 elif line[:4] == "
    ": if line[-6:-1] != "
    ": raise ValueError("malformed line with h5 tag: "+line+" on line "+str(line_no)) sub_sub_sub_sect_no += 1 if numbered_sects: if not h5pat.match(line): raise ValueError("malformed line with h5 tag: "+line+" on line "+str(line_no)) declared_chap_no = h5pat.sub(r'\1',line) declared_sect_no = int(h5pat.sub(r'\2',line)) declared_sub_sect_no = int(h5pat.sub(r'\3',line)) declared_sub_sub_sect_no = int(h5pat.sub(r'\4',line)) declared_sub_sub_sub_sect_no = int(h5pat.sub(r'\5',line)) if (declared_chap_no != chap_no) or (declared_sect_no != sect_no) or (declared_sub_sect_no != sub_sect_no) or (declared_sub_sub_sect_no != sub_sub_sect_no) or (declared_sub_sub_sub_sect_no != sub_sub_sub_sect_no): log_and_print(f'Level five heading "{line[4:-6]}" should be numbered {chap_no}.{str(sect_no)}.{str(sub_sect_no)}.{str(sub_sub_sect_no)}.{str(sub_sub_sub_sect_no)}; correcting!') ssssect_title = h5pat.sub(r'\6',line) else: sssssect_title = line[4:-6] current_fh.write("

    "+chap_no+"."+str(sect_no)+"."+str(sub_sect_no)+"."+str(sub_sub_sect_no)+"."+str(sub_sub_sub_sect_no)+" "+sssssect_title+"

    \n") total_html_lines += 1 else: # the stuff below doesn't handle nested tables very well... if in_LandA_ssec and LandA_w_tables: if line=="
    \n" or line=="\n" or line=="\n": continue if line=="
    \n" or line=="
    \n": current_fh.write('\n') total_html_lines += 1 in_LandA_ssec = False continue if in_SL_ssec: if line=="\n" or line=="\n" or line=="\n" or line=="\n": continue if line=="\n": current_fh.write("\n\n") total_html_lines += 2 in_SL_ssec = False continue if in_LTtL_ssec: if line=="\n" or line=="\n" or line=="\n" or line=="\n": continue if line=="\n": current_fh.write("\n\n") total_html_lines += 2 in_LTtL_ssec = False continue if in_GD_ssec: if line=="\n" or line=="\n" or line=="\n" or line=="\n": continue if line=="\n": current_fh.write("\n\n") total_html_lines += 2 in_GD_ssec = False continue if in_LO_ssec: if line=="
      \n": LO_ssect_ol_lvl += 1 elif line=="
    \n": LO_ssect_ol_lvl -= 1 if not LO_ssect_ol_lvl: current_fh.write("\n\n") total_html_lines += 2 in_LO_ssec = False continue if in_KT_ssec and strong_KTs and KT_pattern.match(line): line=KT_pattern.sub(r'
  4. \1:',line) x=0 while "\n\n") total_html_lines += 2 if in_LandA_ssec: current_fh.write("\n") total_html_lines += 1 if current_fh: current_fh.close() new_html_files += 1 log_and_print(f'Wrote {manifest_part_lines} manifest part lines') manifest_fh.write(manifest_chapters) log_and_print(f'Wrote {manifest_chapter_lines} manifest chapter lines') manifest_fh.close() log_and_print(f'Created {new_html_files} new html files in {output}') log_and_print("Done (on "+time.strftime('%d/%m/%Y')+")!") logfile_fh.write("------------------------------------\n") logfile_fh.close()