@@ -62,6 +62,16 @@ func (e *FileRetrievalError) Error() string {
6262 return fmt .Sprintf ("Error retrieving %q from source: %+v" , e .Source , e .Err )
6363}
6464
65+ // ParentDoesNotExistError is thrown by AddSubSection if the parent with the
66+ // previously defined internal filename does not exist.
67+ type ParentDoesNotExistError struct {
68+ Filename string // Filename that caused the error
69+ }
70+
71+ func (e * ParentDoesNotExistError ) Error () string {
72+ return fmt .Sprintf ("Parent with the internal filename %s does not exist" , e .Filename )
73+ }
74+
6575// Folder names used for resources inside the EPUB
6676const (
6777 CSSFolderName = "css"
@@ -137,6 +147,7 @@ type epubCover struct {
137147type epubSection struct {
138148 filename string
139149 xhtml * xhtml
150+ children * []epubSection
140151}
141152
142153// NewEpub returns a new Epub.
@@ -257,30 +268,86 @@ func (e *Epub) AddVideo(source string, videoFilename string) (string, error) {
257268func (e * Epub ) AddSection (body string , sectionTitle string , internalFilename string , internalCSSPath string ) (string , error ) {
258269 e .Lock ()
259270 defer e .Unlock ()
260- return e .addSection (body , sectionTitle , internalFilename , internalCSSPath )
271+ return e .addSection ("" , body , sectionTitle , internalFilename , internalCSSPath )
261272}
262273
263- func (e * Epub ) addSection (body string , sectionTitle string , internalFilename string , internalCSSPath string ) (string , error ) {
274+ // AddSubSection adds a nested section (chapter, etc) to an existing section.
275+ // The method returns a relative path to the section that can be used from another
276+ // section (for links).
277+ //
278+ // The parent filename must be a valid filename from another section already added.
279+ //
280+ // The body must be valid XHTML that will go between the <body> tags of the
281+ // section XHTML file. The content will not be validated.
282+ //
283+ // The title will be used for the table of contents. The section will be shown
284+ // as a nested entry of the parent section in the table of contents. The
285+ // title is optional; if no title is provided, the section will not be added to
286+ // the table of contents.
287+ //
288+ // The internal filename will be used when storing the section file in the EPUB
289+ // and must be unique among all section files. If the same filename is used more
290+ // than once, FilenameAlreadyUsedError will be returned. The internal filename is
291+ // optional; if no filename is provided, one will be generated.
292+ //
293+ // The internal path to an already-added CSS file (as returned by AddCSS) to be
294+ // used for the section is optional.
295+ func (e * Epub ) AddSubSection (parentFilename string , body string , sectionTitle string , internalFilename string , internalCSSPath string ) (string , error ) {
296+ e .Lock ()
297+ defer e .Unlock ()
298+ return e .addSection (parentFilename , body , sectionTitle , internalFilename , internalCSSPath )
299+ }
300+
301+ func (e * Epub ) addSection (parentFilename string , body string , sectionTitle string , internalFilename string , internalCSSPath string ) (string , error ) {
302+ parentIndex := - 1
303+
264304 // Generate a filename if one isn't provided
265305 if internalFilename == "" {
266306 index := 1
267307 for internalFilename == "" {
268308 internalFilename = fmt .Sprintf (sectionFileFormat , index )
269- for _ , section := range e .sections {
309+ for item , section := range e .sections {
310+ if section .filename == parentFilename {
311+ parentIndex = item
312+ }
270313 if section .filename == internalFilename {
271314 internalFilename , index = "" , index + 1
272- break
315+ if parentFilename == "" || parentIndex != - 1 {
316+ break
317+ }
318+ }
319+ // Check for nested sections with the same filename to avoid duplicate entries
320+ if section .children != nil {
321+ for _ , subsection := range * section .children {
322+ if subsection .filename == internalFilename {
323+ internalFilename , index = "" , index + 1
324+ }
325+ }
273326 }
274327 }
275328 }
276329 } else {
277- for _ , section := range e .sections {
330+ for item , section := range e .sections {
331+ if section .filename == parentFilename {
332+ parentIndex = item
333+ }
278334 if section .filename == internalFilename {
279335 return "" , & FilenameAlreadyUsedError {Filename : internalFilename }
280336 }
337+ if section .children != nil {
338+ for _ , subsection := range * section .children {
339+ if subsection .filename == internalFilename {
340+ return "" , & FilenameAlreadyUsedError {Filename : internalFilename }
341+ }
342+ }
343+ }
281344 }
282345 }
283346
347+ if parentFilename != "" && parentIndex == - 1 {
348+ return "" , & ParentDoesNotExistError {Filename : parentFilename }
349+ }
350+
284351 x := newXhtml (body )
285352 x .setTitle (sectionTitle )
286353 x .setXmlnsEpub (xmlnsEpub )
@@ -292,8 +359,18 @@ func (e *Epub) addSection(body string, sectionTitle string, internalFilename str
292359 s := epubSection {
293360 filename : internalFilename ,
294361 xhtml : x ,
362+ children : nil ,
363+ }
364+
365+ if parentIndex != - 1 {
366+ if e .sections [parentIndex ].children == nil {
367+ var section []epubSection
368+ e .sections [parentIndex ].children = & section
369+ }
370+ (* e .sections [parentIndex ].children ) = append (* e .sections [parentIndex ].children , s )
371+ } else {
372+ e .sections = append (e .sections , s )
295373 }
296- e .sections = append (e .sections , s )
297374
298375 return internalFilename , nil
299376}
@@ -398,10 +475,10 @@ func (e *Epub) SetCover(internalImagePath string, internalCSSPath string) {
398475 coverBody := fmt .Sprintf (defaultCoverBody , internalImagePath )
399476 // Title won't be used since the cover won't be added to the TOC
400477 // First try to use the default cover filename
401- coverPath , err := e .addSection (coverBody , "" , defaultCoverXhtmlFilename , internalCSSPath )
478+ coverPath , err := e .addSection ("" , coverBody , "" , defaultCoverXhtmlFilename , internalCSSPath )
402479 // If that doesn't work, generate a filename
403480 if _ , ok := err .(* FilenameAlreadyUsedError ); ok {
404- coverPath , err = e .addSection (coverBody , "" , "" , internalCSSPath )
481+ coverPath , err = e .addSection ("" , coverBody , "" , "" , internalCSSPath )
405482 if _ , ok := err .(* FilenameAlreadyUsedError ); ok {
406483 // This shouldn't cause an error since we're not specifying a filename
407484 panic (fmt .Sprintf ("Error adding default cover XHTML file: %s" , err ))
0 commit comments