{"id":13880512,"url":"https://github.com/sandrods/odf-report","last_synced_at":"2026-02-16T23:15:34.876Z","repository":{"id":423564,"uuid":"262938","full_name":"sandrods/odf-report","owner":"sandrods","description":"Generates ODF files, given a template (.odt) and data, replacing tags","archived":false,"fork":false,"pushed_at":"2024-03-22T17:41:16.000Z","size":1018,"stargazers_count":281,"open_issues_count":1,"forks_count":100,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-07-04T11:44:28.557Z","etag":null,"topics":["odt","openoffice","reports","ruby"],"latest_commit_sha":null,"homepage":"http://sandrods.github.com/odf-report","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sandrods.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2009-07-28T21:38:39.000Z","updated_at":"2025-03-18T07:01:59.000Z","dependencies_parsed_at":"2024-01-07T01:16:23.136Z","dependency_job_id":"fdb30b30-ca91-430e-88dc-3c3a1eb7141b","html_url":"https://github.com/sandrods/odf-report","commit_stats":{"total_commits":136,"total_committers":17,"mean_commits":8.0,"dds":"0.16911764705882348","last_synced_commit":"9a68b0fd47ee8c8b46cf34153b68a4d4d97e9333"},"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/sandrods/odf-report","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrods%2Fodf-report","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrods%2Fodf-report/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrods%2Fodf-report/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrods%2Fodf-report/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sandrods","download_url":"https://codeload.github.com/sandrods/odf-report/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sandrods%2Fodf-report/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265527541,"owners_count":23782480,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["odt","openoffice","reports","ruby"],"created_at":"2024-08-06T08:03:05.915Z","updated_at":"2026-02-16T23:15:34.845Z","avatar_url":"https://github.com/sandrods.png","language":"Ruby","funding_links":[],"categories":["Ruby","Spreadsheets"],"sub_categories":[],"readme":"\n# ODF-REPORT\n\nGem for generating .odt files by making strings, images, tables and sections replacements in a previously created .odt file.\n\n## INSTALL\n\nIn your Gemfile\n```ruby\ngem 'odf-report'\n```\n\n## USAGE\n\n### Step 1  --  the template\n\nFirst of all, you need a `.odt` file to serve as a template.\nTemplates are normal .odt files with `[PLACEHOLDERS]` for *substitutions*.\nThere are *four* kinds of substitutions available:\n* fields\n* tables\n* images\n* sections\n\n#### Fields\n\nIt's just an upcase sentence, surrounded by brackets. It will be replaced by the value you supply.\n\nIn the folowing example:\n\n```ruby\nreport = ODFReport::Report.new(\"Users/john/my_template.odt\") do |r|\n  r.add_field :user_name, @user.name\n  r.add_field :address, \"My new address\"\nend\n```\n\nAll occurences of `[USER_NAME]` found in the file will be replaced by the value of `@user.name` whereas all `[ADDRESS]` 'es will contains `My new address`\n\n\n#### Tables\n\nTo use table placeholders, you should create a Table in your document and give it a name. In OpenOffice, it's just a matter of right-clicking the table you just created, choose `Table Properties...` and type a name in the Name field.\n\nIf you inform `header: true`, the first row will be treated as a *header* and left untouched. The remaining rows will be used as the template for the table.\n\nIf you have more than one template row, they will be cycled. This is usefull for making zebra tables.\n\nAs with **Field placeholders**, just insert a `[FIELD_NAME]` in each cell and let the magic takes place.\n\nTaking the folowing example:\n\n```ruby\nreport = ODFReport::Report.new(\"Users/john/my_template.odt\") do |r|\n\n  r.add_field \"USER_NAME\", @user.nome\n  r.add_field \"ADDRESS\", @user.address\n\n  r.add_table(\"TABLE_1\", @list_of_items, :header=\u003etrue) do |t|\n    t.add_column(:item_id, :id)\n    t.add_column(:description) { |item| \"==\u003e #{item.description}\" }\n  end\n\nend\n```\n\nand considering you have a table like this in your template\n\n| #ID | Description |\n|--|--|\n| [ITEM_ID] | [DESCRIPTION] |\n\n\nand a collection `@list_of_items`, it will create one row for each item in the collection, and the replacement will take place accordingly.\n\nAny format applied to the fields in the template will be preserved.\n\n#### Sections\n\nSometimes, you have to repeat a whole chunk of a document, in a structure a lot more complex than a table. You can make a Section in your template and use it in this situations. Creating a Section in OpenOffice is as easy as select menu *Insert* and then *Section...*, and then choose a name for it.\n\nSections are lot like Tables, in the sense that you can pass a collection and have that section repeated for each member of the collection. *But*, Sections can have anything inside it, even Tables *and nested Sections*, as long as you provide the appropriate data structure.\n\nLet's see an example:\n\n```ruby\n  @invoices = Invoice.find(:all)\n\n  report = ODFReport::Report.new(\"reports/invoice.odt\") do |r|\n\n    r.add_field(:title, \"INVOICES REPORT\")\n    r.add_field(:date, Date.today)\n\n    r.add_section(\"SC_INVOICE\", @invoices) do |s|\n\n      s.add_field(:number) { |invoice| invoice.number.to_s.rjust(5, '0') }\n      s.add_field(:name,    :customer_name)\n      s.add_field(:address, :customer_address)\n\n      s.add_table(\"TB_ITEMS\", :items, header: true) do |t|\n        t.add_column(:id)\n        t.add_column(:product) {|item| item.product.name }\n        t.add_column(:value, :product_value)\n      end\n\n      s.add_field(:total) do |invoice|\n        if invoice.status == 'CLOSED'\n          invoice.total\n        else\n          invoice.items.sum('product_value')}\n        end\n      end\n\n      s.add_section(\"SUB_NOTES\", :notes) do |s1|\n\n        s1.add_field(:note_title) { |n| n.title }\n\n        s1.add_table ...\n\n      end\n\n    end\n\n  end\n```\n\nNote that when you add a Table to a Section, you don't pass the collection itself, but the attribute of the item of that section that will return the collection for that particular Table. Sounds complicated, huh? But once you get it, it's quite straightforward.\n\nIn the above example, `s.add_table(\"TB_ITEMS\", :items, header: true) do |t|`, the `:items` thing refers to a `invoice.items`. Easy, right?\n\n\n#### Images\n\nYou must put a mock image in your `.odt` template and give it a name. That name will be used to replace the mock image for the actual image.\nYou can also assign any properties you want to the mock image and they will be kept once the image is replaced.\n\nAn image replace would look like this:\n\n```ruby\nreport = ODFReport::Report.new(\"my_template.odt\") do |r|\n  r.add_image :graphic1, \"/path/to/the/image.jpg\"\n\n  r.add_table(\"TABLE_WITH_IMAGES\", @items) do |t|\n    t.add_column(:id)\n    t.add_column(:product, :product_name)\n    t.add_image('PRODUCT_IMAGE') { |item| item.image_path }\n  end  \nend\n```\n\n\u003chr/\u003e\n\n### Step 2  --  generating the document\n\nIt's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.\n\n#### Generating a document in a Rails application\n\nIn a controller, you can have a code like this:\n\n```ruby\ndef print\n\n  @ticket = Ticket.find(params[:id])\n\n  report = ODFReport::Report.new(Rails.root.join(\"/app/reports/ticket.odt\")) do |r|\n\n    r.add_field(:id,         @ticket.id.to_s)\n    r.add_field(:created_by, @ticket.created_by)\n    r.add_field(:created_at, @ticket.created_at.strftime(\"%d/%m/%Y - %H:%M\"))\n    r.add_field(:type,       @ticket.type.name)\n    r.add_field(:status,     @ticket.status_text)\n    r.add_field(:date,       Time.now.strftime(\"%d/%m/%Y - %H:%M\"))\n    r.add_field(:solution,   (@ticket.solution || ''))\n\n    r.add_table(\"OPERATORS\", @ticket.operators) do |t|\n      t.add_column(:name) { |op| \"#{op.name} (#{op.department.short_name})\" }\n    end\n\n    r.add_table(\"FIELDS\", @ticket.fields) do |t|\n      t.add_column(:field_name, :name)\n      t.add_column(:field_value) { |field| field.text_value || \"Empty\" }\n    end\n\n  end\n\n  send_data report.generate,\n\t\t    type: 'application/vnd.oasis.opendocument.text',\n            disposition: 'attachment',\n            filename: 'report.odt'\n\nend\n```\n\n#### Generating a document in a standalone script\n\nIt's very similar to a Rails app, but you can inform the path where the file will be saved.\n\n```ruby\nreport = ODFReport::Report.new(\"ticket.odt\") do |r|\n... populates the report ...\nend\n\nreport.generate(\"./documents/new_ticket.odt\")\n```\n\n#### Using a template stored in the database (or anywhere besides the file system)\nYou can provide an `io:` param, containing the actual file read into a String.\n\n```ruby\nreport = ODFReport::Report.new(io: @template.attachment.read) do |r|\n```\n\n\u003chr/\u003e\n\n#### REQUIREMENTS\n\n**rubyzip**: manipulating the contents of the odt file, since it's actually a zip file.\n**nokogiri**: parsing and manipulating the document xml files.\n**mime-types**: identify images mime types\n\n#### TROUBLESHOOTING\n\n##### Placeholder not replaced\n\nIf your placeholder is not being replaced, the problem might come from OpenOffice/LibreOffice which, when a placeholder is edited,  add some markup that prevents odf-report from identifying the placeholder.\n\nThe golden rule is: NEVER edit the placeholders. If you want to change one, delete it an write again, including the []\nExample: if you have, say, [USER] in your template and you want to change to [USERNAME], you should not edit and type NAME.\nDelete the PLACEHOLDER [USER] and type [USERNAME].\n\n##### Word found unreadable content\n\n- Symptom: You prepare your template file in eg. LibreOffice, and when you open the template in Word it says \"Word found unreadable content\"\n- Solution: Open your template in LibreOffice, save as `.docx`, quit LibreOffice. Open the `.docx` in LibreOffice, save as `.odt`.\n- Hypothesis: Word does not support all ODT features. Saving as `.docx` removes those features of the document.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrods%2Fodf-report","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsandrods%2Fodf-report","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsandrods%2Fodf-report/lists"}