Importing XML file in Rails app, UTF-16 encoding problem - ruby-on-rails

I'm trying to import an XML file via a web page in a Ruby on Rails application, the code ruby view code is as follows (I've removed HTML layout tags to make reading the code easier)
<% form_for( :fmfile, :url => '/fmfiles', :html => { :method => :post, :name => 'Form_Import_DDR', :enctype => 'multipart/form-data' } ) do |f| %>
<%= f.file_field :document, :accept => 'text/xml', :name => 'fmfile_document' %>
<%= submit_tag 'Import DDR' %>
<% end %>
Results in the following HTML form
<form action="/fmfiles" enctype="multipart/form-data" method="post" name="Form_Import_DDR"><div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="3da97372885564a4587774e7e31aaf77119aec62" />
<input accept="text/xml" id="fmfile_document" name="fmfile_document" size="30" type="file" />
<input name="commit" type="submit" value="Import DDR" />
</form>
The Form_Import_DDR method in the 'fmfiles_controller' is the code that does the hard work of reading the XML document in using REXML. The code is as follows
#fmfile = Fmfile.new
#fmfile.user_id = current_user.id
#fmfile.file_group_id = 1
#fmfile.name = params[:fmfile_document].original_filename
respond_to do |format|
if #fmfile.save
require 'rexml/document'
doc = REXML::Document.new(params[:fmfile_document].read)
doc.root.elements['File'].elements['BaseTableCatalog'].each_element('BaseTable') do |n|
#base_table = BaseTable.new
#base_table.base_table_create(#fmfile.user_id, #fmfile.id, n)
end
And it carries on reading all the different XML elements in.
I'm using Rails 2.1.0 and Mongrel 1.1.5 in Development environment on Mac OS X 10.5.4, site DB and browser on same machine.
My question is this. This whole process works fine when reading an XML document with character encoding UTF-8 but fails when the XML file is UTF-16, does anyone know why this is happening and how it can be stopped?
I have included the error output from the debugger console below, it takes about 5 minutes to get this output and the browser times out before the following output with the 'Failed to open page'
Processing FmfilesController#create (for 127.0.0.1 at 2008-09-15 16:50:56) [POST]
Session ID: BAh7CDoMdXNlcl9pZGkGOgxjc3JmX2lkIiVmM2I3YWU2YWI4ODU2NjI0NDM2
NTFmMDE1OGY1OWQxNSIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxh
c2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--dd9f588a68ed628ab398dd1a967eedcd09e505e0
Parameters: {"commit"=>"Import DDR", "authenticity_token"=>"3da97372885564a4587774e7e31aaf77119aec62", "action"=>"create", "fmfile_document"=>#<File:/var/folders/LU/LU50A0vNHA07S4rxDAOk4E+++TI/-Tmp-/CGI.3001.1>, "controller"=>"fmfiles"}
[4;36;1mUser Load (0.000350)[0m [0;1mSELECT * FROM "users" WHERE (id = 1) LIMIT 1[0m
[4;35;1mFmfile Create (0.000483)[0m [0mINSERT INTO "fmfiles" ("name", "file_group_id", "updated_at", "report_created_at", "report_link", "report_version", "option_on_open_account_name", "user_id", "option_default_custom_menu_set", "option_on_close_script", "path", "report_type", "option_on_open_layout", "option_on_open_script", "created_at") VALUES('TheTest_fp7 2.xml', 1, '2008-09-15 15:50:56', NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, '2008-09-15 15:50:56')[0m
REXML::ParseException (#<Iconv::InvalidCharacter: "਼䙍偒数 (followed by a few thousand similar looking chinese characters)
䙍偒数潲琾", ["\n"]>
/Library/Ruby/Site/1.8/rexml/encodings/ICONV.rb:7:in `conv'
/Library/Ruby/Site/1.8/rexml/encodings/ICONV.rb:7:in `decode'
/Library/Ruby/Site/1.8/rexml/source.rb:50:in `encoding='
/Library/Ruby/Site/1.8/rexml/parsers/baseparser.rb:210:in `pull'
/Library/Ruby/Site/1.8/rexml/parsers/treeparser.rb:21:in `parse'
/Library/Ruby/Site/1.8/rexml/document.rb:190:in `build'
/Library/Ruby/Site/1.8/rexml/document.rb:45:in `initialize'

Rather than a rails/mongrel problem, it sounds more likely that there's an issue either with your XML file or with the way REXML handles it. You can check this by writing a short script to read your XML file directly (rather than within a request) and seeing if it still fails.
Assuming it does, there are a couple of things I'd look at. First, I'd check you are running the latest version of REXML. A couple of years ago there was a bug (http://www.germane-software.com/projects/rexml/ticket/63) in its UTF-16 handling.
The second thing I'd check is if you're issue is similar to this: http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/ba7b0585c7a6330d. If so you can try the workaround in that thread.
If none of the above helps, then please reply with more information, such as the exception you are getting when you try and read the file.

Since getting this to work requires me to only change the encoding attribute of the first XML element to have the value UTF-8 instead of UTF-16, the XML file is actually UTF-8 and labelled wrongly by the application that generates it.
The XML file is a FileMaker DDR export produced by FileMaker Pro Advanced 8.5 on OS X 10.5.4

Have you tried doing this using JRuby? I've heard Unicode strings are better supported in JRuby.
One other thing you can try is to use another XML parsing library, such as libxml ou Hpricot.
REXML is one of the slowest Ruby XML libraries you can use and might not scale.

Actually, I think your problem may be related to the problem I just detailed in this post. If I were you, I'd open it up in TextPad in Binary mode and see if there are any Byte Order Marks before your XML starts.

Related

Rails/Capybara: How to make attach_file work with active storage direct upload?

I am having trouble to attach files to inputs which use direct upload inside my system tests (Capybara). All tests worked before I switched to direct upload. I have also tried to manually submit appropriate forms via Browser and everything works there. Unfortunately, no luck with Capybara :/.
Inside view, I have following input:
<%= f.input :desktop_files, as: :file, input_html: { direct_upload: true, multiple: true } %>
and file is attached to input in system test by:
attach_file 'uploads_create_assets_former[desktop_files][]', "#{fixture_path}/files/image.jpg"
When I try to run test which uses similar piece of code, I get:
Selenium::WebDriver::Error::UnexpectedAlertOpenError: unexpected alert open: {Alert text : Error reading image.jpg}
(Session info: headless chrome=94.0.4606.81)
and when I check console inside browser opened by Capabyra, I can see following error:
FileReader error
My suspicion is that Capabyra/Selenium has problem to access attached file, but I don't know about any other way how to assign file to input. Maybe there is some Capybara magic which comes to play here :) -- hopefully, I am not only one who uses Rails direct upload and needs to test this piece of code with system tests...
I am using:
ruby (3.0.0)
rails (6.1.4.1)
selenium-webdriver (4.0.3)
capybara (3.35.3)
webdrivers (4.7.0)
and for capybara:
Capybara.register_driver :headless_chrome do |app|
options = Selenium::WebDriver::Chrome::Options.new(
args: %w[headless disable-gpu no-sandbox window-size=1440x768]
)
options.add_preference(:download, prompt_for_download: false,
default_directory: Rails.root.join('tmp/downloads').to_s)
options.add_preference(:browser, set_download_behavior: { behavior: 'allow' })
Capybara::Selenium::Driver.new(app, browser: :chrome, capabilities: options)
end
Edit:
Html code of form which should do upload looks like this:
<form class="formtastic uploads_create_assets_former" id="new_uploads_create_assets_former" enctype="multipart/form-data" action="/admin/upload/create" accept-charset="UTF-8" method="post">
<fieldset class="inputs">
<ol>
<li class="file input optional" id="uploads_create_assets_former_desktop_files_input"><label for="uploads_create_assets_former_desktop_files" class="label">Dateien (Computer)</label>
<input id="uploads_create_assets_former_desktop_files" multiple="multiple" data-direct-upload-url="http://127.0.0.1:49538/rails/active_storage/direct_uploads" type="file" name="uploads_create_assets_former[desktop_files][]" />
</li>
</ol>
</fieldset>
<fieldset class="actions">
<ol>
<li class="action input_action " id="uploads_create_assets_former_submit_action">
<input type="submit" name="commit" value="Nächster Schritt" data-disable-with="Nächster Schritt" />
</li>
</ol>
</fieldset>
</form>
I have not deviated in any way from Active Storage direct upload documented at https://edgeguides.rubyonrails.org/active_storage_overview.html#direct-uploads. Upload of files starts on former submission.
Another edit:
I have prepared minimalistic Rails app where you can try to play with my issue: https://github.com/martintomas/capybara-direct-upload. I have double checked that path is correct (otherwise Capybara::FileNotFound is raised), tried relative and absolute paths. I have also checked that anybody can read file:
-rw-r--r-- 1 martintomas staff 26436 Oct 22 12:51 image.jpg
Same problem happens when tests are run on my local machine or inside CI environment. To be honest, I have run out of ideas so I have decided to go for hacky solution now.
Hacky solution:
If you absolute trust active storage direct upload implementation and you don't have extra js code related to direct upload, you can turn it off inside system tests.
def attach_file(locator = nil, paths, make_visible: nil, **options)
turn_off_direct_upload # Capybara does not work with direct upload
super
end
def turn_off_direct_upload
page.execute_script 'document.querySelectorAll("input[data-direct-upload-url]:not([data-direct-upload-url=\"\"])").forEach((input) => { delete input.dataset.directUploadUrl } )'
end

Simple_form_for not using AJAX?

I got a weird problem with my form:
= simple_form_for([#item, #item_comment], :remote => true, id: "new_item_comment", :url => item_item_comments_path(#item)) do |f|
= f.input :comment, :label => false
= f.submit "Save", :class => "btn_save left"
Which in my opinion should call:
Started POST "/de-de/items/20150423/item_comments" for 127.0.0.1 at 2015-04-23 12:29:33 +0200
Processing by ItemCommentsController#create as JSON
but instead I get it as HTML:
Started POST "/de-de/items/20150423/item_comments" for 127.0.0.1 at 2015-04-23 12:29:33 +0200
Processing by ItemCommentsController#create as HTML
It used to work but without changing these parts, it only uses HTML.
Does anyone have an idea on how to solve this?
--- Update 1 ---
I added these lines to my coffeescript:
$('form[data-remote]').submit (e)->
e.preventDefault()
$.rails.handleRemote $('form[data-remote]')
And it works but I'm not really satisfied with this solution since I don't know what caused the problem.
Usually it happened to me in 2 cases:
I had a file input on the form (which forces ruby to skip remote: true option)
I had troubles with jquery-ujs javascript file (which actually processes rails html attrs)
So please check your generated html if it has <form .... data-remote='true'..> and check that jquery-ujs (or whatever handler you want to use) is included in a page javascripts.
If you are still having troubles after this, you can put a breakpoint somewhere in jquery-ujs

Why Ajax renders 2 views in rails 3.1 app

When entering log in our rails 3.1 app, ajax is used to render the input screen below. Here is the link for log:
<%= link_to 'Log', new_part_out_log_path(#part, :format => :js), :remote => true, :id => 'new_log_link' %>
And the new.js.erb as this:
$("<%= escape_javascript render(:file => 'out_logs/new.html.erb') %>").insertAfter('#new_log_link');
$('#new_log_link').hide();
$('#close').hide();
the problem is that after clicking 'Log', instead of one view, 2 identical views of out_logs/new.html.erb were rendered. What may be wrong with our code? thank so much.
The problem is related to upgrade to ruby 1.9.3 which causes webrick server (for development) warning message and also rendering the .js.erb file twice on screen. The twice rendering problem disappears on our production server which is running nginx. The following link may help to understand the problem:
http://theresa.multimediatechnology.at/webrick-warnings-in-ruby-1-9-3/
What does "WARN Could not determine content-length of response body." mean and how to I get rid of it?

Multiple files upload with Rails 3 and paperclip on heroku

I need an interface on my Rails 3 app to upload multiple files to Amazon S3 (because i'm on heroku), possibly with progress bars.
I've easily managed how to set up paperclip and upload single files, but i'm really lost now on how to go ahead.
Please can you give me some advices? It's 2 days i'm searching across all the internet, but i can't find a working solution
** EDIT **
I really can't understand... I'm going mad 'cause I'm losing too many hours on this... please help me.
If I try to open the example app cited by Johnny I only get this (and in my app it is the same):
Where is the UI?
Is there something wrong on my browser?
** EDIT 2 **
Here on GitHub you can find my testapp... please can you explain me why the damn upload UI is not showing up? Thanks!
** EDIT 3 **
Thank you very much Johnny, i wasn't aware of the fact that jquery and prototype can't live together.
Now the plugin is showing up correctly, but as a try to upload something it creates a new "upload" record, but its attachment field is blank, and the files are not on s3.
This is what the console is saying:
Started POST "/uploads" for 127.0.0.1 at 2011-06-27 16:17:22 +0200
Processing by UploadsController#create as JSON
Parameters: {"utf8"=>"✓", "authenticity_token"=>"GesRBTiZR1f2LV/bAeAdxWqF++gxcDJw4pPGStYGsH8=", "upload"=>{"attachment"=>[#<ActionDispatch::Http::UploadedFile:0x000001032834b8 #original_filename="animal-tiger-66550.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"upload[attachment][]\"; filename=\"animal-tiger-66550.jpg\"\r\nContent-Type: image/jpeg\r\n", #tempfile=#<File:/var/folders/Qj/QjEqvUUNGTmuki5SXOaaG++++TI/-Tmp-/RackMultipart20110627-1818-1syiex9>>]}}
AREL (0.5ms) INSERT INTO "uploads" ("attachment", "created_at", "updated_at", "attachment_file_name", "attachment_content_type", "attachment_file_size", "attachment_updated_at") VALUES (NULL, '2011-06-27 14:17:23.049136', '2011-06-27 14:17:23.049136', NULL, NULL, NULL, NULL)
[paperclip] Saving attachments.
Completed 200 OK in 64ms (Views: 4.2ms | ActiveRecord: 0.7ms)
You can look at jQuery-File-Upload. Demo here and rails 3/Paperclip setup here.
Edit: As #apneadiving mentioned, the library has been updated to version 5. The script you have is for verison 4. You should try modifying this to work with PaperClip. Copy-pasting the majority of the example code into my app (with a few modifications) worked for me:
#app/public/javascripts/application.js
$(function () {
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload();
// Load existing files:
$.getJSON($('#fileupload form').prop('action'), function (files) {
var fu = $('#fileupload').data('fileupload');
fu._adjustMaxNumberOfFiles(-files.length);
fu._renderDownload(files)
.appendTo($('#fileupload .files'))
.fadeIn(function () {
// Fix for IE7 and lower:
$(this).show();
});
});
// Open download dialogs via iframes,
// to prevent aborting current uploads:
$('#fileupload .files a:not([target^=_blank])').live('click', function (e) {
e.preventDefault();
$('<iframe style="display:none;"></iframe>')
.prop('src', this.href)
.appendTo('body');
});
});
#app/controllers/uploads_controller.rb
def create
#upload = Upload.new(params[:upload])
if #upload.save
render :json => [{
:pic_path => #upload.attachment.url.to_s ,
:name => #upload.attachment.instance.attributes["picture_file_name"]
}], :content_type => 'text/html'
else
render [:json => { :result => 'error'}], :content_type => 'text/html'
end
end
#app/views/uploads/new.html.haml
%link#theme{:href => "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.13/themes/base/jquery-ui.css", :rel => "stylesheet"}
= stylesheet_link_tag 'jquery.fileupload-ui'
#fileupload
= form_for Upload.new, :html => { :multipart => true } do |f|
.fileupload-buttonbar
%label.fileinput-button
%span Add files...
= f.file_field :attachment, :multiple => true
%button.start{:type => "submit"} Start upload
%button.cancel{:type => "reset"} Cancel upload
%button.delete{:type => "button"} Delete files
.fileupload-content
%table.files
.fileupload-progressbar
%script#template-upload{:type => "text/x-jquery-tmpl"}
%tr{:class => "template-upload{{if error}} ui-state-error{{/if}}"}
%td.preview
%td.name ${name}
%td.size ${sizef}
{{if error}}
%td.error{:colspan => "2"}
Error:
{{if error === 'custom_failure'}}Custom Error Message
{{else}}${error}
{{/if}}
{{else}}
%td.progress
%div
%td.start
%button Start
{{/if}}
%td.cancel
%button Cancel
%script#template-download{:type => "text/x-jquery-tmpl"}
%tr{:class => "template-download{{if error}} ui-state-error{{/if}}"}
{{if error}}
%td
%td.name ${name}
%td.size ${sizef}
%td.error{:colspan => "2"}
Error:
{{if error === 1}}File exceeds upload_max_filesize (php.ini directive)
{{else}}${error}
{{/if}}
{{else}}
%td.preview
{{if thumbnail_url}}
%a{:href => "${url}", :target => "_blank"}
%img{:src => "${thumbnail_url}"}/
{{/if}}
%td.name
<a href="${url}"{{if thumbnail_url}} target="_blank"{{/if}}>${name}
%td.size ${sizef}
%td{:colspan => "2"}
{{/if}}
%td.delete
%button{"data-type" => "${delete_type}", "data-url" => "${delete_url}"} Delete
Edit
Had a quick look at your app, the problem is that you are mixing prototype with jquery. The easiest way around this is to switch to jQuery using jquery-rails.
#Gemfile
gem 'jquery-rails'
Next, run bundle install and then rails g jquery:install.
Then change your app/views/layouts/application.erb to this:
<%= stylesheet_link_tag :all %>
<%= csrf_meta_tag %>
<%= javascript_include_tag 'jquery.min' %>
<%= javascript_include_tag 'jquery-ui-1.8.13.custom.min' %>
<%= javascript_include_tag 'jquery.tmpl.min' %>
<%= javascript_include_tag 'jquery.iframe-transport' %>
<%= javascript_include_tag 'jquery.fileupload' %>
<%= javascript_include_tag 'jquery.fileupload-ui' %>
<%= javascript_include_tag 'jquery_ujs' %>
<%= javascript_include_tag 'application' %>
Note that I removed the
<%= javascript_include_tag :defaults %>
So that I can specify the order in which jquery, jquery_ujs, and application are loaded.
I've begun with a very similar task recently, and the swf plugin (at least the more recent one) will indeed let you update paperclip's record. It has callbacks for just about everything you'd need to extend.
:onUploadComplete (upload_options,event)
Here's Nathan Colgate's gist on the matter. He just makes a remote call to the rails server once the upload is finished telling it of the locations for the paperclip attachment.
from his uploadCompleteHandler
var uploadCompleteHandler = function(upload_options,event){
$.ajax({
url: '<%= notify_rails_of_successful_upload_path(:format => :js)%>',
global: false,
type: 'POST',
data: ({
'authenticity_token' : '<%= form_authenticity_token %>',
'upload' : {
'file_file_name' : upload_options.FileName,
'file_file_size' : upload_options.FileSize,
'file_content_type' : upload_options.ContentType
}
}),
dataType: 'script'
}
)
};
I'm not sure if this exact callback gets triggered for each file; it definitely looks like it would. But he passes everything paperclip needs back through an ajax request. filename,size,content-type. This way all that gets sent to heroku is some text about the file, sparing your app a good amount of work by giving it to the client.
edit: flash is the only way I've found to avoid sending a lot of data through heroku to s3. There are a few html5/js-only uploaders that might be able to get the job done, but the ones I have found are still pretty ripe on the dev tree.
As per Heroku support, see this.
Paperclip & multiple files upload, although not S3 specific.
View: (notice the array blog_post[avatars][])
<form accept-charset="UTF-8" action="/blog_posts" enctype="multipart/form-data" id="new_blog_post" method="post">
<div style="margin:0;padding:0;display:inline">
<input name="utf8" type="hidden" value="✓" />
<input name="authenticity_token" type="hidden" value="<%=form_authenticity_token %>" />
</div>
<p><input id="blog_post" name="blog_post[avatars][]" type="file" multiple /></p>
<p><input name="commit" type="submit" value="Upload" /></p>
</form>
Controller:
# POST /blog_posts
# POST /blog_posts.json
def create
#blog_post = BlogPost.new(params[:blog_post])
#blog_post.avatars.each do |avatar|
each_blog_post = BlogPost.new
each_blog_post.avatar = avatar
if each_blog_post.save
end
end
end
Model:
class BlogPost < ActiveRecord::Base
attr_accessible :title, :avatar, :avatars
has_attached_file :avatar
attr_accessor :avatars
end

Rails upload file to ftp server

I'm on Rails 2.3.5 and Ruby 1.8.6 and trying to figure out how to let a user upload a file to a FTP server on a different machine via my Rails app. Also my Rails app will be hosted on Heroku which doesn't facilitate the writing of files to the local filesystem.
index.html.erb
<% form_tag '/ftp/upload', :method => :post, :multipart => true do %>
<label for="file">File to Upload</label> <%= file_field_tag "file" %>
<%= submit_tag 'Upload' %>
<% end %>
ftp_controller.rb
require 'net/ftp'
class FtpController < ApplicationController
def upload
file = params[:file]
ftp = Net::FTP.new('remote-ftp-server')
ftp.login(user = "***", passwd = "***")
ftp.putbinaryfile(file.read, File.basename(file.original_filename))
ftp.quit()
end
def index
end
end
Currently I'm just trying to get the Rails app to work on my Windows laptop. With the above code, I'm getting this error
Errno::ENOENT in FtpController#upload
No such file or directory -.... followed by a dump of the file contents
I'm trying to upload a CSV file if that makes any difference. Anyone knows what's going on?
After much research and head banging, I ended up reading the source code for putbinaryfile method to figure out a workaround for the limitation of putbinaryfile. Here's the working code, replace this line
ftp.putbinaryfile(file.read, File.basename(file.original_filename))
with
ftp.storbinary("STOR " + file.original_filename, StringIO.new(file.read), Net::FTP::DEFAULT_BLOCKSIZE)
And in case you are wondering, STOR is a raw FTP command, yeah it came to that. I'm rather surprised this scenario isn't more easily handled by Ruby standard libraries, it certainly wasn't obvious what needed to be done.
And if your app is on Heroku, add this line
ftp.passive = true
Heroku's firewall setup does not allow for FTP active mode, also make sure that your FTP server supports passive mode.
Seems to me that ftp.putbinaryfile just wants the path and name of the file as the first parameter.

Resources