def test(self): tree = { 'id': '[email protected]', 'shortId': None, 'contents': [ {'id': '209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'shortId': None, 'title': '(title override)'}, {'id': '[email protected]', 'shortId': '[email protected]', 'title': None, 'contents': [ {'id': 'f3c9ab70-a916-4d8c-9256-42953287b4e9@3', 'shortId': '88mrcKkW@3', 'title': '(another title override)'}, {'id': 'd395b566-5fe3-4428-bcb2-19016e3aa3ce@4', 'shortId': '05W1Zl_j@4', 'title': 'Physics: An Introduction'}]}]} nodes = self.target(tree) self.assertEqual( cnxepub.model_to_tree(nodes[0]), {'id': '209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'shortId': 'IJ3rHxpG@7', 'title': '(title override)'} ) self.assertEqual( cnxepub.model_to_tree(nodes[1]), tree['contents'][1] )
def single_html(epub_file_path, html_out=sys.stdout, mathjax_version=None, numchapters=None, includes=None): """Generate complete book HTML.""" epub = cnxepub.EPUB.from_file(epub_file_path) if len(epub) != 1: raise Exception('Expecting an epub with one book') package = epub[0] binder = cnxepub.adapt_package(package) partcount.update({}.fromkeys(parts, 0)) partcount['book'] += 1 html = cnxepub.SingleHTMLFormatter(binder, includes=includes) # Truncate binder to the first N chapters where N = numchapters. logger.debug('Full binder: {}'.format(cnxepub.model_to_tree(binder))) if numchapters is not None: apply_numchapters(html.get_node_type, binder, numchapters) logger.debug('Truncated Binder: {}'.format( cnxepub.model_to_tree(binder))) # Add mathjax to the page. if mathjax_version: etree.SubElement( html.head, 'script', src=MATHJAX_URL.format(mathjax_version=mathjax_version)) print(str(html), file=html_out) if hasattr(html_out, 'name'): # html_out is a file, close after writing html_out.close()
def collate(binder, publisher, message, cursor, includes=None): """Given a `Binder` as `binder`, collate the contents and persist those changes alongside the published content. """ binder = collate_models(binder, ruleset="ruleset.css", includes=includes) def flatten_filter(model): return isinstance(model, cnxepub.CompositeDocument) def only_documents_filter(model): return isinstance(model, cnxepub.Document) \ and not isinstance(model, cnxepub.CompositeDocument) for doc in cnxepub.flatten_to(binder, flatten_filter): publish_composite_model(cursor, doc, binder, publisher, message) for doc in cnxepub.flatten_to(binder, only_documents_filter): publish_collated_document(cursor, doc, binder) tree = cnxepub.model_to_tree(binder) publish_collated_tree(cursor, tree) return []
def test(self, cursor): binder = use_cases.setup_COMPLEX_BOOK_ONE_in_archive(self, cursor) # Build some new metadata for the composite document. metadata = [x.metadata.copy() for x in cnxepub.flatten_to_documents(binder)][0] del metadata['cnx-archive-uri'] del metadata['version'] metadata['title'] = "Made up of other things" publisher = [p['id'] for p in metadata['publishers']][0] message = "Composite addition" # Add some fake collation objects to the book. content = '<p class="para">composite</p>' composite_doc = cnxepub.CompositeDocument(None, content, metadata) from cnxpublishing.publish import publish_composite_model ident_hash = publish_composite_model(cursor, composite_doc, binder, publisher, message) # Shim the composite document into the binder. binder.append(composite_doc) tree = cnxepub.model_to_tree(binder) self.target(cursor, tree) cursor.execute("SELECT tree_to_json(%s, %s, TRUE)::json;", (binder.id, binder.metadata['version'],)) collated_tree = cursor.fetchone()[0] self.assertIn(composite_doc.ident_hash, cnxepub.flatten_tree_to_ident_hashes(collated_tree))
def test_baked(self): ident_hash = '[email protected]' binder = self.target(ident_hash, baked=True) # Briefly check for the existence of metadata. self.assertEqual(binder.metadata['title'], u'College Physics') # Check for containment expected_tree = { 'id': u'[email protected]', 'shortId': None, 'title': u'College Physics', 'contents': [{ 'id': u'209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'shortId': u'IJ3rHxpG@7', 'title': u'New Preface' }, { 'id': u'174c4069-2743-42e9-adfe-4c7084f81fc5@1', 'shortId': u'F0xAaSdD@1', 'title': u'Other Composite' }], } self.assertEqual(cnxepub.model_to_tree(binder), expected_tree)
def publish_model(cursor, model, publisher, message): """Publishes the ``model`` and return its ident_hash.""" publishers = publisher if isinstance(publishers, list) and len(publishers) > 1: raise ValueError("Only one publisher is allowed. '{}' " "were given: {}" \ .format(len(publishers), publishers)) module_ident, ident_hash = _insert_metadata(cursor, model, publisher, message) if isinstance(model, Document): file_arg = { 'module_ident': module_ident, 'filename': 'index.cnxml.html', 'mime_type': 'text/html', 'data': model.html.encode('utf-8'), } cursor.execute("""\ WITH file_insertion AS ( INSERT INTO files (file) VALUES (%(data)s) RETURNING fileid) INSERT INTO module_files (module_ident, fileid, filename, mimetype) VALUES (%(module_ident)s, (SELECT fileid FROM file_insertion), %(filename)s, %(mime_type)s)""", file_arg) for resource in model.resources: _insert_resource_file(cursor, module_ident, resource) elif isinstance(model, Binder): tree = cnxepub.model_to_tree(model) tree = _insert_tree(cursor, tree) return ident_hash
def bake(binder, recipe_id, publisher, message, cursor): """Given a `Binder` as `binder`, bake the contents and persist those changes alongside the published content. """ recipe = _get_recipe(recipe_id, cursor) includes = _formatter_callback_factory() binder = collate_models(binder, ruleset=recipe, includes=includes) def flatten_filter(model): return (isinstance(model, cnxepub.CompositeDocument) or (isinstance(model, cnxepub.Binder) and model.metadata.get('type') == 'composite-chapter')) def only_documents_filter(model): return isinstance(model, cnxepub.Document) \ and not isinstance(model, cnxepub.CompositeDocument) for doc in cnxepub.flatten_to(binder, flatten_filter): publish_composite_model(cursor, doc, binder, publisher, message) for doc in cnxepub.flatten_to(binder, only_documents_filter): publish_collated_document(cursor, doc, binder) tree = cnxepub.model_to_tree(binder) amend_tree_with_slugs(tree) publish_collated_tree(cursor, tree) return []
def bake(binder, recipe_id, publisher, message, cursor): """Given a `Binder` as `binder`, bake the contents and persist those changes alongside the published content. """ recipe = _get_recipe(recipe_id, cursor) includes = _formatter_callback_factory() binder = collate_models(binder, ruleset=recipe, includes=includes) def flatten_filter(model): return (isinstance(model, cnxepub.CompositeDocument) or (isinstance(model, cnxepub.Binder) and model.metadata.get('type') == 'composite-chapter')) def only_documents_filter(model): return isinstance(model, cnxepub.Document) \ and not isinstance(model, cnxepub.CompositeDocument) for doc in cnxepub.flatten_to(binder, flatten_filter): publish_composite_model(cursor, doc, binder, publisher, message) for doc in cnxepub.flatten_to(binder, only_documents_filter): publish_collated_document(cursor, doc, binder) tree = cnxepub.model_to_tree(binder) publish_collated_tree(cursor, tree) return []
def test(self, cursor): binder = use_cases.setup_COMPLEX_BOOK_ONE_in_archive(self, cursor) # Build some new metadata for the composite document. metadata = [ x.metadata.copy() for x in cnxepub.flatten_to_documents(binder) ][0] del metadata['cnx-archive-uri'] del metadata['version'] metadata['title'] = "Made up of other things" publisher = [p['id'] for p in metadata['publishers']][0] message = "Composite addition" # Add some fake collation objects to the book. content = '<p class="para">composite</p>' composite_doc = cnxepub.CompositeDocument(None, content, metadata) from cnxpublishing.publish import publish_composite_model ident_hash = publish_composite_model(cursor, composite_doc, binder, publisher, message) # Shim the composite document into the binder. binder.append(composite_doc) tree = cnxepub.model_to_tree(binder) self.target(cursor, tree) cursor.execute("SELECT tree_to_json(%s, %s, TRUE)::json;", ( binder.id, binder.metadata['version'], )) collated_tree = cursor.fetchone()[0] self.assertIn(composite_doc.ident_hash, cnxepub.flatten_tree_to_ident_hashes(collated_tree))
def main(argv=None): """Parse passed in cooked single HTML.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('collated_html', type=argparse.FileType('r'), help='Path to the collated html' ' file (use - for stdin)') parser.add_argument('-d', '--dump-tree', action='store_true', help='Print out parsed model tree.') parser.add_argument('-o', '--output', type=argparse.FileType('w+'), help='Write out epub of parsed tree.') parser.add_argument('-i', '--input', type=argparse.FileType('r'), help='Read and copy resources/ for output epub.') args = parser.parse_args(argv) if args.input and args.output == sys.stdout: raise ValueError('Cannot output to stdout if reading resources') from cnxepub.collation import reconstitute binder = reconstitute(args.collated_html) if args.dump_tree: print(pformat(cnxepub.model_to_tree(binder)), file=sys.stdout) if args.output: cnxepub.adapters.make_epub(binder, args.output) if args.input: args.output.seek(0) zout = ZipFile(args.output, 'a', ZIP_DEFLATED) zin = ZipFile(args.input, 'r') for res in zin.namelist(): if res.startswith('resources'): zres = zin.open(res) zi = zin.getinfo(res) zout.writestr(zi, zres.read(), ZIP_DEFLATED) zout.close() # TODO Check for documents that have no identifier. # These should likely be composite-documents # or the the metadata got wiped out. # docs = [x for x in cnxepub.flatten_to(binder, only_documents_filter) # if x.ident_hash is None] return 0
def publish_model(cursor, model, publisher, message): """Publishes the ``model`` and return its ident_hash.""" publishers = publisher if isinstance(publishers, list) and len(publishers) > 1: raise ValueError("Only one publisher is allowed. '{}' " "were given: {}".format(len(publishers), publishers)) module_ident, ident_hash = _insert_metadata(cursor, model, publisher, message) for resource in getattr(model, 'resources', []): _insert_resource_file(cursor, module_ident, resource) if isinstance(model, Document): html = str(cnxepub.DocumentContentFormatter(model)).encode('utf-8') sha1 = hashlib.new('sha1', html).hexdigest() cursor.execute("SELECT fileid FROM files WHERE sha1 = %s", (sha1, )) try: fileid = cursor.fetchone()[0] except TypeError: file_args = { 'media_type': 'text/html', 'data': psycopg2.Binary(html), } cursor.execute( """\ insert into files (file, media_type) VALUES (%(data)s, %(media_type)s) returning fileid""", file_args) fileid = cursor.fetchone()[0] args = { 'module_ident': module_ident, 'filename': 'index.cnxml.html', 'fileid': fileid, } cursor.execute( """\ INSERT INTO module_files (module_ident, fileid, filename) VALUES (%(module_ident)s, %(fileid)s, %(filename)s)""", args) elif isinstance(model, Binder): tree = cnxepub.model_to_tree(model) tree = _insert_tree(cursor, tree) return ident_hash
def publish_model(cursor, model, publisher, message): """Publishes the ``model`` and return its ident_hash.""" publishers = publisher if isinstance(publishers, list) and len(publishers) > 1: raise ValueError("Only one publisher is allowed. '{}' " "were given: {}" .format(len(publishers), publishers)) module_ident, ident_hash = _insert_metadata(cursor, model, publisher, message) for resource in getattr(model, 'resources', []): _insert_resource_file(cursor, module_ident, resource) if isinstance(model, Document): html = bytes(cnxepub.DocumentContentFormatter(model)) sha1 = hashlib.new('sha1', html).hexdigest() cursor.execute("SELECT fileid FROM files WHERE sha1 = %s", (sha1,)) try: fileid = cursor.fetchone()[0] except TypeError: file_args = { 'media_type': 'text/html', 'data': psycopg2.Binary(html), } cursor.execute("""\ insert into files (file, media_type) VALUES (%(data)s, %(media_type)s) returning fileid""", file_args) fileid = cursor.fetchone()[0] args = { 'module_ident': module_ident, 'filename': 'index.cnxml.html', 'fileid': fileid, } cursor.execute("""\ INSERT INTO module_files (module_ident, fileid, filename) VALUES (%(module_ident)s, %(fileid)s, %(filename)s)""", args) elif isinstance(model, Binder): tree = cnxepub.model_to_tree(model) tree = _insert_tree(cursor, tree) return ident_hash
def test_baked(self): ident_hash = '[email protected]' binder = self.target(ident_hash, baked=True) # Briefly check for the existence of metadata. self.assertEqual(binder.metadata['title'], u'College Physics') # Check for containment expected_tree = { 'id': u'[email protected]', 'shortId': '[email protected]', 'title': u'College Physics', 'contents': [ {'id': u'209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'shortId': u'IJ3rHxpG@7', 'title': u'New Preface'}, {'id': u'174c4069-2743-42e9-adfe-4c7084f81fc5@1', 'shortId': u'F0xAaSdD@1', 'title': u'Other Composite'} ], } self.assertEqual(cnxepub.model_to_tree(binder), expected_tree)
def test_assembly(self): ident_hash = '[email protected]' binder = self.target(ident_hash) # Briefly check for the existence of metadata. self.assertEqual(binder.metadata['title'], u'College Physics') # Check for containment expected_tree = { 'shortId': None, 'id': u'[email protected]', 'title': u'College Physics', 'contents': [{ 'shortId': u'IJ3rHxpG@7', 'id': u'209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'title': u'Preface' }, { 'shortId': u'subcol', 'id': u'subcol', 'title': u'Introduction: The Nature of Science and Physics', 'contents': [{ 'shortId': u'88mrcKkW@3', 'id': u'f3c9ab70-a916-4d8c-9256-42953287b4e9@3', 'title': u'Introduction to Science and the Realm of Physics, Physical Quantities, and Units' }, { 'shortId': u'05W1Zl_j@4', 'id': u'd395b566-5fe3-4428-bcb2-19016e3aa3ce@4', 'title': u'Physics: An Introduction' }, { 'shortId': u'yL26vGKx@6', 'id': u'c8bdbabc-62b1-4a5f-b291-982ab25756d7@6', 'title': u'Physical Quantities and Units' }, { 'shortId': u'UVLOqIKa@7', 'id': u'5152cea8-829a-4aaf-bcc5-c58a416ecb66@7', 'title': u'Accuracy, Precision, and Significant Figures' }, { 'shortId': u'WDixBUHN@5', 'id': u'5838b105-41cd-4c3d-a957-3ac004a48af3@5', 'title': u'Approximation' }] }, { 'shortId': u'subcol', 'id': u'subcol', 'title': u"Further Applications of Newton's Laws: Friction, Drag, and Elasticity", 'contents': [{ 'shortId': u'JKLtEyKm@2', 'id': u'24a2ed13-22a6-47d6-97a3-c8aa8d54ac6d@2', 'title': u'Introduction: Further Applications of Newton\u2019s Laws' }, { 'shortId': u'6icTBvfy@5', 'id': u'ea271306-f7f2-46ac-b2ec-1d80ff186a59@5', 'title': u'Friction' }, { 'shortId': u'JjRqQoS5@6', 'id': u'26346a42-84b9-48ad-9f6a-62303c16ad41@6', 'title': u'Drag Forces' }, { 'shortId': u'VvHFwUAU@8', 'id': u'56f1c5c1-4014-450d-a477-2121e276beca@8', 'title': u'Elasticity: Stress and Strain' }], }, { 'shortId': u'9gJNihho@3', 'id': u'f6024d8a-1868-44c7-ab65-45419ef54881@3', 'title': u'Atomic Masses' }, { 'shortId': u'clA4axSn@2', 'id': u'7250386b-14a7-41a2-b8bf-9e9ab872f0dc@2', 'title': u'Selected Radioactive Isotopes' }, { 'shortId': u'wKdmWcMR@5', 'id': u'c0a76659-c311-405f-9a99-15c71af39325@5', 'title': u'Useful Inf\xf8rmation' }, { 'shortId': u'rj4Y3mON@5', 'id': u'ae3e18de-638d-4738-b804-dc69cd4db3a3@5', 'title': u'Glossary of Key Symbols and Notation' }], } self.maxDiff = None self.assertEqual(cnxepub.model_to_tree(binder), expected_tree) # Check translucent binder metadata translucent_binder = binder[1] self.assertTrue( isinstance(translucent_binder, cnxepub.TranslucentBinder)) expected_metadata = { 'id': u'subcol', 'shortId': 'subcol', 'title': 'Introduction: The Nature of Science and Physics', } self.assertEqual(expected_metadata, translucent_binder.metadata)
def test_assembly(self): ident_hash = '[email protected]' binder = self.target(ident_hash) # Briefly check for the existence of metadata. self.assertEqual(binder.metadata['title'], u'College Physics') # Check for containment expected_tree = { 'shortId': '[email protected]', 'id': u'[email protected]', 'title': u'College Physics', 'contents': [ {'shortId': u'IJ3rHxpG@7', 'id': u'209deb1f-1a46-4369-9e0d-18674cf58a3e@7', 'title': u'Preface'}, { 'id': u'[email protected]', 'shortId': u'[email protected]', 'title': u'Introduction: The Nature of Science and Physics', 'contents': [ {'shortId': u'88mrcKkW@3', 'id': u'f3c9ab70-a916-4d8c-9256-42953287b4e9@3', 'title': u'Introduction to Science and the Realm of Physics, Physical Quantities, and Units'}, {'shortId': u'05W1Zl_j@4', 'id': u'd395b566-5fe3-4428-bcb2-19016e3aa3ce@4', 'title': u'Physics: An Introduction'}, {'shortId': u'yL26vGKx@6', 'id': u'c8bdbabc-62b1-4a5f-b291-982ab25756d7@6', 'title': u'Physical Quantities and Units'}, {'shortId': u'UVLOqIKa@7', 'id': u'5152cea8-829a-4aaf-bcc5-c58a416ecb66@7', 'title': u'Accuracy, Precision, and Significant Figures'}, {'shortId': u'WDixBUHN@5', 'id': u'5838b105-41cd-4c3d-a957-3ac004a48af3@5', 'title': u'Approximation'}]}, {'shortId': u'[email protected]', 'id': u'[email protected]', 'title': u"Further Applications of Newton's Laws: Friction, Drag, and Elasticity", 'contents': [ {'shortId': u'JKLtEyKm@2', 'id': u'24a2ed13-22a6-47d6-97a3-c8aa8d54ac6d@2', 'title': u'Introduction: Further Applications of Newton\u2019s Laws'}, {'shortId': u'6icTBvfy@5', 'id': u'ea271306-f7f2-46ac-b2ec-1d80ff186a59@5', 'title': u'Friction'}, {'shortId': u'JjRqQoS5@6', 'id': u'26346a42-84b9-48ad-9f6a-62303c16ad41@6', 'title': u'Drag Forces'}, {'shortId': u'VvHFwUAU@8', 'id': u'56f1c5c1-4014-450d-a477-2121e276beca@8', 'title': u'Elasticity: Stress and Strain'}], }, {'shortId': u'9gJNihho@3', 'id': u'f6024d8a-1868-44c7-ab65-45419ef54881@3', 'title': u'Atomic Masses'}, {'shortId': u'clA4axSn@2', 'id': u'7250386b-14a7-41a2-b8bf-9e9ab872f0dc@2', 'title': u'Selected Radioactive Isotopes'}, {'shortId': u'wKdmWcMR@5', 'id': u'c0a76659-c311-405f-9a99-15c71af39325@5', 'title': u'Useful Inf\xf8rmation'}, {'shortId': u'rj4Y3mON@5', 'id': u'ae3e18de-638d-4738-b804-dc69cd4db3a3@5', 'title': u'Glossary of Key Symbols and Notation'}], } self.maxDiff = None self.assertEqual(cnxepub.model_to_tree(binder), expected_tree) # Check translucent binder metadata translucent_binder = binder[1] self.assertTrue( isinstance(translucent_binder, cnxepub.TranslucentBinder)) expected_metadata = { 'id': u'[email protected]', 'shortId': u'[email protected]', 'title': 'Introduction: The Nature of Science and Physics', } for k, v in expected_metadata.items(): self.assertEqual(translucent_binder.metadata[k], v)