Zettelstore

Check-in Differences
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From version-0.0.13 To trunk

2021-06-11
19:43
Remove support for list paging in WebUI ... (Leaf check-in: 88c5a4462e user: stern tags: trunk)
2021-06-09
17:00
Add supported metadata key 'place-number' ... (check-in: 03e324830a user: stern tags: trunk)
2021-06-07
09:11
Increase version to 0.0.14-dev to begin next development cycle ... (check-in: 7dd6f4dd5c user: stern tags: trunk)
2021-06-01
12:35
Version 0.0.13 ... (check-in: 11d9b6da63 user: stern tags: trunk, release, version-0.0.13)
10:14
Log output while starting Command Line Server ... (check-in: 968a91bbaa user: stern tags: trunk)

Changes to VERSION.

     1         -0.0.13
            1  +0.0.14-dev

Changes to ast/ast.go.

    28     28   	Zid     id.Zid         // Zettel identification.
    29     29   	InhMeta *meta.Meta     // Metadata of the zettel, with inherited values.
    30     30   	Ast     BlockSlice     // Zettel abstract syntax tree is a sequence of block nodes.
    31     31   }
    32     32   
    33     33   // Node is the interface, all nodes must implement.
    34     34   type Node interface {
    35         -	Accept(v Visitor)
           35  +	WalkChildren(v Visitor)
    36     36   }
    37     37   
    38     38   // BlockNode is the interface that all block nodes must implement.
    39     39   type BlockNode interface {
    40     40   	Node
    41     41   	blockNode()
    42     42   }

Changes to ast/block.go.

    19     19   	Inlines InlineSlice
    20     20   }
    21     21   
    22     22   func (pn *ParaNode) blockNode()       {}
    23     23   func (pn *ParaNode) itemNode()        {}
    24     24   func (pn *ParaNode) descriptionNode() {}
    25     25   
    26         -// Accept a visitor and visit the node.
    27         -func (pn *ParaNode) Accept(v Visitor) { v.VisitPara(pn) }
           26  +// WalkChildren walks down the inline elements.
           27  +func (pn *ParaNode) WalkChildren(v Visitor) {
           28  +	WalkInlineSlice(v, pn.Inlines)
           29  +}
    28     30   
    29     31   //--------------------------------------------------------------------------
    30     32   
    31     33   // VerbatimNode contains lines of uninterpreted text
    32     34   type VerbatimNode struct {
    33         -	Code  VerbatimCode
           35  +	Kind  VerbatimKind
    34     36   	Attrs *Attributes
    35     37   	Lines []string
    36     38   }
    37     39   
    38         -// VerbatimCode specifies the format that is applied to code inline nodes.
    39         -type VerbatimCode int
           40  +// VerbatimKind specifies the format that is applied to code inline nodes.
           41  +type VerbatimKind uint8
    40     42   
    41     43   // Constants for VerbatimCode
    42     44   const (
    43         -	_               VerbatimCode = iota
           45  +	_               VerbatimKind = iota
    44     46   	VerbatimProg                 // Program code.
    45     47   	VerbatimComment              // Block comment
    46     48   	VerbatimHTML                 // Block HTML, e.g. for Markdown
    47     49   )
    48     50   
    49     51   func (vn *VerbatimNode) blockNode() {}
    50     52   func (vn *VerbatimNode) itemNode()  {}
    51     53   
    52         -// Accept a visitor an visit the node.
    53         -func (vn *VerbatimNode) Accept(v Visitor) { v.VisitVerbatim(vn) }
           54  +// WalkChildren does nothing.
           55  +func (vn *VerbatimNode) WalkChildren(v Visitor) {}
    54     56   
    55     57   //--------------------------------------------------------------------------
    56     58   
    57     59   // RegionNode encapsulates a region of block nodes.
    58     60   type RegionNode struct {
    59         -	Code    RegionCode
           61  +	Kind    RegionKind
    60     62   	Attrs   *Attributes
    61     63   	Blocks  BlockSlice
    62     64   	Inlines InlineSlice // Additional text at the end of the region
    63     65   }
    64     66   
    65         -// RegionCode specifies the actual region type.
    66         -type RegionCode int
           67  +// RegionKind specifies the actual region type.
           68  +type RegionKind uint8
    67     69   
    68     70   // Values for RegionCode
    69     71   const (
    70         -	_           RegionCode = iota
           72  +	_           RegionKind = iota
    71     73   	RegionSpan             // Just a span of blocks
    72     74   	RegionQuote            // A longer quotation
    73     75   	RegionVerse            // Line breaks matter
    74     76   )
    75     77   
    76     78   func (rn *RegionNode) blockNode() {}
    77     79   func (rn *RegionNode) itemNode()  {}
    78     80   
    79         -// Accept a visitor and visit the node.
    80         -func (rn *RegionNode) Accept(v Visitor) { v.VisitRegion(rn) }
           81  +// WalkChildren walks down the blocks and the text.
           82  +func (rn *RegionNode) WalkChildren(v Visitor) {
           83  +	WalkBlockSlice(v, rn.Blocks)
           84  +	WalkInlineSlice(v, rn.Inlines)
           85  +}
    81     86   
    82     87   //--------------------------------------------------------------------------
    83     88   
    84     89   // HeadingNode stores the heading text and level.
    85     90   type HeadingNode struct {
    86     91   	Level   int
    87     92   	Inlines InlineSlice // Heading text, possibly formatted
................................................................................
    88     93   	Slug    string      // Heading text, suitable to be used as an URL fragment
    89     94   	Attrs   *Attributes
    90     95   }
    91     96   
    92     97   func (hn *HeadingNode) blockNode() {}
    93     98   func (hn *HeadingNode) itemNode()  {}
    94     99   
    95         -// Accept a visitor and visit the node.
    96         -func (hn *HeadingNode) Accept(v Visitor) { v.VisitHeading(hn) }
          100  +// WalkChildren walks the heading text.
          101  +func (hn *HeadingNode) WalkChildren(v Visitor) {
          102  +	WalkInlineSlice(v, hn.Inlines)
          103  +}
    97    104   
    98    105   //--------------------------------------------------------------------------
    99    106   
   100    107   // HRuleNode specifies a horizontal rule.
   101    108   type HRuleNode struct {
   102    109   	Attrs *Attributes
   103    110   }
   104    111   
   105    112   func (hn *HRuleNode) blockNode() {}
   106    113   func (hn *HRuleNode) itemNode()  {}
   107    114   
   108         -// Accept a visitor and visit the node.
   109         -func (hn *HRuleNode) Accept(v Visitor) { v.VisitHRule(hn) }
          115  +// WalkChildren does nothing.
          116  +func (hn *HRuleNode) WalkChildren(v Visitor) {}
   110    117   
   111    118   //--------------------------------------------------------------------------
   112    119   
   113    120   // NestedListNode specifies a nestable list, either ordered or unordered.
   114    121   type NestedListNode struct {
   115         -	Code  NestedListCode
          122  +	Kind  NestedListKind
   116    123   	Items []ItemSlice
   117    124   	Attrs *Attributes
   118    125   }
   119    126   
   120         -// NestedListCode specifies the actual list type.
   121         -type NestedListCode int
          127  +// NestedListKind specifies the actual list type.
          128  +type NestedListKind uint8
   122    129   
   123    130   // Values for ListCode
   124    131   const (
   125         -	_                   NestedListCode = iota
          132  +	_                   NestedListKind = iota
   126    133   	NestedListOrdered                  // Ordered list.
   127    134   	NestedListUnordered                // Unordered list.
   128    135   	NestedListQuote                    // Quote list.
   129    136   )
   130    137   
   131    138   func (ln *NestedListNode) blockNode() {}
   132    139   func (ln *NestedListNode) itemNode()  {}
   133    140   
   134         -// Accept a visitor and visit the node.
   135         -func (ln *NestedListNode) Accept(v Visitor) { v.VisitNestedList(ln) }
          141  +// WalkChildren walks down the items.
          142  +func (ln *NestedListNode) WalkChildren(v Visitor) {
          143  +	for _, item := range ln.Items {
          144  +		WalkItemSlice(v, item)
          145  +	}
          146  +}
   136    147   
   137    148   //--------------------------------------------------------------------------
   138    149   
   139    150   // DescriptionListNode specifies a description list.
   140    151   type DescriptionListNode struct {
   141    152   	Descriptions []Description
   142    153   }
................................................................................
   145    156   type Description struct {
   146    157   	Term         InlineSlice
   147    158   	Descriptions []DescriptionSlice
   148    159   }
   149    160   
   150    161   func (dn *DescriptionListNode) blockNode() {}
   151    162   
   152         -// Accept a visitor and visit the node.
   153         -func (dn *DescriptionListNode) Accept(v Visitor) { v.VisitDescriptionList(dn) }
          163  +// WalkChildren walks down to the descriptions.
          164  +func (dn *DescriptionListNode) WalkChildren(v Visitor) {
          165  +	for _, desc := range dn.Descriptions {
          166  +		WalkInlineSlice(v, desc.Term)
          167  +		for _, dns := range desc.Descriptions {
          168  +			WalkDescriptionSlice(v, dns)
          169  +		}
          170  +	}
          171  +}
   154    172   
   155    173   //--------------------------------------------------------------------------
   156    174   
   157    175   // TableNode specifies a full table
   158    176   type TableNode struct {
   159    177   	Header TableRow    // The header row
   160    178   	Align  []Alignment // Default column alignment
................................................................................
   181    199   	AlignLeft              // Left alignment
   182    200   	AlignCenter            // Center the content
   183    201   	AlignRight             // Right alignment
   184    202   )
   185    203   
   186    204   func (tn *TableNode) blockNode() {}
   187    205   
   188         -// Accept a visitor and visit the node.
   189         -func (tn *TableNode) Accept(v Visitor) { v.VisitTable(tn) }
          206  +// WalkChildren walks down to the cells.
          207  +func (tn *TableNode) WalkChildren(v Visitor) {
          208  +	for _, cell := range tn.Header {
          209  +		WalkInlineSlice(v, cell.Inlines)
          210  +	}
          211  +	for _, row := range tn.Rows {
          212  +		for _, cell := range row {
          213  +			WalkInlineSlice(v, cell.Inlines)
          214  +		}
          215  +	}
          216  +}
   190    217   
   191    218   //--------------------------------------------------------------------------
   192    219   
   193    220   // BLOBNode contains just binary data that must be interpreted according to
   194    221   // a syntax.
   195    222   type BLOBNode struct {
   196    223   	Title  string
   197    224   	Syntax string
   198    225   	Blob   []byte
   199    226   }
   200    227   
   201    228   func (bn *BLOBNode) blockNode() {}
   202    229   
   203         -// Accept a visitor and visit the node.
   204         -func (bn *BLOBNode) Accept(v Visitor) { v.VisitBLOB(bn) }
          230  +// WalkChildren does nothing.
          231  +func (bn *BLOBNode) WalkChildren(v Visitor) {}

Changes to ast/inline.go.

    16     16   // TextNode just contains some text.
    17     17   type TextNode struct {
    18     18   	Text string // The text itself.
    19     19   }
    20     20   
    21     21   func (tn *TextNode) inlineNode() {}
    22     22   
    23         -// Accept a visitor and visit the node.
    24         -func (tn *TextNode) Accept(v Visitor) { v.VisitText(tn) }
           23  +// WalkChildren does nothing.
           24  +func (tn *TextNode) WalkChildren(v Visitor) {}
    25     25   
    26     26   // --------------------------------------------------------------------------
    27     27   
    28     28   // TagNode contains a tag.
    29     29   type TagNode struct {
    30     30   	Tag string // The text itself.
    31     31   }
    32     32   
    33     33   func (tn *TagNode) inlineNode() {}
    34     34   
    35         -// Accept a visitor and visit the node.
    36         -func (tn *TagNode) Accept(v Visitor) { v.VisitTag(tn) }
           35  +// WalkChildren does nothing.
           36  +func (tn *TagNode) WalkChildren(v Visitor) {}
    37     37   
    38     38   // --------------------------------------------------------------------------
    39     39   
    40     40   // SpaceNode tracks inter-word space characters.
    41     41   type SpaceNode struct {
    42     42   	Lexeme string
    43     43   }
    44     44   
    45     45   func (sn *SpaceNode) inlineNode() {}
    46     46   
    47         -// Accept a visitor and visit the node.
    48         -func (sn *SpaceNode) Accept(v Visitor) { v.VisitSpace(sn) }
           47  +// WalkChildren does nothing.
           48  +func (sn *SpaceNode) WalkChildren(v Visitor) {}
    49     49   
    50     50   // --------------------------------------------------------------------------
    51     51   
    52     52   // BreakNode signals a new line that must / should be interpreted as a new line break.
    53     53   type BreakNode struct {
    54     54   	Hard bool // Hard line break?
    55     55   }
    56     56   
    57     57   func (bn *BreakNode) inlineNode() {}
    58     58   
    59         -// Accept a visitor and visit the node.
    60         -func (bn *BreakNode) Accept(v Visitor) { v.VisitBreak(bn) }
           59  +// WalkChildren does nothing.
           60  +func (bn *BreakNode) WalkChildren(v Visitor) {}
    61     61   
    62     62   // --------------------------------------------------------------------------
    63     63   
    64     64   // LinkNode contains the specified link.
    65     65   type LinkNode struct {
    66     66   	Ref     *Reference
    67     67   	Inlines InlineSlice // The text associated with the link.
    68     68   	OnlyRef bool        // True if no text was specified.
    69     69   	Attrs   *Attributes // Optional attributes
    70     70   }
    71     71   
    72     72   func (ln *LinkNode) inlineNode() {}
    73     73   
    74         -// Accept a visitor and visit the node.
    75         -func (ln *LinkNode) Accept(v Visitor) { v.VisitLink(ln) }
           74  +// WalkChildren walks to the link text.
           75  +func (ln *LinkNode) WalkChildren(v Visitor) {
           76  +	WalkInlineSlice(v, ln.Inlines)
           77  +}
    76     78   
    77     79   // --------------------------------------------------------------------------
    78     80   
    79     81   // ImageNode contains the specified image reference.
    80     82   type ImageNode struct {
    81     83   	Ref     *Reference  // Reference to image
    82     84   	Blob    []byte      // BLOB data of the image, as an alternative to Ref.
................................................................................
    83     85   	Syntax  string      // Syntax of Blob
    84     86   	Inlines InlineSlice // The text associated with the image.
    85     87   	Attrs   *Attributes // Optional attributes
    86     88   }
    87     89   
    88     90   func (in *ImageNode) inlineNode() {}
    89     91   
    90         -// Accept a visitor and visit the node.
    91         -func (in *ImageNode) Accept(v Visitor) { v.VisitImage(in) }
           92  +// WalkChildren walks to the image text.
           93  +func (in *ImageNode) WalkChildren(v Visitor) {
           94  +	WalkInlineSlice(v, in.Inlines)
           95  +}
    92     96   
    93     97   // --------------------------------------------------------------------------
    94     98   
    95     99   // CiteNode contains the specified citation.
    96    100   type CiteNode struct {
    97    101   	Key     string      // The citation key
    98    102   	Inlines InlineSlice // The text associated with the citation.
    99    103   	Attrs   *Attributes // Optional attributes
   100    104   }
   101    105   
   102    106   func (cn *CiteNode) inlineNode() {}
   103    107   
   104         -// Accept a visitor and visit the node.
   105         -func (cn *CiteNode) Accept(v Visitor) { v.VisitCite(cn) }
          108  +// WalkChildren walks to the cite text.
          109  +func (cn *CiteNode) WalkChildren(v Visitor) {
          110  +	WalkInlineSlice(v, cn.Inlines)
          111  +}
   106    112   
   107    113   // --------------------------------------------------------------------------
   108    114   
   109    115   // MarkNode contains the specified merked position.
   110    116   // It is a BlockNode too, because although it is typically parsed during inline
   111    117   // mode, it is moved into block mode afterwards.
   112    118   type MarkNode struct {
   113    119   	Text string
   114    120   }
   115    121   
   116    122   func (mn *MarkNode) inlineNode() {}
   117    123   
   118         -// Accept a visitor and visit the node.
   119         -func (mn *MarkNode) Accept(v Visitor) { v.VisitMark(mn) }
          124  +// WalkChildren does nothing.
          125  +func (mn *MarkNode) WalkChildren(v Visitor) {}
   120    126   
   121    127   // --------------------------------------------------------------------------
   122    128   
   123    129   // FootnoteNode contains the specified footnote.
   124    130   type FootnoteNode struct {
   125    131   	Inlines InlineSlice // The footnote text.
   126    132   	Attrs   *Attributes // Optional attributes
   127    133   }
   128    134   
   129    135   func (fn *FootnoteNode) inlineNode() {}
   130    136   
   131         -// Accept a visitor and visit the node.
   132         -func (fn *FootnoteNode) Accept(v Visitor) { v.VisitFootnote(fn) }
          137  +// WalkChildren walks to the footnote text.
          138  +func (fn *FootnoteNode) WalkChildren(v Visitor) {
          139  +	WalkInlineSlice(v, fn.Inlines)
          140  +}
   133    141   
   134    142   // --------------------------------------------------------------------------
   135    143   
   136    144   // FormatNode specifies some inline formatting.
   137    145   type FormatNode struct {
   138         -	Code    FormatCode
          146  +	Kind    FormatKind
   139    147   	Attrs   *Attributes // Optional attributes.
   140    148   	Inlines InlineSlice
   141    149   }
   142    150   
   143         -// FormatCode specifies the format that is applied to the inline nodes.
   144         -type FormatCode int
          151  +// FormatKind specifies the format that is applied to the inline nodes.
          152  +type FormatKind uint8
   145    153   
   146    154   // Constants for FormatCode
   147    155   const (
   148         -	_               FormatCode = iota
          156  +	_               FormatKind = iota
   149    157   	FormatItalic               // Italic text.
   150    158   	FormatEmph                 // Semantically emphasized text.
   151    159   	FormatBold                 // Bold text.
   152    160   	FormatStrong               // Semantically strongly emphasized text.
   153    161   	FormatUnder                // Underlined text.
   154    162   	FormatInsert               // Inserted text.
   155    163   	FormatStrike               // Text that is no longer relevant or no longer accurate.
................................................................................
   161    169   	FormatSmall                // Smaller text.
   162    170   	FormatSpan                 // Generic inline container.
   163    171   	FormatMonospace            // Monospaced text.
   164    172   )
   165    173   
   166    174   func (fn *FormatNode) inlineNode() {}
   167    175   
   168         -// Accept a visitor and visit the node.
   169         -func (fn *FormatNode) Accept(v Visitor) { v.VisitFormat(fn) }
          176  +// WalkChildren walks to the formatted text.
          177  +func (fn *FormatNode) WalkChildren(v Visitor) {
          178  +	WalkInlineSlice(v, fn.Inlines)
          179  +}
   170    180   
   171    181   // --------------------------------------------------------------------------
   172    182   
   173    183   // LiteralNode specifies some uninterpreted text.
   174    184   type LiteralNode struct {
   175         -	Code  LiteralCode
          185  +	Kind  LiteralKind
   176    186   	Attrs *Attributes // Optional attributes.
   177    187   	Text  string
   178    188   }
   179    189   
   180         -// LiteralCode specifies the format that is applied to code inline nodes.
   181         -type LiteralCode int
          190  +// LiteralKind specifies the format that is applied to code inline nodes.
          191  +type LiteralKind uint8
   182    192   
   183    193   // Constants for LiteralCode
   184    194   const (
   185         -	_              LiteralCode = iota
          195  +	_              LiteralKind = iota
   186    196   	LiteralProg                // Inline program code.
   187    197   	LiteralKeyb                // Keyboard strokes.
   188    198   	LiteralOutput              // Sample output.
   189    199   	LiteralComment             // Inline comment
   190    200   	LiteralHTML                // Inline HTML, e.g. for Markdown
   191    201   )
   192    202   
   193         -func (rn *LiteralNode) inlineNode() {}
          203  +func (ln *LiteralNode) inlineNode() {}
   194    204   
   195         -// Accept a visitor and visit the node.
   196         -func (rn *LiteralNode) Accept(v Visitor) { v.VisitLiteral(rn) }
          205  +// WalkChildren does nothing.
          206  +func (ln *LiteralNode) WalkChildren(v Visitor) {}

Deleted ast/traverser.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020-2021 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package ast provides the abstract syntax tree.
    12         -package ast
    13         -
    14         -// A traverser is a Visitor that just traverses the AST and delegates node
    15         -// spacific actions to a Visitor. This Visitor should not traverse the AST.
    16         -
    17         -// TopDownTraverser visits first the node and then the children nodes.
    18         -type TopDownTraverser struct {
    19         -	v Visitor
    20         -}
    21         -
    22         -// NewTopDownTraverser creates a new traverser.
    23         -func NewTopDownTraverser(visitor Visitor) TopDownTraverser {
    24         -	return TopDownTraverser{visitor}
    25         -}
    26         -
    27         -// VisitVerbatim has nothing to traverse.
    28         -func (t TopDownTraverser) VisitVerbatim(vn *VerbatimNode) { t.v.VisitVerbatim(vn) }
    29         -
    30         -// VisitRegion traverses the content and the additional text.
    31         -func (t TopDownTraverser) VisitRegion(rn *RegionNode) {
    32         -	t.v.VisitRegion(rn)
    33         -	t.VisitBlockSlice(rn.Blocks)
    34         -	t.VisitInlineSlice(rn.Inlines)
    35         -}
    36         -
    37         -// VisitHeading traverses the heading.
    38         -func (t TopDownTraverser) VisitHeading(hn *HeadingNode) {
    39         -	t.v.VisitHeading(hn)
    40         -	t.VisitInlineSlice(hn.Inlines)
    41         -}
    42         -
    43         -// VisitHRule traverses nothing.
    44         -func (t TopDownTraverser) VisitHRule(hn *HRuleNode) { t.v.VisitHRule(hn) }
    45         -
    46         -// VisitNestedList traverses all nested list elements.
    47         -func (t TopDownTraverser) VisitNestedList(ln *NestedListNode) {
    48         -	t.v.VisitNestedList(ln)
    49         -	for _, item := range ln.Items {
    50         -		t.visitItemSlice(item)
    51         -	}
    52         -}
    53         -
    54         -// VisitDescriptionList traverses all description terms and their associated
    55         -// descriptions.
    56         -func (t TopDownTraverser) VisitDescriptionList(dn *DescriptionListNode) {
    57         -	t.v.VisitDescriptionList(dn)
    58         -	for _, defs := range dn.Descriptions {
    59         -		t.VisitInlineSlice(defs.Term)
    60         -		for _, descr := range defs.Descriptions {
    61         -			t.visitDescriptionSlice(descr)
    62         -		}
    63         -	}
    64         -}
    65         -
    66         -// VisitPara traverses the inlines of a paragraph.
    67         -func (t TopDownTraverser) VisitPara(pn *ParaNode) {
    68         -	t.v.VisitPara(pn)
    69         -	t.VisitInlineSlice(pn.Inlines)
    70         -}
    71         -
    72         -// VisitTable traverses all cells of the header and then row-wise all cells of
    73         -// the table body.
    74         -func (t TopDownTraverser) VisitTable(tn *TableNode) {
    75         -	t.v.VisitTable(tn)
    76         -	for _, col := range tn.Header {
    77         -		t.VisitInlineSlice(col.Inlines)
    78         -	}
    79         -	for _, row := range tn.Rows {
    80         -		for _, col := range row {
    81         -			t.VisitInlineSlice(col.Inlines)
    82         -		}
    83         -	}
    84         -}
    85         -
    86         -// VisitBLOB traverses nothing.
    87         -func (t TopDownTraverser) VisitBLOB(bn *BLOBNode) { t.v.VisitBLOB(bn) }
    88         -
    89         -// VisitText traverses nothing.
    90         -func (t TopDownTraverser) VisitText(tn *TextNode) { t.v.VisitText(tn) }
    91         -
    92         -// VisitTag traverses nothing.
    93         -func (t TopDownTraverser) VisitTag(tn *TagNode) { t.v.VisitTag(tn) }
    94         -
    95         -// VisitSpace traverses nothing.
    96         -func (t TopDownTraverser) VisitSpace(sn *SpaceNode) { t.v.VisitSpace(sn) }
    97         -
    98         -// VisitBreak traverses nothing.
    99         -func (t TopDownTraverser) VisitBreak(bn *BreakNode) { t.v.VisitBreak(bn) }
   100         -
   101         -// VisitLink traverses the link text.
   102         -func (t TopDownTraverser) VisitLink(ln *LinkNode) {
   103         -	t.v.VisitLink(ln)
   104         -	t.VisitInlineSlice(ln.Inlines)
   105         -}
   106         -
   107         -// VisitImage traverses the image text.
   108         -func (t TopDownTraverser) VisitImage(in *ImageNode) {
   109         -	t.v.VisitImage(in)
   110         -	t.VisitInlineSlice(in.Inlines)
   111         -}
   112         -
   113         -// VisitCite traverses the cite text.
   114         -func (t TopDownTraverser) VisitCite(cn *CiteNode) {
   115         -	t.v.VisitCite(cn)
   116         -	t.VisitInlineSlice(cn.Inlines)
   117         -}
   118         -
   119         -// VisitFootnote traverses the footnote text.
   120         -func (t TopDownTraverser) VisitFootnote(fn *FootnoteNode) {
   121         -	t.v.VisitFootnote(fn)
   122         -	t.VisitInlineSlice(fn.Inlines)
   123         -}
   124         -
   125         -// VisitMark traverses nothing.
   126         -func (t TopDownTraverser) VisitMark(mn *MarkNode) { t.v.VisitMark(mn) }
   127         -
   128         -// VisitFormat traverses the formatted text.
   129         -func (t TopDownTraverser) VisitFormat(fn *FormatNode) {
   130         -	t.v.VisitFormat(fn)
   131         -	t.VisitInlineSlice(fn.Inlines)
   132         -}
   133         -
   134         -// VisitLiteral traverses nothing.
   135         -func (t TopDownTraverser) VisitLiteral(ln *LiteralNode) { t.v.VisitLiteral(ln) }
   136         -
   137         -// VisitBlockSlice traverses a block slice.
   138         -func (t TopDownTraverser) VisitBlockSlice(bns BlockSlice) {
   139         -	for _, bn := range bns {
   140         -		bn.Accept(t)
   141         -	}
   142         -}
   143         -
   144         -func (t TopDownTraverser) visitItemSlice(ins ItemSlice) {
   145         -	for _, in := range ins {
   146         -		in.Accept(t)
   147         -	}
   148         -}
   149         -
   150         -func (t TopDownTraverser) visitDescriptionSlice(dns DescriptionSlice) {
   151         -	for _, dn := range dns {
   152         -		dn.Accept(t)
   153         -	}
   154         -}
   155         -
   156         -// VisitInlineSlice traverses a block slice.
   157         -func (t TopDownTraverser) VisitInlineSlice(ins InlineSlice) {
   158         -	for _, in := range ins {
   159         -		in.Accept(t)
   160         -	}
   161         -}

Deleted ast/visitor.go.

     1         -//-----------------------------------------------------------------------------
     2         -// Copyright (c) 2020 Detlef Stern
     3         -//
     4         -// This file is part of zettelstore.
     5         -//
     6         -// Zettelstore is licensed under the latest version of the EUPL (European Union
     7         -// Public License). Please see file LICENSE.txt for your rights and obligations
     8         -// under this license.
     9         -//-----------------------------------------------------------------------------
    10         -
    11         -// Package ast provides the abstract syntax tree.
    12         -package ast
    13         -
    14         -// Visitor is the interface all visitors must implement.
    15         -type Visitor interface {
    16         -	// Block nodes
    17         -	VisitVerbatim(vn *VerbatimNode)
    18         -	VisitRegion(rn *RegionNode)
    19         -	VisitHeading(hn *HeadingNode)
    20         -	VisitHRule(hn *HRuleNode)
    21         -	VisitNestedList(ln *NestedListNode)
    22         -	VisitDescriptionList(dn *DescriptionListNode)
    23         -	VisitPara(pn *ParaNode)
    24         -	VisitTable(tn *TableNode)
    25         -	VisitBLOB(bn *BLOBNode)
    26         -
    27         -	// Inline nodes
    28         -	VisitText(tn *TextNode)
    29         -	VisitTag(tn *TagNode)
    30         -	VisitSpace(sn *SpaceNode)
    31         -	VisitBreak(bn *BreakNode)
    32         -	VisitLink(ln *LinkNode)
    33         -	VisitImage(in *ImageNode)
    34         -	VisitCite(cn *CiteNode)
    35         -	VisitFootnote(fn *FootnoteNode)
    36         -	VisitMark(mn *MarkNode)
    37         -	VisitFormat(fn *FormatNode)
    38         -	VisitLiteral(ln *LiteralNode)
    39         -}

Added ast/walk.go.

            1  +//-----------------------------------------------------------------------------
            2  +// Copyright (c) 2021 Detlef Stern
            3  +//
            4  +// This file is part of zettelstore.
            5  +//
            6  +// Zettelstore is licensed under the latest version of the EUPL (European Union
            7  +// Public License). Please see file LICENSE.txt for your rights and obligations
            8  +// under this license.
            9  +//-----------------------------------------------------------------------------
           10  +
           11  +// Package ast provides the abstract syntax tree.
           12  +package ast
           13  +
           14  +// Visitor is a visitor for walking the AST.
           15  +type Visitor interface {
           16  +	Visit(node Node) Visitor
           17  +}
           18  +
           19  +// Walk traverses the AST.
           20  +func Walk(v Visitor, node Node) {
           21  +	if v = v.Visit(node); v == nil {
           22  +		return
           23  +	}
           24  +	node.WalkChildren(v)
           25  +	v.Visit(nil)
           26  +}
           27  +
           28  +// WalkBlockSlice traverse a block slice.
           29  +func WalkBlockSlice(v Visitor, bns BlockSlice) {
           30  +	for _, bn := range bns {
           31  +		Walk(v, bn)
           32  +	}
           33  +}
           34  +
           35  +// WalkInlineSlice traverses an inline slice.
           36  +func WalkInlineSlice(v Visitor, ins InlineSlice) {
           37  +	for _, in := range ins {
           38  +		Walk(v, in)
           39  +	}
           40  +}
           41  +
           42  +// WalkItemSlice traverses an item slice.
           43  +func WalkItemSlice(v Visitor, ins ItemSlice) {
           44  +	for _, in := range ins {
           45  +		Walk(v, in)
           46  +	}
           47  +}
           48  +
           49  +// WalkDescriptionSlice traverses an item slice.
           50  +func WalkDescriptionSlice(v Visitor, dns DescriptionSlice) {
           51  +	for _, dn := range dns {
           52  +		Walk(v, dn)
           53  +	}
           54  +}

