renamed SCons.Tool.ninja -> SCons.Tool.ninja_tool and added alias in tool loading...
[scons.git] / SCons / Tool / docbook / __init__.py
blob3adb3148bb15478fe23bbc0f8ed16c33bac51000
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """Tool-specific initialization for Docbook.
26 There normally shouldn't be any need to import this module directly.
27 It will usually be imported through the generic SCons.Tool.Tool()
28 selection method.
29 """
31 import os
32 import glob
33 import re
35 import SCons.Action
36 import SCons.Builder
37 import SCons.Defaults
38 import SCons.Script
39 import SCons.Tool
40 import SCons.Util
42 __debug_tool_location = False
43 # Get full path to this script
44 scriptpath = os.path.dirname(os.path.realpath(__file__))
46 # Local folder for the collection of DocBook XSLs
47 db_xsl_folder = 'docbook-xsl-1.76.1'
49 # Do we have lxml?
50 has_lxml = True
51 try:
52 import lxml
53 except Exception:
54 has_lxml = False
56 # Set this to True, to prefer xsltproc over lxml
57 prefer_xsltproc = False
59 # Regexs for parsing Docbook XML sources of MAN pages
60 re_manvolnum = re.compile(r"<manvolnum>([^<]*)</manvolnum>")
61 re_refname = re.compile(r"<refname>([^<]*)</refname>")
64 # lxml etree XSLT global max traversal depth
67 lmxl_xslt_global_max_depth = 3600
69 if has_lxml and lmxl_xslt_global_max_depth:
70 def __lxml_xslt_set_global_max_depth(max_depth) -> None:
71 from lxml import etree
72 etree.XSLT.set_global_max_depth(max_depth)
73 __lxml_xslt_set_global_max_depth(lmxl_xslt_global_max_depth)
76 # Helper functions
78 def __extend_targets_sources(target, source):
79 """ Prepare the lists of target and source files. """
80 if not SCons.Util.is_List(target):
81 target = [target]
82 if not source:
83 source = target[:]
84 elif not SCons.Util.is_List(source):
85 source = [source]
86 if len(target) < len(source):
87 target.extend(source[len(target):])
89 return target, source
91 def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path) -> None:
92 if kw.get('DOCBOOK_XSL','') == '':
93 xsl_style = kw.get('xsl', env.subst(user_xsl_var))
94 if xsl_style == '':
95 path_args = [scriptpath, db_xsl_folder] + default_path
96 xsl_style = os.path.join(*path_args)
97 kw['DOCBOOK_XSL'] = xsl_style
99 def __select_builder(lxml_builder, cmdline_builder):
100 """ Selects a builder, based on which Python modules are present. """
101 if has_lxml and not prefer_xsltproc:
102 return lxml_builder
104 return cmdline_builder
106 def __ensure_suffix(t, suffix):
107 """ Ensure that the target t has the given suffix. """
108 tpath = str(t)
109 if not tpath.endswith(suffix):
110 return tpath+suffix
112 return t
114 def __ensure_suffix_stem(t, suffix):
115 """ Ensure that the target t has the given suffix, and return the file's stem. """
116 tpath = str(t)
117 if not tpath.endswith(suffix):
118 stem = tpath
119 tpath += suffix
121 return tpath, stem
122 else:
123 stem, ext = os.path.splitext(tpath)
125 return t, stem
127 def __get_xml_text(root):
128 """ Return the text for the given root node (xml.dom.minidom). """
129 txt = ""
130 for e in root.childNodes:
131 if e.nodeType == e.TEXT_NODE:
132 txt += e.data
133 return txt
135 def __create_output_dir(base_dir) -> None:
136 """ Ensure that the output directory base_dir exists. """
137 root, tail = os.path.split(base_dir)
138 dir = None
139 if tail:
140 if base_dir.endswith('/'):
141 dir = base_dir
142 else:
143 dir = root
144 else:
145 if base_dir.endswith('/'):
146 dir = base_dir
148 if dir and not os.path.isdir(dir):
149 os.makedirs(dir)
153 # Supported command line tools and their call "signature"
155 xsltproc_com_priority = ['xsltproc', 'saxon', 'saxon-xslt', 'xalan']
157 # TODO: Set minimum version of saxon-xslt to be 8.x (lower than this only supports xslt 1.0.
158 # see: https://saxon.sourceforge.net/saxon6.5.5/
159 # see: https://saxon.sourceforge.net/
160 xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE',
161 'saxon' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
162 # Note if saxon-xslt is version 5.5 the proper arguments are: (swap order of docbook_xsl and source)
163 # 'saxon-xslt' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $SOURCE $DOCBOOK_XSL $DOCBOOK_XSLTPROCPARAMS',
164 'saxon-xslt' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
165 'xalan' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -q -out $TARGET -xsl $DOCBOOK_XSL -in $SOURCE'}
166 xmllint_com = {'xmllint' : '$DOCBOOK_XMLLINT $DOCBOOK_XMLLINTFLAGS --xinclude $SOURCE > $TARGET'}
167 fop_com = {'fop' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -fo $SOURCE -pdf $TARGET',
168 'xep' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -valid -fo $SOURCE -pdf $TARGET',
169 'jw' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -f docbook -b pdf $SOURCE -o $TARGET'}
171 def __detect_cl_tool(env, chainkey, cdict, cpriority=None) -> None:
173 Helper function, picks a command line tool from the list
174 and initializes its environment variables.
176 if env.get(chainkey,'') == '':
177 clpath = ''
179 if cpriority is None:
180 cpriority = cdict.keys()
181 for cltool in cpriority:
182 if __debug_tool_location:
183 print("DocBook: Looking for %s"%cltool)
184 clpath = env.WhereIs(cltool)
185 if clpath:
186 if __debug_tool_location:
187 print("DocBook: Found:%s"%cltool)
188 env[chainkey] = clpath
189 if not env[chainkey + 'COM']:
190 env[chainkey + 'COM'] = cdict[cltool]
191 break
193 def _detect(env) -> None:
195 Detect all the command line tools that we might need for creating
196 the requested output formats.
198 global prefer_xsltproc
200 if env.get('DOCBOOK_PREFER_XSLTPROC',''):
201 prefer_xsltproc = True
203 if (not has_lxml) or prefer_xsltproc:
204 # Try to find the XSLT processors
205 __detect_cl_tool(env, 'DOCBOOK_XSLTPROC', xsltproc_com, xsltproc_com_priority)
206 __detect_cl_tool(env, 'DOCBOOK_XMLLINT', xmllint_com)
208 __detect_cl_tool(env, 'DOCBOOK_FOP', fop_com, ['fop','xep','jw'])
211 # Scanners
213 include_re = re.compile(r'fileref\\s*=\\s*["|\']([^\\n]*)["|\']')
214 sentity_re = re.compile(r'<!ENTITY\\s+%*\\s*[^\\s]+\\s+SYSTEM\\s+["|\']([^\\n]*)["|\']>')
216 def __xml_scan(node, env, path, arg):
217 """ Simple XML file scanner, detecting local images and XIncludes as implicit dependencies. """
218 # Does the node exist yet?
219 if not os.path.isfile(str(node)):
220 return []
222 if env.get('DOCBOOK_SCANENT',''):
223 # Use simple pattern matching for system entities..., no support
224 # for recursion yet.
225 contents = node.get_text_contents()
226 return sentity_re.findall(contents)
228 xsl_file = os.path.join(scriptpath,'utils','xmldepend.xsl')
229 if not has_lxml or prefer_xsltproc:
230 # Try to call xsltproc
231 xsltproc = env.subst("$DOCBOOK_XSLTPROC")
232 if xsltproc and xsltproc.endswith('xsltproc'):
233 result = env.backtick(' '.join([xsltproc, xsl_file, str(node)]))
234 depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
235 return depfiles
236 else:
237 # Use simple pattern matching, there is currently no support
238 # for xi:includes...
239 contents = node.get_text_contents()
240 return include_re.findall(contents)
242 from lxml import etree
244 xsl_tree = etree.parse(xsl_file)
245 doc = etree.parse(str(node))
246 result = doc.xslt(xsl_tree)
248 depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
249 return depfiles
251 # Creating the instance of our XML dependency scanner
252 docbook_xml_scanner = SCons.Script.Scanner(function = __xml_scan,
253 argument = None)
257 # Action generators
259 def __generate_xsltproc_action(source, target, env, for_signature):
260 cmd = env['DOCBOOK_XSLTPROCCOM']
261 # Does the environment have a base_dir defined?
262 base_dir = env.subst('$base_dir')
263 if base_dir:
264 # Yes, so replace target path by its filename
265 return cmd.replace('$TARGET', os.path.join(base_dir, '${TARGET.file}'))
266 return cmd
268 def __generate_xsltproc_nobase_action(source, target, env, for_signature):
269 cmd = env['DOCBOOK_XSLTPROCCOM']
270 # Does the environment have a base_dir defined?
271 base_dir = env.subst('$base_dir')
272 if base_dir:
273 # Yes, so replace target path by its filename
274 return cmd.replace('$TARGET', '${TARGET.file}')
275 return cmd
279 # Emitters
281 def __emit_xsl_basedir(target, source, env):
282 # Does the environment have a base_dir defined?
283 base_dir = env.subst('$base_dir')
284 if base_dir:
285 # Yes, so prepend it to each target
286 return [os.path.join(base_dir, str(t)) for t in target], source
288 # No, so simply pass target and source names through
289 return target, source
293 # Builders
295 def __build_lxml(target, source, env):
297 General XSLT builder (HTML/FO), using the lxml module.
299 from lxml import etree
301 xslt_ac = etree.XSLTAccessControl(read_file=True,
302 write_file=True,
303 create_dir=True,
304 read_network=False,
305 write_network=False)
306 xsl_style = env.subst('$DOCBOOK_XSL')
307 xsl_tree = etree.parse(xsl_style)
308 transform = etree.XSLT(xsl_tree, access_control=xslt_ac)
309 doc = etree.parse(str(source[0]))
310 # Support for additional parameters
311 parampass = {}
312 if parampass:
313 result = transform(doc, **parampass)
314 else:
315 result = transform(doc)
317 try:
318 with open(str(target[0]), "wb") as of:
319 of.write(etree.tostring(result, encoding="utf-8", pretty_print=True))
320 except Exception as e:
321 print(f"ERROR: Failed to write {str(target[0])}")
322 print(e)
324 return None
326 def __build_lxml_noresult(target, source, env):
328 Specialized XSLT builder for transformations without a direct result where the Docbook
329 stylesheet itself creates the target file, using the lxml module.
331 from lxml import etree
333 xslt_ac = etree.XSLTAccessControl(read_file=True,
334 write_file=True,
335 create_dir=True,
336 read_network=False,
337 write_network=False)
338 xsl_style = env.subst('$DOCBOOK_XSL')
339 xsl_tree = etree.parse(xsl_style)
340 transform = etree.XSLT(xsl_tree, access_control=xslt_ac)
341 doc = etree.parse(str(source[0]))
342 # Support for additional parameters
343 parampass = {}
344 if parampass:
345 result = transform(doc, **parampass)
346 else:
347 result = transform(doc)
349 return None
352 def __xinclude_lxml(target, source, env):
354 Resolving XIncludes, using the lxml module.
356 from lxml import etree
358 doc = etree.parse(str(source[0]))
359 doc.xinclude()
360 try:
361 doc.write(str(target[0]), xml_declaration=True,
362 encoding="UTF-8", pretty_print=True)
363 except Exception as e:
364 print(f"ERROR: Failed to write {str(target[0])}")
365 print(e)
367 return None
369 __lxml_builder = SCons.Builder.Builder(
370 action = __build_lxml,
371 src_suffix = '.xml',
372 source_scanner = docbook_xml_scanner,
373 emitter = __emit_xsl_basedir)
375 __lxml_noresult_builder = SCons.Builder.Builder(
376 action = __build_lxml_noresult,
377 src_suffix = '.xml',
378 source_scanner = docbook_xml_scanner,
379 emitter = __emit_xsl_basedir)
381 __xinclude_lxml_builder = SCons.Builder.Builder(
382 action = __xinclude_lxml,
383 suffix = '.xml',
384 src_suffix = '.xml',
385 source_scanner = docbook_xml_scanner)
387 __xsltproc_builder = SCons.Builder.Builder(
388 action = SCons.Action.CommandGeneratorAction(__generate_xsltproc_action,
389 {'cmdstr' : '$DOCBOOK_XSLTPROCCOMSTR'}),
390 src_suffix = '.xml',
391 source_scanner = docbook_xml_scanner,
392 emitter = __emit_xsl_basedir)
393 __xsltproc_nobase_builder = SCons.Builder.Builder(
394 action = SCons.Action.CommandGeneratorAction(__generate_xsltproc_nobase_action,
395 {'cmdstr' : '$DOCBOOK_XSLTPROCCOMSTR'}),
396 src_suffix = '.xml',
397 source_scanner = docbook_xml_scanner,
398 emitter = __emit_xsl_basedir)
399 __xmllint_builder = SCons.Builder.Builder(
400 action = SCons.Action.Action('$DOCBOOK_XMLLINTCOM','$DOCBOOK_XMLLINTCOMSTR'),
401 suffix = '.xml',
402 src_suffix = '.xml',
403 source_scanner = docbook_xml_scanner)
404 __fop_builder = SCons.Builder.Builder(
405 action = SCons.Action.Action('$DOCBOOK_FOPCOM','$DOCBOOK_FOPCOMSTR'),
406 suffix = '.pdf',
407 src_suffix = '.fo',
408 ensure_suffix=1)
410 def DocbookEpub(env, target, source=None, *args, **kw):
412 A pseudo-Builder, providing a Docbook toolchain for ePub output.
414 import zipfile
415 import shutil
417 def build_open_container(target, source, env) -> None:
418 """Generate the *.epub file from intermediate outputs
420 Constructs the epub file according to the Open Container Format. This
421 function could be replaced by a call to the SCons Zip builder if support
422 was added for different compression formats for separate source nodes.
424 with zipfile.ZipFile(str(target[0]), 'w') as zf:
425 with open('mimetype', 'w') as mime_file:
426 mime_file.write('application/epub+zip')
427 zf.write(mime_file.name, compress_type = zipfile.ZIP_STORED)
428 for s in source:
429 if os.path.isfile(str(s)):
430 head, tail = os.path.split(str(s))
431 if not head:
432 continue
433 s = head
434 for dirpath, dirnames, filenames in os.walk(str(s)):
435 for fname in filenames:
436 path = os.path.join(dirpath, fname)
437 if os.path.isfile(path):
438 zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))),
439 zipfile.ZIP_DEFLATED)
441 def add_resources(target, source, env) -> None:
442 """Add missing resources to the OEBPS directory
444 Ensure all the resources in the manifest are present in the OEBPS directory.
446 hrefs = []
447 content_file = os.path.join(source[0].get_abspath(), 'content.opf')
448 if not os.path.isfile(content_file):
449 return
451 hrefs = []
452 if has_lxml:
453 from lxml import etree
455 opf = etree.parse(content_file)
456 # All the opf:item elements are resources
457 for item in opf.xpath('//opf:item',
458 namespaces= { 'opf': 'http://www.idpf.org/2007/opf' }):
459 hrefs.append(item.attrib['href'])
461 for href in hrefs:
462 # If the resource was not already created by DocBook XSL itself,
463 # copy it into the OEBPS folder
464 referenced_file = os.path.join(source[0].get_abspath(), href)
465 if not os.path.exists(referenced_file):
466 shutil.copy(href, os.path.join(source[0].get_abspath(), href))
468 # Init list of targets/sources
469 target, source = __extend_targets_sources(target, source)
471 # Init XSL stylesheet
472 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_EPUB', ['epub','docbook.xsl'])
474 # Setup builder
475 __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
477 # Create targets
478 result = []
479 if not env.GetOption('clean'):
480 # Ensure that the folders OEBPS and META-INF exist
481 __create_output_dir('OEBPS/')
482 __create_output_dir('META-INF/')
483 dirs = env.Dir(['OEBPS', 'META-INF'])
485 # Set the fixed base_dir
486 kw['base_dir'] = 'OEBPS/'
487 tocncx = __builder(env, 'toc.ncx', source[0], **kw)
488 cxml = env.File('META-INF/container.xml')
489 env.SideEffect(cxml, tocncx)
491 env.Depends(tocncx, kw['DOCBOOK_XSL'])
492 result.extend(tocncx+[cxml])
494 container = env.Command(__ensure_suffix(str(target[0]), '.epub'),
495 tocncx+[cxml], [add_resources, build_open_container])
496 mimetype = env.File('mimetype')
497 env.SideEffect(mimetype, container)
499 result.extend(container)
500 # Add supporting files for cleanup
501 env.Clean(tocncx, dirs)
503 return result
505 def DocbookHtml(env, target, source=None, *args, **kw):
507 A pseudo-Builder, providing a Docbook toolchain for HTML output.
509 # Init list of targets/sources
510 target, source = __extend_targets_sources(target, source)
512 # Init XSL stylesheet
513 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTML', ['html','docbook.xsl'])
515 # Setup builder
516 __builder = __select_builder(__lxml_builder, __xsltproc_builder)
518 # Create targets
519 result = []
520 for t,s in zip(target,source):
521 r = __builder(env, __ensure_suffix(t,'.html'), s, **kw)
522 env.Depends(r, kw['DOCBOOK_XSL'])
523 result.extend(r)
525 return result
527 def DocbookHtmlChunked(env, target, source=None, *args, **kw):
529 A pseudo-Builder, providing a Docbook toolchain for chunked HTML output.
531 # Init target/source
532 if not SCons.Util.is_List(target):
533 target = [target]
534 if not source:
535 source = target
536 target = ['index.html']
537 elif not SCons.Util.is_List(source):
538 source = [source]
540 # Init XSL stylesheet
541 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLCHUNKED', ['html','chunkfast.xsl'])
543 # Setup builder
544 __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
546 # Detect base dir
547 base_dir = kw.get('base_dir', '')
548 if base_dir:
549 __create_output_dir(base_dir)
551 # Create targets
552 result = []
553 r = __builder(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
554 env.Depends(r, kw['DOCBOOK_XSL'])
555 result.extend(r)
556 # Add supporting files for cleanup
557 env.Clean(r, glob.glob(os.path.join(base_dir, '*.html')))
559 return result
562 def DocbookHtmlhelp(env, target, source=None, *args, **kw):
564 A pseudo-Builder, providing a Docbook toolchain for HTMLHELP output.
566 # Init target/source
567 if not SCons.Util.is_List(target):
568 target = [target]
569 if not source:
570 source = target
571 target = ['index.html']
572 elif not SCons.Util.is_List(source):
573 source = [source]
575 # Init XSL stylesheet
576 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLHELP', ['htmlhelp','htmlhelp.xsl'])
578 # Setup builder
579 __builder = __select_builder(__lxml_noresult_builder, __xsltproc_nobase_builder)
581 # Detect base dir
582 base_dir = kw.get('base_dir', '')
583 if base_dir:
584 __create_output_dir(base_dir)
586 # Create targets
587 result = []
588 r = __builder(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
589 env.Depends(r, kw['DOCBOOK_XSL'])
590 result.extend(r)
591 # Add supporting files for cleanup
592 env.Clean(r, ['toc.hhc', 'htmlhelp.hhp', 'index.hhk'] +
593 glob.glob(os.path.join(base_dir, '[ar|bk|ch]*.html')))
595 return result
597 def DocbookPdf(env, target, source=None, *args, **kw):
599 A pseudo-Builder, providing a Docbook toolchain for PDF output.
601 # Init list of targets/sources
602 target, source = __extend_targets_sources(target, source)
604 # Init XSL stylesheet
605 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_PDF', ['fo','docbook.xsl'])
607 # Setup builder
608 __builder = __select_builder(__lxml_builder, __xsltproc_builder)
610 # Create targets
611 result = []
612 for t,s in zip(target,source):
613 t, stem = __ensure_suffix_stem(t, '.pdf')
614 xsl = __builder(env, stem+'.fo', s, **kw)
615 result.extend(xsl)
616 env.Depends(xsl, kw['DOCBOOK_XSL'])
617 result.extend(__fop_builder(env, t, xsl, **kw))
619 return result
621 def DocbookMan(env, target, source=None, *args, **kw):
623 A pseudo-Builder, providing a Docbook toolchain for Man page output.
625 # Init list of targets/sources
626 target, source = __extend_targets_sources(target, source)
628 # Init XSL stylesheet
629 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_MAN', ['manpages','docbook.xsl'])
631 # Setup builder
632 __builder = __select_builder(__lxml_noresult_builder, __xsltproc_builder)
634 # Create targets
635 result = []
636 for t,s in zip(target,source):
637 volnum = "1"
638 outfiles = []
639 srcfile = __ensure_suffix(str(s),'.xml')
640 if os.path.isfile(srcfile):
641 try:
642 import xml.dom.minidom
644 dom = xml.dom.minidom.parse(__ensure_suffix(str(s),'.xml'))
645 # Extract volume number, default is 1
646 for node in dom.getElementsByTagName('refmeta'):
647 for vol in node.getElementsByTagName('manvolnum'):
648 volnum = __get_xml_text(vol)
650 # Extract output filenames
651 for node in dom.getElementsByTagName('refnamediv'):
652 for ref in node.getElementsByTagName('refname'):
653 outfiles.append(__get_xml_text(ref)+'.'+volnum)
655 except Exception:
656 # Use simple regex parsing
657 with open(__ensure_suffix(str(s), '.xml')) as f:
658 content = f.read()
660 for m in re_manvolnum.finditer(content):
661 volnum = m.group(1)
663 for m in re_refname.finditer(content):
664 outfiles.append(m.group(1)+'.'+volnum)
666 if not outfiles:
667 # Use stem of the source file
668 spath = str(s)
669 if not spath.endswith('.xml'):
670 outfiles.append(spath+'.'+volnum)
671 else:
672 stem, ext = os.path.splitext(spath)
673 outfiles.append(stem+'.'+volnum)
674 else:
675 # We have to completely rely on the given target name
676 outfiles.append(t)
678 __builder(env, outfiles[0], s, **kw)
679 env.Depends(outfiles[0], kw['DOCBOOK_XSL'])
680 result.append(outfiles[0])
681 if len(outfiles) > 1:
682 env.Clean(outfiles[0], outfiles[1:])
685 return result
687 def DocbookSlidesPdf(env, target, source=None, *args, **kw):
689 A pseudo-Builder, providing a Docbook toolchain for PDF slides output.
691 # Init list of targets/sources
692 target, source = __extend_targets_sources(target, source)
694 # Init XSL stylesheet
695 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESPDF', ['slides','fo','plain.xsl'])
697 # Setup builder
698 __builder = __select_builder(__lxml_builder, __xsltproc_builder)
700 # Create targets
701 result = []
702 for t,s in zip(target,source):
703 t, stem = __ensure_suffix_stem(t, '.pdf')
704 xsl = __builder(env, stem+'.fo', s, **kw)
705 env.Depends(xsl, kw['DOCBOOK_XSL'])
706 result.extend(xsl)
707 result.extend(__fop_builder(env, t, xsl, **kw))
709 return result
711 def DocbookSlidesHtml(env, target, source=None, *args, **kw):
713 A pseudo-Builder, providing a Docbook toolchain for HTML slides output.
715 # Init list of targets/sources
716 if not SCons.Util.is_List(target):
717 target = [target]
718 if not source:
719 source = target
720 target = ['index.html']
721 elif not SCons.Util.is_List(source):
722 source = [source]
724 # Init XSL stylesheet
725 __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESHTML', ['slides','xhtml','plain.xsl'])
727 # Setup builder
728 __builder = __select_builder(__lxml_builder, __xsltproc_builder)
730 # Detect base dir
731 base_dir = kw.get('base_dir', '')
732 if base_dir:
733 __create_output_dir(base_dir)
735 # Create targets
736 result = []
737 r = __builder(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
738 env.Depends(r, kw['DOCBOOK_XSL'])
739 result.extend(r)
740 # Add supporting files for cleanup
741 env.Clean(r, [os.path.join(base_dir, 'toc.html')] +
742 glob.glob(os.path.join(base_dir, 'foil*.html')))
744 return result
746 def DocbookXInclude(env, target, source, *args, **kw):
748 A pseudo-Builder, for resolving XIncludes in a separate processing step.
750 # Init list of targets/sources
751 target, source = __extend_targets_sources(target, source)
753 # Setup builder
754 __builder = __select_builder(__xinclude_lxml_builder,__xmllint_builder)
756 # Create targets
757 result = []
758 for t,s in zip(target,source):
759 result.extend(__builder(env, t, s, **kw))
761 return result
763 def DocbookXslt(env, target, source=None, *args, **kw):
765 A pseudo-Builder, applying a simple XSL transformation to the input file.
767 # Init list of targets/sources
768 target, source = __extend_targets_sources(target, source)
770 # Init XSL stylesheet
771 kw['DOCBOOK_XSL'] = env.File(kw.get('xsl', 'transform.xsl'))
773 # Setup builder
774 __builder = __select_builder(__lxml_builder, __xsltproc_builder)
776 # Create targets
777 result = []
778 for t,s in zip(target,source):
779 r = __builder(env, t, s, **kw)
780 env.Depends(r, kw['DOCBOOK_XSL'])
781 result.extend(r)
783 return result
786 def generate(env) -> None:
787 """Add Builders and construction variables for docbook to an Environment."""
789 env.SetDefault(
790 # Default names for customized XSL stylesheets
791 DOCBOOK_DEFAULT_XSL_EPUB = '',
792 DOCBOOK_DEFAULT_XSL_HTML = '',
793 DOCBOOK_DEFAULT_XSL_HTMLCHUNKED = '',
794 DOCBOOK_DEFAULT_XSL_HTMLHELP = '',
795 DOCBOOK_DEFAULT_XSL_PDF = '',
796 DOCBOOK_DEFAULT_XSL_MAN = '',
797 DOCBOOK_DEFAULT_XSL_SLIDESPDF = '',
798 DOCBOOK_DEFAULT_XSL_SLIDESHTML = '',
800 # Paths to the detected executables
801 DOCBOOK_XSLTPROC = '',
802 DOCBOOK_XMLLINT = '',
803 DOCBOOK_FOP = '',
805 # Additional flags for the text processors
806 DOCBOOK_XSLTPROCFLAGS = SCons.Util.CLVar(''),
807 DOCBOOK_XMLLINTFLAGS = SCons.Util.CLVar(''),
808 DOCBOOK_FOPFLAGS = SCons.Util.CLVar(''),
809 DOCBOOK_XSLTPROCPARAMS = SCons.Util.CLVar(''),
811 # Default command lines for the detected executables
812 DOCBOOK_XSLTPROCCOM = xsltproc_com['xsltproc'],
813 DOCBOOK_XMLLINTCOM = xmllint_com['xmllint'],
814 DOCBOOK_FOPCOM = fop_com['fop'],
816 # Screen output for the text processors
817 DOCBOOK_XSLTPROCCOMSTR = None,
818 DOCBOOK_XMLLINTCOMSTR = None,
819 DOCBOOK_FOPCOMSTR = None,
822 _detect(env)
824 env.AddMethod(DocbookEpub, "DocbookEpub")
825 env.AddMethod(DocbookHtml, "DocbookHtml")
826 env.AddMethod(DocbookHtmlChunked, "DocbookHtmlChunked")
827 env.AddMethod(DocbookHtmlhelp, "DocbookHtmlhelp")
828 env.AddMethod(DocbookPdf, "DocbookPdf")
829 env.AddMethod(DocbookMan, "DocbookMan")
830 env.AddMethod(DocbookSlidesPdf, "DocbookSlidesPdf")
831 env.AddMethod(DocbookSlidesHtml, "DocbookSlidesHtml")
832 env.AddMethod(DocbookXInclude, "DocbookXInclude")
833 env.AddMethod(DocbookXslt, "DocbookXslt")
836 def exists(env) -> bool:
837 return True