使用PHP和Markdown构建ePub

本文介绍了如何使用PHP和Markdown构建符合ePub标准的电子书。通过讲解ePub格式的基本结构,以及一个名为md2epub的开源工具,展示了如何将Markdown文件转换为XHTML并整合成ePub包。md2epub工具处理内容文件,包括Markdown解析、文件组织、元数据处理等,最后生成符合规范的ePub文件。
摘要由CSDN通过智能技术生成

The ePub format is a publishing standard built on top of XHTML, CSS, XML and more. And since PHP is well suited for working with HTML and friends, why not use it to build ebooks? In this article we’ll see what goes into building a tool for creating ePub packages starting from a set of content files. Maybe it’s your next best selling cyber-sci-fi novel or documentation for your latest code project… because we all write good documentation for our projects, don’t we?

ePub格式是在XHTML,CSS,XML等之上构建的发布标准。 而且由于PHP非常适合与HTML和朋友一起使用,所以为什么不使用它来构建电子书呢? 在本文中,我们将介绍如何构建一个用于从一组内容文件开始创建ePub软件包的工具。 也许这是您下一个最畅销的网络科幻小说或最新代码项目的文档……因为我们都为我们的项目编写了很好的文档,不是吗?

In order to make things easy on the writers, our tool will be able to parse the Markdown syntax and the resulting markup will be placed into HTML templates using the RainTPL library.

为了使编写者更轻松,我们的工具将能够解析Markdown语法,并且使用RainTPL库将结果标记放入HTML模板中。

The sample tool I wrote for this article is named md2epub and has become a real open source project on Github. It’s far from perfect at the moment, but feel free to fork and improve it!

我为本文编写的示例工具名为md2epub,已成为Github上真正的开源项目 。 目前尚不完美,但请随意进行改进!

快速入门ePub理论 (Quick Start ePub Theory)

The specifications for the ePub format are detailed by the IDPF (International Digital Publishing Forum), and we’ll refer to version 2.0.1 of the specs which is the most widely used today. Other useful resources are the Wikipedia ePub Page and the Epub Format Construction Guide by Harrison Ainsworth.

ePub格式的规范由IDPF(国际数字出版论坛)详细介绍,我们将参考规范2.0.1版,这是当今使用最广泛的规范。 其他有用的资源包括Wikipedia ePub页面Harrison Ainsworth 撰写的Epub Format建设指南

Basically, an ePub book is a zipped archive with a well defined structure. The contents includes XHTML documents, CSS stylesheets, images (GIFs, JPEGs, PNGs, and SVGs), and Open Type fonts. There are other specific files used to describe the content of the book; these are the package and container files:

基本上,ePub图书是具有明确定义的结构的压缩存档。 内容包括XHTML文档,CSS样式表,图像(GIF,JPEG,PNG和SVG)和Open Type字体。 还有其他一些特定的文件用来描述这本书的内容。 这些是程序包和容器文件:

myBook/
    META-INF/
        container.xml
    mimetype
    content.opf
    toc.ncx
    Stylesheet.css
    BookCover.jpg
    HomePage.xhtml
    Chapter1.xhtml
    ...
    ChapterN.xhtml
    Index.xhtml

The first four files are specific for the ePub package, the others are our content. The mimetype file must contain application/epub+zip in ASCII without an end-of-line character. It must be the first file included in the archive and must not be compressed; we cannot do this with PHP, but I’ll show you a work-around for this later.

前四个文件专用于ePub软件包,其他是我们的内容。 mimetype文件必须包含ASCII格式的application / epub + zip ,且没有换行符。 它必须是归档文件中包含的第一个文件,并且不得压缩; 我们无法使用PHP来做到这一点,但是稍后我将向您展示解决方法。

The file META-INF/container.xml is fixed in name and location. The scope of this file is to tell where the OPF file (usually content.opf) is located relative to the ebook’s root directory.

文件META-INF/container.xml名称和位置固定。 该文件的作用是告诉OPF文件(通常为content.opf )相对于电子书根目录的位置。

content.opf is an XML file that contains the book’s meta data, the references to all the content resources, and the the order in which the contents must be loaded by the reader application.

content.opf是一个XML文件,其中包含书的元数据,对所有内容资源的引用以及阅读器应用程序必须加载内容的顺序。

The toc.ncx file is an optional but recommended XML navigation map of the ebook.

toc.ncx文件是电子书的可选但推荐的XML导航图。

There are also other optional files that are not taken in consideration for now. And I won’t tell you how to add DRM, sorry.

