<?xml version="1.0" encoding="UTF-8"?>
<article>
  <content>&lt;p&gt;There are several ways of generating PDF output with Rails. Specifically with Ruby or better yet, on all frameworks and languages. Some techniques include templates written in LaTeX[1] or DocBook[2], while others require manual generation of the entire PDF. That, actually, is not a pragmatic solution for most real-world environments, nor does it follow the MVC separation principle.&lt;br /&gt;Background and Motivation&lt;br /&gt;At the beginning of 2007, I started working for FAPES (http://www.fapes.es.gov.br), a government foundation that incentives research, science and technology in Esp&amp;iacute;rito Santo, the state where I live. I was given the job to create the site for our freshly created foundation. It was created altogether with SECT, the Secretary of Technology in our state, around 2005, without even a physical place available.&lt;br /&gt;Since it is a small foundation, I was responsible for everything related to IT, including network infrastructure, help-desk and programming. At the time, I was asked to provide maintenance to NOSSABOLSA, a web system implemented in ASP, which was (and still is) an important program of FAPES/SECT for financing studies in private colleges for students of low income families. I knew nothing about ASP or Rails at the time.&lt;br /&gt;My previous serious web programming experience (several years before this job) was with Perl. I didn&amp;rsquo;t work with web programming for lots of years after that. At the middle of 2007, I had to develop a new site for promoting an event we were organizing that needed a subscription system, along with lots of static information. Since I had a Linux server available, I didn&amp;rsquo;t have to implement it in ASP, which would take a lot of days and I only had a week for developing the new site.&lt;br /&gt;I started to then look at the web for available modern web frameworks, that allowed me to develop the website faster. I took a look at Perl frameworks, of course, J2EE, .Net, TurboGears, Django, Rails and a lot more. It became clear that Rails was the right choice. So I learned Rails, and developed the whole site in a week. Since it would be a temporary site, I didn&amp;rsquo;t mind trying a new framework at the time. I was so excited by Rails and specially Ruby, that, when we needed a new permanent site for FAPES, it was the logical choice.&lt;br /&gt;Shortly after the site creation, there was a need to generate some contracts based on input from web forms that could be printed and delivered to FAPES. There were lots of problems generating the contracts in HTML. CSS was not well thought for printing. It was difficult (if at all possible) to setup headers &amp;amp; footers and actually the print depends a lot on browser configurations and rendering engines. As a result, the printed versions would not follow a unique layout, which was a problem to us. So, I started thinking about PDF output. I read all the usual PDF generation techniques but none of them seemed to fit my needs.&lt;br /&gt;Proposed Solution Overview&lt;br /&gt;The problem was that there were a lot of types of contracts and they were a bit long, which would take a lot of time for preparing them without a good template system. I knew the juridic department was unable to give me LaTeX or DocBook-formatted contract models. They only knew MS Word, and I had to live with it. There was also not a lot of time for implementing contracts generation.&lt;br /&gt;It seems that when under pressure, we are extremely creative. Fortunately, I remembered that ODF was, actually, a XML file, along other files in a folder structure that were zipped in an ODF file. I extracted the file and took a look at a special file, called content.xml. Then I realized that it was possible (and pretty easy) to replace some special text templates with form fields submitted to the web server. It was also pretty easy to import MS Word documents in OpenOffice.org[3] and save them in ODT format. And I didn&amp;rsquo;t have to teach the juridic department any new document writing technique, such as Latex or DocBook. They could just use what they were used to.&lt;br /&gt;Additionally, there were some free tools that could convert odt to pdf, using OpenOffice.org in a &amp;ldquo;headless&amp;rdquo; environment, which meant that I could run it as a background daemon without even having a graphical environment installed. This daemon could be run in the same server as the application or in another dedicated server, if you prefer. Here is a possible usage for setting OpenOffice.org as a daemon, listening on port 3003:&lt;br /&gt;soffice -accept=&amp;rdquo;socket,host=localhost,port=3003;urp&amp;rdquo; -norestore -headless -invisible -nofirststartwizard&amp;amp;&lt;br /&gt;Then, any UNO[4] enabled software could convert any ODF file to any OpenOffice.org support output format, including PDF. For instance, one could use PyODConverter[5] for converting an odt document to pdf:&lt;br /&gt;/opt/openoffice.org3/program/python DocumentConverter.py document.odt output.pdf&lt;br /&gt;Just be sure to edit DocumentConverter.py, changing the port to reflect the one OpenOffice.org is listening at, since the port can&amp;rsquo;t be currently passed as a parameter. Following the above example, it means:&lt;br /&gt;DEFAULT_OPENOFFICE_PORT = 3003&lt;br /&gt;The overall idea is illustrated on the following pictures:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;ODF template + HTML form&lt;br /&gt;&lt;br /&gt;Final generated output&lt;br /&gt;A Possible Implementation&lt;br /&gt;I was pretty happy for having found the solution to my problems and only needed some little time to implement the solution. This technique, actually, can be very easily implemented in any language. At least in Ruby, all seem easy to implement. Here is what I currently use for generating PDF contracts (save it to config/initializers/contract.rb):&lt;br /&gt;require &amp;lsquo;rexml/document&amp;rsquo;&lt;br /&gt;module Contract&lt;br /&gt;&amp;nbsp; CONST_FIELDS = {&amp;lsquo;DirectorName&amp;rsquo; =&amp;gt; &amp;lsquo;Name of Director&amp;rsquo;, &amp;lsquo;FAPES_Account&amp;rsquo; =&amp;gt; &amp;lsquo;Account Information&amp;rsquo;}&lt;br /&gt;&amp;nbsp; CONTRACTS_URL=&amp;rsquo;/contracts&amp;rsquo;; CONTRACTS_DIR = Rails.public_path+CONTRACTS_URL&lt;br /&gt;&amp;nbsp; ATTACHMENTS_DIR = &amp;ldquo;#{CONTRACTS_DIR}/attachments/&amp;rdquo;&lt;br /&gt;&amp;nbsp; OUTPUT_DIR = &amp;ldquo;#{CONTRACTS_DIR}/generated/&amp;rdquo;;&amp;nbsp; TEMPLATES_DIR = &amp;ldquo;#{CONTRACTS_DIR}/templates/&amp;rdquo;&lt;br /&gt;&amp;nbsp; class &amp;lt;&amp;lt; self&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # Save the template file at public/contracts/templates/scholarship.odt, then call:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # Contract::generate(&amp;lsquo;scholarship&amp;rsquo;, {&amp;lsquo;student_name&amp;rsquo; =&amp;gt; &amp;lsquo;Rodrigo Rosenfeld&amp;rsquo;}, &amp;lsquo;scholarships/1&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # public/contracts/generated/scholarships/1.pdf will be created. Output is PDF link address.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; def generate (template_file, fields, output_file, options={})&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; fields.merge!(CONST_FIELDS)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; template_file = TEMPLATES_DIR+template_file+&amp;rsquo;.odt&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # replaces non-alphanumerics to underscore. Security is responsability from calling method.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; output_file.gsub! /[^\\\/\w\.\-]/, &amp;lsquo;_&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pdf_output_filename = OUTPUT_DIR+output_file+&amp;rsquo;.pdf&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pdf_output_filename_temp = options[:attachment] ? OUTPUT_DIR+output_file+&amp;rsquo;_without_attachment.pdf&amp;rsquo; :&amp;nbsp; pdf_output_filename&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; output_file = OUTPUT_DIR+output_file+&amp;rsquo;.odt&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; output_dir = File.dirname(output_file); FileUtils.mkdir_p(output_dir)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Kernel.system &amp;ldquo;unzip -o #{template_file} content.xml -d #{output_dir}&amp;rdquo; or return nil&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; content_file = &amp;ldquo;#{File.readlines(&amp;ldquo;#{output_dir}/content.xml&amp;rdquo;)}&amp;rdquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # before replacing expressions, generate table templates&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; options[:tables].each {|t| content_file=generate_table(content_file,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; t[:table_name], t[:line], t[:fields])} if options[:tables]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; replace_expressions(content_file, fields)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; File.open(&amp;ldquo;#{output_dir}/content.xml&amp;rdquo;, &amp;lsquo;w&amp;rsquo;) { |f| f.write content_file }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; FileUtils.cp_r template_file, output_file&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; #pdf-converter is a script, that currently uses PyODConverter (DEFAULT_OPENOFFICE_PORT=3003):&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; #/opt/openoffice.org3/program/python /usr/local/bin/DocumentConverter.py $@&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Kernel.system(&amp;ldquo;zip -j #{output_file} #{output_dir}/content.xml; pdf-converter #{output_file} #{pdf_output_filename_temp}&amp;rdquo;) or return nil&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; return merge_pdf(pdf_output_filename_temp, ATTACHMENTS_DIR + options[:attachment], pdf_output_filename) if options[:attachment]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; pdf_output_filename.sub /\Apublic/, &amp;lsquo;&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; def replace_expressions(str, fields)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # The pattern &amp;ldquo;#{student_name#U}&amp;rdquo; will be replaced by &amp;lsquo;RODRIGO ROSENFELD&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; str.gsub!(/#\{(.*?)(#(.))?\}/) do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; result = (fields[$1] or &amp;lsquo;&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; case $3&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; when &amp;lsquo;U&amp;rsquo;; result.mb_chars.upcase.to_s&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; when &amp;lsquo;d&amp;rsquo;; result.mb_chars.downcase.to_s&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; when &amp;lsquo;C&amp;rsquo;; result.mb_chars.capitalize.to_s&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; # lots of other formatters here for writing number at full length, as currency, etc.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; else; result # doesn&amp;rsquo;t change&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # this generates dynamic tables into ODF Templates. It is necessary to define a name for the table in OpenOffice.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # &amp;lsquo;line&amp;rsquo; starts at 1.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # generate_table(content_string, &amp;lsquo;Items&amp;rsquo;, 2, [{&amp;lsquo;n&amp;rsquo; =&amp;gt; 1, &amp;lsquo;item&amp;rsquo; =&amp;gt; &amp;lsquo;Desktop computer&amp;rsquo;}, {&amp;lsquo;n&amp;rsquo; =&amp;gt; 2, &amp;lsquo;item&amp;rsquo; =&amp;gt; &amp;lsquo;Laser printer&amp;rsquo;}])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; def generate_table(content_xml, table_name, line, fields)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; document = REXML::Document.new(content_xml)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; template_line = document.root.elements[&amp;ldquo;//table:table[@table:name=&amp;rsquo;#{table_name}&amp;rsquo;]/table:table-row[#{line}]&amp;rdquo;].to_s&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; document.to_s.sub(template_line, fields.collect {|f| replace_expressions(template_line.dup, f)}.join)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; # returns url to merged file or nil if it couldn&amp;rsquo;t be generated&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; def merge_pdf(contract, attachment, output)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; [contract, attachment].each {|f| return nil unless File.exist?(f)}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Kernel.system(&amp;ldquo;pdftk #{contract} #{attachment} cat output #{output}&amp;rdquo;) or return nil&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; output.sub(Rails.public_path, &amp;lsquo;&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; end&lt;br /&gt;&amp;nbsp; end&lt;br /&gt;end&lt;br /&gt;The implementation is not really important and I will not talk very much about it, since I am pretty sure a lot of plug-ins with better options and implementation will be developed using this technique. Most of the implementation is trivial to understand. Optimizations can be made, of course. For instance, it should not be necessary to unzip content.xml from the template before each conversion.&lt;br /&gt;I&amp;rsquo;ll just take some time to explain some parts that might not be obvious to all readers.&lt;br /&gt;template_line = document.root.elements[&amp;ldquo;//table:table[@table:name=&amp;rsquo;#{table_name}&amp;rsquo;]/table:table-row[#{line}]&amp;rdquo;].to_s&lt;br /&gt;document.to_s.sub(template_line, fields.collect {|f| replace_expressions(template_line.dup, f)}.join)&lt;br /&gt;It is intended to support simple table templates. Patterns should be written in one line, which will be replaced for several lines containing a collection of data taken from the web, such as Items to Purchase, or whatever. It is necessary to name the table (see table properties in OpenOffice) and tell the method which line should be used as template. First line is number one.&lt;br /&gt;There is also a helper method for merging PDFs, so that one special PDF can be attached at the end of the dynamic one if it is necessary. The implementation could be changed to accept other parameters for specifying a header and a footer PDF. The implementation uses pdftk[6] for merging them.&lt;br /&gt;As you probably noted, you would write ODT files with patterns such as &amp;ldquo;I, #{student_name#U}&amp;rdquo;, agree...&amp;rdquo;, that will be replaced by &amp;ldquo;I, RODRIGO ROSENFELD, agree...&amp;rdquo;, when &amp;ldquo;Rodrigo Rosenfeld&amp;rdquo; is submitted to the web server, in a form.&lt;br /&gt;Test Cases&lt;br /&gt;You should also write an unit test for assuring the output is correctly generated. Here is a possible unit test (test/unit/contract_test.rb):&lt;br /&gt;require &amp;lsquo;test_helper&amp;rsquo;&lt;br /&gt;class ContractTest &amp;lt; ActiveSupport::TestCase&lt;br /&gt;&amp;nbsp; # This test is not definitive, since it doesn&amp;rsquo;t test the generated pdf content. But chances are good to be correct if content.xml is correct and&lt;br /&gt;&amp;nbsp; # the generated file is a PDF (starts with &amp;lsquo;%PDF&amp;rsquo;). However this doesn&amp;rsquo;t test if the merge is good, although it assures a lot of steps are working.&lt;br /&gt;&amp;nbsp; test &amp;lsquo;generate contract&amp;rsquo; do&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; FileUtils::rm_rf Rails.public_path + &amp;lsquo;/contracts/generated/scholarships/test/&amp;rsquo;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; assert_equal &amp;lsquo;/contracts/generated/scholarships/test/rodrigo.pdf&amp;rsquo;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Contract::generate(&amp;lsquo;scholarship&amp;rsquo;, {&amp;lsquo;student_name&amp;rsquo; =&amp;gt; &amp;lsquo;Rodrigo Rosenfeld&amp;rsquo;, &amp;lsquo;address&amp;rsquo; =&amp;gt; &amp;lsquo;R. Nascimento Silva, 107&amp;rsquo;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;lsquo;total&amp;rsquo; =&amp;gt; 4090.49}, &amp;lsquo;scholarships/test/rodrigo&amp;rsquo;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; :tables =&amp;gt; [{:table_name =&amp;gt; &amp;lsquo;Items&amp;rsquo;, :line =&amp;gt; 2, :fields =&amp;gt; [{&amp;lsquo;name&amp;rsquo; =&amp;gt; &amp;lsquo;Books&amp;rsquo;, &amp;lsquo;value&amp;rsquo;=&amp;gt; 90.49},&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp; {&amp;lsquo;name&amp;rsquo; =&amp;gt; &amp;lsquo;Airline Ticketings for International Congress&amp;rsquo;, &amp;lsquo;value&amp;rsquo; =&amp;gt; 4000.00}]}],&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; :attachment =&amp;gt; &amp;lsquo;scholarships/contract_terms.pdf&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; assert FileUtils::identical?(Rails.root.to_s + &amp;lsquo;/test/fixtures/expected_content.xml&amp;rsquo;,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Rails.public_path + &amp;lsquo;/contracts/generated/scholarships/test/content.xml&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; f = File.new(Rails.public_path + &amp;lsquo;/contracts/generated/scholarships/test/rodrigo.pdf&amp;rsquo;)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; assert_equal &amp;lsquo;%PDF&amp;rsquo;, f.read(4) # Is the output a pdf?&lt;br /&gt;&amp;nbsp; end&lt;br /&gt;end&lt;br /&gt;Conclusion&lt;br /&gt;Generating PDF from ODF templates proved to be pretty easy. Enjoy your free time, now that you can save a lot of it manually preparing PDF generation! Or use it for writing a good plug-in for PDF generation using this technique. :)&lt;br /&gt;Recently, a new plugin for ODF generation, written in Ruby, is available at http://github.com/sandrods/odf-report/tree/master. This plugin uses the idea presented in this article for ODF generation, using the rubyzip gem for zipping/unzipping, instead of launching an external program for this task. For readers interested in implementing a plugin for PDF generation in Ruby, I would recomend taking a look at this ODF generator and adapt it to integrate with OpenOffice.org and PyODConverter as demonstrated in this article.&lt;/p&gt;</content>
  <created-at type="datetime">2009-08-30T03:26:55Z</created-at>
  <discuss-url>http://railsmagazine.com/forums/2/topics/68</discuss-url>
  <id type="integer">39</id>
  <issue-id type="integer">4</issue-id>
  <number type="integer">3</number>
  <title>Generating PDF with ODF templates</title>
  <updated-at type="datetime">2009-12-24T20:50:45Z</updated-at>
</article>