Changes to collect/collect.go.

     7      7   // Public License). Please see file LICENSE.txt for your rights and obligations
     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package collect provides functions to collect items from a syntax tree.
    12     12   package collect
    13     13   
    14         -import (
    15         -	"zettelstore.de/z/ast"
    16         -)
           14  +import "zettelstore.de/z/ast"
    17     15   
    18     16   // Summary stores the relevant parts of the syntax tree
    19     17   type Summary struct {
    20     18   	Links  []*ast.Reference // list of all referenced links
    21     19   	Images []*ast.Reference // list of all referenced images
    22     20   	Cites  []*ast.CiteNode  // list of all referenced citations
    23     21   }
    24     22   
    25     23   // References returns all references mentioned in the given zettel. This also
    26     24   // includes references to images.
    27         -func References(zn *ast.ZettelNode) Summary {
    28         -	lv := linkVisitor{}
    29         -	ast.NewTopDownTraverser(&lv).VisitBlockSlice(zn.Ast)
    30         -	return lv.summary
           25  +func References(zn *ast.ZettelNode) (s Summary) {
           26  +	ast.WalkBlockSlice(&s, zn.Ast)
           27  +	return s
    31     28   }
    32     29   
    33         -type linkVisitor struct {
    34         -	summary Summary
    35         -}
    36         -
    37         -// VisitVerbatim does nothing.
    38         -func (lv *linkVisitor) VisitVerbatim(vn *ast.VerbatimNode) {}
    39         -
    40         -// VisitRegion does nothing.
    41         -func (lv *linkVisitor) VisitRegion(rn *ast.RegionNode) {}
    42         -
    43         -// VisitHeading does nothing.
    44         -func (lv *linkVisitor) VisitHeading(hn *ast.HeadingNode) {}
    45         -
    46         -// VisitHRule does nothing.
    47         -func (lv *linkVisitor) VisitHRule(hn *ast.HRuleNode) {}
    48         -
    49         -// VisitList does nothing.
    50         -func (lv *linkVisitor) VisitNestedList(ln *ast.NestedListNode) {}
    51         -
    52         -// VisitDescriptionList does nothing.
    53         -func (lv *linkVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {}
    54         -
    55         -// VisitPara does nothing.
    56         -func (lv *linkVisitor) VisitPara(pn *ast.ParaNode) {}
    57         -
    58         -// VisitTable does nothing.
    59         -func (lv *linkVisitor) VisitTable(tn *ast.TableNode) {}
    60         -
    61         -// VisitBLOB does nothing.
    62         -func (lv *linkVisitor) VisitBLOB(bn *ast.BLOBNode) {}
    63         -
    64         -// VisitText does nothing.
    65         -func (lv *linkVisitor) VisitText(tn *ast.TextNode) {}
    66         -
    67         -// VisitTag does nothing.
    68         -func (lv *linkVisitor) VisitTag(tn *ast.TagNode) {}
    69         -
    70         -// VisitSpace does nothing.
    71         -func (lv *linkVisitor) VisitSpace(sn *ast.SpaceNode) {}
    72         -
    73         -// VisitBreak does nothing.
    74         -func (lv *linkVisitor) VisitBreak(bn *ast.BreakNode) {}
    75         -
    76         -// VisitLink collects the given link as a reference.
    77         -func (lv *linkVisitor) VisitLink(ln *ast.LinkNode) {
    78         -	lv.summary.Links = append(lv.summary.Links, ln.Ref)
    79         -}
    80         -
    81         -// VisitImage collects the image links as a reference.
    82         -func (lv *linkVisitor) VisitImage(in *ast.ImageNode) {
    83         -	if in.Ref != nil {
    84         -		lv.summary.Images = append(lv.summary.Images, in.Ref)
           30  +// Visit all node to collect data for the summary.
           31  +func (s *Summary) Visit(node ast.Node) ast.Visitor {
           32  +	switch n := node.(type) {
           33  +	case *ast.LinkNode:
           34  +		s.Links = append(s.Links, n.Ref)
           35  +	case *ast.ImageNode:
           36  +		if n.Ref != nil {
           37  +			s.Images = append(s.Images, n.Ref)
           38  +		}
           39  +	case *ast.CiteNode:
           40  +		s.Cites = append(s.Cites, n)
    85     41   	}
           42  +	return s
    86     43   }
    87         -
    88         -// VisitCite collects the citation.
    89         -func (lv *linkVisitor) VisitCite(cn *ast.CiteNode) {
    90         -	lv.summary.Cites = append(lv.summary.Cites, cn)
    91         -}
    92         -
    93         -// VisitFootnote does nothing.
    94         -func (lv *linkVisitor) VisitFootnote(fn *ast.FootnoteNode) {}
    95         -
    96         -// VisitMark does nothing.
    97         -func (lv *linkVisitor) VisitMark(mn *ast.MarkNode) {}
    98         -
    99         -// VisitFormat does nothing.
   100         -func (lv *linkVisitor) VisitFormat(fn *ast.FormatNode) {}
   101         -
   102         -// VisitLiteral does nothing.
   103         -func (lv *linkVisitor) VisitLiteral(ln *ast.LiteralNode) {}

Changes to collect/order.go.

    13     13   
    14     14   import "zettelstore.de/z/ast"
    15     15   
    16     16   // Order of internal reference within the given zettel.
    17     17   func Order(zn *ast.ZettelNode) (result []*ast.Reference) {
    18     18   	for _, bn := range zn.Ast {
    19     19   		if ln, ok := bn.(*ast.NestedListNode); ok {
    20         -			switch ln.Code {
           20  +			switch ln.Kind {
    21     21   			case ast.NestedListOrdered, ast.NestedListUnordered:
    22     22   				for _, is := range ln.Items {
    23     23   					if ref := firstItemZettelReference(is); ref != nil {
    24     24   						result = append(result, ref)
    25     25   					}
    26     26   				}
    27     27   			}

Changes to config/config.go.

    52     52   
    53     53   	// GetMarkerExternal returns the current value of the "marker-external" key.
    54     54   	GetMarkerExternal() string
    55     55   
    56     56   	// GetFooterHTML returns HTML code that should be embedded into the footer
    57     57   	// of each WebUI page.
    58     58   	GetFooterHTML() string
    59         -
    60         -	// GetListPageSize returns the maximum length of a list to be returned in WebUI.
    61         -	// A value less or equal to zero signals no limit.
    62         -	GetListPageSize() int
    63     59   }
    64     60   
    65     61   // AuthConfig are relevant configuration values for authentication.
    66     62   type AuthConfig interface {
    67     63   	// GetExpertMode returns the current value of the "expert-mode" key
    68     64   	GetExpertMode() bool
    69     65   

Changes to docs/manual/00001004010000.zettel.

     1      1   id: 00001004010000
     2      2   title: Zettelstore startup configuration
     3      3   role: manual
     4      4   tags: #configuration #manual #zettelstore
     5      5   syntax: zmk
     6         -modified: 20210525121644
            6  +modified: 20210609185100
     7      7   
     8      8   The configuration file, as specified by the ''-c CONFIGFILE'' [[command line option|00001004051000]], allows you to specify some startup options.
     9      9   These options cannot be stored in a [[configuration zettel|00001004020000]] because either they are needed before Zettelstore can start or because of security reasons.
    10     10   For example, Zettelstore need to know in advance, on which network address is must listen or where zettel are placed.
    11     11   An attacker that is able to change the owner can do anything.
    12     12   Therefore only the owner of the computer on which Zettelstore runs can change this information.
    13     13   
................................................................................
    48     48     On these devices, the operating system is free to stop the web browser and to remove temporary cookies.
    49     49     Therefore, an authenticated user will be logged off.
    50     50   
    51     51     If ''true'', a persistent cookie is used.
    52     52     Its lifetime exceeds the lifetime of the authentication token (see option ''token-lifetime-html'') by 30 seconds.
    53     53   
    54     54     Default: ''false''
    55         -; [!place-uri-X]''place-uri-//X//'', where //X// is a number greater or equal to one
           55  +; [!place-uri-x]''place-uri-//X//'', where //X// is a number greater or equal to one
    56     56   : Specifies a [[place|00001004011200]] where zettel are stored.
    57     57     During startup //X// is counted up, starting with one, until no key is found.
    58     58     This allows to configure more than one place.
    59     59   
    60     60     If no ''place-uri-1'' key is given, the overall effect will be the same as if only ''place-uri-1'' was specified with the value ''dir://.zettel''.
    61     61     In this case, even a key ''place-uri-2'' will be ignored.
    62     62   ; [!read-only-mode]''read-only-mode''

Changes to docs/manual/00001004020000.zettel.

     1      1   id: 00001004020000
     2      2   title: Configure the running Zettelstore
     3      3   role: manual
     4      4   tags: #configuration #manual #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210611213730
     6      7   
     7      8   You can configure a running Zettelstore by modifying the special zettel with the ID [[00000000000100]].
     8      9   This zettel is called ""configuration zettel"".
     9     10   The following metadata keys change the appearance / behavior of Zettelstore:
    10     11   
    11     12   ; [!default-copyright]''default-copyright''
    12     13   : Copyright value to be used when rendering content.
................................................................................
    50     51     Default: (the empty string).
    51     52   ; [!home-zettel]''home-zettel''
    52     53   : Specifies the identifier of the zettel, that should be presented for the default view / home view.
    53     54     If not given or if the identifier does not identify a zettel, the zettel with the identifier ''00010000000000'' is shown.
    54     55   ; [!marker-external]''marker-external''
    55     56   : Some HTML code that is displayed after a reference to external material.
    56     57     Default: ''&\#10138;'', to display a ""➚"" sign.
    57         -; [!list-page-size]''list-page-size''
    58         -: If set to a value greater than zero, specifies the number of items shown in WebUI lists.
    59         -  Basically, this is the list of all zettel (possibly restricted) and the list of search results.
    60         -  Default: ''0''.
    61     58   ; [!site-name]''site-name''
    62     59   : Name of the Zettelstore instance.
    63     60     Will be used when displaying some lists.
    64     61     Default: ''Zettelstore''.
    65     62   ; [!yaml-header]''yaml-header''
    66     63   : If true, metadata and content will be separated by ''-\--\\n'' instead of an empty line (''\\n\\n'').
    67     64     Default: ''false''.

Changes to docs/manual/00001006020000.zettel.

     1      1   id: 00001006020000
     2      2   title: Supported Metadata Keys
     3      3   role: manual
     4      4   tags: #manual #meta #reference #zettel #zettelstore
     5      5   syntax: zmk
            6  +modified: 20210609185142
     6      7   
     7      8   Although you are free to define your own metadata, by using any key (according to the [[syntax|00001006010000]]), some keys have a special meaning that is enforced by Zettelstore.
     8      9   See the [[computed list of supported metadata keys|00000000000090]] for details.
     9     10   
    10     11   Most keys conform to a [[type|00001006030000]].
    11     12   
    12     13   ; [!back]''back''
................................................................................
    44     45   : Date and time when a zettel was modified through Zettelstore.
    45     46     If you edit a zettel with an editor software outside Zettelstore, you should set it manually to an appropriate value.
    46     47   
    47     48     This is a computed value.
    48     49     There is no need to set it via Zettelstore.
    49     50   ; [!no-index]''no-index''
    50     51   : If set to true, the zettel will not be indexed and therefore not be found in full-text searches.
           52  +; [!place-number]''place-number''
           53  +: Is a computed value and contains the number of the place where the zettel was found.
           54  +  For all but the [[predefined zettel|00001005090000]], this number is equal to the number //X// specified in startup configuration key [[''place-uri-//X//''|00001004010000#place-uri-x]].
    51     55   ; [!precursor]''precursor''
    52     56   : References zettel for which this zettel is a ""Folgezettel"" / follow-up zettel.
    53     57     Basically the inverse of key [[''folge''|#folge]].
    54     58   ; [!published]''published''
    55     59   : This property contains the timestamp of the mast modification / creation of the zettel.
    56     60     If [[''modified''|#modified]]is set, it contains the same value.
    57     61     Otherwise, if the zettel identifier contains a valid timestamp, the identifier is used.

Changes to domain/meta/meta.go.

   129    129   	KeyExpertMode        = registerKey("expert-mode", TypeBool, usageUser, "")
   130    130   	KeyFolge             = registerKey("folge", TypeIDSet, usageProperty, "")
   131    131   	KeyFooterHTML        = registerKey("footer-html", TypeString, usageUser, "")
   132    132   	KeyForward           = registerKey("forward", TypeIDSet, usageProperty, "")
   133    133   	KeyHomeZettel        = registerKey("home-zettel", TypeID, usageUser, "")
   134    134   	KeyLang              = registerKey("lang", TypeWord, usageUser, "")
   135    135   	KeyLicense           = registerKey("license", TypeEmpty, usageUser, "")
   136         -	KeyListPageSize      = registerKey("list-page-size", TypeNumber, usageUser, "")
   137    136   	KeyMarkerExternal    = registerKey("marker-external", TypeEmpty, usageUser, "")
   138    137   	KeyModified          = registerKey("modified", TypeTimestamp, usageComputed, "")
   139    138   	KeyNoIndex           = registerKey("no-index", TypeBool, usageUser, "")
          139  +	KeyPlaceNumber       = registerKey("place-number", TypeNumber, usageComputed, "")
   140    140   	KeyPrecursor         = registerKey("precursor", TypeIDSet, usageUser, KeyFolge)
   141    141   	KeyPublished         = registerKey("published", TypeTimestamp, usageProperty, "")
   142    142   	KeyReadOnly          = registerKey("read-only", TypeWord, usageUser, "")
   143    143   	KeySiteName          = registerKey("site-name", TypeString, usageUser, "")
   144    144   	KeyURL               = registerKey("url", TypeURL, usageUser, "")
   145    145   	KeyUserID            = registerKey("user-id", TypeWord, usageUser, "")
   146    146   	KeyUserRole          = registerKey("user-role", TypeWord, usageUser, "")

Changes to encoder/htmlenc/block.go.

    15     15   	"fmt"
    16     16   	"strconv"
    17     17   	"strings"
    18     18   
    19     19   	"zettelstore.de/z/ast"
    20     20   )
    21     21   
    22         -// VisitPara emits HTML code for a paragraph: <p>...</p>
    23         -func (v *visitor) VisitPara(pn *ast.ParaNode) {
    24         -	v.b.WriteString("<p>")
    25         -	v.acceptInlineSlice(pn.Inlines)
    26         -	v.writeEndPara()
    27         -}
    28         -
    29         -// VisitVerbatim emits HTML code for verbatim lines.
    30         -func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
    31         -	switch vn.Code {
           22  +func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
           23  +	switch vn.Kind {
    32     24   	case ast.VerbatimProg:
    33     25   		oldVisible := v.visibleSpace
    34     26   		if vn.Attrs != nil {
    35     27   			v.visibleSpace = vn.Attrs.HasDefault()
    36     28   		}
    37     29   		v.b.WriteString("<pre><code")
    38     30   		v.visitAttributes(vn.Attrs)
................................................................................
    57     49   	case ast.VerbatimHTML:
    58     50   		for _, line := range vn.Lines {
    59     51   			if !ignoreHTMLText(line) {
    60     52   				v.b.WriteStrings(line, "\n")
    61     53   			}
    62     54   		}
    63     55   	default:
    64         -		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
           56  +		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
    65     57   	}
    66     58   }
    67     59   
    68     60   var htmlSnippetsIgnore = []string{
    69     61   	"<script",
    70     62   	"</script",
    71     63   	"<iframe",
................................................................................
    99     91   			attrs.Remove("")
   100     92   			attrs = attrs.AddClass("zs-indication").AddClass("zs-" + attrVal)
   101     93   		}
   102     94   	}
   103     95   	return attrs
   104     96   }
   105     97   
   106         -// VisitRegion writes HTML code for block regions.
   107         -func (v *visitor) VisitRegion(rn *ast.RegionNode) {
           98  +func (v *visitor) visitRegion(rn *ast.RegionNode) {
   108     99   	var code string
   109    100   	attrs := rn.Attrs
   110    101   	oldVerse := v.inVerse
   111         -	switch rn.Code {
          102  +	switch rn.Kind {
   112    103   	case ast.RegionSpan:
   113    104   		code = "div"
   114    105   		attrs = processSpanAttributes(attrs)
   115    106   	case ast.RegionVerse:
   116    107   		v.inVerse = true
   117    108   		code = "div"
   118    109   	case ast.RegionQuote:
   119    110   		code = "blockquote"
   120    111   	default:
   121         -		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
          112  +		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
   122    113   	}
   123    114   
   124    115   	v.lang.push(attrs)
   125    116   	defer v.lang.pop()
   126    117   
   127    118   	v.b.WriteStrings("<", code)
   128    119   	v.visitAttributes(attrs)
   129    120   	v.b.WriteString(">\n")
   130         -	v.acceptBlockSlice(rn.Blocks)
          121  +	ast.WalkBlockSlice(v, rn.Blocks)
   131    122   	if len(rn.Inlines) > 0 {
   132    123   		v.b.WriteString("<cite>")
   133         -		v.acceptInlineSlice(rn.Inlines)
          124  +		ast.WalkInlineSlice(v, rn.Inlines)
   134    125   		v.b.WriteString("</cite>\n")
   135    126   	}
   136    127   	v.b.WriteStrings("</", code, ">\n")
   137    128   	v.inVerse = oldVerse
   138    129   }
   139    130   
   140         -// VisitHeading writes the HTML code for a heading.
   141         -func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
          131  +func (v *visitor) visitHeading(hn *ast.HeadingNode) {
   142    132   	v.lang.push(hn.Attrs)
   143    133   	defer v.lang.pop()
   144    134   
   145    135   	lvl := hn.Level
   146    136   	if lvl > 6 {
   147    137   		lvl = 6 // HTML has H1..H6
   148    138   	}
................................................................................
   149    139   	strLvl := strconv.Itoa(lvl)
   150    140   	v.b.WriteStrings("<h", strLvl)
   151    141   	v.visitAttributes(hn.Attrs)
   152    142   	if slug := hn.Slug; len(slug) > 0 {
   153    143   		v.b.WriteStrings(" id=\"", slug, "\"")
   154    144   	}
   155    145   	v.b.WriteByte('>')
   156         -	v.acceptInlineSlice(hn.Inlines)
          146  +	ast.WalkInlineSlice(v, hn.Inlines)
   157    147   	v.b.WriteStrings("</h", strLvl, ">\n")
   158    148   }
   159    149   
   160         -// VisitHRule writes HTML code for a horizontal rule: <hr>.
   161         -func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
   162         -	v.b.WriteString("<hr")
   163         -	v.visitAttributes(hn.Attrs)
   164         -	if v.env.IsXHTML() {
   165         -		v.b.WriteString(" />\n")
   166         -	} else {
   167         -		v.b.WriteString(">\n")
   168         -	}
   169         -}
   170         -
   171         -var listCode = map[ast.NestedListCode]string{
          150  +var mapNestedListKind = map[ast.NestedListKind]string{
   172    151   	ast.NestedListOrdered:   "ol",
   173    152   	ast.NestedListUnordered: "ul",
   174    153   }
   175    154   
   176         -// VisitNestedList writes HTML code for lists and blockquotes.
   177         -func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
          155  +func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
   178    156   	v.lang.push(ln.Attrs)
   179    157   	defer v.lang.pop()
   180    158   
   181         -	if ln.Code == ast.NestedListQuote {
          159  +	if ln.Kind == ast.NestedListQuote {
   182    160   		// NestedListQuote -> HTML <blockquote> doesn't use <li>...</li>
   183    161   		v.writeQuotationList(ln)
   184    162   		return
   185    163   	}
   186    164   
   187         -	code, ok := listCode[ln.Code]
          165  +	code, ok := mapNestedListKind[ln.Kind]
   188    166   	if !ok {
   189         -		panic(fmt.Sprintf("Invalid list code %v", ln.Code))
          167  +		panic(fmt.Sprintf("Invalid list kind %v", ln.Kind))
   190    168   	}
   191    169   
   192    170   	compact := isCompactList(ln.Items)
   193    171   	v.b.WriteStrings("<", code)
   194    172   	v.visitAttributes(ln.Attrs)
   195    173   	v.b.WriteString(">\n")
   196    174   	for _, item := range ln.Items {
................................................................................
   208    186   		if pn := getParaItem(item); pn != nil {
   209    187   			if inPara {
   210    188   				v.b.WriteByte('\n')
   211    189   			} else {
   212    190   				v.b.WriteString("<p>")
   213    191   				inPara = true
   214    192   			}
   215         -			v.acceptInlineSlice(pn.Inlines)
          193  +			ast.WalkInlineSlice(v, pn.Inlines)
   216    194   		} else {
   217    195   			if inPara {
   218    196   				v.writeEndPara()
   219    197   				inPara = false
   220    198   			}
   221         -			v.acceptItemSlice(item)
          199  +			ast.WalkItemSlice(v, item)
   222    200   		}
   223    201   	}
   224    202   	if inPara {
   225    203   		v.writeEndPara()
   226    204   	}
   227    205   	v.b.WriteString("</blockquote>\n")
   228    206   }
................................................................................
   263    241   
   264    242   // writeItemSliceOrPara emits the content of a paragraph if the paragraph is
   265    243   // the only element of the block slice and if compact mode is true. Otherwise,
   266    244   // the item slice is emitted normally.
   267    245   func (v *visitor) writeItemSliceOrPara(ins ast.ItemSlice, compact bool) {
   268    246   	if compact && len(ins) == 1 {
   269    247   		if para, ok := ins[0].(*ast.ParaNode); ok {
   270         -			v.acceptInlineSlice(para.Inlines)
          248  +			ast.WalkInlineSlice(v, para.Inlines)
   271    249   			return
   272    250   		}
   273    251   	}
   274         -	v.acceptItemSlice(ins)
          252  +	ast.WalkItemSlice(v, ins)
   275    253   }
   276    254   
   277    255   func (v *visitor) writeDescriptionsSlice(ds ast.DescriptionSlice) {
   278    256   	if len(ds) == 1 {
   279    257   		if para, ok := ds[0].(*ast.ParaNode); ok {
   280         -			v.acceptInlineSlice(para.Inlines)
          258  +			ast.WalkInlineSlice(v, para.Inlines)
   281    259   			return
   282    260   		}
   283    261   	}
   284         -	for _, dn := range ds {
   285         -		dn.Accept(v)
   286         -	}
          262  +	ast.WalkDescriptionSlice(v, ds)
   287    263   }
   288    264   
   289         -// VisitDescriptionList emits a HTML description list.
   290         -func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
          265  +func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   291    266   	v.b.WriteString("<dl>\n")
   292    267   	for _, descr := range dn.Descriptions {
   293    268   		v.b.WriteString("<dt>")
   294         -		v.acceptInlineSlice(descr.Term)
          269  +		ast.WalkInlineSlice(v, descr.Term)
   295    270   		v.b.WriteString("</dt>\n")
   296    271   
   297    272   		for _, b := range descr.Descriptions {
   298    273   			v.b.WriteString("<dd>")
   299    274   			v.writeDescriptionsSlice(b)
   300    275   			v.b.WriteString("</dd>\n")
   301    276   		}
   302    277   	}
   303    278   	v.b.WriteString("</dl>\n")
   304    279   }
   305    280   
   306         -// VisitTable emits a HTML table.
   307         -func (v *visitor) VisitTable(tn *ast.TableNode) {
          281  +func (v *visitor) visitTable(tn *ast.TableNode) {
   308    282   	v.b.WriteString("<table>\n")
   309    283   	if len(tn.Header) > 0 {
   310    284   		v.b.WriteString("<thead>\n")
   311    285   		v.writeRow(tn.Header, "<th", "</th>")
   312    286   		v.b.WriteString("</thead>\n")
   313    287   	}
   314    288   	if len(tn.Rows) > 0 {
................................................................................
   332    306   	v.b.WriteString("<tr>")
   333    307   	for _, cell := range row {
   334    308   		v.b.WriteString(cellStart)
   335    309   		if len(cell.Inlines) == 0 {
   336    310   			v.b.WriteByte('>')
   337    311   		} else {
   338    312   			v.b.WriteString(alignStyle[cell.Align])
   339         -			v.acceptInlineSlice(cell.Inlines)
          313  +			ast.WalkInlineSlice(v, cell.Inlines)
   340    314   		}
   341    315   		v.b.WriteString(cellEnd)
   342    316   	}
   343    317   	v.b.WriteString("</tr>\n")
   344    318   }
   345    319   
   346         -// VisitBLOB writes the binary object as a value.
   347         -func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
          320  +func (v *visitor) visitBLOB(bn *ast.BLOBNode) {
   348    321   	switch bn.Syntax {
   349    322   	case "gif", "jpeg", "png":
   350    323   		v.b.WriteStrings("<img src=\"data:image/", bn.Syntax, ";base64,")
   351    324   		v.b.WriteBase64(bn.Blob)
   352    325   		v.b.WriteString("\" title=\"")
   353    326   		v.writeQuotedEscaped(bn.Title)
   354    327   		v.b.WriteString("\">\n")

Changes to encoder/htmlenc/htmlenc.go.

    47     47   	v.b.WriteStrings("<title>", encfun.MetaAsText(zn.InhMeta, meta.KeyTitle), "</title>")
    48     48   	if inhMeta {
    49     49   		v.acceptMeta(zn.InhMeta)
    50     50   	} else {
    51     51   		v.acceptMeta(zn.Meta)
    52     52   	}
    53     53   	v.b.WriteString("\n</head>\n<body>\n")
    54         -	v.acceptBlockSlice(zn.Ast)
           54  +	ast.WalkBlockSlice(v, zn.Ast)
    55     55   	v.writeEndnotes()
    56     56   	v.b.WriteString("</body>\n</html>")
    57     57   	length, err := v.b.Flush()
    58     58   	return length, err
    59     59   }
    60     60   
    61     61   // WriteMeta encodes meta data as HTML5.
................................................................................
    81     81   func (he *htmlEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    82     82   	return he.WriteBlocks(w, zn.Ast)
    83     83   }
    84     84   
    85     85   // WriteBlocks encodes a block slice.
    86     86   func (he *htmlEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    87     87   	v := newVisitor(he, w)
    88         -	v.acceptBlockSlice(bs)
           88  +	ast.WalkBlockSlice(v, bs)
    89     89   	v.writeEndnotes()
    90     90   	length, err := v.b.Flush()
    91     91   	return length, err
    92     92   }
    93     93   
    94     94   // WriteInlines writes an inline slice to the writer
    95     95   func (he *htmlEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    96     96   	v := newVisitor(he, w)
    97     97   	if env := he.env; env != nil {
    98     98   		v.inInteractive = env.Interactive
    99     99   	}
   100         -	v.acceptInlineSlice(is)
          100  +	ast.WalkInlineSlice(v, is)
   101    101   	length, err := v.b.Flush()
   102    102   	return length, err
   103    103   }

Changes to encoder/htmlenc/inline.go.

    16     16   	"strconv"
    17     17   	"strings"
    18     18   
    19     19   	"zettelstore.de/z/ast"
    20     20   	"zettelstore.de/z/domain/meta"
    21     21   )
    22     22   
    23         -// VisitText writes text content.
    24         -func (v *visitor) VisitText(tn *ast.TextNode) {
    25         -	v.writeHTMLEscaped(tn.Text)
    26         -}
    27         -
    28         -// VisitTag writes tag content.
    29         -func (v *visitor) VisitTag(tn *ast.TagNode) {
    30         -	// TODO: erst mal als span. Link wäre gut, muss man vermutlich via Callback lösen.
    31         -	v.b.WriteString("<span class=\"zettel-tag\">#")
    32         -	v.writeHTMLEscaped(tn.Tag)
    33         -	v.b.WriteString("</span>")
    34         -}
    35         -
    36         -// VisitSpace emits a white space.
    37         -func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
    38         -	if v.inVerse || v.env.IsXHTML() {
    39         -		v.b.WriteString(sn.Lexeme)
    40         -	} else {
    41         -		v.b.WriteByte(' ')
    42         -	}
    43         -}
    44         -
    45         -// VisitBreak writes HTML code for line breaks.
    46         -func (v *visitor) VisitBreak(bn *ast.BreakNode) {
           23  +func (v *visitor) visitBreak(bn *ast.BreakNode) {
    47     24   	if bn.Hard {
    48     25   		if v.env.IsXHTML() {
    49     26   			v.b.WriteString("<br />\n")
    50     27   		} else {
    51     28   			v.b.WriteString("<br>\n")
    52     29   		}
    53     30   	} else {
    54     31   		v.b.WriteByte('\n')
    55     32   	}
    56     33   }
    57     34   
    58         -// VisitLink writes HTML code for links.
    59         -func (v *visitor) VisitLink(ln *ast.LinkNode) {
           35  +func (v *visitor) visitLink(ln *ast.LinkNode) {
    60     36   	ln, n := v.env.AdaptLink(ln)
    61     37   	if n != nil {
    62         -		n.Accept(v)
           38  +		ast.Walk(v, n)
    63     39   		return
    64     40   	}
    65     41   	v.lang.push(ln.Attrs)
    66     42   	defer v.lang.pop()
    67     43   
    68     44   	switch ln.Ref.State {
    69     45   	case ast.RefStateSelf, ast.RefStateFound, ast.RefStateHosted, ast.RefStateBased:
................................................................................
    90     66   		}
    91     67   		v.b.WriteString("<a href=\"")
    92     68   		v.writeQuotedEscaped(ln.Ref.Value)
    93     69   		v.b.WriteByte('"')
    94     70   		v.visitAttributes(ln.Attrs)
    95     71   		v.b.WriteByte('>')
    96     72   		v.inInteractive = true
    97         -		v.acceptInlineSlice(ln.Inlines)
           73  +		ast.WalkInlineSlice(v, ln.Inlines)
    98     74   		v.inInteractive = false
    99     75   		v.b.WriteString("</a>")
   100     76   	}
   101     77   }
   102     78   
   103     79   func (v *visitor) writeAHref(ref *ast.Reference, attrs *ast.Attributes, ins ast.InlineSlice) {
   104     80   	if v.env.IsInteractive(v.inInteractive) {
................................................................................
   107     83   	}
   108     84   	v.b.WriteString("<a href=\"")
   109     85   	v.writeReference(ref)
   110     86   	v.b.WriteByte('"')
   111     87   	v.visitAttributes(attrs)
   112     88   	v.b.WriteByte('>')
   113     89   	v.inInteractive = true
   114         -	v.acceptInlineSlice(ins)
           90  +	ast.WalkInlineSlice(v, ins)
   115     91   	v.inInteractive = false
   116     92   	v.b.WriteString("</a>")
   117     93   }
   118     94   
   119         -// VisitImage writes HTML code for images.
   120         -func (v *visitor) VisitImage(in *ast.ImageNode) {
           95  +func (v *visitor) visitImage(in *ast.ImageNode) {
   121     96   	in, n := v.env.AdaptImage(in)
   122     97   	if n != nil {
   123         -		n.Accept(v)
           98  +		ast.Walk(v, n)
   124     99   		return
   125    100   	}
   126    101   	v.lang.push(in.Attrs)
   127    102   	defer v.lang.pop()
   128    103   
   129    104   	if in.Ref == nil {
   130    105   		v.b.WriteString("<img src=\"data:image/")
................................................................................
   137    112   			v.b.WriteBase64(in.Blob)
   138    113   		}
   139    114   	} else {
   140    115   		v.b.WriteString("<img src=\"")
   141    116   		v.writeReference(in.Ref)
   142    117   	}
   143    118   	v.b.WriteString("\" alt=\"")
   144         -	v.acceptInlineSlice(in.Inlines)
          119  +	ast.WalkInlineSlice(v, in.Inlines)
   145    120   	v.b.WriteByte('"')
   146    121   	v.visitAttributes(in.Attrs)
   147    122   	if v.env.IsXHTML() {
   148    123   		v.b.WriteString(" />")
   149    124   	} else {
   150    125   		v.b.WriteByte('>')
   151    126   	}
   152    127   }
   153    128   
   154         -// VisitCite writes code for citations.
   155         -func (v *visitor) VisitCite(cn *ast.CiteNode) {
          129  +func (v *visitor) visitCite(cn *ast.CiteNode) {
   156    130   	cn, n := v.env.AdaptCite(cn)
   157    131   	if n != nil {
   158         -		n.Accept(v)
          132  +		ast.Walk(v, n)
   159    133   		return
   160    134   	}
   161    135   	if cn == nil {
   162    136   		return
   163    137   	}
   164    138   	v.lang.push(cn.Attrs)
   165    139   	defer v.lang.pop()
   166    140   	v.b.WriteString(cn.Key)
   167    141   	if len(cn.Inlines) > 0 {
   168    142   		v.b.WriteString(", ")
   169         -		v.acceptInlineSlice(cn.Inlines)
          143  +		ast.WalkInlineSlice(v, cn.Inlines)
   170    144   	}
   171    145   }
   172    146   
   173         -// VisitFootnote write HTML code for a footnote.
   174         -func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
          147  +func (v *visitor) visitFootnote(fn *ast.FootnoteNode) {
   175    148   	v.lang.push(fn.Attrs)
   176    149   	defer v.lang.pop()
   177    150   	if v.env.IsInteractive(v.inInteractive) {
   178    151   		return
   179    152   	}
   180    153   
   181    154   	n := strconv.Itoa(v.env.AddFootnote(fn))
   182    155   	v.b.WriteStrings("<sup id=\"fnref:", n, "\"><a href=\"#fn:", n, "\" class=\"zs-footnote-ref\" role=\"doc-noteref\">", n, "</a></sup>")
   183    156   	// TODO: what to do with Attrs?
   184    157   }
   185    158   
   186         -// VisitMark writes HTML code to mark a position.
   187         -func (v *visitor) VisitMark(mn *ast.MarkNode) {
          159  +func (v *visitor) visitMark(mn *ast.MarkNode) {
   188    160   	if v.env.IsInteractive(v.inInteractive) {
   189    161   		return
   190    162   	}
   191    163   	if len(mn.Text) > 0 {
   192    164   		v.b.WriteStrings("<a id=\"", mn.Text, "\"></a>")
   193    165   	}
   194    166   }
   195    167   
   196         -// VisitFormat write HTML code for formatting text.
   197         -func (v *visitor) VisitFormat(fn *ast.FormatNode) {
          168  +func (v *visitor) visitFormat(fn *ast.FormatNode) {
   198    169   	v.lang.push(fn.Attrs)
   199    170   	defer v.lang.pop()
   200    171   
   201    172   	var code string
   202    173   	attrs := fn.Attrs
   203         -	switch fn.Code {
          174  +	switch fn.Kind {
   204    175   	case ast.FormatItalic:
   205    176   		code = "i"
   206    177   	case ast.FormatEmph:
   207    178   		code = "em"
   208    179   	case ast.FormatBold:
   209    180   		code = "b"
   210    181   	case ast.FormatStrong:
................................................................................
   231    202   	case ast.FormatMonospace:
   232    203   		code = "span"
   233    204   		attrs = attrs.Set("style", "font-family:monospace")
   234    205   	case ast.FormatQuote:
   235    206   		v.visitQuotes(fn)
   236    207   		return
   237    208   	default:
   238         -		panic(fmt.Sprintf("Unknown format code %v", fn.Code))
          209  +		panic(fmt.Sprintf("Unknown format kind %v", fn.Kind))
   239    210   	}
   240    211   	v.b.WriteStrings("<", code)
   241    212   	v.visitAttributes(attrs)
   242    213   	v.b.WriteByte('>')
   243         -	v.acceptInlineSlice(fn.Inlines)
          214  +	ast.WalkInlineSlice(v, fn.Inlines)
   244    215   	v.b.WriteStrings("</", code, ">")
   245    216   }
   246    217   
   247    218   func (v *visitor) writeSpan(ins ast.InlineSlice, attrs *ast.Attributes) {
   248    219   	v.b.WriteString("<span")
   249    220   	v.visitAttributes(attrs)
   250    221   	v.b.WriteByte('>')
   251         -	v.acceptInlineSlice(ins)
          222  +	ast.WalkInlineSlice(v, ins)
   252    223   	v.b.WriteString("</span>")
   253    224   
   254    225   }
   255    226   
   256    227   var langQuotes = map[string][2]string{
   257    228   	meta.ValueLangEN: {"&ldquo;", "&rdquo;"},
   258    229   	"de":             {"&bdquo;", "&ldquo;"},
................................................................................
   277    248   	if withSpan {
   278    249   		v.b.WriteString("<span")
   279    250   		v.visitAttributes(fn.Attrs)
   280    251   		v.b.WriteByte('>')
   281    252   	}
   282    253   	openingQ, closingQ := getQuotes(v.lang.top())
   283    254   	v.b.WriteString(openingQ)
   284         -	v.acceptInlineSlice(fn.Inlines)
          255  +	ast.WalkInlineSlice(v, fn.Inlines)
   285    256   	v.b.WriteString(closingQ)
   286    257   	if withSpan {
   287    258   		v.b.WriteString("</span>")
   288    259   	}
   289    260   }
   290    261   
   291         -// VisitLiteral write HTML code for literal inline text.
   292         -func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
   293         -	switch ln.Code {
          262  +func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
          263  +	switch ln.Kind {
   294    264   	case ast.LiteralProg:
   295    265   		v.writeLiteral("<code", "</code>", ln.Attrs, ln.Text)
   296    266   	case ast.LiteralKeyb:
   297    267   		v.writeLiteral("<kbd", "</kbd>", ln.Attrs, ln.Text)
   298    268   	case ast.LiteralOutput:
   299    269   		v.writeLiteral("<samp", "</samp>", ln.Attrs, ln.Text)
   300    270   	case ast.LiteralComment:
................................................................................
   302    272   		v.writeHTMLEscaped(ln.Text) // writeCommentEscaped
   303    273   		v.b.WriteString(" -->")
   304    274   	case ast.LiteralHTML:
   305    275   		if !ignoreHTMLText(ln.Text) {
   306    276   			v.b.WriteString(ln.Text)
   307    277   		}
   308    278   	default:
   309         -		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
          279  +		panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind))
   310    280   	}
   311    281   }
   312    282   
   313    283   func (v *visitor) writeLiteral(codeS, codeE string, attrs *ast.Attributes, text string) {
   314    284   	oldVisible := v.visibleSpace
   315    285   	if attrs != nil {
   316    286   		v.visibleSpace = attrs.HasDefault()

Changes to encoder/htmlenc/visitor.go.

    40     40   	}
    41     41   	return &visitor{
    42     42   		env:  he.env,
    43     43   		b:    encoder.NewBufWriter(w),
    44     44   		lang: newLangStack(lang),
    45     45   	}
    46     46   }
           47  +
           48  +func (v *visitor) Visit(node ast.Node) ast.Visitor {
           49  +	switch n := node.(type) {
           50  +	case *ast.ParaNode:
           51  +		v.b.WriteString("<p>")
           52  +		ast.WalkInlineSlice(v, n.Inlines)
           53  +		v.writeEndPara()
           54  +	case *ast.VerbatimNode:
           55  +		v.visitVerbatim(n)
           56  +	case *ast.RegionNode:
           57  +		v.visitRegion(n)
           58  +	case *ast.HeadingNode:
           59  +		v.visitHeading(n)
           60  +	case *ast.HRuleNode:
           61  +		v.b.WriteString("<hr")
           62  +		v.visitAttributes(n.Attrs)
           63  +		if v.env.IsXHTML() {
           64  +			v.b.WriteString(" />\n")
           65  +		} else {
           66  +			v.b.WriteString(">\n")
           67  +		}
           68  +	case *ast.NestedListNode:
           69  +		v.visitNestedList(n)
           70  +	case *ast.DescriptionListNode:
           71  +		v.visitDescriptionList(n)
           72  +	case *ast.TableNode:
           73  +		v.visitTable(n)
           74  +	case *ast.BLOBNode:
           75  +		v.visitBLOB(n)
           76  +	case *ast.TextNode:
           77  +		v.writeHTMLEscaped(n.Text)
           78  +	case *ast.TagNode:
           79  +		// TODO: erst mal als span. Link wäre gut, muss man vermutlich via Callback lösen.
           80  +		v.b.WriteString("<span class=\"zettel-tag\">#")
           81  +		v.writeHTMLEscaped(n.Tag)
           82  +		v.b.WriteString("</span>")
           83  +	case *ast.SpaceNode:
           84  +		if v.inVerse || v.env.IsXHTML() {
           85  +			v.b.WriteString(n.Lexeme)
           86  +		} else {
           87  +			v.b.WriteByte(' ')
           88  +		}
           89  +	case *ast.BreakNode:
           90  +		v.visitBreak(n)
           91  +	case *ast.LinkNode:
           92  +		v.visitLink(n)
           93  +	case *ast.ImageNode:
           94  +		v.visitImage(n)
           95  +	case *ast.CiteNode:
           96  +		v.visitCite(n)
           97  +	case *ast.FootnoteNode:
           98  +		v.visitFootnote(n)
           99  +	case *ast.MarkNode:
          100  +		v.visitMark(n)
          101  +	case *ast.FormatNode:
          102  +		v.visitFormat(n)
          103  +	case *ast.LiteralNode:
          104  +		v.visitLiteral(n)
          105  +	default:
          106  +		return v
          107  +	}
          108  +	return nil
          109  +}
    47    110   
    48    111   var mapMetaKey = map[string]string{
    49    112   	meta.KeyCopyright: "copyright",
    50    113   	meta.KeyLicense:   "license",
    51    114   }
    52    115   
    53    116   func (v *visitor) acceptMeta(m *meta.Meta) {
................................................................................
    81    144   
    82    145   func (v *visitor) writeMeta(prefix, key, value string) {
    83    146   	v.b.WriteStrings("\n<meta name=\"", prefix, key, "\" content=\"")
    84    147   	v.writeQuotedEscaped(value)
    85    148   	v.b.WriteString("\">")
    86    149   }
    87    150   
    88         -func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
    89         -	for _, bn := range bns {
    90         -		bn.Accept(v)
    91         -	}
    92         -}
    93         -func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
    94         -	for _, in := range ins {
    95         -		in.Accept(v)
    96         -	}
    97         -}
    98         -func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
    99         -	for _, in := range ins {
   100         -		in.Accept(v)
   101         -	}
   102         -}
   103         -
   104    151   func (v *visitor) writeEndnotes() {
   105    152   	footnotes := v.env.GetCleanFootnotes()
   106    153   	if len(footnotes) > 0 {
   107    154   		v.b.WriteString("<ol class=\"zs-endnotes\">\n")
   108    155   		for i := 0; i < len(footnotes); i++ {
   109    156   			// Do not use a range loop above, because a footnote may contain
   110    157   			// a footnote. Therefore v.enc.footnote may grow during the loop.
   111    158   			fn := footnotes[i]
   112    159   			n := strconv.Itoa(i + 1)
   113    160   			v.b.WriteStrings("<li id=\"fn:", n, "\" role=\"doc-endnote\">")
   114         -			v.acceptInlineSlice(fn.Inlines)
          161  +			ast.WalkInlineSlice(v, fn.Inlines)
   115    162   			v.b.WriteStrings(
   116    163   				" <a href=\"#fnref:",
   117    164   				n,
   118    165   				"\" class=\"zs-footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;</a></li>\n")
   119    166   		}
   120    167   		v.b.WriteString("</ol>\n")
   121    168   	}

Changes to encoder/jsonenc/djsonenc.go.

    33     33   	env *encoder.Environment
    34     34   }
    35     35   
    36     36   // WriteZettel writes the encoded zettel to the writer.
    37     37   func (je *jsonDetailEncoder) WriteZettel(w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    38     38   	v := newDetailVisitor(w, je)
    39     39   	v.b.WriteString("{\"meta\":{\"title\":")
    40         -	v.acceptInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
           40  +	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
    41     41   	if inhMeta {
    42     42   		v.writeMeta(zn.InhMeta)
    43     43   	} else {
    44     44   		v.writeMeta(zn.Meta)
    45     45   	}
    46     46   	v.b.WriteByte('}')
    47     47   	v.b.WriteString(",\"content\":")
    48         -	v.acceptBlockSlice(zn.Ast)
           48  +	v.walkBlockSlice(zn.Ast)
    49     49   	v.b.WriteByte('}')
    50     50   	length, err := v.b.Flush()
    51     51   	return length, err
    52     52   }
    53     53   
    54     54   // WriteMeta encodes meta data as JSON.
    55     55   func (je *jsonDetailEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    56     56   	v := newDetailVisitor(w, je)
    57     57   	v.b.WriteString("{\"title\":")
    58         -	v.acceptInlineSlice(encfun.MetaAsInlineSlice(m, meta.KeyTitle))
           58  +	v.walkInlineSlice(encfun.MetaAsInlineSlice(m, meta.KeyTitle))
    59     59   	v.writeMeta(m)
    60     60   	v.b.WriteByte('}')
    61     61   	length, err := v.b.Flush()
    62     62   	return length, err
    63     63   }
    64     64   
    65     65   func (je *jsonDetailEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    66     66   	return je.WriteBlocks(w, zn.Ast)
    67     67   }
    68     68   
    69     69   // WriteBlocks writes a block slice to the writer
    70     70   func (je *jsonDetailEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    71     71   	v := newDetailVisitor(w, je)
    72         -	v.acceptBlockSlice(bs)
           72  +	v.walkBlockSlice(bs)
    73     73   	length, err := v.b.Flush()
    74     74   	return length, err
    75     75   }
    76     76   
    77     77   // WriteInlines writes an inline slice to the writer
    78     78   func (je *jsonDetailEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    79     79   	v := newDetailVisitor(w, je)
    80         -	v.acceptInlineSlice(is)
           80  +	v.walkInlineSlice(is)
    81     81   	length, err := v.b.Flush()
    82     82   	return length, err
    83     83   }
    84     84   
    85     85   // detailVisitor writes the abstract syntax tree to an io.Writer.
    86     86   type detailVisitor struct {
    87     87   	b   encoder.BufWriter
................................................................................
    88     88   	env *encoder.Environment
    89     89   }
    90     90   
    91     91   func newDetailVisitor(w io.Writer, je *jsonDetailEncoder) *detailVisitor {
    92     92   	return &detailVisitor{b: encoder.NewBufWriter(w), env: je.env}
    93     93   }
    94     94   
    95         -// VisitPara emits JSON code for a paragraph.
    96         -func (v *detailVisitor) VisitPara(pn *ast.ParaNode) {
    97         -	v.writeNodeStart("Para")
    98         -	v.writeContentStart('i')
    99         -	v.acceptInlineSlice(pn.Inlines)
           95  +func (v *detailVisitor) Visit(node ast.Node) ast.Visitor {
           96  +	switch n := node.(type) {
           97  +	case *ast.ParaNode:
           98  +		v.writeNodeStart("Para")
           99  +		v.writeContentStart('i')
          100  +		v.walkInlineSlice(n.Inlines)
          101  +	case *ast.VerbatimNode:
          102  +		v.visitVerbatim(n)
          103  +	case *ast.RegionNode:
          104  +		v.visitRegion(n)
          105  +	case *ast.HeadingNode:
          106  +		v.visitHeading(n)
          107  +	case *ast.HRuleNode:
          108  +		v.writeNodeStart("Hrule")
          109  +		v.visitAttributes(n.Attrs)
          110  +	case *ast.NestedListNode:
          111  +		v.visitNestedList(n)
          112  +	case *ast.DescriptionListNode:
          113  +		v.visitDescriptionList(n)
          114  +	case *ast.TableNode:
          115  +		v.visitTable(n)
          116  +	case *ast.BLOBNode:
          117  +		v.writeNodeStart("Blob")
          118  +		v.writeContentStart('q')
          119  +		writeEscaped(&v.b, n.Title)
          120  +		v.writeContentStart('s')
          121  +		writeEscaped(&v.b, n.Syntax)
          122  +		v.writeContentStart('o')
          123  +		v.b.WriteBase64(n.Blob)
          124  +		v.b.WriteByte('"')
          125  +	case *ast.TextNode:
          126  +		v.writeNodeStart("Text")
          127  +		v.writeContentStart('s')
          128  +		writeEscaped(&v.b, n.Text)
          129  +	case *ast.TagNode:
          130  +		v.writeNodeStart("Tag")
          131  +		v.writeContentStart('s')
          132  +		writeEscaped(&v.b, n.Tag)
          133  +	case *ast.SpaceNode:
          134  +		v.writeNodeStart("Space")
          135  +		if l := len(n.Lexeme); l > 1 {
          136  +			v.writeContentStart('n')
          137  +			v.b.WriteString(strconv.Itoa(l))
          138  +		}
          139  +	case *ast.BreakNode:
          140  +		if n.Hard {
          141  +			v.writeNodeStart("Hard")
          142  +		} else {
          143  +			v.writeNodeStart("Soft")
          144  +		}
          145  +	case *ast.LinkNode:
          146  +		n, n2 := v.env.AdaptLink(n)
          147  +		if n2 != nil {
          148  +			ast.Walk(v, n2)
          149  +			return nil
          150  +		}
          151  +		v.writeNodeStart("Link")
          152  +		v.visitAttributes(n.Attrs)
          153  +		v.writeContentStart('q')
          154  +		writeEscaped(&v.b, mapRefState[n.Ref.State])
          155  +		v.writeContentStart('s')
          156  +		writeEscaped(&v.b, n.Ref.String())
          157  +		v.writeContentStart('i')
          158  +		v.walkInlineSlice(n.Inlines)
          159  +	case *ast.ImageNode:
          160  +		v.visitImage(n)
          161  +	case *ast.CiteNode:
          162  +		v.writeNodeStart("Cite")
          163  +		v.visitAttributes(n.Attrs)
          164  +		v.writeContentStart('s')
          165  +		writeEscaped(&v.b, n.Key)
          166  +		if len(n.Inlines) > 0 {
          167  +			v.writeContentStart('i')
          168  +			v.walkInlineSlice(n.Inlines)
          169  +		}
          170  +	case *ast.FootnoteNode:
          171  +		v.writeNodeStart("Footnote")
          172  +		v.visitAttributes(n.Attrs)
          173  +		v.writeContentStart('i')
          174  +		v.walkInlineSlice(n.Inlines)
          175  +	case *ast.MarkNode:
          176  +		v.writeNodeStart("Mark")
          177  +		if len(n.Text) > 0 {
          178  +			v.writeContentStart('s')
          179  +			writeEscaped(&v.b, n.Text)
          180  +		}
          181  +	case *ast.FormatNode:
          182  +		v.writeNodeStart(mapFormatKind[n.Kind])
          183  +		v.visitAttributes(n.Attrs)
          184  +		v.writeContentStart('i')
          185  +		v.walkInlineSlice(n.Inlines)
          186  +	case *ast.LiteralNode:
          187  +		kind, ok := mapLiteralKind[n.Kind]
          188  +		if !ok {
          189  +			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
          190  +		}
          191  +		v.writeNodeStart(kind)
          192  +		v.visitAttributes(n.Attrs)
          193  +		v.writeContentStart('s')
          194  +		writeEscaped(&v.b, n.Text)
          195  +	default:
          196  +		return v
          197  +	}
   100    198   	v.b.WriteByte('}')
          199  +	return nil
   101    200   }
   102    201   
   103         -var verbatimCode = map[ast.VerbatimCode]string{
          202  +var mapVerbatimKind = map[ast.VerbatimKind]string{
   104    203   	ast.VerbatimProg:    "CodeBlock",
   105    204   	ast.VerbatimComment: "CommentBlock",
   106    205   	ast.VerbatimHTML:    "HTMLBlock",
   107    206   }
   108    207   
   109         -// VisitVerbatim emits JSON code for verbatim lines.
   110         -func (v *detailVisitor) VisitVerbatim(vn *ast.VerbatimNode) {
   111         -	code, ok := verbatimCode[vn.Code]
          208  +func (v *detailVisitor) visitVerbatim(vn *ast.VerbatimNode) {
          209  +	kind, ok := mapVerbatimKind[vn.Kind]
   112    210   	if !ok {
   113         -		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
          211  +		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
   114    212   	}
   115         -	v.writeNodeStart(code)
          213  +	v.writeNodeStart(kind)
   116    214   	v.visitAttributes(vn.Attrs)
   117    215   	v.writeContentStart('l')
   118    216   	for i, line := range vn.Lines {
   119    217   		if i > 0 {
   120    218   			v.b.WriteByte(',')
   121    219   		}
   122    220   		writeEscaped(&v.b, line)
   123    221   	}
   124         -	v.b.WriteString("]}")
          222  +	v.b.WriteByte(']')
   125    223   }
   126    224   
   127         -var regionCode = map[ast.RegionCode]string{
          225  +var mapRegionKind = map[ast.RegionKind]string{
   128    226   	ast.RegionSpan:  "SpanBlock",
   129    227   	ast.RegionQuote: "QuoteBlock",
   130    228   	ast.RegionVerse: "VerseBlock",
   131    229   }
   132    230   
   133         -// VisitRegion writes JSON code for block regions.
   134         -func (v *detailVisitor) VisitRegion(rn *ast.RegionNode) {
   135         -	code, ok := regionCode[rn.Code]
          231  +func (v *detailVisitor) visitRegion(rn *ast.RegionNode) {
          232  +	kind, ok := mapRegionKind[rn.Kind]
   136    233   	if !ok {
   137         -		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
          234  +		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
   138    235   	}
   139         -	v.writeNodeStart(code)
          236  +	v.writeNodeStart(kind)
   140    237   	v.visitAttributes(rn.Attrs)
   141    238   	v.writeContentStart('b')
   142         -	v.acceptBlockSlice(rn.Blocks)
          239  +	v.walkBlockSlice(rn.Blocks)
   143    240   	if len(rn.Inlines) > 0 {
   144    241   		v.writeContentStart('i')
   145         -		v.acceptInlineSlice(rn.Inlines)
          242  +		v.walkInlineSlice(rn.Inlines)
   146    243   	}
   147         -	v.b.WriteByte('}')
   148    244   }
   149    245   
   150         -// VisitHeading writes the JSON code for a heading.
   151         -func (v *detailVisitor) VisitHeading(hn *ast.HeadingNode) {
          246  +func (v *detailVisitor) visitHeading(hn *ast.HeadingNode) {
   152    247   	v.writeNodeStart("Heading")
   153    248   	v.visitAttributes(hn.Attrs)
   154    249   	v.writeContentStart('n')
   155    250   	v.b.WriteString(strconv.Itoa(hn.Level))
   156    251   	if slug := hn.Slug; len(slug) > 0 {
   157    252   		v.writeContentStart('s')
   158    253   		v.b.WriteStrings("\"", slug, "\"")
   159    254   	}
   160    255   	v.writeContentStart('i')
   161         -	v.acceptInlineSlice(hn.Inlines)
   162         -	v.b.WriteByte('}')
          256  +	v.walkInlineSlice(hn.Inlines)
   163    257   }
   164    258   
   165         -// VisitHRule writes JSON code for a horizontal rule: <hr>.
   166         -func (v *detailVisitor) VisitHRule(hn *ast.HRuleNode) {
   167         -	v.writeNodeStart("Hrule")
   168         -	v.visitAttributes(hn.Attrs)
   169         -	v.b.WriteByte('}')
   170         -}
   171         -
   172         -var listCode = map[ast.NestedListCode]string{
          259  +var mapNestedListKind = map[ast.NestedListKind]string{
   173    260   	ast.NestedListOrdered:   "OrderedList",
   174    261   	ast.NestedListUnordered: "BulletList",
   175    262   	ast.NestedListQuote:     "QuoteList",
   176    263   }
   177    264   
   178         -// VisitNestedList writes JSON code for lists and blockquotes.
   179         -func (v *detailVisitor) VisitNestedList(ln *ast.NestedListNode) {
   180         -	v.writeNodeStart(listCode[ln.Code])
          265  +func (v *detailVisitor) visitNestedList(ln *ast.NestedListNode) {
          266  +	v.writeNodeStart(mapNestedListKind[ln.Kind])
   181    267   	v.writeContentStart('c')
   182    268   	for i, item := range ln.Items {
   183    269   		if i > 0 {
   184    270   			v.b.WriteByte(',')
   185    271   		}
   186         -		v.acceptItemSlice(item)
          272  +		v.b.WriteByte('[')
          273  +		for j, in := range item {
          274  +			if j > 0 {
          275  +				v.b.WriteByte(',')
          276  +			}
          277  +			ast.Walk(v, in)
          278  +		}
          279  +		v.b.WriteByte(']')
   187    280   	}
   188         -	v.b.WriteString("]}")
          281  +	v.b.WriteByte(']')
   189    282   }
   190    283   
   191         -// VisitDescriptionList emits a JSON description list.
   192         -func (v *detailVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
          284  +func (v *detailVisitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   193    285   	v.writeNodeStart("DescriptionList")
   194    286   	v.writeContentStart('g')
   195    287   	for i, def := range dn.Descriptions {
   196    288   		if i > 0 {
   197    289   			v.b.WriteByte(',')
   198    290   		}
   199    291   		v.b.WriteByte('[')
   200         -		v.acceptInlineSlice(def.Term)
          292  +		v.walkInlineSlice(def.Term)
   201    293   
   202    294   		if len(def.Descriptions) > 0 {
   203    295   			for _, b := range def.Descriptions {
   204    296   				v.b.WriteByte(',')
   205         -				v.acceptDescriptionSlice(b)
          297  +				v.b.WriteByte('[')
          298  +				for j, dn := range b {
          299  +					if j > 0 {
          300  +						v.b.WriteByte(',')
          301  +					}
          302  +					ast.Walk(v, dn)
          303  +				}
          304  +				v.b.WriteByte(']')
   206    305   			}
   207    306   		}
   208    307   		v.b.WriteByte(']')
   209    308   	}
   210         -	v.b.WriteString("]}")
          309  +	v.b.WriteByte(']')
   211    310   }
   212    311   
   213         -// VisitTable emits a JSON table.
   214         -func (v *detailVisitor) VisitTable(tn *ast.TableNode) {
          312  +func (v *detailVisitor) visitTable(tn *ast.TableNode) {
   215    313   	v.writeNodeStart("Table")
   216    314   	v.writeContentStart('p')
   217    315   
   218    316   	// Table header
   219    317   	v.b.WriteByte('[')
   220    318   	for i, cell := range tn.Header {
   221    319   		if i > 0 {
................................................................................
   236    334   			if j > 0 {
   237    335   				v.b.WriteByte(',')
   238    336   			}
   239    337   			v.writeCell(cell)
   240    338   		}
   241    339   		v.b.WriteByte(']')
   242    340   	}
   243         -	v.b.WriteString("]]}")
          341  +	v.b.WriteString("]]")
   244    342   }
   245    343   
   246    344   var alignmentCode = map[ast.Alignment]string{
   247    345   	ast.AlignDefault: "[\"\",",
   248    346   	ast.AlignLeft:    "[\"<\",",
   249    347   	ast.AlignCenter:  "[\":\",",
   250    348   	ast.AlignRight:   "[\">\",",
   251    349   }
   252    350   
   253    351   func (v *detailVisitor) writeCell(cell *ast.TableCell) {
   254    352   	v.b.WriteString(alignmentCode[cell.Align])
   255         -	v.acceptInlineSlice(cell.Inlines)
          353  +	v.walkInlineSlice(cell.Inlines)
   256    354   	v.b.WriteByte(']')
   257    355   }
   258    356   
   259         -// VisitBLOB writes the binary object as a value.
   260         -func (v *detailVisitor) VisitBLOB(bn *ast.BLOBNode) {
   261         -	v.writeNodeStart("Blob")
   262         -	v.writeContentStart('q')
   263         -	writeEscaped(&v.b, bn.Title)
   264         -	v.writeContentStart('s')
   265         -	writeEscaped(&v.b, bn.Syntax)
   266         -	v.writeContentStart('o')
   267         -	v.b.WriteBase64(bn.Blob)
   268         -	v.b.WriteString("\"}")
   269         -}
   270         -
   271         -// VisitText writes text content.
   272         -func (v *detailVisitor) VisitText(tn *ast.TextNode) {
   273         -	v.writeNodeStart("Text")
   274         -	v.writeContentStart('s')
   275         -	writeEscaped(&v.b, tn.Text)
   276         -	v.b.WriteByte('}')
   277         -}
   278         -
   279         -// VisitTag writes tag content.
   280         -func (v *detailVisitor) VisitTag(tn *ast.TagNode) {
   281         -	v.writeNodeStart("Tag")
   282         -	v.writeContentStart('s')
   283         -	writeEscaped(&v.b, tn.Tag)
   284         -	v.b.WriteByte('}')
   285         -}
   286         -
   287         -// VisitSpace emits a white space.
   288         -func (v *detailVisitor) VisitSpace(sn *ast.SpaceNode) {
   289         -	v.writeNodeStart("Space")
   290         -	if l := len(sn.Lexeme); l > 1 {
   291         -		v.writeContentStart('n')
   292         -		v.b.WriteString(strconv.Itoa(l))
   293         -	}
   294         -	v.b.WriteByte('}')
   295         -}
   296         -
   297         -// VisitBreak writes JSON code for line breaks.
   298         -func (v *detailVisitor) VisitBreak(bn *ast.BreakNode) {
   299         -	if bn.Hard {
   300         -		v.writeNodeStart("Hard")
   301         -	} else {
   302         -		v.writeNodeStart("Soft")
   303         -	}
   304         -	v.b.WriteByte('}')
   305         -}
   306         -
   307    357   var mapRefState = map[ast.RefState]string{
   308    358   	ast.RefStateInvalid:  "invalid",
   309    359   	ast.RefStateZettel:   "zettel",
   310    360   	ast.RefStateSelf:     "self",
   311    361   	ast.RefStateFound:    "zettel",
   312    362   	ast.RefStateBroken:   "broken",
   313    363   	ast.RefStateHosted:   "local",
   314    364   	ast.RefStateBased:    "based",
   315    365   	ast.RefStateExternal: "external",
   316    366   }
   317    367   
   318         -// VisitLink writes JSON code for links.
   319         -func (v *detailVisitor) VisitLink(ln *ast.LinkNode) {
   320         -	ln, n := v.env.AdaptLink(ln)
   321         -	if n != nil {
   322         -		n.Accept(v)
   323         -		return
   324         -	}
   325         -	v.writeNodeStart("Link")
   326         -	v.visitAttributes(ln.Attrs)
   327         -	v.writeContentStart('q')
   328         -	writeEscaped(&v.b, mapRefState[ln.Ref.State])
   329         -	v.writeContentStart('s')
   330         -	writeEscaped(&v.b, ln.Ref.String())
   331         -	v.writeContentStart('i')
   332         -	v.acceptInlineSlice(ln.Inlines)
   333         -	v.b.WriteByte('}')
   334         -}
   335         -
   336         -// VisitImage writes JSON code for images.
   337         -func (v *detailVisitor) VisitImage(in *ast.ImageNode) {
          368  +func (v *detailVisitor) visitImage(in *ast.ImageNode) {
   338    369   	in, n := v.env.AdaptImage(in)
   339    370   	if n != nil {
   340         -		n.Accept(v)
          371  +		ast.Walk(v, n)
   341    372   		return
   342    373   	}
   343    374   	v.writeNodeStart("Image")
   344    375   	v.visitAttributes(in.Attrs)
   345    376   	if in.Ref == nil {
   346    377   		v.writeContentStart('j')
   347    378   		v.b.WriteString("\"s\":")
................................................................................
   358    389   		v.b.WriteByte('}')
   359    390   	} else {
   360    391   		v.writeContentStart('s')
   361    392   		writeEscaped(&v.b, in.Ref.String())
   362    393   	}
   363    394   	if len(in.Inlines) > 0 {
   364    395   		v.writeContentStart('i')
   365         -		v.acceptInlineSlice(in.Inlines)
          396  +		v.walkInlineSlice(in.Inlines)
   366    397   	}
   367         -	v.b.WriteByte('}')
   368    398   }
   369    399   
   370         -// VisitCite writes code for citations.
   371         -func (v *detailVisitor) VisitCite(cn *ast.CiteNode) {
   372         -	v.writeNodeStart("Cite")
   373         -	v.visitAttributes(cn.Attrs)
   374         -	v.writeContentStart('s')
   375         -	writeEscaped(&v.b, cn.Key)
   376         -	if len(cn.Inlines) > 0 {
   377         -		v.writeContentStart('i')
   378         -		v.acceptInlineSlice(cn.Inlines)
   379         -	}
   380         -	v.b.WriteByte('}')
   381         -}
   382         -
   383         -// VisitFootnote write JSON code for a footnote.
   384         -func (v *detailVisitor) VisitFootnote(fn *ast.FootnoteNode) {
   385         -	v.writeNodeStart("Footnote")
   386         -	v.visitAttributes(fn.Attrs)
   387         -	v.writeContentStart('i')
   388         -	v.acceptInlineSlice(fn.Inlines)
   389         -	v.b.WriteByte('}')
   390         -}
   391         -
   392         -// VisitMark writes JSON code to mark a position.
   393         -func (v *detailVisitor) VisitMark(mn *ast.MarkNode) {
   394         -	v.writeNodeStart("Mark")
   395         -	if len(mn.Text) > 0 {
   396         -		v.writeContentStart('s')
   397         -		writeEscaped(&v.b, mn.Text)
   398         -	}
   399         -	v.b.WriteByte('}')
   400         -}
   401         -
   402         -var formatCode = map[ast.FormatCode]string{
          400  +var mapFormatKind = map[ast.FormatKind]string{
   403    401   	ast.FormatItalic:    "Italic",
   404    402   	ast.FormatEmph:      "Emph",
   405    403   	ast.FormatBold:      "Bold",
   406    404   	ast.FormatStrong:    "Strong",
   407    405   	ast.FormatMonospace: "Mono",
   408    406   	ast.FormatStrike:    "Strikethrough",
   409    407   	ast.FormatDelete:    "Delete",
................................................................................
   413    411   	ast.FormatSub:       "Sub",
   414    412   	ast.FormatQuote:     "Quote",
   415    413   	ast.FormatQuotation: "Quotation",
   416    414   	ast.FormatSmall:     "Small",
   417    415   	ast.FormatSpan:      "Span",
   418    416   }
   419    417   
   420         -// VisitFormat write JSON code for formatting text.
   421         -func (v *detailVisitor) VisitFormat(fn *ast.FormatNode) {
   422         -	v.writeNodeStart(formatCode[fn.Code])
   423         -	v.visitAttributes(fn.Attrs)
   424         -	v.writeContentStart('i')
   425         -	v.acceptInlineSlice(fn.Inlines)
   426         -	v.b.WriteByte('}')
   427         -}
   428         -
   429         -var literalCode = map[ast.LiteralCode]string{
          418  +var mapLiteralKind = map[ast.LiteralKind]string{
   430    419   	ast.LiteralProg:    "Code",
   431    420   	ast.LiteralKeyb:    "Input",
   432    421   	ast.LiteralOutput:  "Output",
   433    422   	ast.LiteralComment: "Comment",
   434    423   	ast.LiteralHTML:    "HTML",
   435    424   }
   436    425   
   437         -// VisitLiteral write JSON code for literal inline text.
   438         -func (v *detailVisitor) VisitLiteral(ln *ast.LiteralNode) {
   439         -	code, ok := literalCode[ln.Code]
   440         -	if !ok {
   441         -		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
   442         -	}
   443         -	v.writeNodeStart(code)
   444         -	v.visitAttributes(ln.Attrs)
   445         -	v.writeContentStart('s')
   446         -	writeEscaped(&v.b, ln.Text)
   447         -	v.b.WriteByte('}')
   448         -}
   449         -
   450         -func (v *detailVisitor) acceptBlockSlice(bns ast.BlockSlice) {
          426  +func (v *detailVisitor) walkBlockSlice(bns ast.BlockSlice) {
   451    427   	v.b.WriteByte('[')
   452    428   	for i, bn := range bns {
   453    429   		if i > 0 {
   454    430   			v.b.WriteByte(',')
   455    431   		}
   456         -		bn.Accept(v)
          432  +		ast.Walk(v, bn)
   457    433   	}
   458    434   	v.b.WriteByte(']')
   459    435   }
   460    436   
   461         -func (v *detailVisitor) acceptItemSlice(ins ast.ItemSlice) {
          437  +func (v *detailVisitor) walkInlineSlice(ins ast.InlineSlice) {
   462    438   	v.b.WriteByte('[')
   463    439   	for i, in := range ins {
   464    440   		if i > 0 {
   465    441   			v.b.WriteByte(',')
   466    442   		}
   467         -		in.Accept(v)
   468         -	}
   469         -	v.b.WriteByte(']')
   470         -}
   471         -
   472         -func (v *detailVisitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
   473         -	v.b.WriteByte('[')
   474         -	for i, dn := range dns {
   475         -		if i > 0 {
   476         -			v.b.WriteByte(',')
   477         -		}
   478         -		dn.Accept(v)
   479         -	}
   480         -	v.b.WriteByte(']')
   481         -}
   482         -
   483         -func (v *detailVisitor) acceptInlineSlice(ins ast.InlineSlice) {
   484         -	v.b.WriteByte('[')
   485         -	for i, in := range ins {
   486         -		if i > 0 {
   487         -			v.b.WriteByte(',')
   488         -		}
   489         -		in.Accept(v)
          443  +		ast.Walk(v, in)
   490    444   	}
   491    445   	v.b.WriteByte(']')
   492    446   }
   493    447   
   494    448   // visitAttributes write JSON attributes
   495    449   func (v *detailVisitor) visitAttributes(a *ast.Attributes) {
   496    450   	if a == nil || len(a.Attrs) == 0 {

Changes to encoder/nativeenc/nativeenc.go.

    35     35   }
    36     36   
    37     37   // WriteZettel encodes the zettel to the writer.
    38     38   func (ne *nativeEncoder) WriteZettel(
    39     39   	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    40     40   	v := newVisitor(w, ne)
    41     41   	v.b.WriteString("[Title ")
    42         -	v.acceptInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
           42  +	v.walkInlineSlice(encfun.MetaAsInlineSlice(zn.InhMeta, meta.KeyTitle))
    43     43   	v.b.WriteByte(']')
    44     44   	if inhMeta {
    45     45   		v.acceptMeta(zn.InhMeta, false)
    46     46   	} else {
    47     47   		v.acceptMeta(zn.Meta, false)
    48     48   	}
    49     49   	v.b.WriteByte('\n')
    50         -	v.acceptBlockSlice(zn.Ast)
           50  +	v.walkBlockSlice(zn.Ast)
    51     51   	length, err := v.b.Flush()
    52     52   	return length, err
    53     53   }
    54     54   
    55     55   // WriteMeta encodes meta data in native format.
    56     56   func (ne *nativeEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    57     57   	v := newVisitor(w, ne)
................................................................................
    63     63   func (ne *nativeEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    64     64   	return ne.WriteBlocks(w, zn.Ast)
    65     65   }
    66     66   
    67     67   // WriteBlocks writes a block slice to the writer
    68     68   func (ne *nativeEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    69     69   	v := newVisitor(w, ne)
    70         -	v.acceptBlockSlice(bs)
           70  +	v.walkBlockSlice(bs)
    71     71   	length, err := v.b.Flush()
    72     72   	return length, err
    73     73   }
    74     74   
    75     75   // WriteInlines writes an inline slice to the writer
    76     76   func (ne *nativeEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    77     77   	v := newVisitor(w, ne)
    78         -	v.acceptInlineSlice(is)
           78  +	v.walkInlineSlice(is)
    79     79   	length, err := v.b.Flush()
    80     80   	return length, err
    81     81   }
    82     82   
    83     83   // visitor writes the abstract syntax tree to an io.Writer.
    84     84   type visitor struct {
    85     85   	b     encoder.BufWriter
................................................................................
    86     86   	level int
    87     87   	env   *encoder.Environment
    88     88   }
    89     89   
    90     90   func newVisitor(w io.Writer, enc *nativeEncoder) *visitor {
    91     91   	return &visitor{b: encoder.NewBufWriter(w), env: enc.env}
    92     92   }
           93  +
           94  +func (v *visitor) Visit(node ast.Node) ast.Visitor {
           95  +	switch n := node.(type) {
           96  +	case *ast.ParaNode:
           97  +		v.b.WriteString("[Para ")
           98  +		v.walkInlineSlice(n.Inlines)
           99  +		v.b.WriteByte(']')
          100  +	case *ast.VerbatimNode:
          101  +		v.visitVerbatim(n)
          102  +	case *ast.RegionNode:
          103  +		v.visitRegion(n)
          104  +	case *ast.HeadingNode:
          105  +		v.b.WriteStrings("[Heading ", strconv.Itoa(n.Level), " \"", n.Slug, "\"")
          106  +		v.visitAttributes(n.Attrs)
          107  +		v.b.WriteByte(' ')
          108  +		v.walkInlineSlice(n.Inlines)
          109  +		v.b.WriteByte(']')
          110  +	case *ast.HRuleNode:
          111  +		v.b.WriteString("[Hrule")
          112  +		v.visitAttributes(n.Attrs)
          113  +		v.b.WriteByte(']')
          114  +	case *ast.NestedListNode:
          115  +		v.visitNestedList(n)
          116  +	case *ast.DescriptionListNode:
          117  +		v.visitDescriptionList(n)
          118  +	case *ast.TableNode:
          119  +		v.visitTable(n)
          120  +	case *ast.BLOBNode:
          121  +		v.b.WriteString("[BLOB \"")
          122  +		v.writeEscaped(n.Title)
          123  +		v.b.WriteString("\" \"")
          124  +		v.writeEscaped(n.Syntax)
          125  +		v.b.WriteString("\" \"")
          126  +		v.b.WriteBase64(n.Blob)
          127  +		v.b.WriteString("\"]")
          128  +	case *ast.TextNode:
          129  +		v.b.WriteString("Text \"")
          130  +		v.writeEscaped(n.Text)
          131  +		v.b.WriteByte('"')
          132  +	case *ast.TagNode:
          133  +		v.b.WriteString("Tag \"")
          134  +		v.writeEscaped(n.Tag)
          135  +		v.b.WriteByte('"')
          136  +	case *ast.SpaceNode:
          137  +		v.b.WriteString("Space")
          138  +		if l := len(n.Lexeme); l > 1 {
          139  +			v.b.WriteByte(' ')
          140  +			v.b.WriteString(strconv.Itoa(l))
          141  +		}
          142  +	case *ast.BreakNode:
          143  +		if n.Hard {
          144  +			v.b.WriteString("Break")
          145  +		} else {
          146  +			v.b.WriteString("Space")
          147  +		}
          148  +	case *ast.LinkNode:
          149  +		v.visitLink(n)
          150  +	case *ast.ImageNode:
          151  +		v.visitImage(n)
          152  +	case *ast.CiteNode:
          153  +		v.b.WriteString("Cite")
          154  +		v.visitAttributes(n.Attrs)
          155  +		v.b.WriteString(" \"")
          156  +		v.writeEscaped(n.Key)
          157  +		v.b.WriteByte('"')
          158  +		if len(n.Inlines) > 0 {
          159  +			v.b.WriteString(" [")
          160  +			v.walkInlineSlice(n.Inlines)
          161  +			v.b.WriteByte(']')
          162  +		}
          163  +	case *ast.FootnoteNode:
          164  +		v.b.WriteString("Footnote")
          165  +		v.visitAttributes(n.Attrs)
          166  +		v.b.WriteString(" [")
          167  +		v.walkInlineSlice(n.Inlines)
          168  +		v.b.WriteByte(']')
          169  +	case *ast.MarkNode:
          170  +		v.b.WriteString("Mark")
          171  +		if len(n.Text) > 0 {
          172  +			v.b.WriteString(" \"")
          173  +			v.writeEscaped(n.Text)
          174  +			v.b.WriteByte('"')
          175  +		}
          176  +	case *ast.FormatNode:
          177  +		v.b.Write(mapFormatKind[n.Kind])
          178  +		v.visitAttributes(n.Attrs)
          179  +		v.b.WriteString(" [")
          180  +		v.walkInlineSlice(n.Inlines)
          181  +		v.b.WriteByte(']')
          182  +	case *ast.LiteralNode:
          183  +		kind, ok := mapLiteralKind[n.Kind]
          184  +		if !ok {
          185  +			panic(fmt.Sprintf("Unknown literal kind %v", n.Kind))
          186  +		}
          187  +		v.b.Write(kind)
          188  +		v.visitAttributes(n.Attrs)
          189  +		v.b.WriteString(" \"")
          190  +		v.writeEscaped(n.Text)
          191  +		v.b.WriteByte('"')
          192  +	default:
          193  +		return v
          194  +	}
          195  +	return nil
          196  +}
    93    197   
    94    198   var (
    95    199   	rawBackslash   = []byte{'\\', '\\'}
    96    200   	rawDoubleQuote = []byte{'\\', '"'}
    97    201   	rawNewline     = []byte{'\\', 'n'}
    98    202   )
    99    203   
   100    204   func (v *visitor) acceptMeta(m *meta.Meta, withTitle bool) {
   101    205   	if withTitle {
   102    206   		v.b.WriteString("[Title ")
   103         -		v.acceptInlineSlice(parser.ParseMetadata(m.GetDefault(meta.KeyTitle, "")))
          207  +		v.walkInlineSlice(parser.ParseMetadata(m.GetDefault(meta.KeyTitle, "")))
   104    208   		v.b.WriteByte(']')
   105    209   	}
   106    210   	v.writeMetaString(m, meta.KeyRole, "Role")
   107    211   	v.writeMetaList(m, meta.KeyTags, "Tags")
   108    212   	v.writeMetaString(m, meta.KeySyntax, "Syntax")
   109    213   	pairs := m.PairsRest(true)
   110    214   	if len(pairs) == 0 {
................................................................................
   139    243   			v.b.WriteByte(' ')
   140    244   			v.b.WriteString(val)
   141    245   		}
   142    246   		v.b.WriteByte(']')
   143    247   	}
   144    248   }
   145    249   
   146         -// VisitPara emits native code for a paragraph.
   147         -func (v *visitor) VisitPara(pn *ast.ParaNode) {
   148         -	v.b.WriteString("[Para ")
   149         -	v.acceptInlineSlice(pn.Inlines)
   150         -	v.b.WriteByte(']')
   151         -}
   152         -
   153         -var verbatimCode = map[ast.VerbatimCode][]byte{
          250  +var mapVerbatimKind = map[ast.VerbatimKind][]byte{
   154    251   	ast.VerbatimProg:    []byte("[CodeBlock"),
   155    252   	ast.VerbatimComment: []byte("[CommentBlock"),
   156    253   	ast.VerbatimHTML:    []byte("[HTMLBlock"),
   157    254   }
   158    255   
   159         -// VisitVerbatim emits native code for verbatim lines.
   160         -func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
   161         -	code, ok := verbatimCode[vn.Code]
          256  +func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
          257  +	kind, ok := mapVerbatimKind[vn.Kind]
   162    258   	if !ok {
   163         -		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
          259  +		panic(fmt.Sprintf("Unknown verbatim kind %v", vn.Kind))
   164    260   	}
   165         -	v.b.Write(code)
          261  +	v.b.Write(kind)
   166    262   	v.visitAttributes(vn.Attrs)
   167    263   	v.b.WriteString(" \"")
   168    264   	for i, line := range vn.Lines {
   169    265   		if i > 0 {
   170    266   			v.b.Write(rawNewline)
   171    267   		}
   172    268   		v.writeEscaped(line)
   173    269   	}
   174    270   	v.b.WriteString("\"]")
   175    271   }
   176    272   
   177         -var regionCode = map[ast.RegionCode][]byte{
          273  +var mapRegionKind = map[ast.RegionKind][]byte{
   178    274   	ast.RegionSpan:  []byte("[SpanBlock"),
   179    275   	ast.RegionQuote: []byte("[QuoteBlock"),
   180    276   	ast.RegionVerse: []byte("[VerseBlock"),
   181    277   }
   182    278   
   183         -// VisitRegion writes native code for block regions.
   184         -func (v *visitor) VisitRegion(rn *ast.RegionNode) {
   185         -	code, ok := regionCode[rn.Code]
          279  +func (v *visitor) visitRegion(rn *ast.RegionNode) {
          280  +	kind, ok := mapRegionKind[rn.Kind]
   186    281   	if !ok {
   187         -		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
          282  +		panic(fmt.Sprintf("Unknown region kind %v", rn.Kind))
   188    283   	}
   189         -	v.b.Write(code)
          284  +	v.b.Write(kind)
   190    285   	v.visitAttributes(rn.Attrs)
   191    286   	v.level++
   192    287   	v.writeNewLine()
   193    288   	v.b.WriteByte('[')
   194    289   	v.level++
   195         -	v.acceptBlockSlice(rn.Blocks)
          290  +	v.walkBlockSlice(rn.Blocks)
   196    291   	v.level--
   197    292   	v.b.WriteByte(']')
   198    293   	if len(rn.Inlines) > 0 {
   199    294   		v.b.WriteByte(',')
   200    295   		v.writeNewLine()
   201    296   		v.b.WriteString("[Cite ")
   202         -		v.acceptInlineSlice(rn.Inlines)
          297  +		v.walkInlineSlice(rn.Inlines)
   203    298   		v.b.WriteByte(']')
   204    299   	}
   205    300   	v.level--
   206    301   	v.b.WriteByte(']')
   207    302   }
   208    303   
   209         -// VisitHeading writes the native code for a heading.
   210         -func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
   211         -	v.b.WriteStrings("[Heading ", strconv.Itoa(hn.Level), " \"", hn.Slug, "\"")
   212         -	v.visitAttributes(hn.Attrs)
   213         -	v.b.WriteByte(' ')
   214         -	v.acceptInlineSlice(hn.Inlines)
   215         -	v.b.WriteByte(']')
   216         -}
   217         -
   218         -// VisitHRule writes native code for a horizontal rule: <hr>.
   219         -func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
   220         -	v.b.WriteString("[Hrule")
   221         -	v.visitAttributes(hn.Attrs)
   222         -	v.b.WriteByte(']')
   223         -}
   224         -
   225         -var listCode = map[ast.NestedListCode][]byte{
          304  +var mapNestedListKind = map[ast.NestedListKind][]byte{
   226    305   	ast.NestedListOrdered:   []byte("[OrderedList"),
   227    306   	ast.NestedListUnordered: []byte("[BulletList"),
   228    307   	ast.NestedListQuote:     []byte("[QuoteList"),
   229    308   }
   230    309   
   231         -// VisitNestedList writes native code for lists and blockquotes.
   232         -func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
   233         -	v.b.Write(listCode[ln.Code])
          310  +func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
          311  +	v.b.Write(mapNestedListKind[ln.Kind])
   234    312   	v.level++
   235    313   	for i, item := range ln.Items {
   236    314   		if i > 0 {
   237    315   			v.b.WriteByte(',')
   238    316   		}
   239    317   		v.writeNewLine()
   240    318   		v.level++
   241    319   		v.b.WriteByte('[')
   242         -		v.acceptItemSlice(item)
          320  +		for i, in := range item {
          321  +			if i > 0 {
          322  +				v.b.WriteByte(',')
          323  +				v.writeNewLine()
          324  +			}
          325  +			ast.Walk(v, in)
          326  +		}
   243    327   		v.b.WriteByte(']')
   244    328   		v.level--
   245    329   	}
   246    330   	v.level--
   247    331   	v.b.WriteByte(']')
   248    332   }
   249    333   
   250         -// VisitDescriptionList emits a native description list.
   251         -func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
          334  +func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   252    335   	v.b.WriteString("[DescriptionList")
   253    336   	v.level++
   254    337   	for i, descr := range dn.Descriptions {
   255    338   		if i > 0 {
   256    339   			v.b.WriteByte(',')
   257    340   		}
   258    341   		v.writeNewLine()
   259    342   		v.b.WriteString("[Term [")
   260         -		v.acceptInlineSlice(descr.Term)
          343  +		v.walkInlineSlice(descr.Term)
   261    344   		v.b.WriteByte(']')
   262    345   
   263    346   		if len(descr.Descriptions) > 0 {
   264    347   			v.level++
   265    348   			for _, b := range descr.Descriptions {
   266    349   				v.b.WriteByte(',')
   267    350   				v.writeNewLine()
   268    351   				v.b.WriteString("[Description")
   269    352   				v.level++
   270    353   				v.writeNewLine()
   271         -				v.acceptDescriptionSlice(b)
          354  +				for i, dn := range b {
          355  +					if i > 0 {
          356  +						v.b.WriteByte(',')
          357  +						v.writeNewLine()
          358  +					}
          359  +					ast.Walk(v, dn)
          360  +				}
   272    361   				v.b.WriteByte(']')
   273    362   				v.level--
   274    363   			}
   275    364   			v.level--
   276    365   		}
   277    366   		v.b.WriteByte(']')
   278    367   	}
   279    368   	v.level--
   280    369   	v.b.WriteByte(']')
   281    370   }
   282    371   
   283         -// VisitTable emits a native table.
   284         -func (v *visitor) VisitTable(tn *ast.TableNode) {
          372  +func (v *visitor) visitTable(tn *ast.TableNode) {
   285    373   	v.b.WriteString("[Table")
   286    374   	v.level++
   287    375   	if len(tn.Header) > 0 {
   288    376   		v.writeNewLine()
   289    377   		v.b.WriteString("[Header ")
   290    378   		for i, cell := range tn.Header {
   291    379   			if i > 0 {
................................................................................
   320    408   	ast.AlignRight:   " Right",
   321    409   }
   322    410   
   323    411   func (v *visitor) writeCell(cell *ast.TableCell) {
   324    412   	v.b.WriteStrings("[Cell", alignString[cell.Align])
   325    413   	if len(cell.Inlines) > 0 {
   326    414   		v.b.WriteByte(' ')
   327         -		v.acceptInlineSlice(cell.Inlines)
          415  +		v.walkInlineSlice(cell.Inlines)
   328    416   	}
   329    417   	v.b.WriteByte(']')
   330    418   }
   331    419   
   332         -// VisitBLOB writes the binary object as a value.
   333         -func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
   334         -	v.b.WriteString("[BLOB \"")
   335         -	v.writeEscaped(bn.Title)
   336         -	v.b.WriteString("\" \"")
   337         -	v.writeEscaped(bn.Syntax)
   338         -	v.b.WriteString("\" \"")
   339         -	v.b.WriteBase64(bn.Blob)
   340         -	v.b.WriteString("\"]")
   341         -}
   342         -
   343         -// VisitText writes text content.
   344         -func (v *visitor) VisitText(tn *ast.TextNode) {
   345         -	v.b.WriteString("Text \"")
   346         -	v.writeEscaped(tn.Text)
   347         -	v.b.WriteByte('"')
   348         -}
   349         -
   350         -// VisitTag writes tag content.
   351         -func (v *visitor) VisitTag(tn *ast.TagNode) {
   352         -	v.b.WriteString("Tag \"")
   353         -	v.writeEscaped(tn.Tag)
   354         -	v.b.WriteByte('"')
   355         -}
   356         -
   357         -// VisitSpace emits a white space.
   358         -func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
   359         -	v.b.WriteString("Space")
   360         -	if l := len(sn.Lexeme); l > 1 {
   361         -		v.b.WriteByte(' ')
   362         -		v.b.WriteString(strconv.Itoa(l))
   363         -	}
   364         -}
   365         -
   366         -// VisitBreak writes native code for line breaks.
   367         -func (v *visitor) VisitBreak(bn *ast.BreakNode) {
   368         -	if bn.Hard {
   369         -		v.b.WriteString("Break")
   370         -	} else {
   371         -		v.b.WriteString("Space")
   372         -	}
   373         -}
   374         -
   375    420   var mapRefState = map[ast.RefState]string{
   376    421   	ast.RefStateInvalid:  "INVALID",
   377    422   	ast.RefStateZettel:   "ZETTEL",
   378    423   	ast.RefStateSelf:     "SELF",
   379    424   	ast.RefStateFound:    "ZETTEL",
   380    425   	ast.RefStateBroken:   "BROKEN",
   381    426   	ast.RefStateHosted:   "LOCAL",
   382    427   	ast.RefStateBased:    "BASED",
   383    428   	ast.RefStateExternal: "EXTERNAL",
   384    429   }
   385    430   
   386         -// VisitLink writes native code for links.
   387         -func (v *visitor) VisitLink(ln *ast.LinkNode) {
          431  +func (v *visitor) visitLink(ln *ast.LinkNode) {
   388    432   	ln, n := v.env.AdaptLink(ln)
   389    433   	if n != nil {
   390         -		n.Accept(v)
          434  +		ast.Walk(v, n)
   391    435   		return
   392    436   	}
   393    437   	v.b.WriteString("Link")
   394    438   	v.visitAttributes(ln.Attrs)
   395    439   	v.b.WriteByte(' ')
   396    440   	v.b.WriteString(mapRefState[ln.Ref.State])
   397    441   	v.b.WriteString(" \"")
   398    442   	v.writeEscaped(ln.Ref.String())
   399    443   	v.b.WriteString("\" [")
   400    444   	if !ln.OnlyRef {
   401         -		v.acceptInlineSlice(ln.Inlines)
          445  +		v.walkInlineSlice(ln.Inlines)
   402    446   	}
   403    447   	v.b.WriteByte(']')
   404    448   }
   405    449   
   406         -// VisitImage writes native code for images.
   407         -func (v *visitor) VisitImage(in *ast.ImageNode) {
          450  +func (v *visitor) visitImage(in *ast.ImageNode) {
   408    451   	in, n := v.env.AdaptImage(in)
   409    452   	if n != nil {
   410         -		n.Accept(v)
          453  +		ast.Walk(v, n)
   411    454   		return
   412    455   	}
   413    456   	v.b.WriteString("Image")
   414    457   	v.visitAttributes(in.Attrs)
   415    458   	if in.Ref == nil {
   416    459   		v.b.WriteStrings(" {\"", in.Syntax, "\" \"")
   417    460   		switch in.Syntax {
................................................................................
   423    466   		}
   424    467   		v.b.WriteString("\"}")
   425    468   	} else {
   426    469   		v.b.WriteStrings(" \"", in.Ref.String(), "\"")
   427    470   	}
   428    471   	if len(in.Inlines) > 0 {
   429    472   		v.b.WriteString(" [")
   430         -		v.acceptInlineSlice(in.Inlines)
          473  +		v.walkInlineSlice(in.Inlines)
   431    474   		v.b.WriteByte(']')
   432    475   	}
   433    476   }
   434    477   
   435         -// VisitCite writes code for citations.
   436         -func (v *visitor) VisitCite(cn *ast.CiteNode) {
   437         -	v.b.WriteString("Cite")
   438         -	v.visitAttributes(cn.Attrs)
   439         -	v.b.WriteString(" \"")
   440         -	v.writeEscaped(cn.Key)
   441         -	v.b.WriteByte('"')
   442         -	if len(cn.Inlines) > 0 {
   443         -		v.b.WriteString(" [")
   444         -		v.acceptInlineSlice(cn.Inlines)
   445         -		v.b.WriteByte(']')
   446         -	}
   447         -}
   448         -
   449         -// VisitFootnote write native code for a footnote.
   450         -func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
   451         -	v.b.WriteString("Footnote")
   452         -	v.visitAttributes(fn.Attrs)
   453         -	v.b.WriteString(" [")
   454         -	v.acceptInlineSlice(fn.Inlines)
   455         -	v.b.WriteByte(']')
   456         -}
   457         -
   458         -// VisitMark writes native code to mark a position.
   459         -func (v *visitor) VisitMark(mn *ast.MarkNode) {
   460         -	v.b.WriteString("Mark")
   461         -	if len(mn.Text) > 0 {
   462         -		v.b.WriteString(" \"")
   463         -		v.writeEscaped(mn.Text)
   464         -		v.b.WriteByte('"')
   465         -	}
   466         -}
   467         -
   468         -var formatCode = map[ast.FormatCode][]byte{
          478  +var mapFormatKind = map[ast.FormatKind][]byte{
   469    479   	ast.FormatItalic:    []byte("Italic"),
   470    480   	ast.FormatEmph:      []byte("Emph"),
   471    481   	ast.FormatBold:      []byte("Bold"),
   472    482   	ast.FormatStrong:    []byte("Strong"),
   473    483   	ast.FormatUnder:     []byte("Underline"),
   474    484   	ast.FormatInsert:    []byte("Insert"),
   475    485   	ast.FormatMonospace: []byte("Mono"),
................................................................................
   479    489   	ast.FormatSub:       []byte("Sub"),
   480    490   	ast.FormatQuote:     []byte("Quote"),
   481    491   	ast.FormatQuotation: []byte("Quotation"),
   482    492   	ast.FormatSmall:     []byte("Small"),
   483    493   	ast.FormatSpan:      []byte("Span"),
   484    494   }
   485    495   
   486         -// VisitFormat write native code for formatting text.
   487         -func (v *visitor) VisitFormat(fn *ast.FormatNode) {
   488         -	v.b.Write(formatCode[fn.Code])
   489         -	v.visitAttributes(fn.Attrs)
   490         -	v.b.WriteString(" [")
   491         -	v.acceptInlineSlice(fn.Inlines)
   492         -	v.b.WriteByte(']')
   493         -}
   494         -
   495         -var literalCode = map[ast.LiteralCode][]byte{
          496  +var mapLiteralKind = map[ast.LiteralKind][]byte{
   496    497   	ast.LiteralProg:    []byte("Code"),
   497    498   	ast.LiteralKeyb:    []byte("Input"),
   498    499   	ast.LiteralOutput:  []byte("Output"),
   499    500   	ast.LiteralComment: []byte("Comment"),
   500    501   	ast.LiteralHTML:    []byte("HTML"),
   501    502   }
   502    503   
   503         -// VisitLiteral write native code for code inline text.
   504         -func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
   505         -	code, ok := literalCode[ln.Code]
   506         -	if !ok {
   507         -		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
   508         -	}
   509         -	v.b.Write(code)
   510         -	v.visitAttributes(ln.Attrs)
   511         -	v.b.WriteString(" \"")
   512         -	v.writeEscaped(ln.Text)
   513         -	v.b.WriteByte('"')
   514         -}
   515         -
   516         -func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
          504  +func (v *visitor) walkBlockSlice(bns ast.BlockSlice) {
   517    505   	for i, bn := range bns {
   518    506   		if i > 0 {
   519    507   			v.b.WriteByte(',')
   520    508   			v.writeNewLine()
   521    509   		}
   522         -		bn.Accept(v)
          510  +		ast.Walk(v, bn)
   523    511   	}
   524    512   }
   525         -func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
   526         -	for i, in := range ins {
   527         -		if i > 0 {
   528         -			v.b.WriteByte(',')
   529         -			v.writeNewLine()
   530         -		}
   531         -		in.Accept(v)
   532         -	}
   533         -}
   534         -func (v *visitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
   535         -	for i, dn := range dns {
   536         -		if i > 0 {
   537         -			v.b.WriteByte(',')
   538         -			v.writeNewLine()
   539         -		}
   540         -		dn.Accept(v)
   541         -	}
   542         -}
   543         -func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
          513  +func (v *visitor) walkInlineSlice(ins ast.InlineSlice) {
   544    514   	for i, in := range ins {
   545    515   		if i > 0 {
   546    516   			v.b.WriteByte(',')
   547    517   		}
   548         -		in.Accept(v)
          518  +		ast.Walk(v, in)
   549    519   	}
   550    520   }
   551    521   
   552    522   // visitAttributes write native attributes
   553    523   func (v *visitor) visitAttributes(a *ast.Attributes) {
   554    524   	if a == nil || len(a.Attrs) == 0 {
   555    525   		return

Changes to encoder/textenc/textenc.go.

    81     81   	length, err := v.b.Flush()
    82     82   	return length, err
    83     83   }
    84     84   
    85     85   // WriteInlines writes an inline slice to the writer
    86     86   func (te *textEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    87     87   	v := newVisitor(w)
    88         -	v.acceptInlineSlice(is)
           88  +	ast.WalkInlineSlice(v, is)
    89     89   	length, err := v.b.Flush()
    90     90   	return length, err
    91     91   }
    92     92   
    93     93   // visitor writes the abstract syntax tree to an io.Writer.
    94     94   type visitor struct {
    95     95   	b encoder.BufWriter
    96     96   }
    97     97   
    98     98   func newVisitor(w io.Writer) *visitor {
    99     99   	return &visitor{b: encoder.NewBufWriter(w)}
   100    100   }
   101    101   
   102         -// VisitPara emits text code for a paragraph
   103         -func (v *visitor) VisitPara(pn *ast.ParaNode) {
   104         -	v.acceptInlineSlice(pn.Inlines)
   105         -}
   106         -
   107         -// VisitVerbatim emits text for verbatim lines.
   108         -func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
   109         -	if vn.Code == ast.VerbatimComment {
   110         -		return
   111         -	}
   112         -	for i, line := range vn.Lines {
   113         -		if i > 0 {
          102  +func (v *visitor) Visit(node ast.Node) ast.Visitor {
          103  +	switch n := node.(type) {
          104  +	case *ast.VerbatimNode:
          105  +		if n.Kind == ast.VerbatimComment {
          106  +			return nil
          107  +		}
          108  +		for i, line := range n.Lines {
          109  +			if i > 0 {
          110  +				v.b.WriteByte('\n')
          111  +			}
          112  +			v.b.WriteString(line)
          113  +		}
          114  +		return nil
          115  +	case *ast.RegionNode:
          116  +		v.acceptBlockSlice(n.Blocks)
          117  +		if len(n.Inlines) > 0 {
          118  +			v.b.WriteByte('\n')
          119  +			ast.WalkInlineSlice(v, n.Inlines)
          120  +		}
          121  +		return nil
          122  +	case *ast.NestedListNode:
          123  +		for i, item := range n.Items {
          124  +			if i > 0 {
          125  +				v.b.WriteByte('\n')
          126  +			}
          127  +			for j, it := range item {
          128  +				if j > 0 {
          129  +					v.b.WriteByte('\n')
          130  +				}
          131  +				ast.Walk(v, it)
          132  +			}
          133  +		}
          134  +		return nil
          135  +	case *ast.DescriptionListNode:
          136  +		for i, descr := range n.Descriptions {
          137  +			if i > 0 {
          138  +				v.b.WriteByte('\n')
          139  +			}
          140  +			ast.WalkInlineSlice(v, descr.Term)
          141  +			for _, b := range descr.Descriptions {
          142  +				v.b.WriteByte('\n')
          143  +				for k, d := range b {
          144  +					if k > 0 {
          145  +						v.b.WriteByte('\n')
          146  +					}
          147  +					ast.Walk(v, d)
          148  +				}
          149  +			}
          150  +		}
          151  +		return nil
          152  +	case *ast.TableNode:
          153  +		if len(n.Header) > 0 {
          154  +			v.writeRow(n.Header)
   114    155   			v.b.WriteByte('\n')
   115    156   		}
   116         -		v.b.WriteString(line)
   117         -	}
   118         -}
   119         -
   120         -// VisitRegion writes text code for block regions.
   121         -func (v *visitor) VisitRegion(rn *ast.RegionNode) {
   122         -	v.acceptBlockSlice(rn.Blocks)
   123         -	if len(rn.Inlines) > 0 {
   124         -		v.b.WriteByte('\n')
   125         -		v.acceptInlineSlice(rn.Inlines)
   126         -	}
   127         -}
   128         -
   129         -// VisitHeading writes the text code for a heading.
   130         -func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
   131         -	v.acceptInlineSlice(hn.Inlines)
   132         -}
   133         -
   134         -// VisitHRule writes nothing for a horizontal rule.
   135         -func (v *visitor) VisitHRule(hn *ast.HRuleNode) {}
   136         -
   137         -// VisitNestedList writes text code for lists and blockquotes.
   138         -func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
   139         -	for i, item := range ln.Items {
   140         -		if i > 0 {
          157  +		for i, row := range n.Rows {
          158  +			if i > 0 {
          159  +				v.b.WriteByte('\n')
          160  +			}
          161  +			v.writeRow(row)
          162  +		}
          163  +		return nil
          164  +	case *ast.TextNode:
          165  +		v.b.WriteString(n.Text)
          166  +		return nil
          167  +	case *ast.TagNode:
          168  +		v.b.WriteStrings("#", n.Tag)
          169  +		return nil
          170  +	case *ast.SpaceNode:
          171  +		v.b.WriteByte(' ')
          172  +		return nil
          173  +	case *ast.BreakNode:
          174  +		if n.Hard {
   141    175   			v.b.WriteByte('\n')
          176  +		} else {
          177  +			v.b.WriteByte(' ')
   142    178   		}
   143         -		v.acceptItemSlice(item)
   144         -	}
   145         -}
   146         -
   147         -// VisitDescriptionList emits a text for a description list.
   148         -func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
   149         -	for i, descr := range dn.Descriptions {
   150         -		if i > 0 {
   151         -			v.b.WriteByte('\n')
          179  +		return nil
          180  +	case *ast.LinkNode:
          181  +		if !n.OnlyRef {
          182  +			ast.WalkInlineSlice(v, n.Inlines)
   152    183   		}
   153         -		v.acceptInlineSlice(descr.Term)
   154         -
   155         -		for _, b := range descr.Descriptions {
   156         -			v.b.WriteByte('\n')
   157         -			v.acceptDescriptionSlice(b)
          184  +		return nil
          185  +	case *ast.FootnoteNode:
          186  +		v.b.WriteByte(' ')
          187  +		return v // No 'return nil' to write text
          188  +	case *ast.LiteralNode:
          189  +		if n.Kind != ast.LiteralComment {
          190  +			v.b.WriteString(n.Text)
   158    191   		}
   159    192   	}
   160         -}
   161         -
   162         -// VisitTable emits a text table.
   163         -func (v *visitor) VisitTable(tn *ast.TableNode) {
   164         -	if len(tn.Header) > 0 {
   165         -		for i, cell := range tn.Header {
   166         -			if i > 0 {
   167         -				v.b.WriteByte(' ')
   168         -			}
   169         -			v.acceptInlineSlice(cell.Inlines)
   170         -		}
   171         -		v.b.WriteByte('\n')
   172         -	}
   173         -	for i, row := range tn.Rows {
   174         -		if i > 0 {
   175         -			v.b.WriteByte('\n')
   176         -		}
   177         -		for j, cell := range row {
   178         -			if j > 0 {
   179         -				v.b.WriteByte(' ')
   180         -			}
   181         -			v.acceptInlineSlice(cell.Inlines)
   182         -		}
   183         -	}
          193  +	return v
   184    194   }
   185    195   
   186         -// VisitBLOB writes nothing, because it contains no text.
   187         -func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {}
   188         -
   189         -// VisitText writes text content.
   190         -func (v *visitor) VisitText(tn *ast.TextNode) {
   191         -	v.b.WriteString(tn.Text)
   192         -}
   193         -
   194         -// VisitTag writes tag content.
   195         -func (v *visitor) VisitTag(tn *ast.TagNode) {
   196         -	v.b.WriteStrings("#", tn.Tag)
   197         -}
   198         -
   199         -// VisitSpace emits a white space.
   200         -func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
   201         -	v.b.WriteByte(' ')
   202         -}
   203         -
   204         -// VisitBreak writes text code for line breaks.
   205         -func (v *visitor) VisitBreak(bn *ast.BreakNode) {
   206         -	if bn.Hard {
   207         -		v.b.WriteByte('\n')
   208         -	} else {
   209         -		v.b.WriteByte(' ')
          196  +func (v *visitor) writeRow(row ast.TableRow) {
          197  +	for i, cell := range row {
          198  +		if i > 0 {
          199  +			v.b.WriteByte(' ')
          200  +		}
          201  +		ast.WalkInlineSlice(v, cell.Inlines)
   210    202   	}
   211    203   }
   212    204   
   213         -// VisitLink writes text code for links.
   214         -func (v *visitor) VisitLink(ln *ast.LinkNode) {
   215         -	if !ln.OnlyRef {
   216         -		v.acceptInlineSlice(ln.Inlines)
   217         -	}
   218         -}
   219         -
   220         -// VisitImage writes text code for images.
   221         -func (v *visitor) VisitImage(in *ast.ImageNode) {
   222         -	v.acceptInlineSlice(in.Inlines)
   223         -}
   224         -
   225         -// VisitCite writes code for citations.
   226         -func (v *visitor) VisitCite(cn *ast.CiteNode) {
   227         -	v.acceptInlineSlice(cn.Inlines)
   228         -}
   229         -
   230         -// VisitFootnote write text code for a footnote.
   231         -func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
   232         -	v.b.WriteByte(' ')
   233         -	v.acceptInlineSlice(fn.Inlines)
   234         -}
   235         -
   236         -// VisitMark writes nothing for a mark.
   237         -func (v *visitor) VisitMark(mn *ast.MarkNode) {}
   238         -
   239         -// VisitFormat write text code for formatting text.
   240         -func (v *visitor) VisitFormat(fn *ast.FormatNode) {
   241         -	v.acceptInlineSlice(fn.Inlines)
   242         -}
   243         -
   244         -// VisitLiteral write text code for literal inline text.
   245         -func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
   246         -	if ln.Code != ast.LiteralComment {
   247         -		v.b.WriteString(ln.Text)
   248         -	}
   249         -}
   250         -
   251         -// VisitAttributes never writes any attribute data.
   252         -func (v *visitor) VisitAttributes(a *ast.Attributes) {}
   253         -
   254    205   func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
   255    206   	for i, bn := range bns {
   256    207   		if i > 0 {
   257    208   			v.b.WriteByte('\n')
   258    209   		}
   259         -		bn.Accept(v)
   260         -	}
   261         -}
   262         -func (v *visitor) acceptItemSlice(ins ast.ItemSlice) {
   263         -	for i, in := range ins {
   264         -		if i > 0 {
   265         -			v.b.WriteByte('\n')
   266         -		}
   267         -		in.Accept(v)
   268         -	}
   269         -}
   270         -func (v *visitor) acceptDescriptionSlice(dns ast.DescriptionSlice) {
   271         -	for i, dn := range dns {
   272         -		if i > 0 {
   273         -			v.b.WriteByte('\n')
   274         -		}
   275         -		dn.Accept(v)
   276         -	}
   277         -}
   278         -func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
   279         -	for _, in := range ins {
   280         -		in.Accept(v)
          210  +		ast.Walk(v, bn)
   281    211   	}
   282    212   }

Changes to encoder/zmkenc/zmkenc.go.

    34     34   	w io.Writer, zn *ast.ZettelNode, inhMeta bool) (int, error) {
    35     35   	v := newVisitor(w, ze)
    36     36   	if inhMeta {
    37     37   		zn.InhMeta.WriteAsHeader(&v.b, true)
    38     38   	} else {
    39     39   		zn.Meta.WriteAsHeader(&v.b, true)
    40     40   	}
    41         -	v.acceptBlockSlice(zn.Ast)
           41  +	ast.WalkBlockSlice(v, zn.Ast)
    42     42   	length, err := v.b.Flush()
    43     43   	return length, err
    44     44   }
    45     45   
    46     46   // WriteMeta encodes meta data as zmk.
    47     47   func (ze *zmkEncoder) WriteMeta(w io.Writer, m *meta.Meta) (int, error) {
    48     48   	return m.Write(w, true)
................................................................................
    51     51   func (ze *zmkEncoder) WriteContent(w io.Writer, zn *ast.ZettelNode) (int, error) {
    52     52   	return ze.WriteBlocks(w, zn.Ast)
    53     53   }
    54     54   
    55     55   // WriteBlocks writes the content of a block slice to the writer.
    56     56   func (ze *zmkEncoder) WriteBlocks(w io.Writer, bs ast.BlockSlice) (int, error) {
    57     57   	v := newVisitor(w, ze)
    58         -	v.acceptBlockSlice(bs)
           58  +	ast.WalkBlockSlice(v, bs)
    59     59   	length, err := v.b.Flush()
    60     60   	return length, err
    61     61   }
    62     62   
    63     63   // WriteInlines writes an inline slice to the writer
    64     64   func (ze *zmkEncoder) WriteInlines(w io.Writer, is ast.InlineSlice) (int, error) {
    65     65   	v := newVisitor(w, ze)
    66         -	v.acceptInlineSlice(is)
           66  +	ast.WalkInlineSlice(v, is)
    67     67   	length, err := v.b.Flush()
    68     68   	return length, err
    69     69   }
    70     70   
    71     71   // visitor writes the abstract syntax tree to an io.Writer.
    72     72   type visitor struct {
    73     73   	b      encoder.BufWriter
................................................................................
    78     78   func newVisitor(w io.Writer, enc *zmkEncoder) *visitor {
    79     79   	return &visitor{
    80     80   		b:   encoder.NewBufWriter(w),
    81     81   		enc: enc,
    82     82   	}
    83     83   }
    84     84   
    85         -// VisitPara emits HTML code for a paragraph: <p>...</p>
    86         -func (v *visitor) VisitPara(pn *ast.ParaNode) {
    87         -	v.acceptInlineSlice(pn.Inlines)
    88         -	v.b.WriteByte('\n')
    89         -	if len(v.prefix) == 0 {
           85  +func (v *visitor) Visit(node ast.Node) ast.Visitor {
           86  +	switch n := node.(type) {
           87  +	case *ast.ParaNode:
           88  +		ast.WalkInlineSlice(v, n.Inlines)
           89  +		v.b.WriteByte('\n')
           90  +		if len(v.prefix) == 0 {
           91  +			v.b.WriteByte('\n')
           92  +		}
           93  +	case *ast.VerbatimNode:
           94  +		v.visitVerbatim(n)
           95  +	case *ast.RegionNode:
           96  +		v.visitRegion(n)
           97  +	case *ast.HeadingNode:
           98  +		v.visitHeading(n)
           99  +	case *ast.HRuleNode:
          100  +		v.b.WriteString("---")
          101  +		v.visitAttributes(n.Attrs)
    90    102   		v.b.WriteByte('\n')
          103  +	case *ast.NestedListNode:
          104  +		v.visitNestedList(n)
          105  +	case *ast.DescriptionListNode:
          106  +		v.visitDescriptionList(n)
          107  +	case *ast.TableNode:
          108  +		v.visitTable(n)
          109  +	case *ast.BLOBNode:
          110  +		v.b.WriteStrings(
          111  +			"%% Unable to display BLOB with title '", n.Title,
          112  +			"' and syntax '", n.Syntax, "'\n")
          113  +	case *ast.TextNode:
          114  +		v.visitText(n)
          115  +	case *ast.TagNode:
          116  +		v.b.WriteStrings("#", n.Tag)
          117  +	case *ast.SpaceNode:
          118  +		v.b.WriteString(n.Lexeme)
          119  +	case *ast.BreakNode:
          120  +		v.visitBreak(n)
          121  +	case *ast.LinkNode:
          122  +		v.visitLink(n)
          123  +	case *ast.ImageNode:
          124  +		v.visitImage(n)
          125  +	case *ast.CiteNode:
          126  +		v.visitCite(n)
          127  +	case *ast.FootnoteNode:
          128  +		v.b.WriteString("[^")
          129  +		ast.WalkInlineSlice(v, n.Inlines)
          130  +		v.b.WriteByte(']')
          131  +		v.visitAttributes(n.Attrs)
          132  +	case *ast.MarkNode:
          133  +		v.b.WriteStrings("[!", n.Text, "]")
          134  +	case *ast.FormatNode:
          135  +		v.visitFormat(n)
          136  +	case *ast.LiteralNode:
          137  +		v.visitLiteral(n)
          138  +	default:
          139  +		return v
    91    140   	}
          141  +	return nil
    92    142   }
    93    143   
    94         -// VisitVerbatim emits HTML code for verbatim lines.
    95         -func (v *visitor) VisitVerbatim(vn *ast.VerbatimNode) {
          144  +func (v *visitor) visitVerbatim(vn *ast.VerbatimNode) {
    96    145   	// TODO: scan cn.Lines to find embedded "`"s at beginning
    97    146   	v.b.WriteString("```")
    98    147   	v.visitAttributes(vn.Attrs)
    99    148   	v.b.WriteByte('\n')
   100    149   	for _, line := range vn.Lines {
   101    150   		v.b.WriteStrings(line, "\n")
   102    151   	}
   103    152   	v.b.WriteString("```\n")
   104    153   }
   105    154   
   106         -var regionCode = map[ast.RegionCode]string{
          155  +var mapRegionKind = map[ast.RegionKind]string{
   107    156   	ast.RegionSpan:  ":::",
   108    157   	ast.RegionQuote: "<<<",
   109    158   	ast.RegionVerse: "\"\"\"",
   110    159   }
   111    160   
   112         -// VisitRegion writes HTML code for block regions.
   113         -func (v *visitor) VisitRegion(rn *ast.RegionNode) {
          161  +func (v *visitor) visitRegion(rn *ast.RegionNode) {
   114    162   	// Scan rn.Blocks for embedded regions to adjust length of regionCode
   115         -	code, ok := regionCode[rn.Code]
          163  +	kind, ok := mapRegionKind[rn.Kind]
   116    164   	if !ok {
   117         -		panic(fmt.Sprintf("Unknown region code %d", rn.Code))
          165  +		panic(fmt.Sprintf("Unknown region kind %d", rn.Kind))
   118    166   	}
   119         -	v.b.WriteString(code)
          167  +	v.b.WriteString(kind)
   120    168   	v.visitAttributes(rn.Attrs)
   121    169   	v.b.WriteByte('\n')
   122         -	v.acceptBlockSlice(rn.Blocks)
   123         -	v.b.WriteString(code)
          170  +	ast.WalkBlockSlice(v, rn.Blocks)
          171  +	v.b.WriteString(kind)
   124    172   	if len(rn.Inlines) > 0 {
   125    173   		v.b.WriteByte(' ')
   126         -		v.acceptInlineSlice(rn.Inlines)
          174  +		ast.WalkInlineSlice(v, rn.Inlines)
   127    175   	}
   128    176   	v.b.WriteByte('\n')
   129    177   }
   130    178   
   131         -// VisitHeading writes the HTML code for a heading.
   132         -func (v *visitor) VisitHeading(hn *ast.HeadingNode) {
          179  +func (v *visitor) visitHeading(hn *ast.HeadingNode) {
   133    180   	for i := 0; i <= hn.Level; i++ {
   134    181   		v.b.WriteByte('=')
   135    182   	}
   136    183   	v.b.WriteByte(' ')
   137         -	v.acceptInlineSlice(hn.Inlines)
          184  +	ast.WalkInlineSlice(v, hn.Inlines)
   138    185   	v.visitAttributes(hn.Attrs)
   139    186   	v.b.WriteByte('\n')
   140    187   }
   141    188   
   142         -// VisitHRule writes HTML code for a horizontal rule: <hr>.
   143         -func (v *visitor) VisitHRule(hn *ast.HRuleNode) {
   144         -	v.b.WriteString("---")
   145         -	v.visitAttributes(hn.Attrs)
   146         -	v.b.WriteByte('\n')
   147         -}
   148         -
   149         -var listCode = map[ast.NestedListCode]byte{
          189  +var mapNestedListKind = map[ast.NestedListKind]byte{
   150    190   	ast.NestedListOrdered:   '#',
   151    191   	ast.NestedListUnordered: '*',
   152    192   	ast.NestedListQuote:     '>',
   153    193   }
   154    194   
   155         -// VisitNestedList writes HTML code for lists and blockquotes.
   156         -func (v *visitor) VisitNestedList(ln *ast.NestedListNode) {
   157         -	v.prefix = append(v.prefix, listCode[ln.Code])
          195  +func (v *visitor) visitNestedList(ln *ast.NestedListNode) {
          196  +	v.prefix = append(v.prefix, mapNestedListKind[ln.Kind])
   158    197   	for _, item := range ln.Items {
   159    198   		v.b.Write(v.prefix)
   160    199   		v.b.WriteByte(' ')
   161    200   		for i, in := range item {
   162    201   			if i > 0 {
   163    202   				if _, ok := in.(*ast.ParaNode); ok {
   164    203   					v.b.WriteByte('\n')
   165    204   					for j := 0; j <= len(v.prefix); j++ {
   166    205   						v.b.WriteByte(' ')
   167    206   					}
   168    207   				}
   169    208   			}
   170         -			in.Accept(v)
          209  +			ast.Walk(v, in)
   171    210   		}
   172    211   	}
   173    212   	v.prefix = v.prefix[:len(v.prefix)-1]
   174    213   	v.b.WriteByte('\n')
   175    214   }
   176    215   
   177         -// VisitDescriptionList emits a HTML description list.
   178         -func (v *visitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
          216  +func (v *visitor) visitDescriptionList(dn *ast.DescriptionListNode) {
   179    217   	for _, descr := range dn.Descriptions {
   180    218   		v.b.WriteString("; ")
   181         -		v.acceptInlineSlice(descr.Term)
          219  +		ast.WalkInlineSlice(v, descr.Term)
   182    220   		v.b.WriteByte('\n')
   183    221   
   184    222   		for _, b := range descr.Descriptions {
   185    223   			v.b.WriteString(": ")
   186         -			for _, dn := range b {
   187         -				dn.Accept(v)
   188         -			}
          224  +			ast.WalkDescriptionSlice(v, b)
   189    225   			v.b.WriteByte('\n')
   190    226   		}
   191    227   	}
   192    228   }
   193    229   
   194    230   var alignCode = map[ast.Alignment]string{
   195    231   	ast.AlignDefault: "",
   196    232   	ast.AlignLeft:    "<",
   197    233   	ast.AlignCenter:  ":",
   198    234   	ast.AlignRight:   ">",
   199    235   }
   200    236   
   201         -// VisitTable emits a HTML table.
   202         -func (v *visitor) VisitTable(tn *ast.TableNode) {
          237  +func (v *visitor) visitTable(tn *ast.TableNode) {
   203    238   	if len(tn.Header) > 0 {
   204    239   		for pos, cell := range tn.Header {
   205    240   			v.b.WriteString("|=")
   206    241   			colAlign := tn.Align[pos]
   207    242   			if cell.Align != colAlign {
   208    243   				v.b.WriteString(alignCode[cell.Align])
   209    244   			}
   210         -			v.acceptInlineSlice(cell.Inlines)
          245  +			ast.WalkInlineSlice(v, cell.Inlines)
   211    246   			if colAlign != ast.AlignDefault {
   212    247   				v.b.WriteString(alignCode[colAlign])
   213    248   			}
   214    249   		}
   215    250   		v.b.WriteByte('\n')
   216    251   	}
   217    252   	for _, row := range tn.Rows {
   218    253   		for pos, cell := range row {
   219    254   			v.b.WriteByte('|')
   220    255   			if cell.Align != tn.Align[pos] {
   221    256   				v.b.WriteString(alignCode[cell.Align])
   222    257   			}
   223         -			v.acceptInlineSlice(cell.Inlines)
          258  +			ast.WalkInlineSlice(v, cell.Inlines)
   224    259   		}
   225    260   		v.b.WriteByte('\n')
   226    261   	}
   227    262   	v.b.WriteByte('\n')
   228    263   }
   229    264   
   230         -// VisitBLOB writes the binary object as a value.
   231         -func (v *visitor) VisitBLOB(bn *ast.BLOBNode) {
   232         -	v.b.WriteStrings(
   233         -		"%% Unable to display BLOB with title '",
   234         -		bn.Title,
   235         -		"' and syntax '",
   236         -		bn.Syntax,
   237         -		"'\n")
   238         -}
   239         -
   240    265   var escapeSeqs = map[string]bool{
   241    266   	"\\":   true,
   242    267   	"//":   true,
   243    268   	"**":   true,
   244    269   	"__":   true,
   245    270   	"~~":   true,
   246    271   	"^^":   true,
................................................................................
   251    276   	"::":   true,
   252    277   	"''":   true,
   253    278   	"``":   true,
   254    279   	"++":   true,
   255    280   	"==":   true,
   256    281   }
   257    282   
   258         -// VisitText writes text content.
   259         -func (v *visitor) VisitText(tn *ast.TextNode) {
          283  +func (v *visitor) visitText(tn *ast.TextNode) {
   260    284   	last := 0
   261    285   	for i := 0; i < len(tn.Text); i++ {
   262    286   		if b := tn.Text[i]; b == '\\' {
   263    287   			v.b.WriteString(tn.Text[last:i])
   264    288   			v.b.WriteBytes('\\', b)
   265    289   			last = i + 1
   266    290   			continue
................................................................................
   277    301   				continue
   278    302   			}
   279    303   		}
   280    304   	}
   281    305   	v.b.WriteString(tn.Text[last:])
   282    306   }
   283    307   
   284         -// VisitTag writes tag content.
   285         -func (v *visitor) VisitTag(tn *ast.TagNode) {
   286         -	v.b.WriteStrings("#", tn.Tag)
   287         -}
   288         -
   289         -// VisitSpace emits a white space.
   290         -func (v *visitor) VisitSpace(sn *ast.SpaceNode) {
   291         -	v.b.WriteString(sn.Lexeme)
   292         -}
   293         -
   294         -// VisitBreak writes HTML code for line breaks.
   295         -func (v *visitor) VisitBreak(bn *ast.BreakNode) {
          308  +func (v *visitor) visitBreak(bn *ast.BreakNode) {
   296    309   	if bn.Hard {
   297    310   		v.b.WriteString("\\\n")
   298    311   	} else {
   299    312   		v.b.WriteByte('\n')
   300    313   	}
   301    314   	if prefixLen := len(v.prefix); prefixLen > 0 {
   302    315   		for i := 0; i <= prefixLen; i++ {
   303    316   			v.b.WriteByte(' ')
   304    317   		}
   305    318   	}
   306    319   }
   307    320   
   308         -// VisitLink writes HTML code for links.
   309         -func (v *visitor) VisitLink(ln *ast.LinkNode) {
          321  +func (v *visitor) visitLink(ln *ast.LinkNode) {
   310    322   	v.b.WriteString("[[")
   311    323   	if !ln.OnlyRef {
   312         -		v.acceptInlineSlice(ln.Inlines)
          324  +		ast.WalkInlineSlice(v, ln.Inlines)
   313    325   		v.b.WriteByte('|')
   314    326   	}
   315    327   	v.b.WriteStrings(ln.Ref.String(), "]]")
   316    328   }
   317    329   
   318         -// VisitImage writes HTML code for images.
   319         -func (v *visitor) VisitImage(in *ast.ImageNode) {
          330  +func (v *visitor) visitImage(in *ast.ImageNode) {
   320    331   	if in.Ref != nil {
   321    332   		v.b.WriteString("{{")
   322    333   		if len(in.Inlines) > 0 {
   323         -			v.acceptInlineSlice(in.Inlines)
          334  +			ast.WalkInlineSlice(v, in.Inlines)
   324    335   			v.b.WriteByte('|')
   325    336   		}
   326    337   		v.b.WriteStrings(in.Ref.String(), "}}")
   327    338   	}
   328    339   }
   329    340   
   330         -// VisitCite writes code for citations.
   331         -func (v *visitor) VisitCite(cn *ast.CiteNode) {
          341  +func (v *visitor) visitCite(cn *ast.CiteNode) {
   332    342   	v.b.WriteStrings("[@", cn.Key)
   333    343   	if len(cn.Inlines) > 0 {
   334    344   		v.b.WriteString(", ")
   335         -		v.acceptInlineSlice(cn.Inlines)
          345  +		ast.WalkInlineSlice(v, cn.Inlines)
   336    346   	}
   337    347   	v.b.WriteByte(']')
   338    348   	v.visitAttributes(cn.Attrs)
   339    349   }
   340    350   
   341         -// VisitFootnote write HTML code for a footnote.
   342         -func (v *visitor) VisitFootnote(fn *ast.FootnoteNode) {
   343         -	v.b.WriteString("[^")
   344         -	v.acceptInlineSlice(fn.Inlines)
   345         -	v.b.WriteByte(']')
   346         -	v.visitAttributes(fn.Attrs)
   347         -}
   348         -
   349         -// VisitMark writes HTML code to mark a position.
   350         -func (v *visitor) VisitMark(mn *ast.MarkNode) {
   351         -	v.b.WriteStrings("[!", mn.Text, "]")
   352         -}
   353         -
   354         -var formatCode = map[ast.FormatCode][]byte{
          351  +var mapFormatKind = map[ast.FormatKind][]byte{
   355    352   	ast.FormatItalic:    []byte("//"),
   356    353   	ast.FormatEmph:      []byte("//"),
   357    354   	ast.FormatBold:      []byte("**"),
   358    355   	ast.FormatStrong:    []byte("**"),
   359    356   	ast.FormatUnder:     []byte("__"),
   360    357   	ast.FormatInsert:    []byte("__"),
   361    358   	ast.FormatStrike:    []byte("~~"),
................................................................................
   365    362   	ast.FormatQuotation: []byte("<<"),
   366    363   	ast.FormatQuote:     []byte("\"\""),
   367    364   	ast.FormatSmall:     []byte(";;"),
   368    365   	ast.FormatSpan:      []byte("::"),
   369    366   	ast.FormatMonospace: []byte("''"),
   370    367   }
   371    368   
   372         -// VisitFormat write HTML code for formatting text.
   373         -func (v *visitor) VisitFormat(fn *ast.FormatNode) {
   374         -	code, ok := formatCode[fn.Code]
          369  +func (v *visitor) visitFormat(fn *ast.FormatNode) {
          370  +	kind, ok := mapFormatKind[fn.Kind]
   375    371   	if !ok {
   376         -		panic(fmt.Sprintf("Unknown format code %d", fn.Code))
          372  +		panic(fmt.Sprintf("Unknown format kind %d", fn.Kind))
   377    373   	}
   378    374   	attrs := fn.Attrs
   379         -	switch fn.Code {
          375  +	switch fn.Kind {
   380    376   	case ast.FormatEmph, ast.FormatStrong, ast.FormatInsert, ast.FormatDelete:
   381    377   		attrs = attrs.Clone()
   382    378   		attrs.Set("-", "")
   383    379   	}
   384    380   
   385         -	v.b.Write(code)
   386         -	v.acceptInlineSlice(fn.Inlines)
   387         -	v.b.Write(code)
          381  +	v.b.Write(kind)
          382  +	ast.WalkInlineSlice(v, fn.Inlines)
          383  +	v.b.Write(kind)
   388    384   	v.visitAttributes(attrs)
   389    385   }
   390    386   
   391         -// VisitLiteral write Zettelmarkup for inline literal text.
   392         -func (v *visitor) VisitLiteral(ln *ast.LiteralNode) {
   393         -	switch ln.Code {
          387  +func (v *visitor) visitLiteral(ln *ast.LiteralNode) {
          388  +	switch ln.Kind {
   394    389   	case ast.LiteralProg:
   395    390   		v.writeLiteral('`', ln.Attrs, ln.Text)
   396    391   	case ast.LiteralKeyb:
   397    392   		v.writeLiteral('+', ln.Attrs, ln.Text)
   398    393   	case ast.LiteralOutput:
   399    394   		v.writeLiteral('=', ln.Attrs, ln.Text)
   400    395   	case ast.LiteralComment:
   401    396   		v.b.WriteStrings("%% ", ln.Text)
   402    397   	case ast.LiteralHTML:
   403    398   		v.b.WriteString("``")
   404    399   		v.writeEscaped(ln.Text, '`')
   405    400   		v.b.WriteString("``{=html,.warning}")
   406    401   	default:
   407         -		panic(fmt.Sprintf("Unknown literal code %v", ln.Code))
          402  +		panic(fmt.Sprintf("Unknown literal kind %v", ln.Kind))
   408    403   	}
   409    404   }
   410    405   
   411    406   func (v *visitor) writeLiteral(code byte, attrs *ast.Attributes, text string) {
   412    407   	v.b.WriteBytes(code, code)
   413    408   	v.writeEscaped(text, code)
   414    409   	v.b.WriteBytes(code, code)
   415    410   	v.visitAttributes(attrs)
   416    411   }
   417    412   
   418         -func (v *visitor) acceptBlockSlice(bns ast.BlockSlice) {
   419         -	for _, bn := range bns {
   420         -		bn.Accept(v)
   421         -	}
   422         -}
   423         -func (v *visitor) acceptInlineSlice(ins ast.InlineSlice) {
   424         -	for _, in := range ins {
   425         -		in.Accept(v)
   426         -	}
   427         -}
   428         -
   429    413   // visitAttributes write HTML attributes
   430    414   func (v *visitor) visitAttributes(a *ast.Attributes) {
   431    415   	if a == nil || len(a.Attrs) == 0 {
   432    416   		return
   433    417   	}
   434    418   	keys := make([]string, 0, len(a.Attrs))
   435    419   	for k := range a.Attrs {

Changes to kernel/impl/cfg.go.

    10     10   
    11     11   // Package impl provides the kernel implementation.
    12     12   package impl
    13     13   
    14     14   import (
    15     15   	"context"
    16     16   	"fmt"
    17         -	"strconv"
    18     17   	"strings"
    19     18   	"sync"
    20     19   
    21     20   	"zettelstore.de/z/domain/id"
    22     21   	"zettelstore.de/z/domain/meta"
    23     22   	"zettelstore.de/z/kernel"
    24     23   	"zettelstore.de/z/place"
................................................................................
    44     43   				if vis == meta.VisibilityUnknown {
    45     44   					return nil
    46     45   				}
    47     46   				return vis
    48     47   			},
    49     48   			true,
    50     49   		},
    51         -		meta.KeyExpertMode: {"Expert mode", parseBool, true},
    52         -		meta.KeyFooterHTML: {"Footer HTML", parseString, true},
    53         -		meta.KeyHomeZettel: {"Home zettel", parseZid, true},
    54         -		meta.KeyListPageSize: {
    55         -			"List page size",
    56         -			func(val string) interface{} {
    57         -				iVal, err := strconv.Atoi(val)
    58         -				if err != nil {
    59         -					return nil
    60         -				}
    61         -				return iVal
    62         -			},
    63         -			true,
    64         -		},
           50  +		meta.KeyExpertMode:     {"Expert mode", parseBool, true},
           51  +		meta.KeyFooterHTML:     {"Footer HTML", parseString, true},
           52  +		meta.KeyHomeZettel:     {"Home zettel", parseZid, true},
    65     53   		meta.KeyMarkerExternal: {"Marker external URL", parseString, true},
    66     54   		meta.KeySiteName:       {"Site name", parseString, true},
    67     55   		meta.KeyYAMLHeader:     {"YAML header", parseBool, true},
    68     56   		meta.KeyZettelFileSyntax: {
    69     57   			"Zettel file syntax",
    70     58   			func(val string) interface{} { return strings.Fields(val) },
    71     59   			true,
................................................................................
    77     65   		meta.KeyDefaultRole:       meta.ValueRoleZettel,
    78     66   		meta.KeyDefaultSyntax:     meta.ValueSyntaxZmk,
    79     67   		meta.KeyDefaultTitle:      "Untitled",
    80     68   		meta.KeyDefaultVisibility: meta.VisibilityLogin,
    81     69   		meta.KeyExpertMode:        false,
    82     70   		meta.KeyFooterHTML:        "",
    83     71   		meta.KeyHomeZettel:        id.DefaultHomeZid,
    84         -		meta.KeyListPageSize:      0,
    85     72   		meta.KeyMarkerExternal:    "&#10138;",
    86     73   		meta.KeySiteName:          "Zettelstore",
    87     74   		meta.KeyYAMLHeader:        false,
    88     75   		meta.KeyZettelFileSyntax:  nil,
    89     76   	}
    90     77   }
    91     78   
................................................................................
   256    243   	return cfg.getString(meta.KeyMarkerExternal)
   257    244   }
   258    245   
   259    246   // GetFooterHTML returns HTML code that should be embedded into the footer
   260    247   // of each WebUI page.
   261    248   func (cfg *myConfig) GetFooterHTML() string { return cfg.getString(meta.KeyFooterHTML) }
   262    249   
   263         -// GetListPageSize returns the maximum length of a list to be returned in WebUI.
   264         -// A value less or equal to zero signals no limit.
   265         -func (cfg *myConfig) GetListPageSize() int {
   266         -	cfg.mx.RLock()
   267         -	defer cfg.mx.RUnlock()
   268         -
   269         -	if value, ok := cfg.data.GetNumber(meta.KeyListPageSize); ok {
   270         -		return value
   271         -	}
   272         -	value, _ := cfg.orig.GetNumber(meta.KeyListPageSize)
   273         -	return value
   274         -}
   275         -
   276    250   // GetZettelFileSyntax returns the current value of the "zettel-file-syntax" key.
   277    251   func (cfg *myConfig) GetZettelFileSyntax() []string {
   278    252   	cfg.mx.RLock()
   279    253   	defer cfg.mx.RUnlock()
   280    254   	return cfg.data.GetListOrNil(meta.KeyZettelFileSyntax)
   281    255   }
   282    256   

Changes to parser/cleaner/cleaner.go.

    18     18   	"zettelstore.de/z/ast"
    19     19   	"zettelstore.de/z/encoder"
    20     20   	"zettelstore.de/z/strfun"
    21     21   )
    22     22   
    23     23   // CleanupBlockSlice cleans the given block slice.
    24     24   func CleanupBlockSlice(bs ast.BlockSlice) {
    25         -	cv := &cleanupVisitor{
           25  +	cv := cleanupVisitor{
    26     26   		textEnc: encoder.Create("text", nil),
           27  +		hasMark: false,
    27     28   		doMark:  false,
    28     29   	}
    29         -	t := ast.NewTopDownTraverser(cv)
    30         -	t.VisitBlockSlice(bs)
           30  +	ast.WalkBlockSlice(&cv, bs)
    31     31   	if cv.hasMark {
    32     32   		cv.doMark = true
    33         -		t.VisitBlockSlice(bs)
           33  +		ast.WalkBlockSlice(&cv, bs)
    34     34   	}
    35     35   }
    36     36   
    37     37   type cleanupVisitor struct {
    38     38   	textEnc encoder.Encoder
    39     39   	ids     map[string]ast.Node
    40     40   	hasMark bool
    41     41   	doMark  bool
    42     42   }
    43     43   
    44         -// VisitVerbatim does nothing.
    45         -func (cv *cleanupVisitor) VisitVerbatim(vn *ast.VerbatimNode) {}
    46         -
    47         -// VisitRegion does nothing.
    48         -func (cv *cleanupVisitor) VisitRegion(rn *ast.RegionNode) {}
    49         -
    50         -// VisitHeading calculates the heading slug.
    51         -func (cv *cleanupVisitor) VisitHeading(hn *ast.HeadingNode) {
    52         -	if cv.doMark || hn == nil || hn.Inlines == nil {
    53         -		return
    54         -	}
    55         -	var sb strings.Builder
    56         -	_, err := cv.textEnc.WriteInlines(&sb, hn.Inlines)
    57         -	if err != nil {
    58         -		return
    59         -	}
    60         -	s := strfun.Slugify(sb.String())
    61         -	if len(s) > 0 {
    62         -		hn.Slug = cv.addIdentifier(s, hn)
           44  +func (cv *cleanupVisitor) Visit(node ast.Node) ast.Visitor {
           45  +	switch n := node.(type) {
           46  +	case *ast.HeadingNode:
           47  +		if cv.doMark || n == nil || n.Inlines == nil {
           48  +			return nil
           49  +		}
           50  +		var sb strings.Builder
           51  +		_, err := cv.textEnc.WriteInlines(&sb, n.Inlines)
           52  +		if err != nil {
           53  +			return nil
           54  +		}
           55  +		s := strfun.Slugify(sb.String())
           56  +		if len(s) > 0 {
           57  +			n.Slug = cv.addIdentifier(s, n)
           58  +		}
           59  +		return nil
           60  +	case *ast.MarkNode:
           61  +		if !cv.doMark {
           62  +			cv.hasMark = true
           63  +			return nil
           64  +		}
           65  +		if n.Text == "" {
           66  +			n.Text = cv.addIdentifier("*", n)
           67  +			return nil
           68  +		}
           69  +		n.Text = cv.addIdentifier(n.Text, n)
           70  +		return nil
    63     71   	}
           72  +	return cv
    64     73   }
    65     74   
    66         -// VisitHRule does nothing.
    67         -func (cv *cleanupVisitor) VisitHRule(hn *ast.HRuleNode) {}
    68         -
    69         -// VisitList does nothing.
    70         -func (cv *cleanupVisitor) VisitNestedList(ln *ast.NestedListNode) {}
    71         -
    72         -// VisitDescriptionList does nothing.
    73         -func (cv *cleanupVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {}
    74         -
    75         -// VisitPara does nothing.
    76         -func (cv *cleanupVisitor) VisitPara(pn *ast.ParaNode) {}
    77         -
    78         -// VisitTable does nothing.
    79         -func (cv *cleanupVisitor) VisitTable(tn *ast.TableNode) {}
    80         -
    81         -// VisitBLOB does nothing.
    82         -func (cv *cleanupVisitor) VisitBLOB(bn *ast.BLOBNode) {}
    83         -
    84         -// VisitText does nothing.
    85         -func (cv *cleanupVisitor) VisitText(tn *ast.TextNode) {}
    86         -
    87         -// VisitTag does nothing.
    88         -func (cv *cleanupVisitor) VisitTag(tn *ast.TagNode) {}
    89         -
    90         -// VisitSpace does nothing.
    91         -func (cv *cleanupVisitor) VisitSpace(sn *ast.SpaceNode) {}
    92         -
    93         -// VisitBreak does nothing.
    94         -func (cv *cleanupVisitor) VisitBreak(bn *ast.BreakNode) {}
    95         -
    96         -// VisitLink collects the given link as a reference.
    97         -func (cv *cleanupVisitor) VisitLink(ln *ast.LinkNode) {}
    98         -
    99         -// VisitImage collects the image links as a reference.
   100         -func (cv *cleanupVisitor) VisitImage(in *ast.ImageNode) {}
   101         -
   102         -// VisitCite does nothing.
   103         -func (cv *cleanupVisitor) VisitCite(cn *ast.CiteNode) {}
   104         -
   105         -// VisitFootnote does nothing.
   106         -func (cv *cleanupVisitor) VisitFootnote(fn *ast.FootnoteNode) {}
   107         -
   108         -// VisitMark checks for duplicate marks and changes them.
   109         -func (cv *cleanupVisitor) VisitMark(mn *ast.MarkNode) {
   110         -	if mn == nil {
   111         -		return
   112         -	}
   113         -	if !cv.doMark {
   114         -		cv.hasMark = true
   115         -		return
   116         -	}
   117         -	if mn.Text == "" {
   118         -		mn.Text = cv.addIdentifier("*", mn)
   119         -		return
   120         -	}
   121         -	mn.Text = cv.addIdentifier(mn.Text, mn)
   122         -}
   123         -
   124         -// VisitFormat does nothing.
   125         -func (cv *cleanupVisitor) VisitFormat(fn *ast.FormatNode) {}
   126         -
   127         -// VisitLiteral does nothing.
   128         -func (cv *cleanupVisitor) VisitLiteral(ln *ast.LiteralNode) {}
   129         -
   130     75   func (cv *cleanupVisitor) addIdentifier(id string, node ast.Node) string {
   131     76   	if cv.ids == nil {
   132     77   		cv.ids = map[string]ast.Node{id: node}
   133     78   		return id
   134     79   	}
   135     80   	if n, ok := cv.ids[id]; ok && n != node {
   136     81   		prefix := id + "-"

Changes to parser/markdown/markdown.go.

   121    121   	return &ast.HRuleNode{
   122    122   		Attrs: nil, //TODO
   123    123   	}
   124    124   }
   125    125   
   126    126   func (p *mdP) acceptCodeBlock(node *gmAst.CodeBlock) *ast.VerbatimNode {
   127    127   	return &ast.VerbatimNode{
   128         -		Code:  ast.VerbatimProg,
          128  +		Kind:  ast.VerbatimProg,
   129    129   		Attrs: nil, //TODO
   130    130   		Lines: p.acceptRawText(node),
   131    131   	}
   132    132   }
   133    133   
   134    134   func (p *mdP) acceptFencedCodeBlock(node *gmAst.FencedCodeBlock) *ast.VerbatimNode {
   135    135   	var attrs *ast.Attributes
   136    136   	if language := node.Language(p.source); len(language) > 0 {
   137    137   		attrs = attrs.Set("class", "language-"+cleanText(string(language), true))
   138    138   	}
   139    139   	return &ast.VerbatimNode{
   140         -		Code:  ast.VerbatimProg,
          140  +		Kind:  ast.VerbatimProg,
   141    141   		Attrs: attrs,
   142    142   		Lines: p.acceptRawText(node),
   143    143   	}
   144    144   }
   145    145   
   146    146   func (p *mdP) acceptRawText(node gmAst.Node) []string {
   147    147   	lines := node.Lines()
................................................................................
   159    159   		result = append(result, string(line))
   160    160   	}
   161    161   	return result
   162    162   }
   163    163   
   164    164   func (p *mdP) acceptBlockquote(node *gmAst.Blockquote) *ast.NestedListNode {
   165    165   	return &ast.NestedListNode{
   166         -		Code: ast.NestedListQuote,
          166  +		Kind: ast.NestedListQuote,
   167    167   		Items: []ast.ItemSlice{
   168    168   			p.acceptItemSlice(node),
   169    169   		},
   170    170   	}
   171    171   }
   172    172   
   173    173   func (p *mdP) acceptList(node *gmAst.List) ast.ItemNode {
   174         -	code := ast.NestedListUnordered
          174  +	kind := ast.NestedListUnordered
   175    175   	var attrs *ast.Attributes
   176    176   	if node.IsOrdered() {
   177         -		code = ast.NestedListOrdered
          177  +		kind = ast.NestedListOrdered
   178    178   		if node.Start != 1 {
   179    179   			attrs = attrs.Set("start", fmt.Sprintf("%d", node.Start))
   180    180   		}
   181    181   	}
   182    182   	items := make([]ast.ItemSlice, 0, node.ChildCount())
   183    183   	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
   184    184   		item, ok := child.(*gmAst.ListItem)
   185    185   		if !ok {
   186    186   			panic(fmt.Sprintf("Expected list item node, but got %v", child.Kind()))
   187    187   		}
   188    188   		items = append(items, p.acceptItemSlice(item))
   189    189   	}
   190    190   	return &ast.NestedListNode{
   191         -		Code:  code,
          191  +		Kind:  kind,
   192    192   		Items: items,
   193    193   		Attrs: attrs,
   194    194   	}
   195    195   }
   196    196   
   197    197   func (p *mdP) acceptItemSlice(node gmAst.Node) ast.ItemSlice {
   198    198   	result := make(ast.ItemSlice, 0, node.ChildCount())
................................................................................
   219    219   		closure := string(node.ClosureLine.Value(p.source))
   220    220   		if l := len(closure); l > 1 && closure[l-1] == '\n' {
   221    221   			closure = closure[:l-1]
   222    222   		}
   223    223   		lines = append(lines, closure)
   224    224   	}
   225    225   	return &ast.VerbatimNode{
   226         -		Code:  ast.VerbatimHTML,
          226  +		Kind:  ast.VerbatimHTML,
   227    227   		Lines: lines,
   228    228   	}
   229    229   }
   230    230   
   231    231   func (p *mdP) acceptInlineSlice(node gmAst.Node) ast.InlineSlice {
   232    232   	result := make(ast.InlineSlice, 0, node.ChildCount())
   233    233   	for child := node.FirstChild(); child != nil; child = child.NextSibling() {
................................................................................
   357    357   	}
   358    358   	return sb.String()
   359    359   }
   360    360   
   361    361   func (p *mdP) acceptCodeSpan(node *gmAst.CodeSpan) ast.InlineSlice {
   362    362   	return ast.InlineSlice{
   363    363   		&ast.LiteralNode{
   364         -			Code:  ast.LiteralProg,
          364  +			Kind:  ast.LiteralProg,
   365    365   			Attrs: nil, //TODO
   366    366   			Text:  cleanCodeSpan(string(node.Text(p.source))),
   367    367   		},
   368    368   	}
   369    369   }
   370    370   
   371    371   func cleanCodeSpan(text string) string {
................................................................................
   387    387   		return text
   388    388   	}
   389    389   	sb.WriteString(text[lastPos:])
   390    390   	return sb.String()
   391    391   }
   392    392   
   393    393   func (p *mdP) acceptEmphasis(node *gmAst.Emphasis) ast.InlineSlice {
   394         -	code := ast.FormatEmph
          394  +	kind := ast.FormatEmph
   395    395   	if node.Level == 2 {
   396         -		code = ast.FormatStrong
          396  +		kind = ast.FormatStrong
   397    397   	}
   398    398   	return ast.InlineSlice{
   399    399   		&ast.FormatNode{
   400         -			Code:    code,
          400  +			Kind:    kind,
   401    401   			Attrs:   nil, //TODO
   402    402   			Inlines: p.acceptInlineSlice(node),
   403    403   		},
   404    404   	}
   405    405   }
   406    406   
   407    407   func (p *mdP) acceptLink(node *gmAst.Link) ast.InlineSlice {
................................................................................
   478    478   	segs := make([]string, 0, node.Segments.Len())
   479    479   	for i := 0; i < node.Segments.Len(); i++ {
   480    480   		segment := node.Segments.At(i)
   481    481   		segs = append(segs, string(segment.Value(p.source)))
   482    482   	}
   483    483   	return ast.InlineSlice{
   484    484   		&ast.LiteralNode{
   485         -			Code:  ast.LiteralHTML,
          485  +			Kind:  ast.LiteralHTML,
   486    486   			Attrs: nil, // TODO: add HTML as language
   487    487   			Text:  strings.Join(segs, ""),
   488    488   		},
   489    489   	}
   490    490   }

Changes to parser/none/none.go.

    35     35   	}
    36     36   	return ast.BlockSlice{descrlist}
    37     37   }
    38     38   
    39     39   func getDescription(key, value string) ast.Description {
    40     40   	return ast.Description{
    41     41   		Term: ast.InlineSlice{&ast.TextNode{Text: key}},
    42         -		Descriptions: []ast.DescriptionSlice{
    43         -			ast.DescriptionSlice{
    44         -				&ast.ParaNode{
    45         -					Inlines: convertToInlineSlice(value, meta.Type(key)),
    46         -				},
    47         -			},
           42  +		Descriptions: []ast.DescriptionSlice{{
           43  +			&ast.ParaNode{
           44  +				Inlines: convertToInlineSlice(value, meta.Type(key)),
           45  +			}},
    48     46   		},
    49     47   	}
    50     48   }
    51     49   
    52     50   func convertToInlineSlice(value string, dt *meta.DescriptionType) ast.InlineSlice {
    53     51   	var sliceData []string
    54     52   	if dt.IsSet {
................................................................................
    83     81   	return result
    84     82   }
    85     83   
    86     84   func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
    87     85   	inp.SkipToEOL()
    88     86   	return ast.InlineSlice{
    89     87   		&ast.FormatNode{
    90         -			Code:  ast.FormatSpan,
           88  +			Kind:  ast.FormatSpan,
    91     89   			Attrs: &ast.Attributes{Attrs: map[string]string{"class": "warning"}},
    92     90   			Inlines: ast.InlineSlice{
    93     91   				&ast.TextNode{Text: "parser.meta.ParseInlines:"},
    94     92   				&ast.SpaceNode{Lexeme: " "},
    95     93   				&ast.TextNode{Text: "not"},
    96     94   				&ast.SpaceNode{Lexeme: " "},
    97     95   				&ast.TextNode{Text: "possible"},

Changes to parser/plain/plain.go.

    44     44   		ParseInlines: parseInlines,
    45     45   	})
    46     46   }
    47     47   
    48     48   func parseBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {
    49     49   	return ast.BlockSlice{
    50     50   		&ast.VerbatimNode{
    51         -			Code:  ast.VerbatimProg,
           51  +			Kind:  ast.VerbatimProg,
    52     52   			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
    53     53   			Lines: readLines(inp),
    54     54   		},
    55     55   	}
    56     56   }
    57     57   
    58     58   func readLines(inp *input.Input) (lines []string) {
................................................................................
    67     67   	}
    68     68   }
    69     69   
    70     70   func parseInlines(inp *input.Input, syntax string) ast.InlineSlice {
    71     71   	inp.SkipToEOL()
    72     72   	return ast.InlineSlice{
    73     73   		&ast.LiteralNode{
    74         -			Code:  ast.LiteralProg,
           74  +			Kind:  ast.LiteralProg,
    75     75   			Attrs: &ast.Attributes{Attrs: map[string]string{"": syntax}},
    76     76   			Text:  inp.Src[0:inp.Pos],
    77     77   		},
    78     78   	}
    79     79   }
    80     80   
    81     81   func parseSVGBlocks(inp *input.Input, m *meta.Meta, syntax string) ast.BlockSlice {

Changes to parser/zettelmark/block.go.

   165    165   		return nil, false
   166    166   	}
   167    167   	attrs := cp.parseAttributes(true)
   168    168   	inp.SkipToEOL()
   169    169   	if inp.Ch == input.EOS {
   170    170   		return nil, false
   171    171   	}
   172         -	var code ast.VerbatimCode
          172  +	var kind ast.VerbatimKind
   173    173   	switch fch {
   174    174   	case '`', runeModGrave:
   175         -		code = ast.VerbatimProg
          175  +		kind = ast.VerbatimProg
   176    176   	case '%':
   177         -		code = ast.VerbatimComment
          177  +		kind = ast.VerbatimComment
   178    178   	default:
   179    179   		panic(fmt.Sprintf("%q is not a verbatim char", fch))
   180    180   	}
   181         -	rn = &ast.VerbatimNode{Code: code, Attrs: attrs}
          181  +	rn = &ast.VerbatimNode{Kind: kind, Attrs: attrs}
   182    182   	for {
   183    183   		inp.EatEOL()
   184    184   		posL := inp.Pos
   185    185   		switch inp.Ch {
   186    186   		case fch:
   187    187   			if cp.countDelim(fch) >= cnt {
   188    188   				inp.SkipToEOL()
................................................................................
   193    193   			return nil, false
   194    194   		}
   195    195   		inp.SkipToEOL()
   196    196   		rn.Lines = append(rn.Lines, inp.Src[posL:inp.Pos])
   197    197   	}
   198    198   }
   199    199   
   200         -var runeRegion = map[rune]ast.RegionCode{
          200  +var runeRegion = map[rune]ast.RegionKind{
   201    201   	':': ast.RegionSpan,
   202    202   	'<': ast.RegionQuote,
   203    203   	'"': ast.RegionVerse,
   204    204   }
   205    205   
   206    206   // parseRegion parses a block region.
   207    207   func (cp *zmkP) parseRegion() (rn *ast.RegionNode, success bool) {
   208    208   	inp := cp.inp
   209    209   	fch := inp.Ch
   210         -	code, ok := runeRegion[fch]
          210  +	kind, ok := runeRegion[fch]
   211    211   	if !ok {
   212    212   		panic(fmt.Sprintf("%q is not a region char", fch))
   213    213   	}
   214    214   	cnt := cp.countDelim(fch)
   215    215   	if cnt < 3 {
   216    216   		return nil, false
   217    217   	}
   218    218   	attrs := cp.parseAttributes(true)
   219    219   	inp.SkipToEOL()
   220    220   	if inp.Ch == input.EOS {
   221    221   		return nil, false
   222    222   	}
   223         -	rn = &ast.RegionNode{Code: code, Attrs: attrs}
          223  +	rn = &ast.RegionNode{Kind: kind, Attrs: attrs}
   224    224   	var lastPara *ast.ParaNode
   225    225   	inp.EatEOL()
   226    226   	for {
   227    227   		posL := inp.Pos
   228    228   		switch inp.Ch {
   229    229   		case fch:
   230    230   			if cp.countDelim(fch) >= cnt {
................................................................................
   303    303   		return nil, false
   304    304   	}
   305    305   	attrs := cp.parseAttributes(true)
   306    306   	inp.SkipToEOL()
   307    307   	return &ast.HRuleNode{Attrs: attrs}, true
   308    308   }
   309    309   
   310         -var mapRuneNestedList = map[rune]ast.NestedListCode{
          310  +var mapRuneNestedList = map[rune]ast.NestedListKind{
   311    311   	'*': ast.NestedListUnordered,
   312    312   	'#': ast.NestedListOrdered,
   313    313   	'>': ast.NestedListQuote,
   314    314   }
   315    315   
   316    316   // parseNestedList parses a list.
   317    317   func (cp *zmkP) parseNestedList() (res ast.BlockNode, success bool) {
   318    318   	inp := cp.inp
   319         -	codes := cp.parseNestedListCodes()
   320         -	if codes == nil {
          319  +	kinds := cp.parseNestedListKinds()
          320  +	if kinds == nil {
   321    321   		return nil, false
   322    322   	}
   323    323   	cp.skipSpace()
   324         -	if codes[len(codes)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) {
          324  +	if kinds[len(kinds)-1] != ast.NestedListQuote && input.IsEOLEOS(inp.Ch) {
   325    325   		return nil, false
   326    326   	}
   327    327   
   328         -	if len(codes) < len(cp.lists) {
   329         -		cp.lists = cp.lists[:len(codes)]
          328  +	if len(kinds) < len(cp.lists) {
          329  +		cp.lists = cp.lists[:len(kinds)]
   330    330   	}
   331         -	ln, newLnCount := cp.buildNestedList(codes)
   332         -	ln.Items = append(ln.Items, ast.ItemSlice{cp.parseLinePara()})
          331  +	ln, newLnCount := cp.buildNestedList(kinds)
          332  +	pn := cp.parseLinePara()
          333  +	if pn == nil {
          334  +		pn = &ast.ParaNode{}
          335  +	}
          336  +	ln.Items = append(ln.Items, ast.ItemSlice{pn})
   333    337   	return cp.cleanupParsedNestedList(newLnCount)
   334    338   }
   335    339   
   336         -func (cp *zmkP) parseNestedListCodes() []ast.NestedListCode {
          340  +func (cp *zmkP) parseNestedListKinds() []ast.NestedListKind {
   337    341   	inp := cp.inp
   338         -	codes := make([]ast.NestedListCode, 0, 4)
          342  +	codes := make([]ast.NestedListKind, 0, 4)
   339    343   	for {
   340    344   		code, ok := mapRuneNestedList[inp.Ch]
   341    345   		if !ok {
   342    346   			panic(fmt.Sprintf("%q is not a region char", inp.Ch))
   343    347   		}
   344    348   		codes = append(codes, code)
   345    349   		inp.Next()
................................................................................
   350    354   		default:
   351    355   			return nil
   352    356   		}
   353    357   	}
   354    358   
   355    359   }
   356    360   
   357         -func (cp *zmkP) buildNestedList(codes []ast.NestedListCode) (ln *ast.NestedListNode, newLnCount int) {
   358         -	for i, code := range codes {
          361  +func (cp *zmkP) buildNestedList(kinds []ast.NestedListKind) (ln *ast.NestedListNode, newLnCount int) {
          362  +	for i, kind := range kinds {
   359    363   		if i < len(cp.lists) {
   360         -			if cp.lists[i].Code != code {
   361         -				ln = &ast.NestedListNode{Code: code}
          364  +			if cp.lists[i].Kind != kind {
          365  +				ln = &ast.NestedListNode{Kind: kind}
   362    366   				newLnCount++
   363    367   				cp.lists[i] = ln
   364    368   				cp.lists = cp.lists[:i+1]
   365    369   			} else {
   366    370   				ln = cp.lists[i]
   367    371   			}
   368    372   		} else {
   369         -			ln = &ast.NestedListNode{Code: code}
          373  +			ln = &ast.NestedListNode{Kind: kind}
   370    374   			newLnCount++
   371    375   			cp.lists = append(cp.lists, ln)
   372    376   		}
   373    377   	}
   374    378   	return ln, newLnCount
   375    379   }
   376    380   
................................................................................
   480    484   	}
   481    485   	cp.lists = cp.lists[:cnt]
   482    486   	if cnt == 0 {
   483    487   		return false
   484    488   	}
   485    489   	ln := cp.lists[cnt-1]
   486    490   	pn := cp.parseLinePara()
          491  +	if pn == nil {
          492  +		pn = &ast.ParaNode{}
          493  +	}
   487    494   	lbn := ln.Items[len(ln.Items)-1]
   488    495   	if lpn, ok := lbn[len(lbn)-1].(*ast.ParaNode); ok {
   489    496   		lpn.Inlines = append(lpn.Inlines, pn.Inlines...)
   490    497   	} else {
   491    498   		ln.Items[len(ln.Items)-1] = append(ln.Items[len(ln.Items)-1], pn)
   492    499   	}
   493    500   	return true

Changes to parser/zettelmark/inline.go.

   358    358   	for inp.Ch == '%' {
   359    359   		inp.Next()
   360    360   	}
   361    361   	cp.skipSpace()
   362    362   	pos := inp.Pos
   363    363   	for {
   364    364   		if input.IsEOLEOS(inp.Ch) {
   365         -			return &ast.LiteralNode{Code: ast.LiteralComment, Text: inp.Src[pos:inp.Pos]}, true
          365  +			return &ast.LiteralNode{Kind: ast.LiteralComment, Text: inp.Src[pos:inp.Pos]}, true
   366    366   		}
   367    367   		inp.Next()
   368    368   	}
   369    369   }
   370    370   
   371         -var mapRuneFormat = map[rune]ast.FormatCode{
          371  +var mapRuneFormat = map[rune]ast.FormatKind{
   372    372   	'/':  ast.FormatItalic,
   373    373   	'*':  ast.FormatBold,
   374    374   	'_':  ast.FormatUnder,
   375    375   	'~':  ast.FormatStrike,
   376    376   	'\'': ast.FormatMonospace,
   377    377   	'^':  ast.FormatSuper,
   378    378   	',':  ast.FormatSub,
................................................................................
   381    381   	';':  ast.FormatSmall,
   382    382   	':':  ast.FormatSpan,
   383    383   }
   384    384   
   385    385   func (cp *zmkP) parseFormat() (res ast.InlineNode, success bool) {
   386    386   	inp := cp.inp
   387    387   	fch := inp.Ch
   388         -	code, ok := mapRuneFormat[fch]
          388  +	kind, ok := mapRuneFormat[fch]
   389    389   	if !ok {
   390    390   		panic(fmt.Sprintf("%q is not a formatting char", fch))
   391    391   	}
   392    392   	inp.Next() // read 2nd formatting character
   393    393   	if inp.Ch != fch {
   394    394   		return nil, false
   395    395   	}
   396    396   	inp.Next()
   397         -	fn := &ast.FormatNode{Code: code}
          397  +	fn := &ast.FormatNode{Kind: kind}
   398    398   	for {
   399    399   		if inp.Ch == input.EOS {
   400    400   			return nil, false
   401    401   		}
   402    402   		if inp.Ch == fch {
   403    403   			inp.Next()
   404    404   			if inp.Ch == fch {
................................................................................
   412    412   				return nil, false
   413    413   			}
   414    414   			fn.Inlines = append(fn.Inlines, in)
   415    415   		}
   416    416   	}
   417    417   }
   418    418   
   419         -var mapRuneLiteral = map[rune]ast.LiteralCode{
          419  +var mapRuneLiteral = map[rune]ast.LiteralKind{
   420    420   	'`':          ast.LiteralProg,
   421    421   	runeModGrave: ast.LiteralProg,
   422    422   	'+':          ast.LiteralKeyb,
   423    423   	'=':          ast.LiteralOutput,
   424    424   }
   425    425   
   426    426   func (cp *zmkP) parseLiteral() (res ast.InlineNode, success bool) {
   427    427   	inp := cp.inp
   428    428   	fch := inp.Ch
   429         -	code, ok := mapRuneLiteral[fch]
          429  +	kind, ok := mapRuneLiteral[fch]
   430    430   	if !ok {
   431    431   		panic(fmt.Sprintf("%q is not a formatting char", fch))
   432    432   	}
   433    433   	inp.Next() // read 2nd formatting character
   434    434   	if inp.Ch != fch {
   435    435   		return nil, false
   436    436   	}
   437         -	fn := &ast.LiteralNode{Code: code}
          437  +	fn := &ast.LiteralNode{Kind: kind}
   438    438   	inp.Next()
   439    439   	var sb strings.Builder
   440    440   	for {
   441    441   		if inp.Ch == input.EOS {
   442    442   			return nil, false
   443    443   		}
   444    444   		if inp.Ch == fch {

Changes to parser/zettelmark/node.go.

     7      7   // Public License). Please see file LICENSE.txt for your rights and obligations
     8      8   // under this license.
     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package zettelmark provides a parser for zettelmarkup.
    12     12   package zettelmark
    13     13   
    14         -import (
    15         -	"zettelstore.de/z/ast"
    16         -)
           14  +import "zettelstore.de/z/ast"
    17     15   
    18     16   // Internal nodes for parsing zettelmark. These will be removed in
    19     17   // post-processing.
    20     18   
    21     19   // nullItemNode specifies a removable placeholder for an item node.
    22     20   type nullItemNode struct {
    23     21   	ast.ItemNode
    24     22   }
    25     23   
    26         -// Accept a visitor and visit the node.
    27         -func (nn *nullItemNode) Accept(v ast.Visitor) {}
    28         -
    29     24   // nullDescriptionNode specifies a removable placeholder.
    30     25   type nullDescriptionNode struct {
    31     26   	ast.DescriptionNode
    32     27   }
    33         -
    34         -// Accept a visitor and visit the node.
    35         -func (nn *nullDescriptionNode) Accept(v ast.Visitor) {}

Changes to parser/zettelmark/post-processor.go.

    30     30   }
    31     31   
    32     32   // postProcessor is a visitor that cleans the abstract syntax tree.
    33     33   type postProcessor struct {
    34     34   	inVerse bool
    35     35   }
    36     36   
    37         -// VisitPara post-processes a paragraph.
    38         -func (pp *postProcessor) VisitPara(pn *ast.ParaNode) {
    39         -	if pn != nil {
    40         -		pn.Inlines = pp.processInlineSlice(pn.Inlines)
    41         -	}
    42         -}
    43         -
    44         -// VisitVerbatim does nothing, no post-processing needed.
    45         -func (pp *postProcessor) VisitVerbatim(vn *ast.VerbatimNode) {}
    46         -
    47         -// VisitRegion post-processes a region.
    48         -func (pp *postProcessor) VisitRegion(rn *ast.RegionNode) {
    49         -	oldVerse := pp.inVerse
    50         -	if rn.Code == ast.RegionVerse {
    51         -		pp.inVerse = true
    52         -	}
    53         -	rn.Blocks = pp.processBlockSlice(rn.Blocks)
    54         -	pp.inVerse = oldVerse
    55         -	rn.Inlines = pp.processInlineSlice(rn.Inlines)
    56         -}
    57         -
    58         -// VisitHeading post-processes a heading.
    59         -func (pp *postProcessor) VisitHeading(hn *ast.HeadingNode) {
    60         -	hn.Inlines = pp.processInlineSlice(hn.Inlines)
    61         -}
    62         -
    63         -// VisitHRule does nothing, no post-processing needed.
    64         -func (pp *postProcessor) VisitHRule(hn *ast.HRuleNode) {}
    65         -
    66         -// VisitList post-processes a list.
    67         -func (pp *postProcessor) VisitNestedList(ln *ast.NestedListNode) {
    68         -	for i, item := range ln.Items {
    69         -		ln.Items[i] = pp.processItemSlice(item)
    70         -	}
    71         -}
    72         -
    73         -// VisitDescriptionList post-processes a description list.
    74         -func (pp *postProcessor) VisitDescriptionList(dn *ast.DescriptionListNode) {
    75         -	for i, def := range dn.Descriptions {
    76         -		dn.Descriptions[i].Term = pp.processInlineSlice(def.Term)
    77         -		for j, b := range def.Descriptions {
    78         -			dn.Descriptions[i].Descriptions[j] = pp.processDescriptionSlice(b)
    79         -		}
    80         -	}
    81         -}
    82         -
    83         -// VisitTable post-processes a table.
    84         -func (pp *postProcessor) VisitTable(tn *ast.TableNode) {
    85         -	width := tableWidth(tn)
    86         -	tn.Align = make([]ast.Alignment, width)
    87         -	for i := 0; i < width; i++ {
    88         -		tn.Align[i] = ast.AlignDefault
    89         -	}
    90         -	if len(tn.Rows) > 0 && isHeaderRow(tn.Rows[0]) {
    91         -		tn.Header = tn.Rows[0]
    92         -		tn.Rows = tn.Rows[1:]
    93         -		pp.visitTableHeader(tn)
    94         -	}
    95         -	if len(tn.Header) > 0 {
    96         -		tn.Header = appendCells(tn.Header, width, tn.Align)
    97         -		for i, cell := range tn.Header {
    98         -			pp.processCell(cell, tn.Align[i])
    99         -		}
   100         -	}
   101         -	pp.visitTableRows(tn, width)
           37  +func (pp *postProcessor) Visit(node ast.Node) ast.Visitor {
           38  +	switch n := node.(type) {
           39  +	case *ast.ParaNode:
           40  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           41  +	case *ast.RegionNode:
           42  +		oldVerse := pp.inVerse
           43  +		if n.Kind == ast.RegionVerse {
           44  +			pp.inVerse = true
           45  +		}
           46  +		n.Blocks = pp.processBlockSlice(n.Blocks)
           47  +		pp.inVerse = oldVerse
           48  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           49  +	case *ast.HeadingNode:
           50  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           51  +	case *ast.NestedListNode:
           52  +		for i, item := range n.Items {
           53  +			n.Items[i] = pp.processItemSlice(item)
           54  +		}
           55  +	case *ast.DescriptionListNode:
           56  +		for i, def := range n.Descriptions {
           57  +			n.Descriptions[i].Term = pp.processInlineSlice(def.Term)
           58  +			for j, b := range def.Descriptions {
           59  +				n.Descriptions[i].Descriptions[j] = pp.processDescriptionSlice(b)
           60  +			}
           61  +		}
           62  +	case *ast.TableNode:
           63  +		width := tableWidth(n)
           64  +		n.Align = make([]ast.Alignment, width)
           65  +		for i := 0; i < width; i++ {
           66  +			n.Align[i] = ast.AlignDefault
           67  +		}
           68  +		if len(n.Rows) > 0 && isHeaderRow(n.Rows[0]) {
           69  +			n.Header = n.Rows[0]
           70  +			n.Rows = n.Rows[1:]
           71  +			pp.visitTableHeader(n)
           72  +		}
           73  +		if len(n.Header) > 0 {
           74  +			n.Header = appendCells(n.Header, width, n.Align)
           75  +			for i, cell := range n.Header {
           76  +				pp.processCell(cell, n.Align[i])
           77  +			}
           78  +		}
           79  +		pp.visitTableRows(n, width)
           80  +	case *ast.LinkNode:
           81  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           82  +	case *ast.ImageNode:
           83  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           84  +	case *ast.CiteNode:
           85  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           86  +	case *ast.FootnoteNode:
           87  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           88  +	case *ast.FormatNode:
           89  +		if n.Attrs != nil && n.Attrs.HasDefault() {
           90  +			if newKind, ok := mapSemantic[n.Kind]; ok {
           91  +				n.Attrs.RemoveDefault()
           92  +				n.Kind = newKind
           93  +			}
           94  +		}
           95  +		n.Inlines = pp.processInlineSlice(n.Inlines)
           96  +	}
           97  +	return nil
   102     98   }
   103     99   
   104    100   func (pp *postProcessor) visitTableHeader(tn *ast.TableNode) {
   105    101   	for pos, cell := range tn.Header {
   106    102   		inlines := cell.Inlines
   107    103   		if len(inlines) == 0 {
   108    104   			continue
................................................................................
   190    186   		}
   191    187   	} else {
   192    188   		cell.Align = colAlign
   193    189   	}
   194    190   	cell.Inlines = pp.processInlineSlice(cell.Inlines)
   195    191   }
   196    192   
   197         -// VisitBLOB does nothing.
   198         -func (pp *postProcessor) VisitBLOB(bn *ast.BLOBNode) {}
   199         -
   200         -// VisitText does nothing.
   201         -func (pp *postProcessor) VisitText(tn *ast.TextNode) {}
   202         -
   203         -// VisitTag does nothing.
   204         -func (pp *postProcessor) VisitTag(tn *ast.TagNode) {}
   205         -
   206         -// VisitSpace does nothing.
   207         -func (pp *postProcessor) VisitSpace(sn *ast.SpaceNode) {}
   208         -
   209         -// VisitBreak does nothing.
   210         -func (pp *postProcessor) VisitBreak(bn *ast.BreakNode) {}
   211         -
   212         -// VisitLink post-processes a link.
   213         -func (pp *postProcessor) VisitLink(ln *ast.LinkNode) {
   214         -	ln.Inlines = pp.processInlineSlice(ln.Inlines)
   215         -}
   216         -
   217         -// VisitImage post-processes an image.
   218         -func (pp *postProcessor) VisitImage(in *ast.ImageNode) {
   219         -	if len(in.Inlines) > 0 {
   220         -		in.Inlines = pp.processInlineSlice(in.Inlines)
   221         -	}
   222         -}
   223         -
   224         -// VisitCite post-processes a citation.
   225         -func (pp *postProcessor) VisitCite(cn *ast.CiteNode) {
   226         -	cn.Inlines = pp.processInlineSlice(cn.Inlines)
   227         -}
   228         -
   229         -// VisitFootnote post-processes a footnote.
   230         -func (pp *postProcessor) VisitFootnote(fn *ast.FootnoteNode) {
   231         -	fn.Inlines = pp.processInlineSlice(fn.Inlines)
   232         -}
   233         -
   234         -// VisitMark post-processes a mark.
   235         -func (pp *postProcessor) VisitMark(mn *ast.MarkNode) {}
   236         -
   237         -var mapSemantic = map[ast.FormatCode]ast.FormatCode{
          193  +var mapSemantic = map[ast.FormatKind]ast.FormatKind{
   238    194   	ast.FormatItalic: ast.FormatEmph,
   239    195   	ast.FormatBold:   ast.FormatStrong,
   240    196   	ast.FormatUnder:  ast.FormatInsert,
   241    197   	ast.FormatStrike: ast.FormatDelete,
   242    198   }
   243    199   
   244         -// VisitFormat post-processes formatted inline nodes.
   245         -func (pp *postProcessor) VisitFormat(fn *ast.FormatNode) {
   246         -	if fn.Attrs != nil && fn.Attrs.HasDefault() {
   247         -		if newCode, ok := mapSemantic[fn.Code]; ok {
   248         -			fn.Attrs.RemoveDefault()
   249         -			fn.Code = newCode
   250         -		}
   251         -	}
   252         -	fn.Inlines = pp.processInlineSlice(fn.Inlines)
   253         -}
   254         -
   255         -// VisitLiteral post-processes an inline literal.
   256         -func (pp *postProcessor) VisitLiteral(cn *ast.LiteralNode) {}
   257         -
   258    200   // processBlockSlice post-processes a slice of blocks.
   259    201   // It is one of the working horses for post-processing.
   260    202   func (pp *postProcessor) processBlockSlice(bns ast.BlockSlice) ast.BlockSlice {
   261         -	for _, bn := range bns {
   262         -		bn.Accept(pp)
          203  +	if len(bns) == 0 {
          204  +		return nil
   263    205   	}
          206  +	ast.WalkBlockSlice(pp, bns)
   264    207   	fromPos, toPos := 0, 0
   265    208   	for fromPos < len(bns) {
   266    209   		bns[toPos] = bns[fromPos]
   267    210   		fromPos++
   268    211   		switch bn := bns[toPos].(type) {
   269    212   		case *ast.ParaNode:
   270    213   			if len(bn.Inlines) > 0 {
................................................................................
   281    224   	}
   282    225   	return bns[:toPos:toPos]
   283    226   }
   284    227   
   285    228   // processItemSlice post-processes a slice of items.
   286    229   // It is one of the working horses for post-processing.
   287    230   func (pp *postProcessor) processItemSlice(ins ast.ItemSlice) ast.ItemSlice {
          231  +	if len(ins) == 0 {
          232  +		return nil
          233  +	}
   288    234   	for _, in := range ins {
   289         -		in.Accept(pp)
          235  +		ast.Walk(pp, in)
   290    236   	}
   291    237   	fromPos, toPos := 0, 0
   292    238   	for fromPos < len(ins) {
   293    239   		ins[toPos] = ins[fromPos]
   294    240   		fromPos++
   295    241   		switch in := ins[toPos].(type) {
   296    242   		case *ast.ParaNode:
................................................................................
   308    254   	}
   309    255   	return ins[:toPos:toPos]
   310    256   }
   311    257   
   312    258   // processDescriptionSlice post-processes a slice of descriptions.
   313    259   // It is one of the working horses for post-processing.
   314    260   func (pp *postProcessor) processDescriptionSlice(dns ast.DescriptionSlice) ast.DescriptionSlice {
          261  +	if len(dns) == 0 {
          262  +		return nil
          263  +	}
   315    264   	for _, dn := range dns {
   316         -		dn.Accept(pp)
          265  +		ast.Walk(pp, dn)
   317    266   	}
   318    267   	fromPos, toPos := 0, 0
   319    268   	for fromPos < len(dns) {
   320    269   		dns[toPos] = dns[fromPos]
   321    270   		fromPos++
   322    271   		switch dn := dns[toPos].(type) {
   323    272   		case *ast.ParaNode:
................................................................................
   337    286   
   338    287   // processInlineSlice post-processes a slice of inline nodes.
   339    288   // It is one of the working horses for post-processing.
   340    289   func (pp *postProcessor) processInlineSlice(ins ast.InlineSlice) ast.InlineSlice {
   341    290   	if len(ins) == 0 {
   342    291   		return nil
   343    292   	}
   344         -	for _, in := range ins {
   345         -		in.Accept(pp)
   346         -	}
          293  +	ast.WalkInlineSlice(pp, ins)
   347    294   
   348    295   	if !pp.inVerse {
   349    296   		ins = processInlineSliceHead(ins)
   350    297   	}
   351    298   	toPos := pp.processInlineSliceCopy(ins)
   352    299   	toPos = pp.processInlineSliceTail(ins, toPos)
   353    300   	ins = ins[:toPos:toPos]

Changes to parser/zettelmark/zettelmark_test.go.

    46     46   
    47     47   	for tcn, tc := range tcs {
    48     48   		t.Run(fmt.Sprintf("TC=%02d,src=%q", tcn, tc.source), func(st *testing.T) {
    49     49   			st.Helper()
    50     50   			inp := input.NewInput(tc.source)
    51     51   			bns := parser.ParseBlocks(inp, nil, meta.ValueSyntaxZmk)
    52     52   			var tv TestVisitor
    53         -			tv.visitBlockSlice(bns)
           53  +			ast.WalkBlockSlice(&tv, bns)
    54     54   			got := tv.String()
    55     55   			if tc.want != got {
    56     56   				st.Errorf("\nwant=%q\n got=%q", tc.want, got)
    57     57   			}
    58     58   		})
    59     59   	}
    60     60   }
................................................................................
   484    484   
   485    485   		// A HRule creates a new list
   486    486   		{"* abc\n---\n* def", "(UL {(PARA abc)})(HR)(UL {(PARA def)})"},
   487    487   
   488    488   		// Changing list type adds a new list
   489    489   		{"* abc\n# def", "(UL {(PARA abc)})(OL {(PARA def)})"},
   490    490   
   491         -		// Quotation lists mayx have empty items
          491  +		// Quotation lists may have empty items
   492    492   		{">", "(QL {})"},
   493    493   	})
   494    494   }
   495    495   
   496    496   func TestEnumAfterPara(t *testing.T) {
   497    497   	checkTcs(t, TestCases{
   498    498   		{"abc\n* def", "(PARA abc)(UL {(PARA def)})"},
................................................................................
   612    612   
   613    613   // TestVisitor serializes the abstract syntax tree to a string.
   614    614   type TestVisitor struct {
   615    615   	b strings.Builder
   616    616   }
   617    617   
   618    618   func (tv *TestVisitor) String() string { return tv.b.String() }
   619         -func (tv *TestVisitor) VisitPara(pn *ast.ParaNode) {
   620         -	tv.b.WriteString("(PARA")
   621         -	tv.visitInlineSlice(pn.Inlines)
   622         -	tv.b.WriteByte(')')
          619  +
          620  +func (tv *TestVisitor) Visit(node ast.Node) ast.Visitor {
          621  +	switch n := node.(type) {
          622  +	case *ast.ParaNode:
          623  +		tv.b.WriteString("(PARA")
          624  +		tv.visitInlineSlice(n.Inlines)
          625  +		tv.b.WriteByte(')')
          626  +	case *ast.VerbatimNode:
          627  +		code, ok := mapVerbatimKind[n.Kind]
          628  +		if !ok {
          629  +			panic(fmt.Sprintf("Unknown verbatim code %v", n.Kind))
          630  +		}
          631  +		tv.b.WriteString(code)
          632  +		for _, line := range n.Lines {
          633  +			tv.b.WriteByte('\n')
          634  +			tv.b.WriteString(line)
          635  +		}
          636  +		tv.b.WriteByte(')')
          637  +		tv.visitAttributes(n.Attrs)
          638  +	case *ast.RegionNode:
          639  +		code, ok := mapRegionKind[n.Kind]
          640  +		if !ok {
          641  +			panic(fmt.Sprintf("Unknown region code %v", n.Kind))
          642  +		}
          643  +		tv.b.WriteString(code)
          644  +		if n.Blocks != nil {
          645  +			tv.b.WriteByte(' ')
          646  +			ast.WalkBlockSlice(tv, n.Blocks)
          647  +		}
          648  +		if len(n.Inlines) > 0 {
          649  +			tv.b.WriteString(" (LINE")
          650  +			tv.visitInlineSlice(n.Inlines)
          651  +			tv.b.WriteByte(')')
          652  +		}
          653  +		tv.b.WriteByte(')')
          654  +		tv.visitAttributes(n.Attrs)
          655  +	case *ast.HeadingNode:
          656  +		fmt.Fprintf(&tv.b, "(H%d", n.Level)
          657  +		tv.visitInlineSlice(n.Inlines)
          658  +		tv.b.WriteByte(')')
          659  +		tv.visitAttributes(n.Attrs)
          660  +	case *ast.HRuleNode:
          661  +		tv.b.WriteString("(HR)")
          662  +		tv.visitAttributes(n.Attrs)
          663  +	case *ast.NestedListNode:
          664  +		tv.b.WriteString(mapNestedListKind[n.Kind])
          665  +		for _, item := range n.Items {
          666  +			tv.b.WriteString(" {")
          667  +			ast.WalkItemSlice(tv, item)
          668  +			tv.b.WriteByte('}')
          669  +		}
          670  +		tv.b.WriteByte(')')
          671  +	case *ast.DescriptionListNode:
          672  +		tv.b.WriteString("(DL")
          673  +		for _, def := range n.Descriptions {
          674  +			tv.b.WriteString(" (DT")
          675  +			tv.visitInlineSlice(def.Term)
          676  +			tv.b.WriteByte(')')
          677  +			for _, b := range def.Descriptions {
          678  +				tv.b.WriteString(" (DD ")
          679  +				ast.WalkDescriptionSlice(tv, b)
          680  +				tv.b.WriteByte(')')
          681  +			}
          682  +		}
          683  +		tv.b.WriteByte(')')
          684  +	case *ast.TableNode:
          685  +		tv.b.WriteString("(TAB")
          686  +		if len(n.Header) > 0 {
          687  +			tv.b.WriteString(" (TR")
          688  +			for _, cell := range n.Header {
          689  +				tv.b.WriteString(" (TH")
          690  +				tv.b.WriteString(alignString[cell.Align])
          691  +				tv.visitInlineSlice(cell.Inlines)
          692  +				tv.b.WriteString(")")
          693  +			}
          694  +			tv.b.WriteString(")")
          695  +		}
          696  +		if len(n.Rows) > 0 {
          697  +			tv.b.WriteString(" ")
          698  +			for _, row := range n.Rows {
          699  +				tv.b.WriteString("(TR")
          700  +				for i, cell := range row {
          701  +					if i == 0 {
          702  +						tv.b.WriteString(" ")
          703  +					}
          704  +					tv.b.WriteString("(TD")
          705  +					tv.b.WriteString(alignString[cell.Align])
          706  +					tv.visitInlineSlice(cell.Inlines)
          707  +					tv.b.WriteString(")")
          708  +				}
          709  +				tv.b.WriteString(")")
          710  +			}
          711  +		}
          712  +		tv.b.WriteString(")")
          713  +	case *ast.BLOBNode:
          714  +		tv.b.WriteString("(BLOB ")
          715  +		tv.b.WriteString(n.Syntax)
          716  +		tv.b.WriteString(")")
          717  +	case *ast.TextNode:
          718  +		tv.b.WriteString(n.Text)
          719  +	case *ast.TagNode:
          720  +		tv.b.WriteByte('#')
          721  +		tv.b.WriteString(n.Tag)
          722  +		tv.b.WriteByte('#')
          723  +	case *ast.SpaceNode:
          724  +		if len(n.Lexeme) == 1 {
          725  +			tv.b.WriteString("SP")
          726  +		} else {
          727  +			fmt.Fprintf(&tv.b, "SP%d", len(n.Lexeme))
          728  +		}
          729  +	case *ast.BreakNode:
          730  +		if n.Hard {
          731  +			tv.b.WriteString("HB")
          732  +		} else {
          733  +			tv.b.WriteString("SB")
          734  +		}
          735  +	case *ast.LinkNode:
          736  +		fmt.Fprintf(&tv.b, "(LINK %s", n.Ref)
          737  +		tv.visitInlineSlice(n.Inlines)
          738  +		tv.b.WriteByte(')')
          739  +		tv.visitAttributes(n.Attrs)
          740  +	case *ast.ImageNode:
          741  +		fmt.Fprintf(&tv.b, "(IMAGE %s", n.Ref)
          742  +		tv.visitInlineSlice(n.Inlines)
          743  +		tv.b.WriteByte(')')
          744  +		tv.visitAttributes(n.Attrs)
          745  +	case *ast.CiteNode:
          746  +		fmt.Fprintf(&tv.b, "(CITE %s", n.Key)
          747  +		tv.visitInlineSlice(n.Inlines)
          748  +		tv.b.WriteByte(')')
          749  +		tv.visitAttributes(n.Attrs)
          750  +	case *ast.FootnoteNode:
          751  +		tv.b.WriteString("(FN")
          752  +		tv.visitInlineSlice(n.Inlines)
          753  +		tv.b.WriteByte(')')
          754  +		tv.visitAttributes(n.Attrs)
          755  +	case *ast.MarkNode:
          756  +		tv.b.WriteString("(MARK")
          757  +		if len(n.Text) > 0 {
          758  +			tv.b.WriteByte(' ')
          759  +			tv.b.WriteString(n.Text)
          760  +		}
          761  +		tv.b.WriteByte(')')
          762  +	case *ast.FormatNode:
          763  +		fmt.Fprintf(&tv.b, "{%c", mapFormatKind[n.Kind])
          764  +		tv.visitInlineSlice(n.Inlines)
          765  +		tv.b.WriteByte('}')
          766  +		tv.visitAttributes(n.Attrs)
          767  +	case *ast.LiteralNode:
          768  +		code, ok := mapLiteralKind[n.Kind]
          769  +		if !ok {
          770  +			panic(fmt.Sprintf("No element for code %v", n.Kind))
          771  +		}
          772  +		tv.b.WriteByte('{')
          773  +		tv.b.WriteRune(code)
          774  +		if len(n.Text) > 0 {
          775  +			tv.b.WriteByte(' ')
          776  +			tv.b.WriteString(n.Text)
          777  +		}
          778  +		tv.b.WriteByte('}')
          779  +		tv.visitAttributes(n.Attrs)
          780  +	}
          781  +	return nil
   623    782   }
   624    783   
   625         -var mapVerbatimCode = map[ast.VerbatimCode]string{
          784  +var mapVerbatimKind = map[ast.VerbatimKind]string{
   626    785   	ast.VerbatimProg: "(PROG",
   627    786   }
   628    787   
   629         -func (tv *TestVisitor) VisitVerbatim(vn *ast.VerbatimNode) {
   630         -	code, ok := mapVerbatimCode[vn.Code]
   631         -	if !ok {
   632         -		panic(fmt.Sprintf("Unknown verbatim code %v", vn.Code))
   633         -	}
   634         -	tv.b.WriteString(code)
   635         -	for _, line := range vn.Lines {
   636         -		tv.b.WriteByte('\n')
   637         -		tv.b.WriteString(line)
   638         -	}
   639         -	tv.b.WriteByte(')')
   640         -	tv.visitAttributes(vn.Attrs)
   641         -}
   642         -
   643         -var mapRegionCode = map[ast.RegionCode]string{
          788  +var mapRegionKind = map[ast.RegionKind]string{
   644    789   	ast.RegionSpan:  "(SPAN",
   645    790   	ast.RegionQuote: "(QUOTE",
   646    791   	ast.RegionVerse: "(VERSE",
   647    792   }
   648    793   
   649         -// VisitRegion stores information about a region.
   650         -func (tv *TestVisitor) VisitRegion(rn *ast.RegionNode) {
   651         -	code, ok := mapRegionCode[rn.Code]
   652         -	if !ok {
   653         -		panic(fmt.Sprintf("Unknown region code %v", rn.Code))
   654         -	}
   655         -	tv.b.WriteString(code)
   656         -	if rn.Blocks != nil {
   657         -		tv.b.WriteByte(' ')
   658         -		tv.visitBlockSlice(rn.Blocks)
   659         -	}
   660         -	if len(rn.Inlines) > 0 {
   661         -		tv.b.WriteString(" (LINE")
   662         -		tv.visitInlineSlice(rn.Inlines)
   663         -		tv.b.WriteByte(')')
   664         -	}
   665         -	tv.b.WriteByte(')')
   666         -	tv.visitAttributes(rn.Attrs)
   667         -}
   668         -
   669         -func (tv *TestVisitor) VisitHeading(hn *ast.HeadingNode) {
   670         -	fmt.Fprintf(&tv.b, "(H%d", hn.Level)
   671         -	tv.visitInlineSlice(hn.Inlines)
   672         -	tv.b.WriteByte(')')
   673         -	tv.visitAttributes(hn.Attrs)
   674         -}
   675         -func (tv *TestVisitor) VisitHRule(hn *ast.HRuleNode) {
   676         -	tv.b.WriteString("(HR)")
   677         -	tv.visitAttributes(hn.Attrs)
   678         -}
   679         -
   680         -var mapNestedListCode = map[ast.NestedListCode]string{
          794  +var mapNestedListKind = map[ast.NestedListKind]string{
   681    795   	ast.NestedListOrdered:   "(OL",
   682    796   	ast.NestedListUnordered: "(UL",
   683    797   	ast.NestedListQuote:     "(QL",
   684    798   }
   685    799   
   686         -func (tv *TestVisitor) VisitNestedList(ln *ast.NestedListNode) {
   687         -	tv.b.WriteString(mapNestedListCode[ln.Code])
   688         -	for _, item := range ln.Items {
   689         -		tv.b.WriteString(" {")
   690         -		tv.visitItemSlice(item)
   691         -		tv.b.WriteByte('}')
   692         -	}
   693         -	tv.b.WriteByte(')')
   694         -}
   695         -func (tv *TestVisitor) VisitDescriptionList(dn *ast.DescriptionListNode) {
   696         -	tv.b.WriteString("(DL")
   697         -	for _, def := range dn.Descriptions {
   698         -		tv.b.WriteString(" (DT")
   699         -		tv.visitInlineSlice(def.Term)
   700         -		tv.b.WriteByte(')')
   701         -		for _, b := range def.Descriptions {
   702         -			tv.b.WriteString(" (DD ")
   703         -			tv.visitDescriptionSlice(b)
   704         -			tv.b.WriteByte(')')
   705         -		}
   706         -	}
   707         -	tv.b.WriteByte(')')
   708         -}
   709         -
   710    800   var alignString = map[ast.Alignment]string{
   711    801   	ast.AlignDefault: "",
   712    802   	ast.AlignLeft:    "l",
   713    803   	ast.AlignCenter:  "c",
   714    804   	ast.AlignRight:   "r",
   715    805   }
   716    806   
   717         -// VisitTable emits a HTML table.
   718         -func (tv *TestVisitor) VisitTable(tn *ast.TableNode) {
   719         -	tv.b.WriteString("(TAB")
   720         -	if len(tn.Header) > 0 {
   721         -		tv.b.WriteString(" (TR")
   722         -		for _, cell := range tn.Header {
   723         -			tv.b.WriteString(" (TH")
   724         -			tv.b.WriteString(alignString[cell.Align])
   725         -			tv.visitInlineSlice(cell.Inlines)
   726         -			tv.b.WriteString(")")
   727         -		}
   728         -		tv.b.WriteString(")")
   729         -	}
   730         -	if len(tn.Rows) > 0 {
   731         -		tv.b.WriteString(" ")
   732         -		for _, row := range tn.Rows {
   733         -			tv.b.WriteString("(TR")
   734         -			for i, cell := range row {
   735         -				if i == 0 {
   736         -					tv.b.WriteString(" ")
   737         -				}
   738         -				tv.b.WriteString("(TD")
   739         -				tv.b.WriteString(alignString[cell.Align])
   740         -				tv.visitInlineSlice(cell.Inlines)
   741         -				tv.b.WriteString(")")
   742         -			}
   743         -			tv.b.WriteString(")")
   744         -		}
   745         -	}
   746         -	tv.b.WriteString(")")
   747         -}
   748         -
   749         -func (tv *TestVisitor) VisitBLOB(bn *ast.BLOBNode) {
   750         -	tv.b.WriteString("(BLOB ")
   751         -	tv.b.WriteString(bn.Syntax)
   752         -	tv.b.WriteString(")")
   753         -}
   754         -
   755         -func (tv *TestVisitor) VisitText(tn *ast.TextNode) {
   756         -	tv.b.WriteString(tn.Text)
   757         -}
   758         -func (tv *TestVisitor) VisitTag(tn *ast.TagNode) {
   759         -	tv.b.WriteByte('#')
   760         -	tv.b.WriteString(tn.Tag)
   761         -	tv.b.WriteByte('#')
   762         -}
   763         -func (tv *TestVisitor) VisitSpace(sn *ast.SpaceNode) {
   764         -	if len(sn.Lexeme) == 1 {
   765         -		tv.b.WriteString("SP")
   766         -	} else {
   767         -		fmt.Fprintf(&tv.b, "SP%d", len(sn.Lexeme))
   768         -	}
   769         -}
   770         -func (tv *TestVisitor) VisitBreak(bn *ast.BreakNode) {
   771         -	if bn.Hard {
   772         -		tv.b.WriteString("HB")
   773         -	} else {
   774         -		tv.b.WriteString("SB")
   775         -	}
   776         -}
   777         -func (tv *TestVisitor) VisitLink(tn *ast.LinkNode) {
   778         -	fmt.Fprintf(&tv.b, "(LINK %s", tn.Ref)
   779         -	tv.visitInlineSlice(tn.Inlines)
   780         -	tv.b.WriteByte(')')
   781         -	tv.visitAttributes(tn.Attrs)
   782         -}
   783         -func (tv *TestVisitor) VisitImage(in *ast.ImageNode) {
   784         -	fmt.Fprintf(&tv.b, "(IMAGE %s", in.Ref)
   785         -	tv.visitInlineSlice(in.Inlines)
   786         -	tv.b.WriteByte(')')
   787         -	tv.visitAttributes(in.Attrs)
   788         -}
   789         -func (tv *TestVisitor) VisitCite(cn *ast.CiteNode) {
   790         -	fmt.Fprintf(&tv.b, "(CITE %s", cn.Key)
   791         -	tv.visitInlineSlice(cn.Inlines)
   792         -	tv.b.WriteByte(')')
   793         -	tv.visitAttributes(cn.Attrs)
   794         -}
   795         -func (tv *TestVisitor) VisitFootnote(fn *ast.FootnoteNode) {
   796         -	tv.b.WriteString("(FN")
   797         -	tv.visitInlineSlice(fn.Inlines)
   798         -	tv.b.WriteByte(')')
   799         -	tv.visitAttributes(fn.Attrs)
   800         -}
   801         -func (tv *TestVisitor) VisitMark(mn *ast.MarkNode) {
   802         -	tv.b.WriteString("(MARK")
   803         -	if len(mn.Text) > 0 {
   804         -		tv.b.WriteByte(' ')
   805         -		tv.b.WriteString(mn.Text)
   806         -	}
   807         -	tv.b.WriteByte(')')
   808         -}
   809         -
   810         -var mapCode = map[ast.FormatCode]rune{
          807  +var mapFormatKind = map[ast.FormatKind]rune{
   811    808   	ast.FormatItalic:    '/',
   812    809   	ast.FormatBold:      '*',
   813    810   	ast.FormatUnder:     '_',
   814    811   	ast.FormatStrike:    '~',
   815    812   	ast.FormatMonospace: '\'',
   816    813   	ast.FormatSuper:     '^',
   817    814   	ast.FormatSub:       ',',
   818    815   	ast.FormatQuote:     '"',
   819    816   	ast.FormatQuotation: '<',
   820    817   	ast.FormatSmall:     ';',
   821    818   	ast.FormatSpan:      ':',
   822    819   }
   823    820   
   824         -func (tv *TestVisitor) VisitFormat(fn *ast.FormatNode) {
   825         -	fmt.Fprintf(&tv.b, "{%c", mapCode[fn.Code])
   826         -	tv.visitInlineSlice(fn.Inlines)
   827         -	tv.b.WriteByte('}')
   828         -	tv.visitAttributes(fn.Attrs)
   829         -}
   830         -
   831         -var mapLiteralCode = map[ast.LiteralCode]rune{
          821  +var mapLiteralKind = map[ast.LiteralKind]rune{
   832    822   	ast.LiteralProg:    '`',
   833    823   	ast.LiteralKeyb:    '+',
   834    824   	ast.LiteralOutput:  '=',
   835    825   	ast.LiteralComment: '%',
   836    826   }
   837    827   
   838         -func (tv *TestVisitor) VisitLiteral(ln *ast.LiteralNode) {
   839         -	code, ok := mapLiteralCode[ln.Code]
   840         -	if !ok {
   841         -		panic(fmt.Sprintf("No element for code %v", ln.Code))
   842         -	}
   843         -	tv.b.WriteByte('{')
   844         -	tv.b.WriteRune(code)
   845         -	if len(ln.Text) > 0 {
   846         -		tv.b.WriteByte(' ')
   847         -		tv.b.WriteString(ln.Text)
   848         -	}
   849         -	tv.b.WriteByte('}')
   850         -	tv.visitAttributes(ln.Attrs)
   851         -}
   852         -func (tv *TestVisitor) visitBlockSlice(bns ast.BlockSlice) {
   853         -	for _, bn := range bns {
   854         -		bn.Accept(tv)
   855         -	}
   856         -}
   857         -func (tv *TestVisitor) visitItemSlice(ins ast.ItemSlice) {
   858         -	for _, in := range ins {
   859         -		in.Accept(tv)
   860         -	}
   861         -}
   862         -func (tv *TestVisitor) visitDescriptionSlice(dns ast.DescriptionSlice) {
   863         -	for _, dn := range dns {
   864         -		dn.Accept(tv)
   865         -	}
   866         -}
   867    828   func (tv *TestVisitor) visitInlineSlice(ins ast.InlineSlice) {
   868    829   	for _, in := range ins {
   869    830   		tv.b.WriteByte(' ')
   870         -		in.Accept(tv)
          831  +		ast.Walk(tv, in)
   871    832   	}
   872    833   }
          834  +
   873    835   func (tv *TestVisitor) visitAttributes(a *ast.Attributes) {
   874    836   	if a == nil || len(a.Attrs) == 0 {
   875    837   		return
   876    838   	}
   877    839   	tv.b.WriteString("[ATTR")
   878    840   
   879    841   	keys := make([]string, 0, len(a.Attrs))

Changes to place/constplace/constplace.go.

    24     24   	"zettelstore.de/z/search"
    25     25   )
    26     26   
    27     27   func init() {
    28     28   	manager.Register(
    29     29   		" const",
    30     30   		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
    31         -			return &constPlace{zettel: constZettelMap, enricher: cdata.Enricher}, nil
           31  +			return &constPlace{
           32  +				number:   cdata.Number,
           33  +				zettel:   constZettelMap,
           34  +				enricher: cdata.Enricher,
           35  +			}, nil
    32     36   		})
    33     37   }
    34     38   
    35     39   type constHeader map[string]string
    36     40   
    37     41   func makeMeta(zid id.Zid, h constHeader) *meta.Meta {
    38     42   	m := meta.New(zid)
................................................................................
    44     48   
    45     49   type constZettel struct {
    46     50   	header  constHeader
    47     51   	content domain.Content
    48     52   }
    49     53   
    50     54   type constPlace struct {
           55  +	number   int
    51     56   	zettel   map[id.Zid]constZettel
    52     57   	enricher place.Enricher
    53     58   }
    54     59   
    55     60   func (cp *constPlace) Location() string {
    56     61   	return "const:"
    57     62   }
................................................................................
    83     88   	}
    84     89   	return result, nil
    85     90   }
    86     91   
    87     92   func (cp *constPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
    88     93   	for zid, zettel := range cp.zettel {
    89     94   		m := makeMeta(zid, zettel.header)
    90         -		cp.enricher.Enrich(ctx, m)
           95  +		cp.enricher.Enrich(ctx, m, cp.number)
    91     96   		if match(m) {
    92     97   			res = append(res, m)
    93     98   		}
    94     99   	}
    95    100   	return res, nil
    96    101   }
    97    102   

Changes to place/constplace/listzettel.mustache.

     1         -<nav>
     2      1   <header>
     3      2   <h1>{{Title}}</h1>
     4      3   </header>
     5      4   <ul>
     6      5   {{#Metas}}<li><a href="{{{URL}}}">{{{Text}}}</a></li>
     7      6   {{/Metas}}</ul>
     8         -{{#HasPrevNext}}
     9         -<p>
    10         -{{#HasPrev}}
    11         -<a href="{{{PrevURL}}}" rel="prev">Prev</a>
    12         -{{#HasNext}},{{/HasNext}}
    13         -{{/HasPrev}}
    14         -{{#HasNext}}
    15         -<a href="{{{NextURL}}}" rel="next">Next</a>
    16         -{{/HasNext}}
    17         -</p>
    18         -{{/HasPrevNext}}
    19         -</nav>

Changes to place/dirplace/dirplace.go.

    36     36   	manager.Register("dir", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
    37     37   		path := getDirPath(u)
    38     38   		if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
    39     39   			return nil, err
    40     40   		}
    41     41   		dirSrvSpec, defWorker, maxWorker := getDirSrvInfo(u.Query().Get("type"))
    42     42   		dp := dirPlace{
           43  +			number:     cdata.Number,
    43     44   			location:   u.String(),
    44     45   			readonly:   getQueryBool(u, "readonly"),
    45     46   			cdata:      *cdata,
    46     47   			dir:        path,
    47     48   			dirRescan:  time.Duration(getQueryInt(u, "rescan", 60, 3600, 30*24*60*60)) * time.Second,
    48     49   			dirSrvSpec: dirSrvSpec,
    49     50   			fSrvs:      uint32(getQueryInt(u, "worker", 1, defWorker, maxWorker)),
................................................................................
    89     90   		return max
    90     91   	}
    91     92   	return iVal
    92     93   }
    93     94   
    94     95   // dirPlace uses a directory to store zettel as files.
    95     96   type dirPlace struct {
           97  +	number     int
    96     98   	location   string
    97     99   	readonly   bool
    98    100   	cdata      manager.ConnectData
    99    101   	dir        string
   100    102   	dirRescan  time.Duration
   101    103   	dirSrvSpec directoryServiceSpec
   102    104   	dirSrv     directory.Service
................................................................................
   142    144   			chci <- place.UpdateInfo{Reason: reason, Zid: zid}
   143    145   		}
   144    146   	}
   145    147   }
   146    148   
   147    149   func (dp *dirPlace) getFileChan(zid id.Zid) chan fileCmd {
   148    150   	// Based on https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
   149         -	var sum uint32 = 2166136261 ^ uint32(zid)
          151  +	sum := 2166136261 ^ uint32(zid)
   150    152   	sum *= 16777619
   151    153   	sum ^= uint32(zid >> 32)
   152    154   	sum *= 16777619
   153    155   
   154    156   	dp.mxCmds.RLock()
   155    157   	defer dp.mxCmds.RUnlock()
   156    158   	return dp.fCmds[sum%dp.fSrvs]
................................................................................
   230    232   	for _, entry := range entries {
   231    233   		m, err1 := getMeta(dp, entry, entry.Zid)
   232    234   		err = err1
   233    235   		if err != nil {
   234    236   			continue
   235    237   		}
   236    238   		dp.cleanupMeta(ctx, m)
   237         -		dp.cdata.Enricher.Enrich(ctx, m)
          239  +		dp.cdata.Enricher.Enrich(ctx, m, dp.number)
   238    240   
   239    241   		if match(m) {
   240    242   			res = append(res, m)
   241    243   		}
   242    244   	}
   243    245   	if err != nil {
   244    246   		return nil, err

Changes to place/fileplace/fileplace.go.

    26     26   func init() {
    27     27   	manager.Register("file", func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
    28     28   		path := getFilepathFromURL(u)
    29     29   		ext := strings.ToLower(filepath.Ext(path))
    30     30   		if ext != ".zip" {
    31     31   			return nil, errors.New("unknown extension '" + ext + "' in place URL: " + u.String())
    32     32   		}
    33         -		return &zipPlace{name: path, enricher: cdata.Enricher}, nil
           33  +		return &zipPlace{
           34  +			number:   cdata.Number,
           35  +			name:     path,
           36  +			enricher: cdata.Enricher,
           37  +		}, nil
    34     38   	})
    35     39   }
    36     40   
    37     41   func getFilepathFromURL(u *url.URL) string {
    38     42   	name := u.Opaque
    39     43   	if name == "" {
    40     44   		name = u.Path

Changes to place/fileplace/zipplace.go.

    36     36   	metaName     string
    37     37   	contentName  string
    38     38   	contentExt   string // (normalized) file extension of zettel content
    39     39   	metaInHeader bool
    40     40   }
    41     41   
    42     42   type zipPlace struct {
           43  +	number   int
    43     44   	name     string
    44     45   	enricher place.Enricher
    45     46   	zettel   map[id.Zid]*zipEntry // no lock needed, because read-only after creation
    46     47   }
    47     48   
    48     49   func (zp *zipPlace) Location() string {
    49     50   	if strings.HasPrefix(zp.name, "/") {
................................................................................
   175    176   	}
   176    177   	defer reader.Close()
   177    178   	for zid, entry := range zp.zettel {
   178    179   		m, err := readZipMeta(reader, zid, entry)
   179    180   		if err != nil {
   180    181   			continue
   181    182   		}
   182         -		zp.enricher.Enrich(ctx, m)
          183  +		zp.enricher.Enrich(ctx, m, zp.number)
   183    184   		if match(m) {
   184    185   			res = append(res, m)
   185    186   		}
   186    187   	}
   187    188   	return res, nil
   188    189   }
   189    190   

Changes to place/manager/collect.go.

    29     29   func (data *collectData) initialize() {
    30     30   	data.refs = id.NewSet()
    31     31   	data.words = store.NewWordSet()
    32     32   	data.urls = store.NewWordSet()
    33     33   }
    34     34   
    35     35   func collectZettelIndexData(zn *ast.ZettelNode, data *collectData) {
    36         -	ast.NewTopDownTraverser(data).VisitBlockSlice(zn.Ast)
           36  +	ast.WalkBlockSlice(data, zn.Ast)
    37     37   }
    38     38   
    39     39   func collectInlineIndexData(ins ast.InlineSlice, data *collectData) {
    40         -	ast.NewTopDownTraverser(data).VisitInlineSlice(ins)
           40  +	ast.WalkInlineSlice(data, ins)
           41  +}
           42  +
           43  +func (data *collectData) Visit(node ast.Node) ast.Visitor {
           44  +	switch n := node.(type) {
           45  +	case *ast.VerbatimNode:
           46  +		for _, line := range n.Lines {
           47  +			data.addText(line)
           48  +		}
           49  +	case *ast.TextNode:
           50  +		data.addText(n.Text)
           51  +	case *ast.TagNode:
           52  +		data.addText(n.Tag)
           53  +	case *ast.LinkNode:
           54  +		data.addRef(n.Ref)
           55  +	case *ast.ImageNode:
           56  +		data.addRef(n.Ref)
           57  +	case *ast.LiteralNode:
           58  +		data.addText(n.Text)
           59  +	}
           60  +	return data
    41     61   }
    42     62   
    43         -// VisitVerbatim collects the verbatim text in the word set.
    44         -func (data *collectData) VisitVerbatim(vn *ast.VerbatimNode) {
    45         -	for _, line := range vn.Lines {
    46         -		data.addText(line)
           63  +func (data *collectData) addText(s string) {
           64  +	for _, word := range strfun.NormalizeWords(s) {
           65  +		data.words.Add(word)
    47     66   	}
    48     67   }
    49     68   
    50         -// VisitRegion does nothing.
    51         -func (data *collectData) VisitRegion(rn *ast.RegionNode) {}
    52         -
    53         -// VisitHeading does nothing.
    54         -func (data *collectData) VisitHeading(hn *ast.HeadingNode) {}
    55         -
    56         -// VisitHRule does nothing.
    57         -func (data *collectData) VisitHRule(hn *ast.HRuleNode) {}
    58         -
    59         -// VisitList does nothing.
    60         -func (data *collectData) VisitNestedList(ln *ast.NestedListNode) {}
    61         -
    62         -// VisitDescriptionList does nothing.
    63         -func (data *collectData) VisitDescriptionList(dn *ast.DescriptionListNode) {}
    64         -
    65         -// VisitPara does nothing.
    66         -func (data *collectData) VisitPara(pn *ast.ParaNode) {}
    67         -
    68         -// VisitTable does nothing.
    69         -func (data *collectData) VisitTable(tn *ast.TableNode) {}
    70         -
    71         -// VisitBLOB does nothing.
    72         -func (data *collectData) VisitBLOB(bn *ast.BLOBNode) {}
    73         -
    74         -// VisitText collects the text in the word set.
    75         -func (data *collectData) VisitText(tn *ast.TextNode) {
    76         -	data.addText(tn.Text)
    77         -}
    78         -
    79         -// VisitTag collects the tag name in the word set.
    80         -func (data *collectData) VisitTag(tn *ast.TagNode) {
    81         -	data.addText(tn.Tag)
    82         -}
    83         -
    84         -// VisitSpace does nothing.
    85         -func (data *collectData) VisitSpace(sn *ast.SpaceNode) {}
    86         -
    87         -// VisitBreak does nothing.
    88         -func (data *collectData) VisitBreak(bn *ast.BreakNode) {}
    89         -
    90         -// VisitLink collects the given link as a reference.
    91         -func (data *collectData) VisitLink(ln *ast.LinkNode) {
    92         -	ref := ln.Ref
           69  +func (data *collectData) addRef(ref *ast.Reference) {
    93     70   	if ref == nil {
    94     71   		return
    95     72   	}
    96     73   	if ref.IsExternal() {
    97     74   		data.urls.Add(strings.ToLower(ref.Value))
    98     75   	}
    99     76   	if !ref.IsZettel() {
   100     77   		return
   101     78   	}
   102     79   	if zid, err := id.Parse(ref.URL.Path); err == nil {
   103     80   		data.refs[zid] = true
   104     81   	}
   105     82   }
   106         -
   107         -// VisitImage collects the image links as a reference.
   108         -func (data *collectData) VisitImage(in *ast.ImageNode) {
   109         -	ref := in.Ref
   110         -	if ref == nil {
   111         -		return
   112         -	}
   113         -	if ref.IsExternal() {
   114         -		data.urls.Add(strings.ToLower(ref.Value))
   115         -	}
   116         -	if !ref.IsZettel() {
   117         -		return
   118         -	}
   119         -	if zid, err := id.Parse(ref.URL.Path); err == nil {
   120         -		data.refs[zid] = true
   121         -	}
   122         -}
   123         -
   124         -// VisitCite does nothing.
   125         -func (data *collectData) VisitCite(cn *ast.CiteNode) {}
   126         -
   127         -// VisitFootnote does nothing.
   128         -func (data *collectData) VisitFootnote(fn *ast.FootnoteNode) {}
   129         -
   130         -// VisitMark does nothing.
   131         -func (data *collectData) VisitMark(mn *ast.MarkNode) {}
   132         -
   133         -// VisitFormat does nothing.
   134         -func (data *collectData) VisitFormat(fn *ast.FormatNode) {}
   135         -
   136         -// VisitLiteral collects the literal words in the word set.
   137         -func (data *collectData) VisitLiteral(ln *ast.LiteralNode) {
   138         -	data.addText(ln.Text)
   139         -}
   140         -
   141         -func (data *collectData) addText(s string) {
   142         -	for _, word := range strfun.NormalizeWords(s) {
   143         -		data.words.Add(word)
   144         -	}
   145         -}

Changes to place/manager/enrich.go.

     9      9   //-----------------------------------------------------------------------------
    10     10   
    11     11   // Package manager coordinates the various places and indexes of a Zettelstore.
    12     12   package manager
    13     13   
    14     14   import (
    15     15   	"context"
           16  +	"strconv"
    16     17   
    17     18   	"zettelstore.de/z/domain/meta"
    18     19   	"zettelstore.de/z/place"
    19     20   )
    20     21   
    21     22   // Enrich computes additional properties and updates the given metadata.
    22         -func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta) {
           23  +func (mgr *Manager) Enrich(ctx context.Context, m *meta.Meta, placeNumber int) {
    23     24   	if place.DoNotEnrich(ctx) {
    24     25   		// Enrich is called indirectly via indexer or enrichment is not requested
    25     26   		// because of other reasons -> ignore this call, do not update meta data
    26     27   		return
    27     28   	}
           29  +	m.Set(meta.KeyPlaceNumber, strconv.Itoa(placeNumber))
    28     30   	computePublished(m)
    29     31   	mgr.idxStore.Enrich(ctx, m)
    30     32   }
    31     33   
    32     34   func computePublished(m *meta.Meta) {
    33     35   	if _, ok := m.Get(meta.KeyPublished); ok {
    34     36   		return

Changes to place/manager/indexer.go.

    89     89   				mgr.idxAr.Reload(nil, zids)
    90     90   				mgr.idxMx.Lock()
    91     91   				mgr.idxLastReload = time.Now()
    92     92   				mgr.idxSinceReload = 0
    93     93   				mgr.idxMx.Unlock()
    94     94   			}
    95     95   		case arUpdate:
    96         -			changed = true
    97         -			mgr.idxMx.Lock()
    98         -			mgr.idxSinceReload++
    99         -			mgr.idxMx.Unlock()
   100     96   			zettel, err := mgr.GetZettel(ctx, zid)
   101     97   			if err != nil {
   102     98   				// TODO: on some errors put the zid into a "try later" set
   103     99   				continue
   104    100   			}
          101  +			changed = true
          102  +			mgr.idxMx.Lock()
          103  +			mgr.idxSinceReload++
          104  +			mgr.idxMx.Unlock()
   105    105   			mgr.idxUpdateZettel(ctx, zettel)
   106    106   		case arDelete:
          107  +			if _, err := mgr.GetMeta(ctx, zid); err == nil {
          108  +				// Zettel was not deleted. This might occur, if zettel was
          109  +				// deleted in secondary dirplace, but is still present in
          110  +				// first dirplace (or vice versa). Re-index zettel in case
          111  +				// a hidden zettel was recovered
          112  +				mgr.idxAr.Enqueue(zid, arUpdate)
          113  +			}
   107    114   			changed = true
   108    115   			mgr.idxMx.Lock()
   109    116   			mgr.idxSinceReload++
   110    117   			mgr.idxMx.Unlock()
   111    118   			mgr.idxDeleteZettel(zid)
   112    119   		}
   113    120   	}

Changes to place/manager/manager.go.

    28     28   	"zettelstore.de/z/place"
    29     29   	"zettelstore.de/z/place/manager/memstore"
    30     30   	"zettelstore.de/z/place/manager/store"
    31     31   )
    32     32   
    33     33   // ConnectData contains all administration related values.
    34     34   type ConnectData struct {
           35  +	Number   int // number of the place, starting with 1.
    35     36   	Config   config.Config
    36     37   	Enricher place.Enricher
    37     38   	Notify   chan<- place.UpdateInfo
    38     39   }
    39     40   
    40     41   // Connect returns a handle to the specified place
    41     42   func Connect(u *url.URL, authManager auth.BaseManager, cdata *ConnectData) (place.ManagedPlace, error) {
................................................................................
   124    125   		infos:        make(chan place.UpdateInfo, len(placeURIs)*10),
   125    126   		propertyKeys: propertyKeys,
   126    127   
   127    128   		idxStore: memstore.New(),
   128    129   		idxAr:    newAnterooms(10),
   129    130   		idxReady: make(chan struct{}, 1),
   130    131   	}
   131         -	cdata := ConnectData{Config: rtConfig, Enricher: mgr, Notify: mgr.infos}
          132  +	cdata := ConnectData{Number: 1, Config: rtConfig, Enricher: mgr, Notify: mgr.infos}
   132    133   	subplaces := make([]place.ManagedPlace, 0, len(placeURIs)+2)
   133    134   	for _, uri := range placeURIs {
   134    135   		p, err := Connect(uri, authManager, &cdata)
   135    136   		if err != nil {
   136    137   			return nil, err
   137    138   		}
   138    139   		if p != nil {
   139    140   			subplaces = append(subplaces, p)
          141  +			cdata.Number++
   140    142   		}
   141    143   	}
   142    144   	constplace, err := registry[" const"](nil, &cdata)
   143    145   	if err != nil {
   144    146   		return nil, err
   145    147   	}
          148  +	cdata.Number++
   146    149   	progplace, err := registry[" prog"](nil, &cdata)
   147    150   	if err != nil {
   148    151   		return nil, err
   149    152   	}
          153  +	cdata.Number++
   150    154   	subplaces = append(subplaces, constplace, progplace)
   151    155   	mgr.subplaces = subplaces
   152    156   	return mgr, nil
   153    157   }
   154    158   
   155    159   // RegisterObserver registers an observer that will be notified
   156    160   // if a zettel was found to be changed.

Changes to place/manager/place.go.

    61     61   // GetZettel retrieves a specific zettel.
    62     62   func (mgr *Manager) GetZettel(ctx context.Context, zid id.Zid) (domain.Zettel, error) {
    63     63   	mgr.mgrMx.RLock()
    64     64   	defer mgr.mgrMx.RUnlock()
    65     65   	if !mgr.started {
    66     66   		return domain.Zettel{}, place.ErrStopped
    67     67   	}
    68         -	for _, p := range mgr.subplaces {
           68  +	for i, p := range mgr.subplaces {
    69     69   		if z, err := p.GetZettel(ctx, zid); err != place.ErrNotFound {
    70     70   			if err == nil {
    71         -				mgr.Enrich(ctx, z.Meta)
           71  +				mgr.Enrich(ctx, z.Meta, i+1)
    72     72   			}
    73     73   			return z, err
    74     74   		}
    75     75   	}
    76     76   	return domain.Zettel{}, place.ErrNotFound
    77     77   }
    78     78   
................................................................................
    79     79   // GetMeta retrieves just the meta data of a specific zettel.
    80     80   func (mgr *Manager) GetMeta(ctx context.Context, zid id.Zid) (*meta.Meta, error) {
    81     81   	mgr.mgrMx.RLock()
    82     82   	defer mgr.mgrMx.RUnlock()
    83     83   	if !mgr.started {
    84     84   		return nil, place.ErrStopped
    85     85   	}
    86         -	for _, p := range mgr.subplaces {
           86  +	for i, p := range mgr.subplaces {
    87     87   		if m, err := p.GetMeta(ctx, zid); err != place.ErrNotFound {
    88     88   			if err == nil {
    89         -				mgr.Enrich(ctx, m)
           89  +				mgr.Enrich(ctx, m, i+1)
    90     90   			}
    91     91   			return m, err
    92     92   		}
    93     93   	}
    94     94   	return nil, place.ErrNotFound
    95     95   }
    96     96   

Changes to place/manager/store/store.go.

    12     12   package store
    13     13   
    14     14   import (
    15     15   	"context"
    16     16   	"io"
    17     17   
    18     18   	"zettelstore.de/z/domain/id"
    19         -	"zettelstore.de/z/place"
           19  +	"zettelstore.de/z/domain/meta"
    20     20   	"zettelstore.de/z/search"
    21     21   )
    22     22   
    23     23   // Stats records statistics about the store.
    24     24   type Stats struct {
    25     25   	// Zettel is the number of zettel managed by the indexer.
    26     26   	Zettel int
................................................................................
    34     34   	// Urls count the different URLs stored in the store.
    35     35   	Urls uint64
    36     36   }
    37     37   
    38     38   // Store all relevant zettel data. There may be multiple implementations, i.e.
    39     39   // memory-based, file-based, based on SQLite, ...
    40     40   type Store interface {
    41         -	place.Enricher
    42     41   	search.Selector
           42  +
           43  +	// Entrich metadata with data from store.
           44  +	Enrich(ctx context.Context, m *meta.Meta)
    43     45   
    44     46   	// UpdateReferences for a specific zettel.
    45     47   	// Returns set of zettel identifier that must also be checked for changes.
    46     48   	UpdateReferences(context.Context, *ZettelIndex) id.Set
    47     49   
    48     50   	// DeleteZettel removes index data for given zettel.
    49     51   	// Returns set of zettel identifier that must also be checked for changes.

Changes to place/memplace/memplace.go.

   116    116   }
   117    117   
   118    118   func (mp *memPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) ([]*meta.Meta, error) {
   119    119   	result := make([]*meta.Meta, 0, len(mp.zettel))
   120    120   	mp.mx.RLock()
   121    121   	for _, zettel := range mp.zettel {
   122    122   		m := zettel.Meta.Clone()
   123         -		mp.cdata.Enricher.Enrich(ctx, m)
          123  +		mp.cdata.Enricher.Enrich(ctx, m, mp.cdata.Number)
   124    124   		if match(m) {
   125    125   			result = append(result, m)
   126    126   		}
   127    127   	}
   128    128   	mp.mx.RUnlock()
   129    129   	return result, nil
   130    130   }

Changes to place/place.go.

   179    179   	RegisterObserver(UpdateFunc)
   180    180   }
   181    181   
   182    182   // Enricher is used to update metadata by adding new properties.
   183    183   type Enricher interface {
   184    184   	// Enrich computes additional properties and updates the given metadata.
   185    185   	// It is typically called by zettel reading methods.
   186         -	Enrich(ctx context.Context, m *meta.Meta)
          186  +	Enrich(ctx context.Context, m *meta.Meta, placeNumber int)
   187    187   }
   188    188   
   189    189   // NoEnrichContext will signal an enricher that nothing has to be done.
   190    190   // This is useful for an Indexer, but also for some place.Place calls, when
   191    191   // just the plain metadata is needed.
   192    192   func NoEnrichContext(ctx context.Context) context.Context {
   193    193   	return context.WithValue(ctx, ctxNoEnrichKey, &ctxNoEnrichKey)

Changes to place/progplace/progplace.go.

    24     24   	"zettelstore.de/z/search"
    25     25   )
    26     26   
    27     27   func init() {
    28     28   	manager.Register(
    29     29   		" prog",
    30     30   		func(u *url.URL, cdata *manager.ConnectData) (place.ManagedPlace, error) {
    31         -			return getPlace(cdata.Enricher), nil
           31  +			return getPlace(cdata.Number, cdata.Enricher), nil
    32     32   		})
    33     33   }
    34     34   
    35     35   type progPlace struct {
           36  +	number int
    36     37   	filter place.Enricher
    37     38   }
    38     39   
    39     40   var myConfig *meta.Meta
    40     41   var myZettel = map[id.Zid]struct {
    41     42   	meta    func(id.Zid) *meta.Meta
    42     43   	content func(*meta.Meta) string
................................................................................
    46     47   	id.OperatingSystemZid:      {genVersionOSM, genVersionOSC},
    47     48   	id.PlaceManagerZid:         {genManagerM, genManagerC},
    48     49   	id.MetadataKeyZid:          {genKeysM, genKeysC},
    49     50   	id.StartupConfigurationZid: {genConfigZettelM, genConfigZettelC},
    50     51   }
    51     52   
    52     53   // Get returns the one program place.
    53         -func getPlace(mf place.Enricher) place.ManagedPlace {
    54         -	return &progPlace{filter: mf}
           54  +func getPlace(placeNumber int, mf place.Enricher) place.ManagedPlace {
           55  +	return &progPlace{number: placeNumber, filter: mf}
    55     56   }
    56     57   
    57     58   // Setup remembers important values.
    58     59   func Setup(cfg *meta.Meta) { myConfig = cfg.Clone() }
    59     60   
    60     61   func (pp *progPlace) Location() string { return "" }
    61     62   
................................................................................
   107    108   }
   108    109   
   109    110   func (pp *progPlace) SelectMeta(ctx context.Context, match search.MetaMatchFunc) (res []*meta.Meta, err error) {
   110    111   	for zid, gen := range myZettel {
   111    112   		if genMeta := gen.meta; genMeta != nil {
   112    113   			if m := genMeta(zid); m != nil {
   113    114   				updateMeta(m)
   114         -				pp.filter.Enrich(ctx, m)
          115  +				pp.filter.Enrich(ctx, m, pp.number)
   115    116   				if match(m) {
   116    117   					res = append(res, m)
   117    118   				}
   118    119   			}
   119    120   		}
   120    121   	}
   121    122   	return res, nil

Changes to tests/regression_test.go.

    65     65   		}
    66     66   	}
    67     67   	return root, places
    68     68   }
    69     69   
    70     70   type noEnrich struct{}
    71     71   
    72         -func (nf *noEnrich) Enrich(ctx context.Context, m *meta.Meta) {}
    73         -func (nf *noEnrich) Remove(ctx context.Context, m *meta.Meta) {}
           72  +func (nf *noEnrich) Enrich(context.Context, *meta.Meta, int) {}
           73  +func (nf *noEnrich) Remove(context.Context, *meta.Meta)      {}
    74     74   
    75     75   type noAuth struct{}
    76     76   
    77     77   func (na *noAuth) IsReadonly() bool { return false }
    78     78   
    79     79   func trimLastEOL(s string) string {
    80     80   	if lastPos := len(s) - 1; lastPos >= 0 && s[lastPos] == '\n' {

Changes to web/adapter/encoding.go.

    69     69   		zid, err := id.Parse(origRef.URL.Path)
    70     70   		if err != nil {
    71     71   			panic(err)
    72     72   		}
    73     73   		_, err = getMeta.Run(place.NoEnrichContext(ctx), zid)
    74     74   		if errors.Is(err, &place.ErrNotAllowed{}) {
    75     75   			return &ast.FormatNode{
    76         -				Code:    ast.FormatSpan,
           76  +				Kind:    ast.FormatSpan,
    77     77   				Attrs:   origLink.Attrs,
    78     78   				Inlines: origLink.Inlines,
    79     79   			}
    80     80   		}
    81     81   		var newRef *ast.Reference
    82     82   		if err == nil {
    83     83   			ub := b.NewURLBuilder(key).SetZid(zid)

Changes to web/adapter/webui/htmlmeta.go.

    42     42   	case meta.TypeID:
    43     43   		wui.writeIdentifier(w, m.GetDefault(key, "???i"), getTitle)
    44     44   	case meta.TypeIDSet:
    45     45   		if l, ok := m.GetList(key); ok {
    46     46   			wui.writeIdentifierSet(w, l, getTitle)
    47     47   		}
    48     48   	case meta.TypeNumber:
    49         -		writeNumber(w, m.GetDefault(key, "???n"))
           49  +		wui.writeNumber(w, key, m.GetDefault(key, "???n"))
    50     50   	case meta.TypeString:
    51     51   		writeString(w, m.GetDefault(key, "???s"))
    52     52   	case meta.TypeTagSet:
    53     53   		if l, ok := m.GetList(key); ok {
    54     54   			wui.writeTagSet(w, key, l)
    55     55   		}
    56     56   	case meta.TypeTimestamp:
................................................................................
   117    117   		if i > 0 {
   118    118   			w.Write(space)
   119    119   		}
   120    120   		wui.writeIdentifier(w, val, getTitle)
   121    121   	}
   122    122   }
   123    123   
   124         -func writeNumber(w io.Writer, val string) {
   125         -	strfun.HTMLEscape(w, val, false)
          124  +func (wui *WebUI) writeNumber(w io.Writer, key, val string) {
          125  +	wui.writeLink(w, key, val, val)
   126    126   }
   127    127   
   128    128   func writeString(w io.Writer, val string) {
   129    129   	strfun.HTMLEscape(w, val, false)
   130    130   }
   131    131   
   132    132   func writeUnknown(w io.Writer, val string) {

Changes to web/adapter/webui/lists.go.

   267    267   	ctx context.Context,
   268    268   	w http.ResponseWriter,
   269    269   	title string,
   270    270   	s *search.Search,
   271    271   	ucMetaList func(sorter *search.Search) ([]*meta.Meta, error),
   272    272   	pageURL func(int) string) {
   273    273   
   274         -	var metaList []*meta.Meta
   275         -	var err error
   276         -	var prevURL, nextURL string
   277         -	if lps := wui.rtConfig.GetListPageSize(); lps > 0 {
   278         -		if s.GetLimit() < lps {
   279         -			s.SetLimit(lps + 1)
   280         -		}
   281         -
   282         -		metaList, err = ucMetaList(s)
   283         -		if err != nil {
   284         -			wui.reportError(ctx, w, err)
   285         -			return
   286         -		}
   287         -		if offset := s.GetOffset(); offset > 0 {
   288         -			offset -= lps
   289         -			if offset < 0 {
   290         -				offset = 0
   291         -			}
   292         -			prevURL = pageURL(offset)
   293         -		}
   294         -		if len(metaList) >= s.GetLimit() {
   295         -			nextURL = pageURL(s.GetOffset() + lps)
   296         -			metaList = metaList[:len(metaList)-1]
   297         -		}
   298         -	} else {
   299         -		metaList, err = ucMetaList(s)
   300         -		if err != nil {
   301         -			wui.reportError(ctx, w, err)
   302         -			return
   303         -		}
          274  +	metaList, err := ucMetaList(s)
          275  +	if err != nil {
          276  +		wui.reportError(ctx, w, err)
          277  +		return
   304    278   	}
   305    279   	user := wui.getUser(ctx)
   306    280   	metas, err := wui.buildHTMLMetaList(metaList)
   307    281   	if err != nil {
   308    282   		wui.reportError(ctx, w, err)
   309    283   		return
   310    284   	}
   311    285   	var base baseData
   312    286   	wui.makeBaseData(ctx, wui.rtConfig.GetDefaultLang(), wui.rtConfig.GetSiteName(), user, &base)
   313    287   	wui.renderTemplate(ctx, w, id.ListTemplateZid, &base, struct {
   314         -		Title       string
   315         -		Metas       []simpleLink
   316         -		HasPrevNext bool
   317         -		HasPrev     bool
   318         -		PrevURL     string
   319         -		HasNext     bool
   320         -		NextURL     string
          288  +		Title string
          289  +		Metas []simpleLink
   321    290   	}{
   322         -		Title:       title,
   323         -		Metas:       metas,
   324         -		HasPrevNext: len(prevURL) > 0 || len(nextURL) > 0,
   325         -		HasPrev:     len(prevURL) > 0,
   326         -		PrevURL:     prevURL,
   327         -		HasNext:     len(nextURL) > 0,
   328         -		NextURL:     nextURL,
          291  +		Title: title,
          292  +		Metas: metas,
   329    293   	})
   330    294   }
   331    295   
   332    296   func (wui *WebUI) listTitleSearch(prefix string, s *search.Search) string {
   333    297   	if s == nil {
   334    298   		return wui.rtConfig.GetSiteName()
   335    299   	}

Changes to www/changes.wiki.

     1      1   <title>Change Log</title>
     2      2   
     3      3   <a name="0_0_14"></a>
     4      4   <h2>Changes for Version 0.0.14 (pending)</h2>
            5  +  *  Remove support for paging of WebUI list. Runtime configuration key
            6  +     <tt>list-page-size</tt> is removed.
            7  +     (major: webui)
            8  +  *  New suported metadata key <tt>place-number</tt>, which gives an indication
            9  +     from which place the zettel was loaded.
           10  +     (minor)
     5     11   
     6     12   <a name="0_0_13"></a>
     7     13   <h2>Changes for Version 0.0.13 (2021-06-01)</h2>
     8     14     *  Startup configuration <tt>place-<em>X</em>-uri</tt> (where <em>X</em> is a
     9     15        number greater than zero) has been renamed to
    10     16        <tt>place-uri-<em>X</em></tt>.
    11     17        (breaking)