You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

883 lines
29 KiB

6 years ago
  1. """SCons.Tool.docbook
  2. Tool-specific initialization for Docbook.
  3. There normally shouldn't be any need to import this module directly.
  4. It will usually be imported through the generic SCons.Tool.Tool()
  5. selection method.
  6. """
  7. #
  8. # Copyright (c) 2001 - 2017 The SCons Foundation
  9. #
  10. # Permission is hereby granted, free of charge, to any person obtaining
  11. # a copy of this software and associated documentation files (the
  12. # "Software"), to deal in the Software without restriction, including
  13. # without limitation the rights to use, copy, modify, merge, publish,
  14. # distribute, sublicense, and/or sell copies of the Software, and to
  15. # permit persons to whom the Software is furnished to do so, subject to
  16. # the following conditions:
  17. #
  18. # The above copyright notice and this permission notice shall be included
  19. # in all copies or substantial portions of the Software.
  20. #
  21. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  22. # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  23. # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. #
  29. import os
  30. import glob
  31. import re
  32. import SCons.Action
  33. import SCons.Builder
  34. import SCons.Defaults
  35. import SCons.Script
  36. import SCons.Tool
  37. import SCons.Util
  38. __debug_tool_location = False
  39. # Get full path to this script
  40. scriptpath = os.path.dirname(os.path.realpath(__file__))
  41. # Local folder for the collection of DocBook XSLs
  42. db_xsl_folder = 'docbook-xsl-1.76.1'
  43. # Do we have libxml2/libxslt/lxml?
  44. has_libxml2 = True
  45. has_lxml = True
  46. try:
  47. import libxml2
  48. import libxslt
  49. except:
  50. has_libxml2 = False
  51. try:
  52. import lxml
  53. except:
  54. has_lxml = False
  55. # Set this to True, to prefer xsltproc over libxml2 and lxml
  56. prefer_xsltproc = False
  57. # Regexs for parsing Docbook XML sources of MAN pages
  58. re_manvolnum = re.compile("<manvolnum>([^<]*)</manvolnum>")
  59. re_refname = re.compile("<refname>([^<]*)</refname>")
  60. #
  61. # Helper functions
  62. #
  63. def __extend_targets_sources(target, source):
  64. """ Prepare the lists of target and source files. """
  65. if not SCons.Util.is_List(target):
  66. target = [target]
  67. if not source:
  68. source = target[:]
  69. elif not SCons.Util.is_List(source):
  70. source = [source]
  71. if len(target) < len(source):
  72. target.extend(source[len(target):])
  73. return target, source
  74. def __init_xsl_stylesheet(kw, env, user_xsl_var, default_path):
  75. if kw.get('DOCBOOK_XSL','') == '':
  76. xsl_style = kw.get('xsl', env.subst(user_xsl_var))
  77. if xsl_style == '':
  78. path_args = [scriptpath, db_xsl_folder] + default_path
  79. xsl_style = os.path.join(*path_args)
  80. kw['DOCBOOK_XSL'] = xsl_style
  81. def __select_builder(lxml_builder, libxml2_builder, cmdline_builder):
  82. """ Selects a builder, based on which Python modules are present. """
  83. if prefer_xsltproc:
  84. return cmdline_builder
  85. if not has_libxml2:
  86. # At the moment we prefer libxml2 over lxml, the latter can lead
  87. # to conflicts when installed together with libxml2.
  88. if has_lxml:
  89. return lxml_builder
  90. else:
  91. return cmdline_builder
  92. return libxml2_builder
  93. def __ensure_suffix(t, suffix):
  94. """ Ensure that the target t has the given suffix. """
  95. tpath = str(t)
  96. if not tpath.endswith(suffix):
  97. return tpath+suffix
  98. return t
  99. def __ensure_suffix_stem(t, suffix):
  100. """ Ensure that the target t has the given suffix, and return the file's stem. """
  101. tpath = str(t)
  102. if not tpath.endswith(suffix):
  103. stem = tpath
  104. tpath += suffix
  105. return tpath, stem
  106. else:
  107. stem, ext = os.path.splitext(tpath)
  108. return t, stem
  109. def __get_xml_text(root):
  110. """ Return the text for the given root node (xml.dom.minidom). """
  111. txt = ""
  112. for e in root.childNodes:
  113. if (e.nodeType == e.TEXT_NODE):
  114. txt += e.data
  115. return txt
  116. def __create_output_dir(base_dir):
  117. """ Ensure that the output directory base_dir exists. """
  118. root, tail = os.path.split(base_dir)
  119. dir = None
  120. if tail:
  121. if base_dir.endswith('/'):
  122. dir = base_dir
  123. else:
  124. dir = root
  125. else:
  126. if base_dir.endswith('/'):
  127. dir = base_dir
  128. if dir and not os.path.isdir(dir):
  129. os.makedirs(dir)
  130. #
  131. # Supported command line tools and their call "signature"
  132. #
  133. xsltproc_com_priority = ['xsltproc', 'saxon', 'saxon-xslt', 'xalan']
  134. # TODO: Set minimum version of saxon-xslt to be 8.x (lower than this only supports xslt 1.0.
  135. # see: http://saxon.sourceforge.net/saxon6.5.5/
  136. # see: http://saxon.sourceforge.net/
  137. xsltproc_com = {'xsltproc' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE',
  138. 'saxon' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
  139. 'saxon-xslt' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -o $TARGET $DOCBOOK_XSL $SOURCE $DOCBOOK_XSLTPROCPARAMS',
  140. 'xalan' : '$DOCBOOK_XSLTPROC $DOCBOOK_XSLTPROCFLAGS -q -out $TARGET -xsl $DOCBOOK_XSL -in $SOURCE'}
  141. xmllint_com = {'xmllint' : '$DOCBOOK_XMLLINT $DOCBOOK_XMLLINTFLAGS --xinclude $SOURCE > $TARGET'}
  142. fop_com = {'fop' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -fo $SOURCE -pdf $TARGET',
  143. 'xep' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -valid -fo $SOURCE -pdf $TARGET',
  144. 'jw' : '$DOCBOOK_FOP $DOCBOOK_FOPFLAGS -f docbook -b pdf $SOURCE -o $TARGET'}
  145. def __detect_cl_tool(env, chainkey, cdict, cpriority=None):
  146. """
  147. Helper function, picks a command line tool from the list
  148. and initializes its environment variables.
  149. """
  150. if env.get(chainkey,'') == '':
  151. clpath = ''
  152. if cpriority is None:
  153. cpriority = cdict.keys()
  154. for cltool in cpriority:
  155. if __debug_tool_location:
  156. print("DocBook: Looking for %s"%cltool)
  157. clpath = env.WhereIs(cltool)
  158. if clpath:
  159. if __debug_tool_location:
  160. print("DocBook: Found:%s"%cltool)
  161. env[chainkey] = clpath
  162. if not env[chainkey + 'COM']:
  163. env[chainkey + 'COM'] = cdict[cltool]
  164. break
  165. def _detect(env):
  166. """
  167. Detect all the command line tools that we might need for creating
  168. the requested output formats.
  169. """
  170. global prefer_xsltproc
  171. if env.get('DOCBOOK_PREFER_XSLTPROC',''):
  172. prefer_xsltproc = True
  173. if ((not has_libxml2 and not has_lxml) or (prefer_xsltproc)):
  174. # Try to find the XSLT processors
  175. __detect_cl_tool(env, 'DOCBOOK_XSLTPROC', xsltproc_com, xsltproc_com_priority)
  176. __detect_cl_tool(env, 'DOCBOOK_XMLLINT', xmllint_com)
  177. __detect_cl_tool(env, 'DOCBOOK_FOP', fop_com, ['fop','xep','jw'])
  178. #
  179. # Scanners
  180. #
  181. include_re = re.compile('fileref\\s*=\\s*["|\']([^\\n]*)["|\']')
  182. sentity_re = re.compile('<!ENTITY\\s+%*\\s*[^\\s]+\\s+SYSTEM\\s+["|\']([^\\n]*)["|\']>')
  183. def __xml_scan(node, env, path, arg):
  184. """ Simple XML file scanner, detecting local images and XIncludes as implicit dependencies. """
  185. # Does the node exist yet?
  186. if not os.path.isfile(str(node)):
  187. return []
  188. if env.get('DOCBOOK_SCANENT',''):
  189. # Use simple pattern matching for system entities..., no support
  190. # for recursion yet.
  191. contents = node.get_text_contents()
  192. return sentity_re.findall(contents)
  193. xsl_file = os.path.join(scriptpath,'utils','xmldepend.xsl')
  194. if not has_libxml2 or prefer_xsltproc:
  195. if has_lxml and not prefer_xsltproc:
  196. from lxml import etree
  197. xsl_tree = etree.parse(xsl_file)
  198. doc = etree.parse(str(node))
  199. result = doc.xslt(xsl_tree)
  200. depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
  201. return depfiles
  202. else:
  203. # Try to call xsltproc
  204. xsltproc = env.subst("$DOCBOOK_XSLTPROC")
  205. if xsltproc and xsltproc.endswith('xsltproc'):
  206. result = env.backtick(' '.join([xsltproc, xsl_file, str(node)]))
  207. depfiles = [x.strip() for x in str(result).splitlines() if x.strip() != "" and not x.startswith("<?xml ")]
  208. return depfiles
  209. else:
  210. # Use simple pattern matching, there is currently no support
  211. # for xi:includes...
  212. contents = node.get_text_contents()
  213. return include_re.findall(contents)
  214. styledoc = libxml2.parseFile(xsl_file)
  215. style = libxslt.parseStylesheetDoc(styledoc)
  216. doc = libxml2.readFile(str(node), None, libxml2.XML_PARSE_NOENT)
  217. result = style.applyStylesheet(doc, None)
  218. depfiles = []
  219. for x in str(result).splitlines():
  220. if x.strip() != "" and not x.startswith("<?xml "):
  221. depfiles.extend(x.strip().split())
  222. style.freeStylesheet()
  223. doc.freeDoc()
  224. result.freeDoc()
  225. return depfiles
  226. # Creating the instance of our XML dependency scanner
  227. docbook_xml_scanner = SCons.Script.Scanner(function = __xml_scan,
  228. argument = None)
  229. #
  230. # Action generators
  231. #
  232. def __generate_xsltproc_action(source, target, env, for_signature):
  233. cmd = env['DOCBOOK_XSLTPROCCOM']
  234. # Does the environment have a base_dir defined?
  235. base_dir = env.subst('$base_dir')
  236. if base_dir:
  237. # Yes, so replace target path by its filename
  238. return cmd.replace('$TARGET','${TARGET.file}')
  239. return cmd
  240. #
  241. # Emitters
  242. #
  243. def __emit_xsl_basedir(target, source, env):
  244. # Does the environment have a base_dir defined?
  245. base_dir = env.subst('$base_dir')
  246. if base_dir:
  247. # Yes, so prepend it to each target
  248. return [os.path.join(base_dir, str(t)) for t in target], source
  249. # No, so simply pass target and source names through
  250. return target, source
  251. #
  252. # Builders
  253. #
  254. def __build_libxml2(target, source, env):
  255. """
  256. General XSLT builder (HTML/FO), using the libxml2 module.
  257. """
  258. xsl_style = env.subst('$DOCBOOK_XSL')
  259. styledoc = libxml2.parseFile(xsl_style)
  260. style = libxslt.parseStylesheetDoc(styledoc)
  261. doc = libxml2.readFile(str(source[0]),None,libxml2.XML_PARSE_NOENT)
  262. # Support for additional parameters
  263. parampass = {}
  264. if parampass:
  265. result = style.applyStylesheet(doc, parampass)
  266. else:
  267. result = style.applyStylesheet(doc, None)
  268. style.saveResultToFilename(str(target[0]), result, 0)
  269. style.freeStylesheet()
  270. doc.freeDoc()
  271. result.freeDoc()
  272. return None
  273. def __build_lxml(target, source, env):
  274. """
  275. General XSLT builder (HTML/FO), using the lxml module.
  276. """
  277. from lxml import etree
  278. xslt_ac = etree.XSLTAccessControl(read_file=True,
  279. write_file=True,
  280. create_dir=True,
  281. read_network=False,
  282. write_network=False)
  283. xsl_style = env.subst('$DOCBOOK_XSL')
  284. xsl_tree = etree.parse(xsl_style)
  285. transform = etree.XSLT(xsl_tree, access_control=xslt_ac)
  286. doc = etree.parse(str(source[0]))
  287. # Support for additional parameters
  288. parampass = {}
  289. if parampass:
  290. result = transform(doc, **parampass)
  291. else:
  292. result = transform(doc)
  293. try:
  294. of = open(str(target[0]), "wb")
  295. of.write(of.write(etree.tostring(result, pretty_print=True)))
  296. of.close()
  297. except:
  298. pass
  299. return None
  300. def __xinclude_libxml2(target, source, env):
  301. """
  302. Resolving XIncludes, using the libxml2 module.
  303. """
  304. doc = libxml2.readFile(str(source[0]), None, libxml2.XML_PARSE_NOENT)
  305. doc.xincludeProcessFlags(libxml2.XML_PARSE_NOENT)
  306. doc.saveFile(str(target[0]))
  307. doc.freeDoc()
  308. return None
  309. def __xinclude_lxml(target, source, env):
  310. """
  311. Resolving XIncludes, using the lxml module.
  312. """
  313. from lxml import etree
  314. doc = etree.parse(str(source[0]))
  315. doc.xinclude()
  316. try:
  317. doc.write(str(target[0]), xml_declaration=True,
  318. encoding="UTF-8", pretty_print=True)
  319. except:
  320. pass
  321. return None
  322. __libxml2_builder = SCons.Builder.Builder(
  323. action = __build_libxml2,
  324. src_suffix = '.xml',
  325. source_scanner = docbook_xml_scanner,
  326. emitter = __emit_xsl_basedir)
  327. __lxml_builder = SCons.Builder.Builder(
  328. action = __build_lxml,
  329. src_suffix = '.xml',
  330. source_scanner = docbook_xml_scanner,
  331. emitter = __emit_xsl_basedir)
  332. __xinclude_libxml2_builder = SCons.Builder.Builder(
  333. action = __xinclude_libxml2,
  334. suffix = '.xml',
  335. src_suffix = '.xml',
  336. source_scanner = docbook_xml_scanner)
  337. __xinclude_lxml_builder = SCons.Builder.Builder(
  338. action = __xinclude_lxml,
  339. suffix = '.xml',
  340. src_suffix = '.xml',
  341. source_scanner = docbook_xml_scanner)
  342. __xsltproc_builder = SCons.Builder.Builder(
  343. action = SCons.Action.CommandGeneratorAction(__generate_xsltproc_action,
  344. {'cmdstr' : '$DOCBOOK_XSLTPROCCOMSTR'}),
  345. src_suffix = '.xml',
  346. source_scanner = docbook_xml_scanner,
  347. emitter = __emit_xsl_basedir)
  348. __xmllint_builder = SCons.Builder.Builder(
  349. action = SCons.Action.Action('$DOCBOOK_XMLLINTCOM','$DOCBOOK_XMLLINTCOMSTR'),
  350. suffix = '.xml',
  351. src_suffix = '.xml',
  352. source_scanner = docbook_xml_scanner)
  353. __fop_builder = SCons.Builder.Builder(
  354. action = SCons.Action.Action('$DOCBOOK_FOPCOM','$DOCBOOK_FOPCOMSTR'),
  355. suffix = '.pdf',
  356. src_suffix = '.fo',
  357. ensure_suffix=1)
  358. def DocbookEpub(env, target, source=None, *args, **kw):
  359. """
  360. A pseudo-Builder, providing a Docbook toolchain for ePub output.
  361. """
  362. import zipfile
  363. import shutil
  364. def build_open_container(target, source, env):
  365. """Generate the *.epub file from intermediate outputs
  366. Constructs the epub file according to the Open Container Format. This
  367. function could be replaced by a call to the SCons Zip builder if support
  368. was added for different compression formats for separate source nodes.
  369. """
  370. zf = zipfile.ZipFile(str(target[0]), 'w')
  371. mime_file = open('mimetype', 'w')
  372. mime_file.write('application/epub+zip')
  373. mime_file.close()
  374. zf.write(mime_file.name, compress_type = zipfile.ZIP_STORED)
  375. for s in source:
  376. if os.path.isfile(str(s)):
  377. head, tail = os.path.split(str(s))
  378. if not head:
  379. continue
  380. s = head
  381. for dirpath, dirnames, filenames in os.walk(str(s)):
  382. for fname in filenames:
  383. path = os.path.join(dirpath, fname)
  384. if os.path.isfile(path):
  385. zf.write(path, os.path.relpath(path, str(env.get('ZIPROOT', ''))),
  386. zipfile.ZIP_DEFLATED)
  387. zf.close()
  388. def add_resources(target, source, env):
  389. """Add missing resources to the OEBPS directory
  390. Ensure all the resources in the manifest are present in the OEBPS directory.
  391. """
  392. hrefs = []
  393. content_file = os.path.join(source[0].get_abspath(), 'content.opf')
  394. if not os.path.isfile(content_file):
  395. return
  396. hrefs = []
  397. if has_libxml2:
  398. nsmap = {'opf' : 'http://www.idpf.org/2007/opf'}
  399. # Read file and resolve entities
  400. doc = libxml2.readFile(content_file, None, 0)
  401. opf = doc.getRootElement()
  402. # Create xpath context
  403. xpath_context = doc.xpathNewContext()
  404. # Register namespaces
  405. for key, val in nsmap.items():
  406. xpath_context.xpathRegisterNs(key, val)
  407. if hasattr(opf, 'xpathEval') and xpath_context:
  408. # Use the xpath context
  409. xpath_context.setContextNode(opf)
  410. items = xpath_context.xpathEval(".//opf:item")
  411. else:
  412. items = opf.findall(".//{'http://www.idpf.org/2007/opf'}item")
  413. for item in items:
  414. if hasattr(item, 'prop'):
  415. hrefs.append(item.prop('href'))
  416. else:
  417. hrefs.append(item.attrib['href'])
  418. doc.freeDoc()
  419. xpath_context.xpathFreeContext()
  420. elif has_lxml:
  421. from lxml import etree
  422. opf = etree.parse(content_file)
  423. # All the opf:item elements are resources
  424. for item in opf.xpath('//opf:item',
  425. namespaces= { 'opf': 'http://www.idpf.org/2007/opf' }):
  426. hrefs.append(item.attrib['href'])
  427. for href in hrefs:
  428. # If the resource was not already created by DocBook XSL itself,
  429. # copy it into the OEBPS folder
  430. referenced_file = os.path.join(source[0].get_abspath(), href)
  431. if not os.path.exists(referenced_file):
  432. shutil.copy(href, os.path.join(source[0].get_abspath(), href))
  433. # Init list of targets/sources
  434. target, source = __extend_targets_sources(target, source)
  435. # Init XSL stylesheet
  436. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_EPUB', ['epub','docbook.xsl'])
  437. # Setup builder
  438. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  439. # Create targets
  440. result = []
  441. if not env.GetOption('clean'):
  442. # Ensure that the folders OEBPS and META-INF exist
  443. __create_output_dir('OEBPS/')
  444. __create_output_dir('META-INF/')
  445. dirs = env.Dir(['OEBPS', 'META-INF'])
  446. # Set the fixed base_dir
  447. kw['base_dir'] = 'OEBPS/'
  448. tocncx = __builder.__call__(env, 'toc.ncx', source[0], **kw)
  449. cxml = env.File('META-INF/container.xml')
  450. env.SideEffect(cxml, tocncx)
  451. env.Depends(tocncx, kw['DOCBOOK_XSL'])
  452. result.extend(tocncx+[cxml])
  453. container = env.Command(__ensure_suffix(str(target[0]), '.epub'),
  454. tocncx+[cxml], [add_resources, build_open_container])
  455. mimetype = env.File('mimetype')
  456. env.SideEffect(mimetype, container)
  457. result.extend(container)
  458. # Add supporting files for cleanup
  459. env.Clean(tocncx, dirs)
  460. return result
  461. def DocbookHtml(env, target, source=None, *args, **kw):
  462. """
  463. A pseudo-Builder, providing a Docbook toolchain for HTML output.
  464. """
  465. # Init list of targets/sources
  466. target, source = __extend_targets_sources(target, source)
  467. # Init XSL stylesheet
  468. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTML', ['html','docbook.xsl'])
  469. # Setup builder
  470. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  471. # Create targets
  472. result = []
  473. for t,s in zip(target,source):
  474. r = __builder.__call__(env, __ensure_suffix(t,'.html'), s, **kw)
  475. env.Depends(r, kw['DOCBOOK_XSL'])
  476. result.extend(r)
  477. return result
  478. def DocbookHtmlChunked(env, target, source=None, *args, **kw):
  479. """
  480. A pseudo-Builder, providing a Docbook toolchain for chunked HTML output.
  481. """
  482. # Init target/source
  483. if not SCons.Util.is_List(target):
  484. target = [target]
  485. if not source:
  486. source = target
  487. target = ['index.html']
  488. elif not SCons.Util.is_List(source):
  489. source = [source]
  490. # Init XSL stylesheet
  491. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLCHUNKED', ['html','chunkfast.xsl'])
  492. # Setup builder
  493. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  494. # Detect base dir
  495. base_dir = kw.get('base_dir', '')
  496. if base_dir:
  497. __create_output_dir(base_dir)
  498. # Create targets
  499. result = []
  500. r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
  501. env.Depends(r, kw['DOCBOOK_XSL'])
  502. result.extend(r)
  503. # Add supporting files for cleanup
  504. env.Clean(r, glob.glob(os.path.join(base_dir, '*.html')))
  505. return result
  506. def DocbookHtmlhelp(env, target, source=None, *args, **kw):
  507. """
  508. A pseudo-Builder, providing a Docbook toolchain for HTMLHELP output.
  509. """
  510. # Init target/source
  511. if not SCons.Util.is_List(target):
  512. target = [target]
  513. if not source:
  514. source = target
  515. target = ['index.html']
  516. elif not SCons.Util.is_List(source):
  517. source = [source]
  518. # Init XSL stylesheet
  519. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_HTMLHELP', ['htmlhelp','htmlhelp.xsl'])
  520. # Setup builder
  521. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  522. # Detect base dir
  523. base_dir = kw.get('base_dir', '')
  524. if base_dir:
  525. __create_output_dir(base_dir)
  526. # Create targets
  527. result = []
  528. r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
  529. env.Depends(r, kw['DOCBOOK_XSL'])
  530. result.extend(r)
  531. # Add supporting files for cleanup
  532. env.Clean(r, ['toc.hhc', 'htmlhelp.hhp', 'index.hhk'] +
  533. glob.glob(os.path.join(base_dir, '[ar|bk|ch]*.html')))
  534. return result
  535. def DocbookPdf(env, target, source=None, *args, **kw):
  536. """
  537. A pseudo-Builder, providing a Docbook toolchain for PDF output.
  538. """
  539. # Init list of targets/sources
  540. target, source = __extend_targets_sources(target, source)
  541. # Init XSL stylesheet
  542. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_PDF', ['fo','docbook.xsl'])
  543. # Setup builder
  544. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  545. # Create targets
  546. result = []
  547. for t,s in zip(target,source):
  548. t, stem = __ensure_suffix_stem(t, '.pdf')
  549. xsl = __builder.__call__(env, stem+'.fo', s, **kw)
  550. result.extend(xsl)
  551. env.Depends(xsl, kw['DOCBOOK_XSL'])
  552. result.extend(__fop_builder.__call__(env, t, xsl, **kw))
  553. return result
  554. def DocbookMan(env, target, source=None, *args, **kw):
  555. """
  556. A pseudo-Builder, providing a Docbook toolchain for Man page output.
  557. """
  558. # Init list of targets/sources
  559. target, source = __extend_targets_sources(target, source)
  560. # Init XSL stylesheet
  561. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_MAN', ['manpages','docbook.xsl'])
  562. # Setup builder
  563. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  564. # Create targets
  565. result = []
  566. for t,s in zip(target,source):
  567. volnum = "1"
  568. outfiles = []
  569. srcfile = __ensure_suffix(str(s),'.xml')
  570. if os.path.isfile(srcfile):
  571. try:
  572. import xml.dom.minidom
  573. dom = xml.dom.minidom.parse(__ensure_suffix(str(s),'.xml'))
  574. # Extract volume number, default is 1
  575. for node in dom.getElementsByTagName('refmeta'):
  576. for vol in node.getElementsByTagName('manvolnum'):
  577. volnum = __get_xml_text(vol)
  578. # Extract output filenames
  579. for node in dom.getElementsByTagName('refnamediv'):
  580. for ref in node.getElementsByTagName('refname'):
  581. outfiles.append(__get_xml_text(ref)+'.'+volnum)
  582. except:
  583. # Use simple regex parsing
  584. f = open(__ensure_suffix(str(s),'.xml'), 'r')
  585. content = f.read()
  586. f.close()
  587. for m in re_manvolnum.finditer(content):
  588. volnum = m.group(1)
  589. for m in re_refname.finditer(content):
  590. outfiles.append(m.group(1)+'.'+volnum)
  591. if not outfiles:
  592. # Use stem of the source file
  593. spath = str(s)
  594. if not spath.endswith('.xml'):
  595. outfiles.append(spath+'.'+volnum)
  596. else:
  597. stem, ext = os.path.splitext(spath)
  598. outfiles.append(stem+'.'+volnum)
  599. else:
  600. # We have to completely rely on the given target name
  601. outfiles.append(t)
  602. __builder.__call__(env, outfiles[0], s, **kw)
  603. env.Depends(outfiles[0], kw['DOCBOOK_XSL'])
  604. result.append(outfiles[0])
  605. if len(outfiles) > 1:
  606. env.Clean(outfiles[0], outfiles[1:])
  607. return result
  608. def DocbookSlidesPdf(env, target, source=None, *args, **kw):
  609. """
  610. A pseudo-Builder, providing a Docbook toolchain for PDF slides output.
  611. """
  612. # Init list of targets/sources
  613. target, source = __extend_targets_sources(target, source)
  614. # Init XSL stylesheet
  615. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESPDF', ['slides','fo','plain.xsl'])
  616. # Setup builder
  617. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  618. # Create targets
  619. result = []
  620. for t,s in zip(target,source):
  621. t, stem = __ensure_suffix_stem(t, '.pdf')
  622. xsl = __builder.__call__(env, stem+'.fo', s, **kw)
  623. env.Depends(xsl, kw['DOCBOOK_XSL'])
  624. result.extend(xsl)
  625. result.extend(__fop_builder.__call__(env, t, xsl, **kw))
  626. return result
  627. def DocbookSlidesHtml(env, target, source=None, *args, **kw):
  628. """
  629. A pseudo-Builder, providing a Docbook toolchain for HTML slides output.
  630. """
  631. # Init list of targets/sources
  632. if not SCons.Util.is_List(target):
  633. target = [target]
  634. if not source:
  635. source = target
  636. target = ['index.html']
  637. elif not SCons.Util.is_List(source):
  638. source = [source]
  639. # Init XSL stylesheet
  640. __init_xsl_stylesheet(kw, env, '$DOCBOOK_DEFAULT_XSL_SLIDESHTML', ['slides','html','plain.xsl'])
  641. # Setup builder
  642. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  643. # Detect base dir
  644. base_dir = kw.get('base_dir', '')
  645. if base_dir:
  646. __create_output_dir(base_dir)
  647. # Create targets
  648. result = []
  649. r = __builder.__call__(env, __ensure_suffix(str(target[0]), '.html'), source[0], **kw)
  650. env.Depends(r, kw['DOCBOOK_XSL'])
  651. result.extend(r)
  652. # Add supporting files for cleanup
  653. env.Clean(r, [os.path.join(base_dir, 'toc.html')] +
  654. glob.glob(os.path.join(base_dir, 'foil*.html')))
  655. return result
  656. def DocbookXInclude(env, target, source, *args, **kw):
  657. """
  658. A pseudo-Builder, for resolving XIncludes in a separate processing step.
  659. """
  660. # Init list of targets/sources
  661. target, source = __extend_targets_sources(target, source)
  662. # Setup builder
  663. __builder = __select_builder(__xinclude_lxml_builder,__xinclude_libxml2_builder,__xmllint_builder)
  664. # Create targets
  665. result = []
  666. for t,s in zip(target,source):
  667. result.extend(__builder.__call__(env, t, s, **kw))
  668. return result
  669. def DocbookXslt(env, target, source=None, *args, **kw):
  670. """
  671. A pseudo-Builder, applying a simple XSL transformation to the input file.
  672. """
  673. # Init list of targets/sources
  674. target, source = __extend_targets_sources(target, source)
  675. # Init XSL stylesheet
  676. kw['DOCBOOK_XSL'] = kw.get('xsl', 'transform.xsl')
  677. # Setup builder
  678. __builder = __select_builder(__lxml_builder, __libxml2_builder, __xsltproc_builder)
  679. # Create targets
  680. result = []
  681. for t,s in zip(target,source):
  682. r = __builder.__call__(env, t, s, **kw)
  683. env.Depends(r, kw['DOCBOOK_XSL'])
  684. result.extend(r)
  685. return result
  686. def generate(env):
  687. """Add Builders and construction variables for docbook to an Environment."""
  688. env.SetDefault(
  689. # Default names for customized XSL stylesheets
  690. DOCBOOK_DEFAULT_XSL_EPUB = '',
  691. DOCBOOK_DEFAULT_XSL_HTML = '',
  692. DOCBOOK_DEFAULT_XSL_HTMLCHUNKED = '',
  693. DOCBOOK_DEFAULT_XSL_HTMLHELP = '',
  694. DOCBOOK_DEFAULT_XSL_PDF = '',
  695. DOCBOOK_DEFAULT_XSL_MAN = '',
  696. DOCBOOK_DEFAULT_XSL_SLIDESPDF = '',
  697. DOCBOOK_DEFAULT_XSL_SLIDESHTML = '',
  698. # Paths to the detected executables
  699. DOCBOOK_XSLTPROC = '',
  700. DOCBOOK_XMLLINT = '',
  701. DOCBOOK_FOP = '',
  702. # Additional flags for the text processors
  703. DOCBOOK_XSLTPROCFLAGS = SCons.Util.CLVar(''),
  704. DOCBOOK_XMLLINTFLAGS = SCons.Util.CLVar(''),
  705. DOCBOOK_FOPFLAGS = SCons.Util.CLVar(''),
  706. DOCBOOK_XSLTPROCPARAMS = SCons.Util.CLVar(''),
  707. # Default command lines for the detected executables
  708. DOCBOOK_XSLTPROCCOM = xsltproc_com['xsltproc'],
  709. DOCBOOK_XMLLINTCOM = xmllint_com['xmllint'],
  710. DOCBOOK_FOPCOM = fop_com['fop'],
  711. # Screen output for the text processors
  712. DOCBOOK_XSLTPROCCOMSTR = None,
  713. DOCBOOK_XMLLINTCOMSTR = None,
  714. DOCBOOK_FOPCOMSTR = None,
  715. )
  716. _detect(env)
  717. env.AddMethod(DocbookEpub, "DocbookEpub")
  718. env.AddMethod(DocbookHtml, "DocbookHtml")
  719. env.AddMethod(DocbookHtmlChunked, "DocbookHtmlChunked")
  720. env.AddMethod(DocbookHtmlhelp, "DocbookHtmlhelp")
  721. env.AddMethod(DocbookPdf, "DocbookPdf")
  722. env.AddMethod(DocbookMan, "DocbookMan")
  723. env.AddMethod(DocbookSlidesPdf, "DocbookSlidesPdf")
  724. env.AddMethod(DocbookSlidesHtml, "DocbookSlidesHtml")
  725. env.AddMethod(DocbookXInclude, "DocbookXInclude")
  726. env.AddMethod(DocbookXslt, "DocbookXslt")
  727. def exists(env):
  728. return 1