现在还没有考虑其他可选文件。 而且,我不会告诉您如何添加DRM。

我们的第一本电子书 (Our First eBook)

So how do we prepare our content for ePub-lishing? I’ve set up a test book directory like this:

那么,我们如何准备内容以进行电子出版呢? 我已经建立了一个像这样的测试书目录:

mybook/
    01-first-chapter.md
    02-second-chapter.md
    book.json
    cover.jpg
    coverpage.md
    index.md
    style.css
    titlepage.md
    media/
        *.jpg

Most of the files are content files for the publication, markdown text and images. We then have a style.css file (the name and position should be fixed for now). Keep in mind that the ePub format understands only a subset of the CSS 2.1 specs.

大多数文件是用于发布,标记文本和图像的内容文件。 然后,我们有了一个style.css文件(名称和位置现在应该固定)。 请记住,ePub格式仅理解CSS 2.1规范的一部分。

At last we have a special file called book.json. This file contains all the data that our md2epub tool will need to generate the standard package files. Most of the keys in this file have a direct match with the OPF and NCX files. The JSON file is structured like this:

最后,我们有一个名为book.json的特殊文件。 此文件包含md2epub工具生成标准软件包文件所需的所有数据。 该文件中的大多数键与OPF和NCX文件直接匹配。 JSON文件的结构如下:

{
    "id": "com.acme.books.MyUniqueBookID",
    "title": "Sample eBook Title",
    "language": "en",

    "authors": [
        { "name": "John Smith", "role": "aut"},
        { "name": "Jane Appleseed", "role": "dsr"}
    ],

    "description": "Brief description of the book",
    "subject": "List of keywords, pertinent to content, separated by commas",
    "publisher": "ACME Publishing Inc.",
    "rights": "Copyright (c) 2013, Someone",
    "date": "2013-02-27",
    "relation": "http://www.acme.com/books/MyUniqueBookIDWebEdition/",

    "files": {
        "coverpage": "coverpage.md",
        "title-page": "titlepage.md",
        "include": [
            { "id":"ncx", "path":"toc.ncx" },
            "cover.jpg",
            "style.css",
            "*.md",
            "media/*"
        ],
        "index": "index.md",
        "exclude": []
    },

    "spine": {
        "toc": "ncx",
        "items": [
            "coverpage",
            "title-page",
            "copyright",
            "foreword",
            "|^c\d{1,2}-.*$|",
            "index"
        ]
    }
}

The first three groups of keys from id to relation maps to the <metadata> section of the OPF file. They contain information about the book, the author, the publisher, and so on. The keys id, title, and language are required. All the others are optional and their meaning is self-explanatory, except maybe for relation which is used to link to another publication using an ISBN format or a site URL. The value of id must be unique, and I’ve chosen the reverse domain format com.publisher.series.BookID although you can use whatever format you like.

idrelation前三组键映射到OPF文件的<metadata>部分。 它们包含有关书籍,作者,出版商等的信息。 密钥idtitlelanguage是必需的。 所有其他选项都是可选的,它们的含义是不言自明的,除了用于使用ISBN格式或网站URL链接到另一个出版物的relation之外。 id的值必须是唯一的,尽管您可以使用任意格式,但我选择了反向域格式com.publisher.series.BookID

The files key of the JSON maps to the <manifest> section of the OPF. This section contains references to every file used in the book, including fonts, stylesheets, images, and special files. Every item in this section needs a unique id, path to the resource (href), and its mimetype. md2epub will take care of the media type and will generate a suitable ID if it’s not provided.

JSON的files密钥映射到OPF的<manifest>部分。 本节包含对本书中使用的每个文件的引用,包括字体,样式表,图像和特殊文件。 本节中的每个项目都需要一个唯一的ID,资源(href)的路径及其模仿类型。 md2epub将处理媒体类型,如果未提供,则会生成合适的ID。

