1#!/usr/bin/env python 2# 3# Copyright (C) 2017 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17"""Outputs HTML based on an input JSON file. 18 19Outputs HTML tables suitable for inclusion in the Android documentation that 20reflect the crypto algorithm support shown in the provided data file. 21""" 22 23import argparse 24import operator 25 26import crypto_docs 27 28 29find_by_name = crypto_docs.find_by_name 30 31 32def sort_by_name(seq): 33 return sorted(seq, key=lambda x: x['name']) 34 35 36def main(): 37 parser = argparse.ArgumentParser(description='Output algorithm support HTML tables') 38 parser.add_argument('--for_javadoc', 39 action='store_true', 40 help='If specified, format for inclusion in class documentation') 41 parser.add_argument('file', 42 help='The JSON file to use for data') 43 args = parser.parse_args() 44 45 output = [] 46 data = crypto_docs.load_json(args.file) 47 categories = sort_by_name(data['categories']) 48 output.append('<h2 id="SupportedAlgorithms">Supported Algorithms</h2>') 49 output.append('') 50 output.append('<ul>') 51 for category in categories: 52 if not category['name'].endswith('.Enabled'): 53 output.append(' <li><a href="#Supported{name}">' 54 '<code>{name}</code></a></li>'.format(**category)) 55 output.append('</ul>') 56 for category in categories: 57 if category['name'].endswith('.Enabled'): 58 # These are handled in the "Supported" section below 59 continue 60 if category['name'] == 'Cipher': 61 # We display ciphers in a four-column table to conserve space and 62 # so that it's more comprehensible. To do this, we have to 63 # collapse all our ciphers into "equivalence classes" of a sort. 64 65 # First, collect the relevant data for each algorithm into a tuple. 66 # The mode and padding are in lists because we are going to collapse 67 # multiple tuples with those in later steps. 68 algorithms = sort_by_name(category['algorithms']) 69 tuples = [] 70 for algorithm in algorithms: 71 name, mode, padding = algorithm['name'].split('/') 72 tuples.append(( 73 name, 74 [mode], 75 [padding], 76 algorithm['supported_api_levels'], 77 'deprecated' in algorithm and algorithm['deprecated'])) 78 # Sort the tuples by all items except padding, then collapse 79 # items with all non-padding values the same (which will always be 80 # neighboring items) into a single item. 81 tuples.sort(key=operator.itemgetter(0, 1, 3, 4)) 82 i = 0 83 while i < len(tuples) - 1: 84 if (tuples[i][0] == tuples[i+1][0] 85 and tuples[i][1] == tuples[i+1][1] 86 and tuples[i][3] == tuples[i+1][3] 87 and tuples[i][4] == tuples[i+1][4]): 88 tuples[i][2].extend(tuples[i+1][2]) 89 del tuples[i+1] 90 else: 91 i += 1 92 # Do the same thing as above, but with modes. 93 tuples.sort(key=operator.itemgetter(0, 2, 3, 4)) 94 i = 0 95 while i < len(tuples) - 1: 96 if (tuples[i][0] == tuples[i+1][0] 97 and tuples[i][2] == tuples[i+1][2] 98 and tuples[i][3] == tuples[i+1][3] 99 and tuples[i][4] == tuples[i+1][4]): 100 tuples[i][1].extend(tuples[i+1][1]) 101 del tuples[i+1] 102 else: 103 i += 1 104 # Display the table with rowspans for those entries where all the 105 # items have the same algorithm, mode, etc 106 output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category)) 107 output.append('<table>') 108 output.append(' <thead>') 109 output.append(' <tr>') 110 output.append(' <th>Algorithm</th>') 111 output.append(' <th>Modes</th>') 112 output.append(' <th>Paddings</th>') 113 output.append(' <th>Supported API Levels</th>') 114 output.append(' </tr>') 115 output.append(' </thead>') 116 output.append(' <tbody>') 117 tuples.sort(key=operator.itemgetter(0, 4, 1, 2, 3)) 118 i = 0 119 cur_deprecated = None 120 cur_algorithm = None 121 cur_mode = None 122 while i < len(tuples): 123 row = tuples[i] 124 if row[4] != cur_deprecated: 125 cur_deprecated = row[4] 126 cur_algorithm = None 127 cur_mode = None 128 if cur_deprecated: 129 output.append(' <tr class="deprecated">') 130 else: 131 output.append(' <tr>') 132 if row[0] != cur_algorithm: 133 cur_algorithm = row[0] 134 cur_mode = None 135 j = i + 1 136 while (j < len(tuples) 137 and tuples[j][4] == cur_deprecated 138 and tuples[j][0] == cur_algorithm): 139 j += 1 140 rowspan = j - i 141 if rowspan > 1: 142 output.append(' <td rowspan="%d">%s</td>' % (rowspan, cur_algorithm)) 143 else: 144 output.append(' <td>%s</td>' % cur_algorithm) 145 if row[1] != cur_mode: 146 cur_mode = row[1] 147 j = i + 1 148 while (j < len(tuples) 149 and tuples[j][4] == cur_deprecated 150 and tuples[j][0] == cur_algorithm 151 and tuples[j][1] == cur_mode): 152 j += 1 153 rowspan = j - i 154 modestring = '<br>'.join(cur_mode) 155 if rowspan > 1: 156 output.append(' <td rowspan="%d">%s</td>' % (rowspan, modestring)) 157 else: 158 output.append(' <td>%s</td>' % modestring) 159 output.append(' <td>%s</td>' % '<br>'.join(row[2])) 160 output.append(' <td>%s</td>' % row[3]) 161 output.append(' </tr>') 162 i += 1 163 output.append(' </tbody>') 164 output.append('</table>') 165 elif category['name'].endswith('.Supported'): 166 # Some categories come with a "Supported" and "Enabled" list, and we 167 # group those together in one table for display. Every entry that's enabled 168 # must be supported, so we can just look up the enabled version for each 169 # supported item 170 basename = category['name'][:-len('.Supported')] 171 supported = sort_by_name(category['algorithms']) 172 enabled = sort_by_name(find_by_name(categories, basename + '.Enabled')['algorithms']) 173 output.append('<h3 id="Supported{0}">{0}</h3>'.format(basename)) 174 output.append('<table>') 175 output.append(' <thead>') 176 output.append(' <tr>') 177 output.append(' <th>Algorithm</th>') 178 output.append(' <th>Supported API Levels</th>') 179 output.append(' <th>Enabled By Default</th>') 180 output.append(' </tr>') 181 output.append(' </thead>') 182 output.append(' <tbody>') 183 for algorithm in supported: 184 if 'deprecated' in algorithm and algorithm['deprecated']: 185 output.append(' <tr class="deprecated">') 186 else: 187 output.append(' <tr>') 188 output.append(' <td>{name}</td>'.format(**algorithm)) 189 output.append(' <td>{supported_api_levels}</td>'.format(**algorithm)) 190 enabled_alg = find_by_name(enabled, algorithm['name']) 191 if enabled_alg is None: 192 output.append(' <td></td>') 193 else: 194 output.append(' <td>{supported_api_levels}</td>'.format(**enabled_alg)) 195 output.append(' </tr>') 196 output.append(' </tbody>') 197 output.append('</table>') 198 else: 199 output.append('<h3 id="Supported{name}">{name}</h3>'.format(**category)) 200 output.append('<table>') 201 output.append(' <thead>') 202 output.append(' <tr>') 203 output.append(' <th>Algorithm</th>') 204 output.append(' <th>Supported API Levels</th>') 205 output.append(' </tr>') 206 output.append(' </thead>') 207 output.append(' <tbody>') 208 algorithms = sort_by_name(category['algorithms']) 209 for algorithm in algorithms: 210 if 'deprecated' in algorithm and algorithm['deprecated']: 211 output.append(' <tr class="deprecated">') 212 else: 213 output.append(' <tr>') 214 output.append(' <td>{name}</td>'.format(**algorithm)) 215 output.append(' <td>{supported_api_levels}</td>'.format(**algorithm)) 216 output.append(' </tr>') 217 output.append(' </tbody>') 218 output.append('</table>') 219 if args.for_javadoc: 220 for i in range(len(output)): 221 output[i] = ' * ' + output[i] 222 print '\n'.join(output) 223 224 225if __name__ == '__main__': 226 main() 227