I'm trying to help out with linux documentation. Right now, that uses a huge toolchain involving custom scripts and docbook. We'd like to use asciidoc, but in its current form, you either get to retain a dependency on docbook, along with all that entails, or the resulting html requires javascript to add a table of contents and other similar navigational aids.
Here's a set of patches which provide a general mechanism to divert output from a conf file section into an attribute, and then to insert that attribute into any location of the document. -- You received this message because you are subscribed to the Google Groups "asciidoc" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. To post to this group, send email to [email protected]. Visit this group at https://groups.google.com/group/asciidoc. For more options, visit https://groups.google.com/d/optout.
>From 66508ea3a243c01f1672b7c1b95e2795508ab769 Mon Sep 17 00:00:00 2001 From: Keith Packard <[email protected]> Date: Fri, 12 Feb 2016 18:39:54 -0800 Subject: [PATCH 3/5] Add | operator to attribute processing. The | operator operates like the = operator except that the substitution is delayed until after the entire document has been processed once so that the replacement value will contain the final value of the attribute rather than the current value. Signed-off-by: Keith Packard <[email protected]> --- asciidoc.py | 22 ++++++++++++++++++---- doc/asciidoc.txt | 5 +++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/asciidoc.py b/asciidoc.py index d1a8929..2c85e36 100755 --- a/asciidoc.py +++ b/asciidoc.py @@ -1002,7 +1002,7 @@ def system(name, args, is_macro=False, attrs=None): result = '\x07' + str(len(macros.passthroughs)-1) + '\x07' return result -def subs_attrs(lines, dictionary=None): +def subs_attrs(lines, dictionary=None, leavedivert=True): """Substitute 'lines' of text with attributes from the global document.attributes dictionary and from 'dictionary' ('dictionary' entries take precedence). Return a tuple of the substituted lines. 'lines' @@ -1077,11 +1077,11 @@ def subs_attrs(lines, dictionary=None): # Expand conditional attributes. # Single name -- higher precedence. reo1 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w]*?)' \ - r'(?P<op>\=|\?|!|#|%|@|\$)' \ + r'(?P<op>\=|\?|!|#|%|@|\$|\|)' \ r'(?P<value>.*?)\}(?!\\)') # Multiple names (n1,n2,... or n1+n2+...) -- lower precedence. reo2 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w'+OR+AND+r']*?)' \ - r'(?P<op>\=|\?|!|#|%|@|\$)' \ + r'(?P<op>\=|\?|!|#|%|@|\$|\|)' \ r'(?P<value>.*?)\}(?!\\)') for reo in [reo1,reo2]: pos = 0 @@ -1128,6 +1128,11 @@ def subs_attrs(lines, dictionary=None): elif op == '!': s = rval elif op == '#': s = UNDEFINED # So the line is dropped. elif op == '%': s = rval + elif op == '|': + if leavedivert: + s = '{' + name + '|}' + else: + s = rval elif op in ('@','$'): s = UNDEFINED # So the line is dropped. else: @@ -1138,6 +1143,11 @@ def subs_attrs(lines, dictionary=None): elif op == '!': s = '' elif op == '#': s = rval elif op == '%': s = UNDEFINED # So the line is dropped. + elif op == '|': + if leavedivert: + s = '{' + name + '|}' + else: + s = lval elif op in ('@','$'): v = re.split(r'(?<!\\):',rval) if len(v) not in (2,3): @@ -4420,6 +4430,7 @@ class Writer: self.fname = None # Output file name. self.lines_out = 0 # Number of lines written. self.skip_blank_lines = False # If True don't output blank lines. + self.lines = [] def open(self,fname,bom=None): ''' bom is optional byte order mark. @@ -4436,10 +4447,13 @@ class Writer: self.lines_out = 0 def close(self): if self.fname != '<stdout>': + lines = subs_attrs(self.lines, leavedivert=False) + for line in lines: + self.f.write((line or '') + self.newline) self.f.close() def write_line(self, line=None): if not (self.skip_blank_lines and (not line or not line.strip())): - self.f.write((line or '') + self.newline) + self.lines.append(line) self.lines_out = self.lines_out + 1 def write(self,*args,**kwargs): """Iterates arguments, writes tuple and list arguments one line per diff --git a/doc/asciidoc.txt b/doc/asciidoc.txt index fd33c61..9cfe818 100644 --- a/doc/asciidoc.txt +++ b/doc/asciidoc.txt @@ -4443,6 +4443,11 @@ the following forms: defined otherwise the containing line is dropped. `<value>` can contain simple attribute references. +`{<names>|<value>}`:: + This works exactly like `=`, except that substitution is + delayed until the entire document has been processed to + capture the final value rather than the current value. + `{<names>@<regexp>:<value1>[:<value2>]}`:: `<value1>` is substituted if the value of attribute `<names>` matches the regular expression `<regexp>` otherwise `<value2>` -- 2.7.0
>From 8cee7b12b74983ce14fd14fecd47c1bdb07a5603 Mon Sep 17 00:00:00 2001 From: Keith Packard <[email protected]> Date: Fri, 12 Feb 2016 18:42:05 -0800 Subject: [PATCH 4/5] Add diversions to conf sections Diversions append text in the conf file section to an attribute rather than emitting text directly. This can be used to generate a table of contents or list of figures. Signed-off-by: Keith Packard <[email protected]> --- asciidoc.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 10 deletions(-) diff --git a/asciidoc.py b/asciidoc.py index 2c85e36..68871b7 100755 --- a/asciidoc.py +++ b/asciidoc.py @@ -1575,6 +1575,17 @@ class Document(object): finished = False AttributeList.translate() return result + def divert(self,dname,content): + """ + Append 'content' to attribute 'dname' + """ + value = "" + for s in content: + value = value + s + '\n' + if self.attributes.get(dname) is None: + self.attributes[dname] = value + else: + self.attributes[dname] += value def parse_header(self,doctype,backend): """ Parses header, sets corresponding document attributes and finalizes @@ -1656,10 +1667,14 @@ class Document(object): # Translate 'preamble' (untitled elements between header # and first section title). if Lex.next() is not Title: - stag,etag = config.section2tags('preamble') + stag,etag,dtag = config.section2tags('preamble') + if dtag: + document.divert(dtag[0], dtag[1]) writer.write(stag,trace='preamble open') Section.translate_body() writer.write(etag,trace='preamble close') + if dtag: + document.divert(dtag[0], dtag[2]) elif self.doctype == 'manpage' and 'name' in config.sections: writer.write(config.subs_section('name',{}), trace='name') else: @@ -2217,7 +2232,7 @@ class FloatingTitle(Title): AttributeList.consume(Title.attributes) template = 'floatingtitle' if template in config.sections: - stag,etag = config.section2tags(template,Title.attributes) + stag,etag,dtag = config.section2tags(template,Title.attributes) writer.write(stag,trace='floating title') else: message.warning('missing template section: [%s]' % template) @@ -2226,6 +2241,7 @@ class FloatingTitle(Title): class Section: """Static methods and attributes only.""" endtags = [] # Stack of currently open section (level,endtag) tuples. + enddiv = [] # Stack of currently open section (level,diversion) tuples. ids = [] # List of already used ids. def __init__(self): raise AssertionError,'no class instances allowed' @@ -2234,10 +2250,17 @@ class Section: """Save section end.""" Section.endtags.append((level,etag)) @staticmethod + def savediv(level,dtag): + """Save section end.""" + Section.enddiv.append((level,dtag)) + @staticmethod def setlevel(level): """Set document level and write open section close tags up to level.""" while Section.endtags and Section.endtags[-1][0] >= level: writer.write(Section.endtags.pop()[1],trace='section close') + while Section.enddiv and Section.enddiv[-1][0] >= level: + dtag = Section.enddiv.pop()[1] + document.divert(dtag[0], dtag[2]) document.level = level @staticmethod def gen_id(title): @@ -2310,8 +2333,11 @@ class Section: else: Title.attributes['sectnum'] = '' AttributeList.consume(Title.attributes) - stag,etag = config.section2tags(Title.sectname,Title.attributes) + stag,etag,dtag = config.section2tags(Title.sectname,Title.attributes) Section.savetag(Title.level,etag) + if dtag: + document.divert(dtag[0], dtag[1]) + Section.savediv(Title.level,dtag) writer.write(stag,trace='section open: level %d: %s' % (Title.level, Title.attributes['title'])) Section.translate_body() @@ -3079,10 +3105,14 @@ class DelimitedBlock(AbstractBlock): name = self.short_name()+' block' if 'sectionbody' in options: # The body is treated like a section body. - stag,etag = config.section2tags(template,self.attributes) + stag,etag,dtag = config.section2tags(template,self.attributes) + if dtag: + document.divert(dtag[0], dtag[1]) writer.write(stag,trace=name+' open') Section.translate_body(self) writer.write(etag,trace=name+' close') + if dtag: + document.divert(dtag[0], dtag[2]) else: stag = config.section2tags(template,self.attributes,skipend=True)[0] body = reader.read_until(self.delimiter,same_file=True) @@ -5124,23 +5154,57 @@ class Config: for k,v in self.sections.items(): self.sections[k] = self.expand_templates(v) - def section2tags(self, section, d={}, skipstart=False, skipend=False): + def section2tags(self, section, d={}, skipstart=False, skipend=False, skipdiv=False): """Perform attribute substitution on 'section' using document - attributes plus 'd' attributes. Return tuple (stag,etag) containing - pre and post | placeholder tags. 'skipstart' and 'skipend' are - used to suppress substitution.""" + attributes plus 'd' attributes. Return tuple (stag,etag,dtag) + containing pre and post | placeholder tags and any preceding + diversion. 'skipstart', 'skipend' and 'skipdiv' are used to + suppress substitution.""" assert section is not None if section in self.sections: body = self.sections[section] else: message.warning('missing section: [%s]' % section) body = () - # Split macro body into start and end tag lists. + # Split macro body into start, end and diversion tag lists. + dstag = [] + detag = [] + dname = [] stag = [] etag = [] + in_dstag = False + in_detag = False in_stag = True for s in body: if in_stag: + mo = re.match(r'\|\[(?P<dname>[^\]]*)\](?P<dstag>.*)', s) + if mo: + if mo.group('dname'): + dname = mo.group('dname') + else: + dname = 'diversion' + if mo.group('dstag'): + dstag = mo.group('dstag') + in_dstag = True + continue + if in_dstag: + mo = re.match(r'\|(?P<detag>.*)', s) + if mo: + in_dstag = False + in_detag = True + if mo.group('detag'): + detag = mo.group('detag') + else: + dstag.append(s) + continue + if in_detag: + mo = re.match(r'\|', s) + if mo: + in_detag = False + else: + detag.append(s) + continue + if in_stag: mo = re.match(r'(?P<stag>.*)\|(?P<etag>.*)',s) if mo: if mo.group('stag'): @@ -5161,12 +5225,19 @@ class Config: stag = subs_attrs(stag, d) if not skipend: etag = subs_attrs(etag, d) + if not skipdiv: + dstag = subs_attrs(dstag, d) + detag = subs_attrs(detag, d) # Put the {title} back. if title: stag = map(lambda x: x.replace(chr(0), title), stag) etag = map(lambda x: x.replace(chr(0), title), etag) + dstag = map(lambda x: x.replace(chr(0), title), dstag) + detag = map(lambda x: x.replace(chr(0), title), detag) d['title'] = title - return (stag,etag) + if dname: + dstag = (dname, dstag, detag) + return (stag,etag,dstag) #--------------------------------------------------------------------------- -- 2.7.0
>From 11bae358340b9c4c534a67eeb4d2e5b6e4de5dea Mon Sep 17 00:00:00 2001 From: Keith Packard <[email protected]> Date: Fri, 12 Feb 2016 19:07:14 -0800 Subject: [PATCH 5/5] xhtml11: Use new 'diversions' to generate table of contents This produces a table of contents at the top of the resulting HTML file without requiring javascript. Signed-off-by: Keith Packard <[email protected]> --- xhtml11.conf | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/xhtml11.conf b/xhtml11.conf index 87ab469..6bd765e 100644 --- a/xhtml11.conf +++ b/xhtml11.conf @@ -485,6 +485,14 @@ cellspacing="0" cellpadding="4"> | [sect1] +|[tocdivert] +<dl class="toc"> +<dt><span class="section"><a href="#{id}">{numbered?{sectnum} }{title}</a></span></dt> +<dd> +| +</dd> +</dl> +| <div class="sect1{style? {style}}{role? {role}}"> <h2{id? id="{id}"}>{numbered?{sectnum} }{title}</h2> <div class="sectionbody"> @@ -493,26 +501,64 @@ cellspacing="0" cellpadding="4"> </div> [sect2] +ifeval::[{toclevels}>=2] +|[tocdivert] +<dl class="toc"> +<dt><span class="section"><a href="#{id}">{numbered?{sectnum} }{title}</a></span></dt> +<dd> +| +</dd> +</dl> +| +endif::[] <div class="sect2{style? {style}}{role? {role}}"> <h3{id? id="{id}"}>{numbered?{sectnum} }{title}</h3> | </div> [sect3] +ifeval::[{toclevels}>=3] +|[tocdivert] +<dl class="toc"> +<dt><span class="section"><a href="#{id}">{numbered?{sectnum} }{title}</a></span></dt> +<dd> +| +</dd> +</dl> +| +endif::[] <div class="sect3{style? {style}}{role? {role}}"> <h4{id? id="{id}"}>{numbered?{sectnum} }{title}</h4> | </div> [sect4] +ifeval::[{toclevels}>=4] +|[tocdivert] +<dl class="toc"> +<dt><span class="section"><a href="#{id}">{title}</a></span></dt> +<dd> +| +</dd> +</dl> +| +endif::[] <div class="sect4{style? {style}}{role? {role}}"> <h5{id? id="{id}"}>{title}</h5> | </div> [appendix] +|[tocdivert] +<dl class="toc"> +<dt><span class="appendix"><a href="#{id}">{appendix-caption} {appendix-number}: {title}</a></span></dt> +<dd> +| +</dd> +</dl> +| <div class="sect1{style? {style}}{role? {role}}"> -<h2{id? id="{id}"}>{numbered?{sectnum} }{appendix-caption} {counter:appendix-number:A}: {title}</h2> +<h2{id? id="{id}"}>{appendix-caption} {counter:appendix-number:A}: {title}</h2> <div class="sectionbody"> | </div> @@ -521,7 +567,7 @@ cellspacing="0" cellpadding="4"> [toc] <div id="toc"> <div id="toctitle">{toc-title}</div> - <noscript><p><b>JavaScript must be enabled in your browser to display the table of contents.</b></p></noscript> + {tocdivert|} </div> [header] -- 2.7.0
