1313# You should have received a copy of the GNU General Public License
1414# along with this program. If not, see <http://www.gnu.org/licenses/>.
1515#
16- __author__ = ' bmiller'
16+ __author__ = " bmiller"
1717
18- import re , os
18+ import csv
19+ import io
20+ import os
21+ import re
1922from docutils import nodes
2023from docutils .parsers .rst import directives
21- from runestone .common .runestonedirective import RunestoneNode , RunestoneIdDirective , get_node_line
24+ from runestone .common .runestonedirective import (
25+ RunestoneNode ,
26+ RunestoneIdDirective ,
27+ get_node_line ,
28+ )
2229from runestone .server .componentdb import addQuestionToDB , addHTMLToDB
2330
2431#
2532# setup is called when extensions are loaded. This registers the new directive and
2633# logs any js or css files that should be loaded for this extension.
2734#
2835def setup (app ):
29- app .add_directive (' spreadsheet' , SpreadSheet )
36+ app .add_directive (" spreadsheet" , SpreadSheet )
3037
31- app .add_autoversioned_javascript (' spreadsheet.js' )
32- app .add_javascript (' jexcel.js' )
33- app .add_javascript (' japp.js' )
38+ app .add_autoversioned_javascript (" spreadsheet.js" )
39+ app .add_javascript (" jexcel.js" )
40+ app .add_javascript (" japp.js" )
3441
35- app .add_autoversioned_stylesheet (' spreadsheet.css' )
36- app .add_stylesheet (' jexcel.css' )
37- app .add_stylesheet (' japp.css' )
42+ app .add_autoversioned_stylesheet (" spreadsheet.css" )
43+ app .add_stylesheet (" jexcel.css" )
44+ app .add_stylesheet (" japp.css" )
3845
3946 app .add_node (SpreadSheetNode , html = (visit_ss_node , depart_ss_node ))
4047
@@ -54,6 +61,7 @@ def __init__(self, content, **kwargs):
5461 super (SpreadSheetNode , self ).__init__ (** kwargs )
5562 self .ss_options = content
5663
64+
5765#
5866# The spreadsheet class implements the directive.
5967# When the directive is processed the run method is called.
@@ -71,81 +79,104 @@ class SpreadSheet(RunestoneIdDirective):
7179 A1,B1,C1,D1...
7280 A2,B2,C2,D2...
7381 """
82+
7483 required_arguments = 1
7584 optional_arguments = 5
7685 has_content = True
7786 option_spec = RunestoneIdDirective .option_spec .copy ()
78- option_spec .update ({
79- 'fromcsv' : directives .unchanged ,
80- 'colwidths' : directives .unchanged ,
81- 'coltitles' : directives .unchanged ,
82- 'mindimensions' : directives .unchanged
83- })
84-
87+ option_spec .update (
88+ {
89+ "fromcsv" : directives .unchanged ,
90+ "colwidths" : directives .unchanged ,
91+ "coltitles" : directives .unchanged ,
92+ "mindimensions" : directives .unchanged ,
93+ }
94+ )
8595
8696 def run (self ):
8797 super (SpreadSheet , self ).run ()
8898 env = self .state .document .settings .env
8999
90- self .options [' divid' ] = self .arguments [0 ].strip ()
100+ self .options [" divid" ] = self .arguments [0 ].strip ()
91101
92- if ' ====' in self .content :
93- idx = self .content .index (' ====' )
94- suffix = self .content [idx + 1 :]
95- self .options [' asserts' ] = suffix
96- self .options [' autograde' ] = 'data-autograde="true"'
102+ if " ====" in self .content :
103+ idx = self .content .index (" ====" )
104+ suffix = self .content [idx + 1 :]
105+ self .options [" asserts" ] = suffix
106+ self .options [" autograde" ] = 'data-autograde="true"'
97107 else :
98- self .options [' asserts' ] = '""'
99- self .options [' autograde' ] = ''
108+ self .options [" asserts" ] = '""'
109+ self .options [" autograde" ] = ""
100110
101- if ' fromcsv' in self .options :
102- self .content = self .body_from_csv (env , self .options [' fromcsv' ])
111+ if " fromcsv" in self .options :
112+ self .content = self .body_from_csv (env , self .options [" fromcsv" ])
103113 else :
104114 if self .content :
105115 self .content = self .body_to_csv (self .content )
106116 else :
107- raise ValueError ("You must specify either from csv or provide content in the body" )
117+ raise ValueError (
118+ "You must specify either from csv or provide content in the body"
119+ )
108120
109- self .options [' data' ] = self .content
121+ self .options [" data" ] = self .content
110122
111- if ' coltitles' not in self .options :
112- self .options [' coltitles' ] = ""
123+ if " coltitles" not in self .options :
124+ self .options [" coltitles" ] = ""
113125 else :
114- self .options ['coltitles' ] = "data-coltitles=[{}]" .format (self .options ['coltitles' ])
126+ self .options ["coltitles" ] = "data-coltitles=[{}]" .format (
127+ "," .join ([x .strip () for x in self .options ["coltitles" ].split ("," )])
128+ )
115129
116- if ' mindimensions' not in self .options :
117- self .options [' mindimensions' ] = ""
130+ if " mindimensions" not in self .options :
131+ self .options [" mindimensions" ] = ""
118132 else :
119- self .options ['mindimensions' ] = "data-mindimensions=[{}]" .format (self .options ['mindimensions' ])
133+ self .options ["mindimensions" ] = "data-mindimensions=[{}]" .format (
134+ "," .join ([x .strip () for x in self .options ["mindimensions" ].split ("," )])
135+ )
120136
121- if ' colwidths' not in self .options :
122- self .options [' colwidths' ] = ""
137+ if " colwidths" not in self .options :
138+ self .options [" colwidths" ] = ""
123139 else :
124- self .options ['colwidths' ] = "data-colwidths=[{}]" .format (self .options ['colwidths' ])
140+ self .options ["colwidths" ] = "data-colwidths=[{}]" .format (
141+ "," .join ([x .strip () for x in self .options ["colwidths" ].split ("," )])
142+ )
125143
126144 ssnode = SpreadSheetNode (self .options , rawsource = self .block_text )
127145 ssnode .source , ssnode .line = self .state_machine .get_source_and_line (self .lineno )
128- self .add_name (ssnode ) # make this divid available as a target for :ref:
146+ self .add_name (ssnode ) # make this divid available as a target for :ref:
129147
130148 return [ssnode ]
131149
150+ def body_to_csv (self , row_list ):
151+ """
152+ Use the csv reader and writer functionality to better parse the body.
132153
154+ 1. Convert the contents to a StringIO object
155+ 2. Then read and process using a csv reader
156+ 3. Formulas with ,'s in them should be in double quotes, if there are "'s in the
157+ cell then they should be ""-ed
158+ """
133159
134- def body_to_csv (self , row_list ):
135160 csvlist = []
161+ body_list = []
136162 for row in row_list :
137- if re .match (r' ^\s*====' , row ):
163+ if re .match (r" ^\s*====" , row ):
138164 break
139- items = row .split (',' )
165+ body_list .append (row )
166+ body_file = io .StringIO ("\n " .join (body_list ))
167+ body_reader = csv .reader (body_file )
168+ for row in body_reader :
140169 ilist = []
141- for item in items :
170+ for item in row :
142171 item = item .strip ()
172+ if item and item [0 ] == '"' and item [- 1 ] == '"' :
173+ item = item [1 :- 1 ]
143174 if is_float (item ):
144175 ilist .append (as_int_or_float (item ))
145176 elif item .startswith ("=" ):
146- ilist .append ('{}' .format (item .upper ()))
177+ ilist .append ("{}" .format (item .upper ()))
147178 else :
148- ilist .append ('{}' .format (item ))
179+ ilist .append ("{}" .format (item ))
149180 csvlist .append (ilist )
150181 return csvlist
151182
@@ -155,18 +186,21 @@ def body_from_csv(self, env, csvfile):
155186 filename = os .path .join (env .srcdir , ffpath , csvfile )
156187
157188 print ("\n \n PATH=" , self .srcpath )
158- with open (filename ,'r' ) as csv :
189+ with open (filename , "r" ) as csv :
159190 content = csv .readlines ()
160191
192+ content = [line [:- 1 ] for line in content ]
161193 return self .body_to_csv (content )
162194
195+
163196def is_float (s ):
164197 try :
165198 x = float (s )
166199 return True
167200 except :
168201 return False
169202
203+
170204def as_int_or_float (s ):
171205 try :
172206 x = int (s )
@@ -187,9 +221,11 @@ def as_int_or_float(s):
187221</div>
188222"""
189223
190- def visit_ss_node (self ,node ):
224+
225+ def visit_ss_node (self , node ):
191226 res = TEMPLATE .format (** node .ss_options )
192227 self .body .append (res )
193228
194- def depart_ss_node (self ,node ):
195- pass
229+
230+ def depart_ss_node (self , node ):
231+ pass
0 commit comments