I also wanted it to be able to specify bulk file inclusions using wildcards such as *.md and media/*, although the exclude list is not parsed at the moment either.

我还希望它能够使用通配符(例如* .mdmedia / *)指定大容量文件包含,尽管目前也不对排除列表进行解析。

The spine JSON key maps to <spine> in the OPF, it describes the order in which the various chapters should appear in the book.

spine JSON密钥映射到OPF中的<spine> ,它描述了各章在本书中出现的顺序。

Starting with these data our md2epub will:

从这些数据开始,我们的md2epub将:

  • create a well organized temporary book directory

    创建井井有条的临时书籍目录
  • copy all the referenced files

    复制所有引用的文件
  • convert and copy all the markdown file to XHTML

    转换所有markdown文件并将其复制到XHTML
  • generate the ePub required files

    生成ePub所需文件
  • create the zip/epub archive and cleanup

    创建zip / epub存档和清理

The final file can (and should) be validated by the official EPUB Validator which you can also download and run locally as a Java console application.

最终文件可以(并且应该)由官方EPUB验证器验证 ,您也可以下载该文件并作为Java控制台应用程序在本地运行。

Md2Epub –的制作 (Md2Epub – The Making of)

In order to stay on topic in this article, I’ll discuss only the most epub-specific code and leave the “utility code” for you to explore.

为了保持本文的主题不变,我将仅讨论最特定于epub的代码,并保留“实用程序代码”供您探索。

The complete program consists of a console PHP script md2epub.php that collects the user’s input, creates the working directory, and passes all of the data to the EBook class. The md2epub file is a shell wrapper.

完整的程序由控制台PHP脚本md2epub.php组成,该脚本收集用户的输入,创建工作目录,并将所有数据传递给EBook类。 md2epub文件是一个外壳包装程序。

The EBook class performs all of the operations and checks. It uses the RainTPL library for template parsing and the PHP Markdown Extra library as a content filter. An interesting thing about this last component is that the Markdown() function is provided to the EBook class as an external filter to be applied to *.md files. In this way we can inject any other text filters such as Textile or a wiki syntax parser.

EBook类执行所有操作和检查。 它使用RainTPL库进行模板解析,并使用PHP Markdown Extra库作为内容过滤器。 关于最后一个组件,有趣的是, Markdown()函数作为要应用于*.md文件的外部过滤器提供给EBook类。 通过这种方式,我们可以注入任何其他文本过滤器,例如Textile或Wiki语法解析器。

The share directory contains common files, in particular the XHTML and XML template files; you can edit the markup of these files to customize your specific book type. The mimetype.zip file is part of the work-around I’ve mentioned earlier – since the PHP Zip library does not allow us to specify a compression level when creating an archive, I’ve created this archive stub using the command line zip program:

共享目录包含公用文件,尤其是XHTML和XML模板文件; 您可以编辑这些文件的标记以自定义您的特定书籍类型。 mimetype.zip文件是我前面提到的变通方法的一部分–由于PHP Zip库不允许我们在创建档案时指定压缩级别,因此我已经使用命令行zip程序创建了该档案存根。 :

zip -0 -D -X mimetype.zip mimetype

PHP will copy this basic archive and add the other files in sequence.

PHP将复制此基本存档,并依次添加其他文件。

探索电子书对象 (Exploring the EBook Object)

The constructor of the EBook class takes two arguments: the source directory of the book and an optional array of parameters. The parameters specify the name of the JSON file to parse (which defaults to book.json) and the directory that will contain the content files of the compiled book. I chose OEBPS, which stands for Open eBook Publication Structure, as it’s a convention used by many of the ebooks I’ve explored.

EBook类的构造函数带有两个参数:书籍的源目录和一个可选的参数数组。 参数指定要解析的JSON文件的名称(默认为book.json )以及将包含已编译书的内容文件的目录。 我选择OEBPS ,它代表开放式电子书出版结构,因为这是我探索过的许多电子书所使用的约定。

The role of the parseFiles() method is to normalize the files section loaded from the JSON. The wildcard paths are expanded using glob() and the IDs and media types are generated. At the end, it gives us an associative array where each file is represented in this way:

parseFiles()方法的作用是规范化从JSON加载的文件部分。 使用glob()扩展通配符路径,并生成ID和媒体类型。 最后,它为我们提供了一个关联数组,其中每个文件都以这种方式表示:

'cover' => Array (
    'path' => 'cover.jpg',
    'type' => 'image/jpeg'
)

Each file must not be listed more than once. The path is relative to the content directory, the directory that will contain the OPF file. Markdown files are of type text/plain in this step and they will be converted later.

每个文件不得被列出多次。 该路径相对于内容目录,该目录将包含OPF文件。 Markdown文件在此步骤中为text / plain类型,稍后将对其进行转换。

The parseSpine() method does a similar job. It sets a NCX/TOC file and compiles a list of declared IDs. The IDs can be regular expressions. For example, I’ve specified the pattern ^cd{1,2}-.*$ that matches the IDs for the chapters (c01-first-chapter, c02-second-chapter, etc). The beginning “c” is important since XML id attributes cannot start with a number.

parseSpine()方法执行类似的工作。 它设置一个NCX / TOC文件并编译已声明ID的列表。 ID可以是正则表达式。 例如,我指定了与各章的ID相匹配的模式^cd{1,2}-.*$ ( c01-first-chapterc02-second-chapter等)。 开头的“ c”很重要,因为XML id属性不能以数字开头。

The powerful makeEpub() method then starts the transformation process for our ebook. The data needed is:

功能强大的makeEpub()方法随后为我们的电子书启动转换过程。 所需的数据是:

  • the target .epub file path

    目标.epub文件路径
  • the temporary directory in which compile and copy all the resources

    在其中编译和复制所有资源的临时目录
  • the source directory for template files

    模板文件的源目录
  • the optional content filters, in this case Markdown linked to the md extension

    可选的内容过滤器,在这种情况下,Markdown链接到md扩展名

It first checks the input parameters to be sure it has the right filesystem permissions, and then calls four helper methods in sequence, corresponding to the required steps:

它首先检查输入参数以确保它具有正确的文件系统权限,然后按照所需的步骤依次调用四个帮助程序方法:

  • create the META-INF directory and data

    创建META-INF目录和数据
  • export all the content files, compile templates and apply filters

    导出所有内容文件,编译模板并应用过滤器
  • create the OPF manifest file

    创建OPF清单文件
  • create the NCX navigation file (if needed)

    创建NCX导航文件(如果需要)
  • create the compressed epub archive

    创建压缩的epub存档

The createMetaInf() method is the simplest and helps us to understand the template logic behind the others:

createMetaInf()方法是最简单的方法,可帮助我们理解其他方法背后的模板逻辑:

<?php
protected function createMetaInf($workDir)
{
    // create destination directory
    if (!mkdir("$workDir/META-INF")) {
        throw new Exception('Unable to create content META-INF directory');
    }

    // compile file
    $tpl = $this->initTemplateEngine(
        array(
            'tpl_dir'   => "{$this->params['templates_dir']}/",
            'tpl_ext'   => 'xml',
            'cache_dir' => sys_get_temp_dir() . '/'
        )
    );
    $tpl->assign('ContentDirectory', $this->params['content_dir']);
    $container = $tpl->draw('book/META-INF/container', true);

    // write compiled file to destination
    if (file_put_contents("$workDir/META-INF/container.xml", $container) === false) {
        throw new Exception("Unable to create content META-INF/container.xml");
    }
}

The container.xml template has only one variable to set which is the path of the content.opf file relative to the base directory of the ebook. The syntax is {$VariableName}.

container.xml模板仅可设置一个变量,该变量是content.opf文件相对于电子书基本目录的路径。 语法为{$VariableName}

<?xml version="1.0"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
 <rootfiles>
  <rootfile full-path="{$ContentDirectory}/content.opf"
   media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>

First the RainTPL library is initialized with the templates directory, the extension of the template files, and a temporary cache directory. Then the template variable is assigned ($tpl->assign()) and the draw() method parses the provided template file. Its second parameter tells draw() whether to return the result. The compiled file is then saved to the destination path.

首先,使用模板目录,模板文件的扩展名和临时缓存目录初始化RainTPL库。 然后,分配模板变量( $tpl->assign() ),而draw()方法将解析提供的模板文件。 它的第二个参数告诉draw()是否返回结果。 然后,已编译的文件将保存到目标路径。

The createOpf() method has the same logic and some more variables to assign. I chose the pattern Book<Varname> so the optional meta data is assigned in the loop:

createOpf()方法具有相同的逻辑和更多要分配的变量。 我选择了模式Book<Varname>因此在循环中分配了可选的元数据:

<?php
$optParams = array(
    'authors',
    'date',
    'description',
    'publisher',
    'relation',
    'rights',
    'subject'
);

foreach ($optParams as $p) {
    if (isset($this->$p)) {
        $tpl->assign('Book' . ucfirst($p), $this->$p);
    }
}

And they are inserted in the template with a conditional syntax:

然后使用条件语法将它们插入模板:

{if="$BookDescription"}<dc:description>{$BookDescription}</dc:description>{/if}

The files and spine variables are formatted in the method and are inserted in the template with a loop:

文件和主干变量在方法中格式化,并通过循环插入模板中:

<manifest>
    {loop="$BookFiles"}<item id="{$key}" href="{$value.path}" media-type="{$value.type}"/>
    {/loop}
</manifest>

The processBookFiles() is called before the createOpf() and runs a big loop with all the referenced resources ($this->files). For each resource it can choose between two actions:

processBookFiles()createOpf()之前调用,并与所有引用的资源( $this->files )运行一个大循环。 对于每种资源,它可以在两个操作之间进行选择:

  • process and filter the file

    处理和过滤文件
  • copy the file path “as is” in the destination directory

    将文件路径“按原样”复制到目标目录中

The filter applied to a file must be a callable PHP function or class method that takes one parameter: the content. The file content is loaded from the original source using file_get_contents() and the filter is applied using the native call_user_func():

应用于文件的过滤器必须是可调用PHP函数或带有一个参数(内容)的类方法。 使用file_get_contents()从原始源加载文件内容,并使用本机call_user_func()应用过滤器:

<?php
$content = call_user_func($filters[$ext], $content);

The processed content is then injected in an XHTML template, similar to the previous methods. In this case there is also the possibility to use a custom template. If there is a file named .xhtml in the templates directory, that file will be used overriding the default page.xhtml. Also, if a style.css file is present then the {$BookStyle} template variable is set and the CSS is linked to the page.

然后,类似于以前的方法,将处理后的内容注入XHTML模板中。 在这种情况下,也可以使用自定义模板。 如果templates目录中有一个名为.xhtml的文件,则将使用该文件覆盖默认的page.xhtml 。 另外,如果存在style.css文件,则将设置{$BookStyle}模板变量,并将CSS链接到页面。

The resulting file is then written to the destination directory and the manifest entry for that file is updated with the new path and media type, ready for createOpf().

然后将生成的文件写入目标目录,并使用可用于createOpf()的新路径和媒体类型更新该文件的清单条目。

The createNcx() is a little tricky because it has to look into the items of the spine section and extract the content of the H1 and H2 tags. The toc.ncx file has this structure, first a standardized header:

createNcx()有点棘手,因为它必须查看书脊部分的内容并提取H1和H2标签的内容。 toc.ncx文件具有以下结构,首先是标准化的标头:

<?xml version="1.0" encoding="UTF-8"?>
<ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1">
 <head>
  <meta name="dtb:uid" content="{$BookID}"/>

  <!-- 1 = chapters only, 2 = subchapters, 3 = third level section -->
  <meta name="dtb:depth" content="2"/>

  <!-- Both required but unused, can be 0 -->
  <meta name="dtb:totalPageCount" content="0"/>
  <meta name="dtb:maxPageNumber" content="0"/>
 </head>

 <docTitle>
  <text>{$BookTitle}</text>
 </docTitle>

The customized dtb:uid meta indicates our book ID, and the dtb:depth meta tag says how deep our navigation is. I choose a fixed value of 2 here so in the parsing code I have to extract the first two level headers.

定制的dtb:uid元数据表示我们的图书ID,而dtb:depth元数据标签表示导航的深度。 我在这里选择固定值为2,因此在解析代码中,我必须提取前两个级别的标头。

After the header there’s the <navMap> that is generated using a template loop.

标头之后是使用模板循环生成的<navMap>

{$playOrder=1}
{loop="$BookChapters"}
 <navPoint id="{$key}" playOrder="{$playOrder++}">
  <navLabel>
   <text>{$value.title}</text>
  </navLabel>
  <content src="{$value.path}"/>
  {if="isset($value.sections)"}{loop="$value.sections"}
  <navPoint id="{$key}" playOrder="{$playOrder++}">
   <navLabel>
    <text>{$value.title}</text>
   </navLabel>
   <content src="{$value.path}"/>
  </navPoint>
  {/loop}{/if}
 </navPoint>
{/loop}

Each H1 becomes a <navPoint>, the id is the corresponding resource ID (ex c01-first-chapter, etc), the H1 content becomes the label, and the playOrder attribute indicates the global reading order. In our example, each nav point can have nested nav points for H2 subtitles.

每个H1成为<navPoint>id是对应的资源ID(例如c01-first-chapter等),H1内容成为标签,并且playOrder属性指示全局读取顺序。 在我们的示例中,每个导航点可以具有用于H2字幕的嵌套导航点。

In the method, a double loop generates an array structured like this:

在该方法中,双循环生成一个结构如下的数组:

$chapters['c01-first-chapter'] = array(
    'title' => 'Title for chapter 1',
    'path' => '01-first-chapter.xhtml',
    'sections' => array(
        'section-1-1' => array(
            'title' => 'Title for subsection 1.1',
            'path' => '01-first-chapter.xhtml#section-1-1',
        )
    )
)

In the external loop, each item is loaded as a SimpleXML object:

在外部循环中,每个项目都作为SimpleXML对象加载:

<?php
$doc = simplexml_load_file("$workDir/{$this->params['content_dir']}/{$this->files[$item]['path']}");
if ($doc && isset($doc->body->h1)) {
    // chapter title
    $chapters[$item] = array(
        'title' => $doc->body->h1,
        'path'  => $this->files[$item]['path']
    );

    // subchapter title
    foreach ($doc->body->h2 as $section) {
        if (!empty($section['id'])) {
            $section_id = (string) $section['id'];
            $chapters[$item]['sections'][$section_id] = array(
                'title' => $section,
                'path' => $this->files[$item]['path'] . '#' . $section['id']
            );
        }
    }
}

If the document has a H1 header, a first level item is inserted into the $chapters array. Then the second level headers are queried, each header that has a non-empty id attribute is included in the sections for each chapter. The $chapters array becomes {$BookChapters} in the template. The template is then rendered and saved to its destination.

如果文档具有H1标头,则将第一级项目插入$chapters数组。 然后查询第二级标题,每章的部分中都包含具有非空id属性的每个标题。 $chapters数组在模板中变为{$BookChapters} 。 然后,将渲染模板并将其保存到其目标位置。

Now we have a destination working directory somewhere at /tmp/path/com.publisher.BookID that can be packed by the final step createArchive():

现在,我们在/tmp/path/com.publisher.BookID处有一个目标工作目录,可以通过最后一步createArchive()将其打包:

<?php
protected function createArchive($workDir, $epubFile)
{
    $excludes = array('.DS_Store', 'mimetype');

    $mimeZip = "{$this->params['templates_dir']}/mimetype.zip";
    $zipFile = sys_get_temp_dir() . '/book.zip';

    if (!copy($mimeZip, $zipFile)) {
        throw new Exception("Unable to copy temporary archive file");
    }

    $zip = new ZipArchive();
    if ($zip->open($zipFile, ZipArchive::CREATE) != true) {
        throw new Exception("Unable open archive '$zipFile'");
    }

    $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($workDir), RecursiveIteratorIterator::SELF_FIRST);
    foreach ($files as $file) {
        if (in_array(basename($file), $excludes)) {
            continue;
        }
        if (is_dir($file)) {
            $zip->addEmptyDir(str_replace("$workDir/", '', "$file/"));
        } elseif (is_file($file)) {
            $zip->addFromString(
                str_replace("$workDir/", '', $file),
                file_get_contents($file)
            );
        }
    }
    $zip->close();

    rename($zipFile, $epubFile);
}

The $excludes array contains special and system files that we don’t want in the archive. A temporary path for an empty $zipFile is created, and then the $zipFile path is filled with a copy of our basic mimetype.zip that is loaded in a ZipArchive object. At this point the content of the working directory is recursively loaded into the $files array using the RecursiveIteratorIterator and RecursiveDirectoryIterator objects. In the following loop the structure of the working directory is replicated in the zip archive. As a finishing touch, the archive is moved to the destination path indicated by the caller.

$excludes数组包含不需要的特殊文件和系统文件。 创建一个空的$zipFile临时路径,然后在$zipFile路径中填充一个基本的mimetype.zip副本,该副本已加载到ZipArchive对象中。 此时,使用RecursiveIteratorIteratorRecursiveDirectoryIterator对象将工作目录的内容递归加载到$files数组中。 在以下循环中,工作目录的结构将复制到zip归档文件中。 最后,将存档移动到调用方指示的目标路径。

摘要 (Summary)

When all is said and done, we have our shiny new eBook. We have a good chance that it will be validated by the EpubCheck app and, most importantly, we have another useful tool to add to our PHP-super-hero belt! And this is only a starting point, you can play with the code to add filters, features, themes and styles and more if you like. Keep calm, have fun, and write those eBooks!

一切都说完了,我们就有了崭新的电子书。 我们很有可能会通过EpubCheck应用程序进行验证,最重要的是,我们还有另一个有用的工具可以添加到我们PHP超级英雄腰带中! 这只是一个起点,您可以使用代码来添加过滤器,功能,主题和样式,并且可以根据需要添加更多内容。 保持镇定,玩得开心,写那些电子书!

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/building-epub-with-php-and-markdown/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值