module XeroGateway class Report include Money include Dates attr_reader :errors attr_accessor :report_id, :report_name, :report_type, :report_titles, :report_date, :updated_at, :body, :column_names def initialize(params={}) @errors ||= [] @report_titles ||= [] @body ||= [] params.each do |k,v| self.send("#{k}=", v) end end def self.from_xml(report_element) report = Report.new report_element.children.each do | element | case element.name when 'ReportID' then report.report_id = element.text when 'ReportName' then report.report_name = element.text when 'ReportType' then report.report_type = element.text when 'ReportTitles' each_title(element) do |title| report.report_titles << title end when 'ReportDate' then report.report_date = Date.parse(element.text) when 'UpdatedDateUTC' then report.updated_at = parse_date_time_utc(element.text) when 'Rows' report.column_names ||= find_body_column_names(element) each_row_content(element) do |content_hash| report.body << OpenStruct.new(content_hash) end end end report end private def self.each_row_content(xml_element, &block) column_names = find_body_column_names(xml_element).keys xpath_body = REXML::XPath.first(xml_element, "//RowType[text()='Section']").parent rows_contents = [] xpath_body.elements.each("Rows/Row") do |xpath_cells| values = find_body_cell_values(xpath_cells) content_hash = Hash[column_names.zip values] rows_contents << content_hash yield content_hash if block_given? end rows_contents end def self.each_title(xml_element, &block) xpath_titles = REXML::XPath.first(xml_element, "//ReportTitles") xpath_titles.elements.each("//ReportTitle") do |xpath_title| title = xpath_title.text.strip yield title if block_given? end end def self.find_body_cell_values(xml_cells) values = [] xml_cells.elements.each("Cells/Cell") do |xml_cell| if value = xml_cell.children.first # finds ... values << value.text.try(:strip) next end values << nil end values end # returns something like { column_1: "Amount", column_2: "Description", ... } def self.find_body_column_names(body) header = REXML::XPath.first(body, "//RowType[text()='Header']") names_map = {} column_count = 0 header.parent.elements.each("Cells/Cell") do |header_cell| column_count += 1 column_key = "column_#{column_count}".to_sym column_name = nil name_value = header_cell.children.first column_name = name_value.text.strip unless name_value.blank? # finds ... names_map[column_key] = column_name end names_map end end end