{ "metadata": { "name": "06-NBconvert-Doc-Draft" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "How to Use NBConvert" ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Warning" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This might have been redacted with a slighly modified version of nbconvert to fix a few bug and reflect more what shoudl happend than what is happening with current nbconvert." ] }, { "cell_type": "heading", "level": 4, "metadata": {}, "source": [ "Intro" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this post I will introduce you to the programatic API of nbconvert to show you how to use it in various context. \n", "\n", "For this I will use one of [@jakevdp](https://github.com/jakevdp) great [blog post](http://jakevdp.github.io/blog/2013/04/15/code-golf-in-python-sudoku/).\n", "I've explicitely chosen a post with no javascript tricks as Jake seem to be found of right now, for the reason that the becommings of embeding javascript in nbviewer, which is based on nbconvert is not fully decided yet. \n", "\n", "\n", "This will not focus on using the command line tool to convert file. The attentive reader will point-out that no data are read from, or written to disk during the conversion process. Indeed, nbconvert as been though as much as\n", "possible to avoid IO operation and work as well in a database, or web-based environement." ] }, { "cell_type": "heading", "level": 4, "metadata": {}, "source": [ "Quick overview" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The main principle of nbconvert is to instanciate a `Exporter` that controle\n", "a pipeline through which each notebook you want to export with go through.\n", "\n", "Let's start by importing what we need from the API, and download @jakevdp's notebook." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import requests\n", "response = requests.get('http://jakevdp.github.com/downloads/notebooks/XKCD_plots.ipynb')\n", "response.content[0:60]+'...'" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 2, "text": [ "'{\\n \"metadata\": {\\n \"name\": \"XKCD_plots\"\\n },\\n \"nbformat\": 3,\\n...'" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We read the response into a slightly more convenient format which represent IPython notebook. \n", "There are not real advantages for now, except some convenient methods, but with time this structure should be able to\n", "guarantee that the notebook structure is valid." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.nbformat import current as nbformat\n", "jake_notebook = nbformat.reads_json(response.content)\n", "jake_notebook.worksheets[0].cells[0]" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 3, "text": [ "{u'cell_type': u'heading',\n", " u'level': 1,\n", " u'metadata': {},\n", " u'source': u'XKCD plots in Matplotlib'}" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we have here Jake's notebook in a convenient for, which is mainly a Super-Powered dict and list nested.\n", "You don't need to worry about the exact structure." ] }, { "cell_type": "code", "collapsed": false, "input": [ "cd ~/nbconvert/" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "/Users/bussonniermatthias/nbconvert\n" ] } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The nbconvert API exposes some basic exporter for common format and default options. We will start\n", "by using one of them. First we import it, instanciate an instance with all the defautl parameters and fed it\n", "the downloaded notebook. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "import nbconvert\n", "from nbconvert import BasicHtmlExporter\n", "\n", "exportHtml = BasicHtmlExporter()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 5 }, { "cell_type": "code", "collapsed": false, "input": [ "(body,resources) = exportHtml.from_notebook_node(jake_notebook)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 6 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The exporter returns a tuple containing the body of the converted notebook, here raw HTML, as well as a resources dict.\n", "The resource dict contains (among many things) the extracted PNG, JPG [...etc] from the notebook when applicable.\n", "The basic HTML exporter does keep them as embeded base64 into the notebook, but one can do ask the figures to be extracted. Cf advance use. So for now the resource dict **should** be mostly empty, except for 1 key containing some css.\n", "\n", "Exporter are stateless, you won't be able to extract any usefull information (except their configuration) from them.\n", "You can directly re-use the instance to convert another notebook. Each exporter expose for convenience a `from_file` and `from_filename` methods if you need." ] }, { "cell_type": "code", "collapsed": false, "input": [ "resources.keys()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": [ "['inlining']" ] } ], "prompt_number": 7 }, { "cell_type": "code", "collapsed": false, "input": [ "# Part of the body, here the first Heading\n", "print body[:200]+'...'" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\n", "
\n", "

\n", "\n", "\n", " XKCD plots in Matplotlib\n", "\n", "

\n", "`_\n", "by Jake Vanderplas.\n", "\n", " One of the problems I've had with typical matplotlib figures is that\n", "everything in them is so precise, so perfect. For an example of what I\n", "mean, take a look at this figure:\n", "\n", "In[1]:\n", "\n", ".. code:: python\n", "\n", " from IPython.display import Image\n", " Image('http://jakevdp.github.com/figures/xkcd_version.png')\n", "\n", "\n", ".. image:: _fig_01.png\n", "\n", "\n", "Sometimes wh...\n", "[.....]\n", "om/figures/mpl_version.png')\n", "\n", "\n", ".. image:: _fig_03.png\n", "\n", "\n", "It just doesn't have the same effect. Matplotlib is great for scientific\n", "plots, but sometimes you don't want to be so precise.\n", "\n", "This subject has...\n" ] } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we see that base64 images are not embeded, but we get what look like file name. Actually those are (Configurable) keys to get back the binary data from the resources dict we havent inspected earlier.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So when writing a Rst Plugin for any blogengine, Sphinx or anything else, you will be responsible for writing all those data to disk, in the right place. \n", "Of course to help you in this task all those naming are configurable in the right place." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "let's try to see how to get one of these images" ] }, { "cell_type": "code", "collapsed": false, "input": [ "resources['figures']['binary'].keys()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 11, "text": [ "[u'_fig_07.png',\n", " u'_fig_09.png',\n", " u'_fig_03.png',\n", " u'_fig_12.png',\n", " u'_fig_01.png']" ] } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have extracted 5 binary figures, here `png`s, but they could have been svg, and then wouldn't appear in the binary sub dict.\n", "keep in mind that a object having multiple _repr_ will store all it's repr in the notebook. \n", "\n", "Hence if you provide `_repr_javascript_`,`_repr_latex_` and `_repr_png_`to an object, you will be able to determine at conversion time which representaition is the more appropriate. You could even decide to show all the representaition of an object, it's up to you. But this will require beeing a little more involve and write a few line of Jinja template. This will probably be the subject of another tutorial.\n", "\n", "Back to our images,\n", "\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.display import Image\n", "Image(data=resources['figures']['binary']['_fig_07.png'],format='png')" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "png": "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\nYII=\n", "prompt_number": 12, "text": [ "" ] } ], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Yep, this is indeed the image we were expecting, and I was able to see it without ever writing or reading it from disk. I don't think I'll have to show to you what to do with those data, as if you are here you are most probably familiar with IO." ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "Extracting figures with HTML Exporter ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use case:\n", "\n", "> I write an [awesome blog](http://jakevdp.github.io/) in HTML, and I want all but having base64 embeded images. \n", "Having one html file with all inside is nice to send to coworker, but I definitively want resources to be cached !\n", "So I need an HTML exporter, and I want it to extract the figures !" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Some theory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The process of converting a notebook to a another format with the nbconvert Exporters happend in a few steps:\n", "\n", " - Get the notebook data and other required files. (you are responsible for that)\n", " - Feed them to the exporter that will\n", " - sequentially feed the data to a number of `Transformers`. Transformer only act on the **structure**\n", " of the notebook, and have access to it all. \n", " - feed the notebook through the jinja templating engine\n", " - the use templates are configurable.\n", " - templates make use of configurable macros called filters.\n", " - The exporter return the converted notebook as well as other relevant resources as a tuple.\n", " - Write what you need to disk, or elsewhere. (You are responsible for it)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here we'll be interested in the `Transformers`. Each `Transformer` is applied successively and in order on the notebook before going through the conversion process.\n", "\n", "We provide some transformer that do some modification on the notebook structure by default.\n", "One of them, the `ExtractFigureTransformer` is responsible for crawling notebook,\n", "finding all the figures, and put them into the resources directory, as well as choosing the key\n", "(`_fig_xx.png`) that can replace the figure in the template.\n", "\n", "\n", "Thes `ExtractFigureTransformer` is special in the fact that it **should** be availlable on all `Exporter`s, but is just inactive." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# second transformer shoudl be Instance of ExtractFigureTransformer\n", "print exportHtml.transformers" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[, , ]\n" ] } ], "prompt_number": 13 }, { "cell_type": "code", "collapsed": false, "input": [ "print rst_export.transformers" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[, ]\n" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "To enable it we will use IPython configuration/Traitlets system. If you are familiar with it, \n", "this will look pretty familiar to you. Configuration option are always of the form:\n", "\n", " ClassName.attribute_name = value\n", " \n", "A few ways exist to create such config, like reading a config file in your profile, but you can also do it programatically usign a dictionary. Let's create such a config object, and see the difference if we pass it to our `HtmlExporter`" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.config import Config\n", "\n", "c = Config({\n", " 'ExtractFigureTransformer':{'enabled':True}\n", " })\n", "\n", "exportHtml = BasicHtmlExporter()\n", "exportHtml_and_figs = BasicHtmlExporter(config=c)\n", "\n", "(_, resources) = exportHtml.from_notebook_node(jake_notebook)\n", "(_, resources_with_fig) = exportHtml_and_figs.from_notebook_node(jake_notebook)\n", "\n", "print 'resources without the \"figures\" key :'\n", "print resources.keys()\n", "\n", "print ''\n", "print 'resources with the \"figures\" key, subkey \"binary\" with each figures as subkeys and values :'\n", "print resources_with_fig['figures']['binary'].keys() " ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "resources without the \"figures\" key :\n", "['inlining']\n", "\n", "resources with the \"figures\" key, subkey \"binary\" with each figures as subkeys and values :\n", "[u'_fig_07.png', u'_fig_09.png', u'_fig_03.png', u'_fig_12.png', u'_fig_01.png']\n" ] } ], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So now you can loop through the dict and write all those figures to disk in the right place... well at least you got the basics, this won't be enough for your blog,I'll let you fire out why by yourself. But more on that later. " ] }, { "cell_type": "heading", "level": 4, "metadata": {}, "source": [ "Custom transformer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course you can imagine many transformation that you would like to apply to a notebook. This is one of the reason we provide a way to register your own transformers that will be applied to the notebook after the default ones.\n", "\n", "To do so you'll have to pass an ordered list of `Transformer`s to the Exporter constructor. \n", "\n", "But what is an transformer ? Transformer can be either *decorated function* for dead-simple `Transformer`s that apply\n", "independently to each cell, for more advance transformation that support configurability You have to inherit from\n", "`Transformer` and define a `call` method as we'll see below.\n", "\n", "Here, in particular I'll inherit `ActivatableTransformer` that will give your transformer a magic attribute that allows it to be activated/disactivate from the config dict.\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from nbconvert.transformers.activatable import ActivatableTransformer, ConfigurableTransformer\n", "import IPython.config\n", "print \"Four relevant docstring\"\n", "print '============================='\n", "print ActivatableTransformer.__doc__\n", "print '============================='\n", "print ConfigurableTransformer.__doc__\n", "print '============================='\n", "print ConfigurableTransformer.call.__doc__\n", "print '============================='\n", "print ConfigurableTransformer.cell_transform.__doc__\n", "print '============================='" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Four relevant docstring\n", "=============================\n", "ConfigurableTransformer that has an enabled flag\n", "\n", " Inherit from this if you just want to have a transformer which is\n", " disable by default and can be enabled via the config by\n", " 'c.YourTransformerName.enabled = True'\n", " \n", "=============================\n", " A configurable transformer\n", "\n", " Inherit from this class if you wish to have configurability for your\n", " transformer.\n", "\n", " Any configurable traitlets this class exposed will be configurable in profiles\n", " using c.SubClassName.atribute=value\n", "\n", " you can overwrite cell_transform to apply a transformation independently on each cell\n", " or __call__ if you prefer your own logic. See orresponding docstring for informations.\n", " \n", "=============================\n", "\n", " Transformation to apply on each notebook.\n", " \n", " You should return modified nb, resources.\n", " If you wish to apply your transform on each cell, you might want to \n", " overwrite cell_transform method instead.\n", " \n", " Parameters\n", " ----------\n", " nb : NotebookNode\n", " Notebook being converted\n", " resources : dictionary\n", " Additional resources used in the conversion process. Allows\n", " transformers to pass variables into the Jinja engine.\n", " \n", "=============================\n", "\n", " Overwrite if you want to apply a transformation on each cell. You \n", " should return modified cell and resource dictionary.\n", " \n", " Parameters\n", " ----------\n", " cell : NotebookNode cell\n", " Notebook cell being processed\n", " resources : dictionary\n", " Additional resources used in the conversion process. Allows\n", " transformers to pass variables into the Jinja engine.\n", " index : int\n", " Index of the cell being processed\n", " \n", "=============================\n" ] } ], "prompt_number": 16 }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "We don't provide convenient method to be aplied on each worksheet as the **data structure** for worksheet will be removed. (not the worksheet functionnality, which is still on it's way)\n", "***" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "Example" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I'll now demonstrate a specific example [requested](https://github.com/ipython/nbconvert/pull/137#issuecomment-18658235) while nbconvert 2 was beeing developped. The ability to exclude cell from the conversion process based on their index. \n", "\n", "I'll let you imagin how to inject cell, if what you just want is to happend static content at the beginning/end of a notebook, plese refer to templating section, it will be much easier and cleaner." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.utils.traitlets import Integer, Bool\n", "from copy import deepcopy" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 17 }, { "cell_type": "code", "collapsed": false, "input": [ "class PelicanSubCell(ActivatableTransformer):\n", " \"\"\"A Pelican specific transformer to remove somme of the cells of a notebook\"\"\"\n", " \n", " # I could also read the cells from nbc.metadata.pelican is someone wrote a JS extension\n", " # But I'll stay with configurable value. \n", " start = Integer(0, config=True, help=\"first cell of notebook to be converted\")\n", " end = Integer(-1, config=True, help=\"last cell of notebook to be converted\")\n", " verbose = Bool(False, config=True, help=\"Shoudl I speek too much\")\n", " \n", " def call(self, nb, resources):\n", "\n", " #nbc = deepcopy(nb)\n", " nbc = nb\n", " # don't print in real transformer !!!\n", " if self.verbose :\n", " print \"I'll keep only cells from \", self.start, \"to \", self.end, \"\\n\"\n", " for worksheet in nbc.worksheets :\n", " cells = worksheet.cells[:]\n", " worksheet.cells = cells[self.start:self.end] \n", " return nbc, resources" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": [ "# I create this on the fly, but this could be loaded from a DB, and config object support merging...\n", "c = Config({\n", " 'PelicanSubCell':{\n", " 'enabled':True,\n", " 'start':4,\n", " 'end':6,\n", " }\n", " })\n", "\n", "# additionaly, I'll make if verbose\n", "c.PelicanSubCell.verbose = True" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 19 }, { "cell_type": "markdown", "metadata": {}, "source": [ "I'm creating a pelican exporter that take `PelicanSubCell` extra transformers and a `config` object as parameter. This might seem redundant, but with configuration system you'll see that one can register an inactive transformer on all exporters and activate it at will form its config files and command line. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "pelican = RstExporter(transformers=[PelicanSubCell], config=c)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 20 }, { "cell_type": "code", "collapsed": false, "input": [ "print pelican.from_notebook_node(jake_notebook)[0]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "I'll keep only cells from 4 to 6 \n", "\n", "Sometimes when showing schematic plots, this is the type of figure I\n", "want to display. But drawing it by hand is a pain: I'd rather just use\n", "matplotlib. The problem is, matplotlib is a bit too precise. Attempting\n", "to duplicate this figure in matplotlib leads to something like this:\n", "\n", "In[2]:\n", "\n", ".. code:: python\n", "\n", " Image('http://jakevdp.github.com/figures/mpl_version.png')\n", "\n", "\n", ".. image:: _fig_03.png\n", "\n", "\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 21 }, { "cell_type": "heading", "level": 4, "metadata": {}, "source": [ "Changing references to figures into converted Format" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This paragraph will be more a specifc example that something general, but it hav enough specificity to be fully explain. \n", "In most of the conversion process, you will probably want to have the extracted figures/graphs not in the same folder than the\n", "generated html or tex file. \n", "\n", "I'll take the example of the LaTeX converter where, most of the time, you will want the figures to be saved under the `figs/` folder. \n", "and As you can't directly include the `svg`, you will need another name to be included in the `.tex` file\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How does part of the latex Exporter look like without customisation ?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from nbconvert.exporters.latex import LatexExporter" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 22 }, { "cell_type": "code", "collapsed": false, "input": [ "# same config as before\n", "c = Config({\n", " 'PelicanSubCell':{\n", " 'enabled':True,\n", " 'start':4,\n", " 'end':6,\n", " },\n", " })\n", "lpelican = LatexExporter(transformers=[PelicanSubCell], config=c)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 23 }, { "cell_type": "code", "collapsed": false, "input": [ "print lpelican.from_notebook_node(jake_notebook)[0][4027:]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\\begin{document}\n", "\n", "\n", "Sometimes when showing schematic plots, this is the type of figure I\n", "want to display. But drawing it by hand is a pain: I'd rather just use\n", "matplotlib. The problem is, matplotlib is a bit too precise. Attempting\n", "to duplicate this figure in matplotlib leads to something like this:\n", "\\begin{codecell}\n", "\\begin{codeinput}\n", "\\begin{lstlisting}\n", "Image('http://jakevdp.github.com/figures/mpl_version.png')\n", "\\end{lstlisting}\n", "\\end{codeinput}\n", "\\begin{codeoutput}\n", "\n", "\\begin{center}\n", "\\includegraphics[width=0.7\\textwidth, height=0.9\\textheight, keepaspectratio]{_fig_03.png}\n", "\\par\n", "\\end{center}\n", "\n", "\\end{codeoutput}\n", "\\end{codecell}\n", "\n", "\n", "\n", "\\end{document}\n", "\n", "\n" ] } ], "prompt_number": 24 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Clearly the `\\includegraphics[...]{_fig_03.png}` is probably not what you want. To help us with that we will use 2 configurables values of the `ExtractFigureTransformers`\n", "\n", "***\n", "### Disclamer : ###\n", "I'm not particulary happy with this part of the configurability, the exact naming may change, feedback and ideas welcommed\n", "***\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "the property are the following :\n", "\n", " - key_format_map\n", " - figure_name_format_map\n", " \n", "They respectively control the template of *key*s in the `resources` dict returned, the template of the figure names as they appear in the converted document." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "Carefull reader will discover in the above cell a nice way to modify aconfig object, and will recognize\n", "something which is really close to the IPython config file syntax. One might forsee how the user\n", "will be able to create it's own converter through config file in a near future.\n", "\n", "The curious reader wan create multiple config object and will look at the possibilities that the `.update()` methods allow.\n", "***" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A practical example would be to extract `svg`s into a `figs/svgs/` folder then convert them to `.ps` in `figs/ps/`.\n", "\n", "As the notebook I'm working with does not contain svgs, let's say that I will extract `png`s into the `figs/pngs/` folder, and will convert them to tiffs into the `figs/tiffs` folder. So I need the `.tex` file to have the correct `includegraphics`...." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# use a diffrent templates for pngs links into tex file:\n", "\n", "c.ExtractFigureTransformer.update({'figure_name_format_map':{'png':'figs/tiffs/fig_{index:04d}.tiff'}})\n", "c" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 25, "text": [ "{'ExtractFigureTransformer': {'figure_name_format_map': {'png': 'figs/tiffs/fig_{index:04d}.tiff'}},\n", " 'PelicanSubCell': {'enabled': True, 'end': 6, 'start': 4}}" ] } ], "prompt_number": 25 }, { "cell_type": "code", "collapsed": false, "input": [ "lpelican = LatexExporter(transformers=[PelicanSubCell], config=c)\n", "\n", "body,resources = lpelican.from_notebook_node(jake_notebook)\n", "\n", "print body[4509:-70]" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "includegraphics[width=0.7\\textwidth, height=0.9\\textheight, keepaspectratio]{figs/tiffs/fig_0003.tiff}\n", "\n" ] } ], "prompt_number": 26 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Great, so now, the value in the converted document have changed, but not the keys in the `resources` dict. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "lpelican.config" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 27, "text": [ "{'ExtractFigureTransformer': {'enabled': True,\n", " 'extra_ext_map': {'svg': 'pdf'},\n", " 'figure_name_format_map': {'png': 'figs/tiffs/fig_{index:04d}.tiff'}},\n", " 'GlobalConfigurable': {'display_data_priority': ['latex',\n", " 'svg',\n", " 'png',\n", " 'jpg',\n", " 'jpeg',\n", " 'text']},\n", " 'PelicanSubCell': {'enabled': True, 'end': 6, 'start': 4}}" ] } ], "prompt_number": 27 }, { "cell_type": "code", "collapsed": false, "input": [ "resources['figures']['binary'].keys()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 28, "text": [ "[u'_fig_07.png',\n", " u'_fig_09.png',\n", " u'_fig_03.png',\n", " u'_fig_12.png',\n", " u'_fig_01.png']" ] } ], "prompt_number": 28 }, { "cell_type": "markdown", "metadata": {}, "source": [ "I can fix that by setting the `key_format_map`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "c.ExtractFigureTransformer.key_format_map = {'png':'png-tiff.{index}.{ext}'}" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 29 }, { "cell_type": "code", "collapsed": false, "input": [ "c" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 30, "text": [ "{'ExtractFigureTransformer': {'figure_name_format_map': {'png': 'figs/tiffs/fig_{index:04d}.tiff'},\n", " 'key_format_map': {'png': 'png-tiff.{index}.{ext}'}},\n", " 'PelicanSubCell': {'enabled': True, 'end': 6, 'start': 4}}" ] } ], "prompt_number": 30 }, { "cell_type": "code", "collapsed": false, "input": [ "lpelican = LatexExporter(transformers=[PelicanSubCell], config=c)\n", "(body,resources)= lpelican.from_notebook_node(jake_notebook)\n", "resources['figures']['binary'].keys()" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 31, "text": [ "['png-tiff.9.png',\n", " 'png-tiff.1.png',\n", " 'png-tiff.12.png',\n", " 'png-tiff.3.png',\n", " 'png-tiff.7.png']" ] } ], "prompt_number": 31 }, { "cell_type": "markdown", "metadata": {}, "source": [ "***\n", "The current version of the ipynb files store the data relative to each display format by using file extension (png, json, jpeg), you can use `{ext}` to access those in the previous templates.\n", "\n", "In following version of ipynb format, the data will most likely be organized by mimetype. Which will require some change in this place. \n", "***" ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "More to come. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I think this is enough for now, As you have seen there are a few bugs here and there I need to correct before continuing. \n", "Next time I'll show you how to modify template : \n", "\n", " {%- extends 'fullhtml.tpl' -%}\n", " {% block input_group -%}\n", " {% endblock input_group %}\n", " \n", "... and you just removed all the codecell by keeping the output and markdown codecell, isn't that wonderfull ?\n", "You want to wrap each cell in your own div ?\n", "\n", " {%- extends 'fullhtml.tpl' -%}\n", " {% block codecell %}\n", "
\n", " {{ super() }}\n", "
\n", " {%- endblock codecell %}\n", "\n", "Try to look at what [Jinja](http://jinja.pocoo.org/docs/) can do, then learn about Jinja Filters and imagine\n", "they can magically read your config file. \n", "\n", "For example we provide a filter that highlight by presupposing code is Python. Or one that wraps text at a default length of 80 char... Want a rot13 filter on some codecell when doing exercises for student ? See you next time !\n" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from nbconvert.exporters.reveal import RevealExporter" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 32 }, { "cell_type": "code", "collapsed": false, "input": [ "r = RevealExporter()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 33 }, { "cell_type": "code", "collapsed": false, "input": [ "r.config" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 34, "text": [ "{'CSSHtmlHeaderTransformer': {'enabled': True}}" ] } ], "prompt_number": 34 }, { "cell_type": "code", "collapsed": false, "input": [ "%config IPCompleter.greedy = True" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "cd ~/nbconvert" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "/Users/bussonniermatthias/nbconvert\n" ] } ], "prompt_number": 2 }, { "cell_type": "code", "collapsed": false, "input": [ "from nbconvert.exporters import LatexExporter\n", "s = LatexExporter()\n", "print s.transformers[1].config\n", "print s.transformers[1].display_data_priority" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "{'ExtractFigureTransformer': {'enabled': True, 'extra_ext_map': {'svg': 'pdf'}}, 'GlobalConfigurable': {'display_data_priority': ['latex', 'svg', 'png', 'jpg', 'jpeg', 'text']}}\n", "['latex', 'svg', 'png', 'jpg', 'jpeg', 'text']\n" ] } ], "prompt_number": 3 }, { "cell_type": "code", "collapsed": false, "input": [], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 36 }, { "cell_type": "markdown", "metadata": {}, "source": [] } ], "metadata": {} } ] }