Implement CSV export

This commit is contained in:
don-tnowe 2022-10-23 21:01:43 +03:00
parent a6a42b34fc
commit 05120ad51a
6 changed files with 310 additions and 105 deletions

View File

@ -21,16 +21,6 @@ func save_entries(all_entries : Array, indices : Array, repeat : bool = true):
var file = File.new()
var space_after_delimeter = import_data.delimeter.ends_with(" ")
file.open(import_data.edited_path, File.WRITE)
if import_data.remove_first_row:
var names = []
names.resize(import_data.prop_names.size())
for i in names.size():
names[i] = TextEditingUtils.string_snake_to_naming_case(import_data.prop_names[i])
if i != 0 and space_after_delimeter:
names[i] = " " + names[i]
file.store_csv_line(names, import_data.delimeter[0])
for x in csv_rows:
if space_after_delimeter:
for i in x.size():
@ -41,7 +31,7 @@ func save_entries(all_entries : Array, indices : Array, repeat : bool = true):
file.close()
if repeat:
timer = editor_view.get_tree().create_timer(5.0)
timer = editor_view.get_tree().create_timer(3.0)
timer.connect("timeout", self, "save_entries", [all_entries, indices, false])
@ -53,38 +43,19 @@ func import_from_path(path : String, insert_func : FuncRef, sort_by : String, so
import_data = load(path)
var file = File.new()
file.open(import_data.edited_path, File.READ)
var line
var first = true
var space_after_delimeter = import_data.delimeter.ends_with(" ")
csv_rows = []
while !file.eof_reached():
line = file.get_csv_line(import_data.delimeter[0])
if space_after_delimeter:
for i in line.size():
line[i] = line[i].trim_prefix(" ")
if first and import_data.remove_first_row:
line = " "
first = false
continue
if csv_rows.size() == 0:
csv_rows.append(line)
elif line.size() != 1:
if line.size() != csv_rows[0].size():
line.resize(csv_rows[0].size())
csv_rows.append(line)
csv_rows = SpreadsheetImportFormatCsv.import_as_arrays(import_data)
var rows := []
var res : Resource
resource_original_positions.clear()
for i in csv_rows.size():
if import_data.remove_first_row and i == 0:
continue
res = import_data.strings_to_resource(csv_rows[i])
insert_func.call_func(res, rows, sort_by, sort_reverse)
resource_original_positions[res] = i
editor_view.fill_property_data(rows[0])
file.close()
return rows

View File

@ -0,0 +1,31 @@
class_name SpreadsheetExportFormatCsv
extends Reference
static func export_to_file(entries_array : Array, column_names : Array, into_path : String, import_data):
var file = File.new()
file.open(into_path, File.WRITE)
var line = PoolStringArray()
var space_after_delimeter = import_data.delimeter.ends_with(" ")
import_data.prop_names = column_names
import_data.prop_types = import_data.get_resource_property_types(entries_array[0], column_names)
import_data.resource_path = ""
line.resize(column_names.size())
if import_data.remove_first_row:
for j in column_names.size():
line[j] = column_names[j]
if space_after_delimeter and j != 0:
line[j] = " " + line[j]
file.store_csv_line(line, import_data.delimeter[0])
for i in entries_array.size():
for j in column_names.size():
line[j] = import_data.property_to_string((entries_array[i].get(column_names[j])), j)
if space_after_delimeter and j != 0:
line[j] = " " + line[j]
file.store_csv_line(line, import_data.delimeter[0])
file.close()

View File

@ -0,0 +1,49 @@
class_name SpreadsheetImportFormatCsv
extends Reference
static func can_edit_path(path : String):
return path.ends_with(".csv")
static func import_as_arrays(import_data) -> Array:
var file = File.new()
file.open(import_data.edited_path, File.READ)
import_data.delimeter = ";"
var text_lines := [file.get_line().split(import_data.delimeter)]
var space_after_delimeter = false
var line = text_lines[0]
if line.size() == 1:
import_data.delimeter = ","
line = line[0].split(import_data.delimeter)
text_lines[0] = line
if line[1].begins_with(" "):
for i in line.size():
line[i] = line[i].trim_prefix(" ")
text_lines[0] = line
space_after_delimeter = true
import_data.delimeter = ", "
while !file.eof_reached():
line = file.get_csv_line(import_data.delimeter[0])
if space_after_delimeter:
for i in line.size():
line[i] = line[i].trim_prefix(" ")
if line.size() == text_lines[0].size():
text_lines.append(line)
elif line.size() != 1:
line.resize(text_lines[0].size())
text_lines.append(line)
file.close()
var entries = []
entries.resize(text_lines.size())
for i in entries.size():
entries[i] = text_lines[i]
return entries

View File

@ -2,6 +2,8 @@ tool
extends WindowDialog
export var prop_list_item_scene : PackedScene
export(Array, Script) var formats_export
export(Array, Script) var formats_import
onready var editor_view := $"../.."
onready var node_filename_options := $"TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/OptionButton"
@ -18,62 +20,25 @@ var import_data : SpreadsheetImport
func _on_FileDialogText_file_selected(path : String):
import_data = SpreadsheetImport.new()
import_data.initialize(path)
_open_dialog()
_reset_controls()
_open_dialog(path)
popup_centered()
func _open_dialog():
func _open_dialog(path : String):
node_classname_field.text = TextEditingUtils\
.string_snake_to_naming_case(import_data.edited_path.get_file().get_basename())\
.replace(" ", "")
import_data.script_classname = node_classname_field.text
_load_entries()
for x in formats_import:
if x.new().can_edit_path(path):
entries = x.new().import_as_arrays(import_data)
_load_property_names()
_create_prop_editors()
func _load_entries():
var file = File.new()
file.open(import_data.edited_path, File.READ)
import_data.delimeter = ";"
var text_lines := [file.get_line().split(import_data.delimeter)]
var space_after_delimeter = false
var line = text_lines[0]
if line.size() == 1:
import_data.delimeter = ","
line = line[0].split(import_data.delimeter)
text_lines[0] = line
if line[1].begins_with(" "):
for i in line.size():
line[i] = line[i].trim_prefix(" ")
text_lines[0] = line
space_after_delimeter = true
import_data.delimeter = ", "
while !file.eof_reached():
line = file.get_csv_line(import_data.delimeter[0])
if space_after_delimeter:
for i in line.size():
line[i] = line[i].trim_prefix(" ")
if line.size() == text_lines[0].size():
text_lines.append(line)
elif line.size() != 1:
line.resize(text_lines[0].size())
text_lines.append(line)
entries = []
entries.resize(text_lines.size())
for i in entries.size():
entries[i] = text_lines[i]
func _load_property_names():
import_data.prop_names = Array(entries[0])
import_data.prop_types.resize(import_data.prop_names.size())
@ -89,10 +54,8 @@ func _load_property_names():
.replace("\\", "_")\
.to_lower()
if entries[1][i].is_valid_integer():
import_data.prop_types[i] = SpreadsheetImport.PropType.INT
elif entries[1][i].is_valid_float():
# Don't imply Ints automatically - further rows might have floats
if entries[1][i].is_valid_float():
import_data.prop_types[i] = SpreadsheetImport.PropType.REAL
elif entries[1][i].begins_with("res://"):
@ -118,29 +81,16 @@ func _create_prop_editors():
func _generate_class(save_script = true):
var new_script = GDScript.new()
if import_data.script_classname != "":
if save_script and import_data.script_classname != "":
new_script.source_code = "class_name " + import_data.script_classname + " \nextends Resource\n\n"
else:
new_script.source_code = "extends Resource\n\n"
# Enums
var uniques = {}
import_data.uniques = uniques
import_data.uniques = import_data.get_uniques(entries)
for i in import_data.prop_types.size():
if import_data.prop_types[i] == SpreadsheetImport.PropType.ENUM:
var cur_value := ""
uniques[i] = {}
for j in entries.size():
if j == 0 and import_data.remove_first_row: continue
cur_value = entries[j][i].replace(" ", "_").to_upper()
if cur_value == "":
cur_value = "N_A"
if !uniques[i].has(cur_value):
uniques[i][cur_value] = uniques[i].size()
new_script.source_code += import_data.create_enum_for_prop(i)
# Properties
@ -169,7 +119,7 @@ func _export_tres_folder():
ResourceSaver.save(new_res.resource_path, new_res)
func _on_Ok_pressed():
func _on_import_to_tres_pressed():
hide()
_generate_class()
_export_tres_folder()
@ -179,9 +129,10 @@ func _on_Ok_pressed():
editor_view.refresh()
func _on_OkSafe_pressed():
func _on_import_edit_pressed():
hide()
_generate_class(false)
import_data.prop_used_as_filename = ""
import_data.save()
yield(get_tree(), "idle_frame")
editor_view.display_folder(import_data.resource_path)
@ -189,6 +140,19 @@ func _on_OkSafe_pressed():
editor_view.refresh()
func _on_export_csv_pressed():
hide()
var exported_cols = editor_view.columns.duplicate()
exported_cols.erase("resource_path")
exported_cols.erase("resource_local_to_scene")
for x in editor_view.hidden_columns[editor_view.current_path].keys():
exported_cols.erase(x)
SpreadsheetExportFormatCsv.export_to_file(editor_view.rows, exported_cols, import_data.edited_path, import_data)
yield(get_tree(), "idle_frame")
editor_view.refresh()
# Input controls
func _on_classname_field_text_changed(new_text : String):
import_data.script_classname = new_text.replace(" ", "")
@ -196,6 +160,8 @@ func _on_classname_field_text_changed(new_text : String):
func _on_remove_first_row_toggled(button_pressed : bool):
import_data.remove_first_row = button_pressed
$"TabContainer/Export/HBoxContainer2/Button".pressed = true
$"TabContainer/Export/HBoxContainer3/CheckBox".pressed = true
func _on_filename_options_item_selected(index):
@ -208,3 +174,20 @@ func _on_list_item_type_selected(type : int, index : int):
func _on_list_item_name_changed(name : String, index : int):
import_data.prop_names[index] = name.replace(" ", "")
func _on_export_delimeter_pressed(del : String):
import_data.delimeter = del + import_data.delimeter.substr(1)
func _on_export_space_toggled(button_pressed : bool):
import_data.delimeter = (
import_data.delimeter[0]
if !button_pressed else
import_data.delimeter + " "
)
func _reset_controls():
$"TabContainer/Export/HBoxContainer2/CheckBox".pressed = false
_on_remove_first_row_toggled(true)

View File

@ -1,21 +1,27 @@
[gd_scene load_steps=3 format=2]
[gd_scene load_steps=5 format=2]
[ext_resource path="res://addons/resources_speadsheet_view/import_export/import_export_dialog.gd" type="Script" id=1]
[ext_resource path="res://addons/resources_speadsheet_view/import_export/property_list_item.tscn" type="PackedScene" id=2]
[ext_resource path="res://addons/resources_speadsheet_view/import_export/formats_import/import_csv.gd" type="Script" id=3]
[sub_resource type="ButtonGroup" id=1]
[node name="Control" type="WindowDialog"]
pause_mode = 2
visible = true
margin_right = 493.0
margin_bottom = 294.0
popup_exclusive = true
window_title = "Import/Export As Text"
script = ExtResource( 1 )
prop_list_item_scene = ExtResource( 2 )
formats_import = [ ExtResource( 3 ) ]
[node name="TabContainer" type="TabContainer" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
tab_align = 0
use_hidden_tabs_for_min_size = true
[node name="Import" type="VBoxContainer" parent="TabContainer"]
anchor_right = 1.0
@ -112,9 +118,120 @@ margin_right = 434.0
margin_bottom = 20.0
text = "Cancel"
[node name="Export" type="VBoxContainer" parent="TabContainer"]
visible = false
anchor_right = 1.0
anchor_bottom = 1.0
margin_left = 4.0
margin_top = 32.0
margin_right = -4.0
margin_bottom = -4.0
[node name="Info" type="Label" parent="TabContainer/Export"]
margin_right = 485.0
margin_bottom = 65.0
size_flags_vertical = 0
text = "The currently edited folder will be exported into the selected file.
Rows hidden by the filter will NOT be exported, and order follows the current sorting key. Rows on non-selected pages will not be removed.
Hidden columns will NOT be exported."
autowrap = true
[node name="HSeparator" type="HSeparator" parent="TabContainer/Export"]
margin_top = 69.0
margin_right = 485.0
margin_bottom = 73.0
[node name="HBoxContainer2" type="HBoxContainer" parent="TabContainer/Export"]
margin_top = 77.0
margin_right = 485.0
margin_bottom = 101.0
alignment = 1
[node name="Label2" type="Label" parent="TabContainer/Export/HBoxContainer2"]
margin_top = 5.0
margin_right = 131.0
margin_bottom = 19.0
size_flags_horizontal = 3
text = "Delimeter:"
[node name="Button" type="Button" parent="TabContainer/Export/HBoxContainer2"]
margin_left = 135.0
margin_right = 212.0
margin_bottom = 24.0
toggle_mode = true
pressed = true
group = SubResource( 1 )
text = "Comma (,)"
[node name="Button2" type="Button" parent="TabContainer/Export/HBoxContainer2"]
margin_left = 216.0
margin_right = 311.0
margin_bottom = 24.0
toggle_mode = true
group = SubResource( 1 )
text = "Semicolon (;)"
[node name="Button3" type="Button" parent="TabContainer/Export/HBoxContainer2"]
margin_left = 315.0
margin_right = 349.0
margin_bottom = 24.0
toggle_mode = true
group = SubResource( 1 )
text = "Tab"
[node name="CheckBox" type="CheckBox" parent="TabContainer/Export/HBoxContainer2"]
margin_left = 353.0
margin_right = 485.0
margin_bottom = 24.0
text = "With space after"
[node name="HBoxContainer3" type="HBoxContainer" parent="TabContainer/Export"]
margin_top = 105.0
margin_right = 485.0
margin_bottom = 129.0
[node name="CheckBox" type="CheckBox" parent="TabContainer/Export/HBoxContainer3"]
margin_right = 246.0
margin_bottom = 24.0
pressed = true
text = "First row contains property names (CSV)"
[node name="Control" type="Control" parent="TabContainer/Export"]
margin_top = 133.0
margin_right = 485.0
margin_bottom = 234.0
size_flags_vertical = 3
[node name="HBoxContainer" type="HBoxContainer" parent="TabContainer/Export"]
margin_top = 238.0
margin_right = 485.0
margin_bottom = 258.0
alignment = 1
[node name="Button" type="Button" parent="TabContainer/Export/HBoxContainer"]
margin_left = 165.0
margin_right = 261.0
margin_bottom = 20.0
text = "Export to CSV"
[node name="Cancel" type="Button" parent="TabContainer/Export/HBoxContainer"]
margin_left = 265.0
margin_right = 319.0
margin_bottom = 20.0
text = "Cancel"
[connection signal="item_selected" from="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/OptionButton" to="." method="_on_filename_options_item_selected"]
[connection signal="text_changed" from="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/LineEdit" to="." method="_on_classname_field_text_changed"]
[connection signal="toggled" from="TabContainer/Import/MarginContainer/ScrollContainer/VBoxContainer/GridContainer/CheckBox" to="." method="_on_remove_first_row_toggled"]
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Ok2" to="." method="_on_OkSafe_pressed"]
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Ok" to="." method="_on_Ok_pressed"]
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Ok2" to="." method="_on_import_edit_pressed"]
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Ok" to="." method="_on_import_to_tres_pressed"]
[connection signal="pressed" from="TabContainer/Import/HBoxContainer/Cancel" to="." method="hide"]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer2/Button" to="." method="_on_export_delimeter_pressed" binds= [ "," ]]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer2/Button2" to="." method="_on_export_delimeter_pressed" binds= [ ";" ]]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer2/Button3" to="." method="_on_export_delimeter_pressed" binds= [ " " ]]
[connection signal="toggled" from="TabContainer/Export/HBoxContainer2/CheckBox" to="." method="_on_export_space_toggled"]
[connection signal="toggled" from="TabContainer/Export/HBoxContainer3/CheckBox" to="." method="_on_remove_first_row_toggled"]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer/Button" to="." method="_on_export_csv_pressed"]
[connection signal="pressed" from="TabContainer/Export/HBoxContainer/Cancel" to="." method="hide"]

View File

@ -2,8 +2,6 @@ tool
class_name SpreadsheetImport
extends Resource
const SUFFIX := "_spreadsheet_import.tres"
enum PropType {
BOOL,
INT,
@ -19,6 +17,16 @@ enum PropType {
MAX,
}
const SUFFIX := "_spreadsheet_import.tres"
const TYPE_MAP := {
TYPE_STRING : PropType.STRING,
TYPE_REAL : PropType.REAL,
TYPE_BOOL : PropType.BOOL,
TYPE_INT : PropType.INT,
TYPE_OBJECT : PropType.OBJECT,
TYPE_COLOR : PropType.COLOR,
}
export var prop_types : Array
export var prop_names : Array
@ -75,6 +83,11 @@ func string_to_property(string : String, col_index : int):
func property_to_string(value, col_index : int) -> String:
if prop_types[col_index] is PoolStringArray:
return TextEditingUtils.string_snake_to_naming_case(
prop_types[col_index][value]
)
match prop_types[col_index]:
PropType.STRING:
return value
@ -185,3 +198,44 @@ func resource_to_strings(res : Resource):
strings[i] = property_to_string(res.get(prop_names[i]), i)
return PoolStringArray(strings)
func get_uniques(entries : Array) -> Dictionary:
var result = {}
for i in prop_types.size():
if prop_types[i] == PropType.ENUM:
var cur_value := ""
result[i] = {}
for j in entries.size():
if j == 0 and remove_first_row: continue
cur_value = entries[j][i].replace(" ", "_").to_upper()
if cur_value == "":
cur_value = "N_A"
if !result[i].has(cur_value):
result[i][cur_value] = result[i].size()
return result
static func get_resource_property_types(res : Resource, properties : Array) -> Array:
var result = []
result.resize(properties.size())
result.fill(PropType.STRING)
var cur_type := 0
for x in res.get_property_list():
var found = properties.find(x["name"])
if found == -1: continue
if x["usage"] & PROPERTY_USAGE_EDITOR != 0:
if x["hint"] == PROPERTY_HINT_ENUM:
var enum_values = x["hint_string"].split(",")
for i in enum_values.size():
enum_values[i] = enum_values[i].left(enum_values[i].find(":"))
result[found] = enum_values
else:
result[found] = TYPE_MAP.get(x["type"], PropType.STRING)
return result