@@ -2,10 +2,11 @@ function plugindef()
22 finaleplugin .RequireDocument = false
33 finaleplugin .RequireSelection = false
44 finaleplugin .NoStore = true
5- finaleplugin .Author = " Robert Patterson"
5+ finaleplugin .Author = " Robert Patterson (folder scanning added by Carl Vine) "
66 finaleplugin .Copyright = " CC0 https://creativecommons.org/publicdomain/zero/1.0/"
7- finaleplugin .Version = " 1.0.1"
8- finaleplugin .Date = " September 25, 2024"
7+ finaleplugin .Version = " 1.0.4"
8+ finaleplugin .Date = " October 1, 2024"
9+ finaleplugin .LoadLuaOSUtils = true
910 finaleplugin .CategoryTags = " Document"
1011 finaleplugin .MinJWLuaVersion = 0.74
1112 finaleplugin .Notes = [[
@@ -24,67 +25,91 @@ function plugindef()
2425 Due to a limitation in the xml parser, all xml processing instructions are removed. These are metadata that neither
2526 Dorico nor MuseScore use, so their removal should not affect importing into those programs.
2627 ]]
27- return " Massage MusicXML..." , " " , " Massages MusicXML to make it easier to import to Dorico and MuseScore."
28+ return " Massage MusicXML..." ,
29+ " Massage MusicXML" ,
30+ " Massages MusicXML to make it easier to import to Dorico and MuseScore."
2831end
2932
30- local text_extension = " .musicxml"
33+ local xml_extension = " .musicxml"
34+ local add_to_filename = " massaged"
3135
32- local function remove_processing_instructions (file_path , output_name )
33- -- Open the original file for reading
34- local input_file <close> = io.open (file_path , " r" )
36+ local function alert_error (file_list )
37+ local msg = (# file_list > 1 and " These files do not " or " This file does not " )
38+ .. " appear to be MusicXML exported from Finale:\n\n "
39+ .. table.concat (file_list , " \n " )
40+ finenv .UI ():AlertError (msg , plugindef ())
41+ end
42+
43+ local function remove_processing_instructions (input_name , output_name )
44+ local input_file <close> = io.open (input_name , " r" )
3545 if not input_file then
36- error (" Cannot open file: " .. file_path )
46+ error (" Cannot open file: " .. input_name )
3747 end
38- -- Read the contents of the file
39- local lines = {}
48+ local lines = {} -- assemble the output file line by line
4049 for line in input_file :lines () do
41- -- Keep the XML declaration (<?xml ... ?>), remove other processing instructions (<?...?>)
4250 if line :match (" ^%s*<%?xml" ) or not line :match (" ^%s*<%?.*%?>" ) then
4351 table.insert (lines , line )
4452 end
4553 end
46- -- Close the input file
4754 input_file :close ()
48- -- Open the file for writing (overwrite any file already there)
4955 local output_file <close> = io.open (output_name , " w" )
5056 if not output_file then
51- error (" Cannot open file for writing: " .. file_path )
57+ error (" Cannot open file for writing: " .. output_name )
5258 end
53- -- Write the cleaned lines to the file
5459 for _ , line in ipairs (lines ) do
5560 output_file :write (line .. " \n " )
5661 end
57- -- Close the output file
5862 output_file :close ()
5963end
6064
61- function do_open_dialog (document )
62- local path_name = finale .FCString ()
63- local file_name = finale .FCString ()
64- local file_path = finale .FCString ()
65- if document then
66- document :GetPath (file_path )
67- file_path :SplitToPathAndFile (path_name , file_name )
68- end
69- local full_file_name = file_name .LuaString
70- local extension = finale .FCString (file_name .LuaString )
71- extension :ExtractFileExtension ()
72- if extension .Length > 0 then
73- file_name :TruncateAt (file_name :FindLast (" ." .. extension .LuaString ))
74- end
75- file_name :AppendLuaString (text_extension )
76- local open_dialog = finale .FCFileOpenDialog (finenv .UI ())
77- open_dialog :SetWindowTitle (finale .FCString (" Open MusicXML for " .. full_file_name ))
78- open_dialog :AddFilter (finale .FCString (" *" .. text_extension ), finale .FCString (" MusicXML File" ))
79- open_dialog :SetInitFolder (path_name )
80- open_dialog :SetFileName (file_name )
81- open_dialog :AssureFileExtension (text_extension )
82- if not open_dialog :Execute () then
83- return nil
84- end
85- local selected_file_name = finale .FCString ()
86- open_dialog :GetFileName (selected_file_name )
87- return selected_file_name .LuaString
65+ local function choose_extraction_method ()
66+ local fs = finale .FCString
67+ local dialog = finale .FCCustomLuaWindow ()
68+ dialog :SetTitle (fs (plugindef ()))
69+ local stat = dialog :CreateStatic (0 , 0 )
70+ stat :SetText (fs (" Massage the MusicXML for:" ))
71+ stat :SetWidth (150 )
72+ local labels = finale .FCStrings ()
73+ labels :CopyFromStringTable { " one MusicXML file" , " a folder of MusicXML files" }
74+ local method = dialog :CreateRadioButtonGroup (0 , 20 , 2 )
75+ method :SetText (labels )
76+ method :SetWidth (160 )
77+ method :SetSelectedItem (1 ) -- assume "folder"
78+ dialog :CreateOkButton ()
79+ dialog :CreateCancelButton ()
80+ local ok = (dialog :ExecuteModal (nil ) == finale .EXECMODAL_OK )
81+ return ok , (method :GetSelectedItem () == 1 )
82+ end
83+
84+ local function choose_new_folder_dialog ()
85+ local fs = finale .FCString
86+ local dialog = finale .FCCustomLuaWindow ()
87+ dialog :SetTitle (fs (plugindef ()))
88+ local stat = dialog :CreateStatic (0 , 0 )
89+ stat :SetText (fs (" Select a Different Folder" ))
90+ stat :SetWidth (150 )
91+ stat = dialog :CreateStatic (0 , 15 )
92+ stat :SetText (fs (" for the Massaged Files:" ))
93+ stat :SetWidth (150 )
94+ local labels = finale .FCStrings ()
95+ labels :CopyFromStringTable { " YES" , " NO" }
96+ local new_folder = dialog :CreateRadioButtonGroup (0 , 35 , 2 )
97+ new_folder :SetText (labels )
98+ new_folder :SetWidth (80 )
99+ new_folder :SetSelectedItem (0 )
100+ local add = dialog :CreateCheckbox (0 , 85 )
101+ add :SetText (fs (" Don't Add \" " .. add_to_filename .. " \" to Filenames" ))
102+ add :SetWidth (210 )
103+ add :SetCheck (1 )
104+ stat = dialog :CreateStatic (15 , 100 )
105+ stat :SetText (fs (" When Using a Different Folder" ))
106+ stat :SetWidth (180 )
107+ dialog :CreateOkButton ()
108+ dialog :CreateCancelButton ()
109+ local ok = (dialog :ExecuteModal (nil ) == finale .EXECMODAL_OK )
110+ local do_change_filename = (add :GetCheck () == 0 )
111+ local select_new_folder = (new_folder :GetSelectedItem () == 0 )
112+ return ok , select_new_folder , do_change_filename
88113end
89114
90115function fix_octave_shift (xml_measure )
@@ -102,7 +127,7 @@ function fix_octave_shift(xml_measure)
102127 xml_measure :InsertAfterChild (next_note , direction_copy )
103128 end
104129 elseif shift_type == " up" or shift_type == " down" then
105- local sign = shift_type == " down" and 1 or - 1 -- direction to transpose grace notes
130+ local sign = shift_type == " down" and 1 or - 1
106131 local octaves = (octave_shift :IntAttribute (" size" , 8 ) - 1 ) / 7
107132 local prev_grace_note
108133 local prev_note = xml_direction :PreviousSiblingElement (" note" )
@@ -142,44 +167,126 @@ function process_xml(score_partwise)
142167 end
143168end
144169
145- function append_massaged_to_filename ( filepath )
146- -- Extract the path, filename, and extension
147- local path , filename , extension = filepath :match (" ^(.-)([^\\ /]-)%.([^\\ /%.]+)$" )
148-
149- -- Check if the path extraction was successful
150- if not path or not filename or not extension then
151- error ( " Invalid file path format " )
170+ function process_one_file ( input_file , output_file , do_change_filename )
171+ if do_change_filename then -- add "massaged" to output_file
172+ local path , filename , extension = output_file :match (" ^(.-)([^\\ /]-)%.([^\\ /%.]+)$" )
173+ if not path or not filename or not extension then
174+ error ( " Invalid file path format " )
175+ end
176+ output_file = path .. filename .. " " .. add_to_filename .. " . " .. extension
152177 end
153178
154- -- Construct the new file path with " massaged" appended to the filename
155- local new_filepath = path .. filename .. " massaged." .. extension
156- return new_filepath
157- end
158-
159- function music_xml_massage_export ()
160- local documents = finale .FCDocuments ()
161- documents :LoadAll ()
162- local document = documents :FindCurrent ()
163- local xml_file = do_open_dialog (document )
164- if not xml_file then
165- return
166- end
167- local output_name = append_massaged_to_filename (xml_file )
168- -- tinyxml2 can't parse processing instructions, so remove them
169- remove_processing_instructions (xml_file , output_name ) -- hopefully not necessary forever
179+ remove_processing_instructions (input_file , output_file )
170180 local musicxml = tinyxml2 .XMLDocument ()
171- local result = musicxml :LoadFile (output_name )
181+ local result = musicxml :LoadFile (output_file )
172182 if result ~= tinyxml2 .XML_SUCCESS then
173- error ( " Unable to process " .. xml_file .. " . " .. musicxml : ErrorStr ())
174- return
183+ os.remove ( output_file ) -- delete erroneous file
184+ return input_file -- XML error
175185 end
176186 local score_partwise = musicxml :FirstChildElement (" score-partwise" )
177187 if not score_partwise then
178- error (" File " .. xml_file .. " does not appear to be a Finale-exported MusicXML file." )
188+ os.remove (output_file ) -- delete erroneous file
189+ return input_file -- massaging failed
179190 end
180191 process_xml (score_partwise )
181- musicxml :SaveFile (output_name )
182- finenv .UI ():AlertInfo (" Processed to " .. output_name .. " ." , " Processed File" )
192+ musicxml :SaveFile (output_file )
193+ return " "
194+ end
195+
196+ function do_open_directory ()
197+ local src_dialog = finale .FCFolderBrowseDialog (finenv .UI ())
198+ src_dialog :SetWindowTitle (finale .FCString (" Open Folder of MusicXML Files:" ))
199+ if not src_dialog :Execute () then
200+ return nil -- user cancelled
201+ end
202+ local selected_directory = finale .FCString ()
203+ src_dialog :GetFolderPath (selected_directory )
204+ local src_dir = selected_directory .LuaString
205+ local out_dir = src_dir -- duplicate source to output (for now)
206+
207+ local ok , select_new_folder , do_change_filename = choose_new_folder_dialog ()
208+ if not ok then return end -- cancelled
209+ if select_new_folder then -- choose alternate destination dir
210+ local out_dialog = finale .FCFolderBrowseDialog (finenv .UI ())
211+ out_dialog :SetWindowTitle (finale .FCString (" Choose Folder for Massaged Files:" ))
212+ if not out_dialog :Execute () then return end -- user cancelled
213+
214+ out_dialog :GetFolderPath (selected_directory )
215+ out_dir = selected_directory .LuaString
216+ end
217+ if out_dir == src_dir then -- user might "choose" same folder as original
218+ do_change_filename = true -- always change filenames in same directory
219+ end
220+ local osutils = finenv .EmbeddedLuaOSUtils and require (" luaosutils" )
221+ if not osutils then return end -- can't get a directory listing
222+ local options = finenv .UI ():IsOnWindows () and " /b /ad" or " -1"
223+ local file_list = osutils .process .list_dir (src_dir , options )
224+ if file_list == " " then return end -- empty directory
225+
226+ -- run through the file list, identifying valid candidates
227+ local error_list = {}
228+ for x_file in file_list :gmatch (" ([^\r\n ]*)[\r\n ]?" ) do
229+ if x_file :sub (- xml_extension :len ()) == xml_extension then
230+ local src_file = src_dir .. " /" .. x_file
231+ local dest_file = out_dir .. " /" .. x_file
232+ local file_error = process_one_file (src_file , dest_file , do_change_filename )
233+ if file_error ~= " " then
234+ table.insert (error_list , file_error )
235+ end
236+ end
237+ end
238+ if # error_list > 0 then
239+ alert_error (error_list )
240+ end
241+ end
242+
243+ function do_open_dialog (document )
244+ local path_name = finale .FCString ()
245+ local file_name = finale .FCString ()
246+ local file_path = finale .FCString ()
247+ if document then
248+ document :GetPath (file_path )
249+ file_path :SplitToPathAndFile (path_name , file_name )
250+ end
251+ local full_file_name = file_name .LuaString
252+ local extension = finale .FCString (file_name .LuaString )
253+ extension :ExtractFileExtension ()
254+ if extension .Length > 0 then
255+ file_name :TruncateAt (file_name :FindLast (" ." .. extension .LuaString ))
256+ end
257+ file_name :AppendLuaString (xml_extension )
258+ local open_dialog = finale .FCFileOpenDialog (finenv .UI ())
259+ open_dialog :SetWindowTitle (finale .FCString (" Open MusicXML for " .. full_file_name ))
260+ open_dialog :AddFilter (finale .FCString (" *" .. xml_extension ), finale .FCString (" MusicXML File" ))
261+ open_dialog :SetInitFolder (path_name )
262+ open_dialog :SetFileName (file_name )
263+ open_dialog :AssureFileExtension (xml_extension )
264+ if not open_dialog :Execute () then
265+ return nil
266+ end
267+ local selected_file_name = finale .FCString ()
268+ open_dialog :GetFileName (selected_file_name )
269+ return selected_file_name .LuaString
270+ end
271+
272+ function music_xml_massage_export ()
273+ local ok , full_directory = choose_extraction_method ()
274+ if not ok then return end -- user cancelled
275+
276+ if full_directory then
277+ do_open_directory ()
278+ else -- only one file
279+ local documents = finale .FCDocuments ()
280+ documents :LoadAll ()
281+ local document = documents :FindCurrent ()
282+ local xml_file = do_open_dialog (document )
283+ if xml_file then
284+ local file_error = process_one_file (xml_file , xml_file , true )
285+ if file_error ~= " " then
286+ alert_error {file_error }
287+ end
288+ end
289+ end
183290end
184291
185292music_xml_massage_export ()
0 commit comments