1# json.tcl --
2#
3#	The JSON export plugin. Generation of Java Script Object Notation.
4#
5# Copyright (c) 2009 Andreas Kupries <andreas_kupries@sourceforge.net>
6#
7# See the file "license.terms" for information on usage and redistribution
8# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
9#
10# RCS: @(#) $Id: export_json.tcl,v 1.2 2009/08/07 18:53:11 andreas_kupries Exp $
11
12# This package is a plugin for the doctools::idx v2 system.  It takes
13# the list serialization of a keyword index and produces text in JSON
14# format.
15
16# ### ### ### ######### ######### #########
17## Requisites
18
19# @mdgen NODEP: doctools::idx::export::plugin
20
21package require Tcl 8.4
22package require doctools::idx::export::plugin ; # Presence of this
23						# pseudo package
24						# indicates execution
25						# inside of a properly
26						# initialized plugin
27						# interpreter.
28package require doctools::idx::structure      ; # Verification that
29						# the input is proper.
30package require textutil::adjust
31
32# ### ### ### ######### ######### #########
33## API.
34
35proc export {serial configuration} {
36
37    # Phase I. Check that we got a canonical index serialization. That
38    #          makes the unpacking easier, as we can mix it with the
39    #          generation of the output, knowing that everything is
40    #          already sorted as it should be.
41
42    ::doctools::idx::structure verify-as-canonical $serial
43
44    # ### ### ### ######### ######### #########
45    # Configuration ...
46    # * Standard entries
47    #   - user   = person running the application doing the formatting
48    #   - format = name of this format
49    #   - file   = name of the file the index came from. Optional.
50    #   - map    = maps symbolic references to actual file path. Optional.
51    # * json/format specific entries
52    #   - indented = boolean. objects indented per the index structure.
53    #   - aligned  = boolean. object keys tabular aligned vertically.
54    #
55    # Notes
56    # * This format ignores 'map' even if set, as the written json
57    #   contains the symbolic references and only them.
58    # * aligned  => indented
59
60    # Combinations of the format specific entries
61    # N I A |
62    # - - - + ---------------------
63    # 0 0 0 | Ultracompact (no whitespace, single line)
64    # 1 0 0 | Compact (no whitespace, multiple lines)
65    # 1 1 0 | Indented
66    # 1 0 1 | Tabular aligned references
67    # 1 1 1 | Indented + Tabular aligned references
68    # - - - + ---------------------
69    # 0 1 0 | Not possible, per the implications above.
70    # 0 0 1 | ditto
71    # 0 1 1 | ditto
72    # - - - + ---------------------
73
74    # Import the configuration and initialize the internal state
75    array set config {
76	indented 0
77	aligned  0
78    }
79    array set config $configuration
80
81    # Force the implications mentioned in the notes above.
82    if {$config(aligned)} {
83	set config(indented) 1
84    }
85
86    # ### ### ### ######### ######### #########
87
88    # Phase II. Generate the output, taking the configuration into
89    #           account. We construct this from the inside out.
90
91    # Unpack the serialization.
92    array set idx $serial
93    array set idx $idx(doctools::idx)
94    unset     idx(doctools::idx)
95
96    set keywords {}
97    foreach {kw references} $idx(keywords) {
98	set tmp {}
99	foreach id $references { lappend tmp [JsonString $id] }
100	lappend keywords $kw [JsonArrayList $tmp]
101    }
102
103    if {$config(aligned)} { set max 9 }
104
105    set references {}
106    foreach {id decl} $idx(references) {
107	foreach {type label} $decl break
108	set type  [JsonString $type]
109	set label [JsonString $label]
110	if {$config(aligned)} {
111	    set type [FmtR max $type]
112	}
113	lappend references $id [JsonArray $type $label]
114    }
115
116    return [JsonObject doctools::idx \
117		[JsonObject \
118		     label      [JsonString $idx(label)] \
119		     keywords   [JsonObjectDict $keywords] \
120		     references [JsonObjectDict $references] \
121		     title      [JsonString $idx(title)]]]
122
123    # ### ### ### ######### ######### #########
124}
125
126# ### ### ### ######### ######### #########
127
128proc JsonQuotes {} {
129    return [list "\"" "\\\"" / \\/ \\ \\\\ \b \\b \f \\f \n \\n \r \\r \t \\t]
130}
131
132proc JsonString {s} {
133    return "\"[string map [JsonQuotes] $s]\""
134}
135
136proc JsonArray {args} {
137    upvar 1 config config
138    return [JsonArrayList $args]
139}
140
141proc JsonArrayList {list} {
142    # compact form.
143    return "\[[join $list ,]\]"
144}
145
146proc JsonObject {args} {
147    upvar 1 config config
148    return [JsonObjectDict $args]
149}
150
151proc JsonObjectDict {dict} {
152    # The dict maps string keys to json-formatted data. I.e. we have
153    # to quote the keys, but not the values, as the latter are already
154    # in the proper format.
155    upvar 1 config config
156
157    set tmp {}
158    foreach {k v} $dict { lappend tmp [JsonString $k] $v }
159    set dict $tmp
160
161    if {$config(aligned)} { Align $dict max }
162
163    if {$config(indented)} {
164	set content {}
165	foreach {k v} $dict {
166	    if {$config(aligned)} { set k [FmtR max $k] }
167	    if {[string match *\n* $v]} {
168		# multi-line value
169		lappend content "    $k : [textutil::adjust::indent $v {    } 1]"
170	    } else {
171		# single line value.
172		lappend content "    $k : $v"
173	    }
174	}
175	if {[llength $content]} {
176	    return "\{\n[join $content ,\n]\n\}"
177	} else {
178	    return "\{\}"
179	}
180    } else {
181	# ultra compact form.
182	set tmp {}
183	foreach {k v} $dict { lappend tmp "$k:$v" }
184	return "\{[join $tmp ,]\}"
185    }
186}
187
188proc Align {dict mv} {
189    upvar 1 $mv max
190    # Generate a list of references sortable by name, and also find the
191    # max length of all relevant names.
192    set max 0
193    foreach {str _} $dict { Max max $str }
194    return
195}
196
197proc Max {v str} {
198    upvar 1 $v max
199    set x [string length $str]
200    if {$x <= $max} return
201    set max $x
202    return
203}
204
205proc FmtR {v str} {
206    upvar 1 $v max
207    return $str[string repeat { } [expr {$max - [string length $str]}]]
208}
209
210# ### ### ### ######### ######### #########
211## Ready
212
213package provide doctools::idx::export::json 0.1
214return
215