From bb6df9a0a991d05c6b3dc9d591d91a44cb03b3eb Mon Sep 17 00:00:00 2001 From: Gauvain Pocentek Date: Fri, 18 Jul 2014 15:44:18 +0200 Subject: [PATCH] Provide openstack-dn2osdbk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tool converts docutils native XML to docbook. The conversion is done using XSLT, with some custom XML treatment. The XSL source is a slightly modified version of Dn2dbk written by Éric Bellot (http://ebellot.chez.com/dn2dbk/index.htm). Modify doctest to check and build the HOT guide. Implements: blueprint heat-templates Change-Id: If866a5ffe19165c38981493684de550e0df0e36d --- README.rst | 2 + os_doc_tools/dn2osdbk.py | 225 ++++++++ os_doc_tools/doctest.py | 25 + os_doc_tools/resources/dn2osdbk.xsl | 787 ++++++++++++++++++++++++++++ setup.cfg | 1 + 5 files changed, 1040 insertions(+) create mode 100644 os_doc_tools/dn2osdbk.py create mode 100644 os_doc_tools/resources/dn2osdbk.xsl diff --git a/README.rst b/README.rst index 3aae154c..655b49d7 100644 --- a/README.rst +++ b/README.rst @@ -102,6 +102,8 @@ Release notes * Added support for *-manage CLI doc generation. * Various smaller fixes and improvements. +* ``openstack-dn2osdbk``: Converts Docutils Native XML to docbook. +* ``openstack-doc-test``: Handle the upcoming HOT guide. 0.16.1 ------ diff --git a/os_doc_tools/dn2osdbk.py b/os_doc_tools/dn2osdbk.py new file mode 100644 index 00000000..24fe53c6 --- /dev/null +++ b/os_doc_tools/dn2osdbk.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import argparse +import glob +import os +import sys + +from lxml import etree + + +OS_DOC_TOOLS_DIR = os.path.dirname(__file__) +DN2DBK = os.path.join(OS_DOC_TOOLS_DIR, 'resources', 'dn2osdbk.xsl') +XML_NS = '{http://www.w3.org/XML/1998/namespace}' +TRANSFORMERS = {'chapter': 'ChapterTransformer', 'book': 'BookTransformer'} + + +class XMLFileTransformer(object): + """Transform a single DN XML file to docbook. + + Call the transform() method to generate the docbook output. + """ + + def __init__(self, source_file, toplevel='chapter'): + """Initialize an instance. + + :param source_file: The path to the source DN XML file. + :param toplevel: The top level tag of the generated docbook ('book' or + 'chapter'). + """ + self.source_file = source_file + self.toplevel = toplevel + basename = os.path.basename(self.source_file) + self.basename = os.path.splitext(basename)[0] + self.xslt_file = DN2DBK + + def _xslt_transform(self, xml): + with open(self.xslt_file) as fd: + xslt = fd.read() + xslt_root = etree.XML(xslt) + transform = etree.XSLT(xslt_root) + return transform(xml) + + def _custom_transform(self, tree): + # Set the correct root tag + root = tree.getroot() + root.tag = '{http://docbook.org/ns/docbook}%s' % self.toplevel + + # Add a comment to warn that the file is autogenerated + comment = etree.Comment("WARNING: This file is automatically " + "generated. Do not edit it.") + root.insert(0, comment) + + for item in tree.iter(): + # Find tags with an 'id' attribute, and prefix its value with the + # basename of the file being processed. This avoids id conflicts + # when working with multiple source files. + id_attrib = '%sid' % XML_NS + id = item.get(id_attrib) + if id is not None: + id = id.split(' ')[-1] + id = "%s_%s" % (self.basename, id) + item.attrib[id_attrib] = id + + # Same for the linkend attribute. + linkend = item.get('linkend') + if linkend is not None: + item.attrib['linkend'] = "%s_%s" % (self.basename, linkend) + + return tree + + def transform(self): + """Generate the docbook XML.""" + with open(self.source_file) as fd: + source_xml = fd.read() + + source_doc = etree.XML(source_xml) + result_tree = self._xslt_transform(source_doc) + result_tree = self._custom_transform(result_tree) + return etree.tostring(result_tree, pretty_print=True, + xml_declaration=True, encoding="UTF-8") + + +class BaseFolderTransformer(object): + """Base class for generating docbook from an DN XML source dir.""" + file_toplevel = 'section' + + def __init__(self, source_dir, output_dir, index='index.xml'): + """Initialize an instance. + + :param source_dir: The DN XML source directory. + :param output_dir: The directory in which docbook files will be stored. + This directory is created if it doesn't exist. + :param index: The name of the index file in the source dir. + """ + self.source_dir = source_dir + self.output_dir = output_dir + self.index = index + self.includes = [] + self.title = None + + self.parse_index() + + if not os.path.isdir(self.output_dir): + os.makedirs(self.output_dir) + + def parse_index(self): + """Generates part of the docbook index from the DN XML index. + + The full index is generated in the write_index method. + """ + src = os.path.join(self.source_dir, 'index.xml') + src_xml = etree.XML(open(src).read()) + for reference in src_xml.iter('reference'): + if '#' in reference.get('refuri'): + continue + + self.includes.append("%s_%s.xml" % (self.file_toplevel, + reference.get('refuri'))) + + self.title = src_xml.find('section/title').text + + def write_index(self): + """Write the index file. + + This method must be implemented by subclasses. + """ + raise NotImplementedError + + def transform(self): + """Perform the actual conversion.""" + files = glob.glob(os.path.join(self.source_dir, '*.xml')) + for src in files: + if src.endswith('/index.xml'): + continue + basename = '%s_%s' % (self.file_toplevel, os.path.basename(src)) + output_file = os.path.join(self.output_dir, basename) + transformer = XMLFileTransformer(src, self.file_toplevel) + xml = transformer.transform() + open(output_file, 'w').write(xml) + self.write_index() + + +class BookTransformer(BaseFolderTransformer): + """Create a docbook book.""" + file_toplevel = 'chapter' + + def write_index(self): + output_file = os.path.join(self.output_dir, 'index.xml') + xml_id = self.title.lower().replace(' ', '-') + includes = "\n ".join([ + '' % i for i in self.includes]) + + output = ''' + + + + + + %(title)s + %(includes)s +''' % {'xml_id': xml_id, 'title': self.title, 'includes': includes} + + open(output_file, 'w').write(output) + + +class ChapterTransformer(BaseFolderTransformer): + """Create a docbook chapter.""" + file_toplevel = 'section' + + def write_index(self): + output_file = os.path.join(self.output_dir, 'index.xml') + xml_id = self.title.lower().replace(' ', '-') + includes = "\n ".join([ + '' % i for i in self.includes]) + + output = ''' + + + + + %(title) + %(includes)s +''' % {'xml_id': xml_id, 'title': self.title, 'includes': includes} + + open(output_file, 'w').write(output) + + +def main(): + parser = argparse.ArgumentParser(description="Generate docbook from " + "DocUtils Native XML format") + parser.add_argument('source', help='Source directory.') + parser.add_argument('output', help='Output file.') + parser.add_argument('--toplevel', help='Toplevel flag.', + choices=['book', 'chapter'], + default='chapter') + args = parser.parse_args() + + cls = globals()[TRANSFORMERS[args.toplevel]] + transformer = cls(args.source, args.output) + sys.exit(transformer.transform()) + + +if __name__ == "__main__": + main() diff --git a/os_doc_tools/doctest.py b/os_doc_tools/doctest.py index b781a733..387502ae 100755 --- a/os_doc_tools/doctest.py +++ b/os_doc_tools/doctest.py @@ -798,6 +798,28 @@ def build_book(book, publish_path, log_path): ) # Success base_book = "install-guide (for Debian, Fedora, openSUSE, Ubuntu)" + # HOT template guide + elif base_book == 'hot-guide': + # Make sure that the build dir is clean + if os.path.isdir('build'): + shutil.rmtree('build') + # Generate the DN XML + output = subprocess.check_output( + ["make", "xml"], + stderr=subprocess.STDOUT + ) + out_file.write(output) + # Generate the docbook book + output = subprocess.check_output( + ["openstack-dn2osdbk", "build/xml", "build/docbook", + "--toplevel", "book"], + stderr=subprocess.STDOUT + ) + out_file.write(output) + output = subprocess.check_output( + ["mvn", "generate-sources", comments, release, "-B"], + stderr=subprocess.STDOUT + ) # Repository: identity-api elif (cfg.CONF.repo_name == "identity-api" and book.endswith("v3")): @@ -918,6 +940,9 @@ def find_affected_books(rootdir, book_exceptions, file_exceptions, f_abs = os.path.abspath(os.path.join(root, f)) if is_book_master(f_base): book_bk[f_abs] = book_root + if "doc/hot-guide/" in f_abs: + affected_books.add('hot-guide') + continue if not is_testable_xml_file(f, file_exceptions): continue diff --git a/os_doc_tools/resources/dn2osdbk.xsl b/os_doc_tools/resources/dn2osdbk.xsl new file mode 100644 index 00000000..40671284 --- /dev/null +++ b/os_doc_tools/resources/dn2osdbk.xsl @@ -0,0 +1,787 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:apply-templates/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+
+ + + + <xsl:apply-templates/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <xsl:apply-templates/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/setup.cfg b/setup.cfg index 86796ecc..fe7e8aad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,6 +37,7 @@ console_scripts = openstack-generate-docbook = os_doc_tools.handle_pot:generatedocbook openstack-generate-pot = os_doc_tools.handle_pot:generatepot openstack-jsoncheck = os_doc_tools.jsoncheck:main + openstack-dn2osdbk = os_doc_tools.dn2osdbk:main [build_sphinx] source-dir = doc/source