sablon 0.0.18 → 0.0.19.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/.travis.yml +3 -3
 - data/Gemfile.lock +5 -5
 - data/README.md +33 -1
 - data/lib/sablon.rb +8 -3
 - data/lib/sablon/content.rb +17 -0
 - data/lib/sablon/html/ast.rb +130 -0
 - data/lib/sablon/html/converter.rb +133 -0
 - data/lib/sablon/html/visitor.rb +26 -0
 - data/lib/sablon/numbering.rb +28 -0
 - data/lib/sablon/processor/document.rb +193 -0
 - data/lib/sablon/processor/numbering.rb +47 -0
 - data/lib/sablon/processor/section_properties.rb +1 -1
 - data/lib/sablon/template.rb +8 -4
 - data/lib/sablon/version.rb +1 -1
 - data/test/fixtures/html_sample.docx +0 -0
 - data/test/fixtures/insertion_template.docx +0 -0
 - data/test/fixtures/insertion_template_no_styles.docx +0 -0
 - data/test/html/converter_test.rb +303 -0
 - data/test/html_test.rb +45 -0
 - data/test/{processor_test.rb → processor/document_test.rb} +2 -2
 - data/test/test_helper.rb +4 -0
 - metadata +22 -7
 - data/lib/sablon/processor.rb +0 -191
 
    
        data/test/html_test.rb
    ADDED
    
    | 
         @@ -0,0 +1,45 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # -*- coding: utf-8 -*-
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "test_helper"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "support/xml_snippets"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            class SablonHTMLTest < Sablon::TestCase
         
     | 
| 
      
 6 
     | 
    
         
            +
              include Sablon::Test::Assertions
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def setup
         
     | 
| 
      
 9 
     | 
    
         
            +
                super
         
     | 
| 
      
 10 
     | 
    
         
            +
                @base_path = Pathname.new(File.expand_path("../", __FILE__))
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                @sample_path = @base_path + "fixtures/html_sample.docx"
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def test_generate_document_from_template_with_styles_and_html
         
     | 
| 
      
 16 
     | 
    
         
            +
                template_path = @base_path + "fixtures/insertion_template.docx"
         
     | 
| 
      
 17 
     | 
    
         
            +
                output_path = @base_path + "sandbox/html.docx"
         
     | 
| 
      
 18 
     | 
    
         
            +
                template = Sablon.template template_path
         
     | 
| 
      
 19 
     | 
    
         
            +
                context = {'html:content' => content}
         
     | 
| 
      
 20 
     | 
    
         
            +
                template.render_to_file output_path, context
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                assert_docx_equal @sample_path, output_path
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def test_generate_document_from_template_without_styles_and_html
         
     | 
| 
      
 26 
     | 
    
         
            +
                template_path = @base_path + "fixtures/insertion_template_no_styles.docx"
         
     | 
| 
      
 27 
     | 
    
         
            +
                output_path = @base_path + "sandbox/html_no_styles.docx"
         
     | 
| 
      
 28 
     | 
    
         
            +
                template = Sablon.template template_path
         
     | 
| 
      
 29 
     | 
    
         
            +
                context = {'html:content' => content}
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                e = assert_raises(ArgumentError) do
         
     | 
| 
      
 32 
     | 
    
         
            +
                  template.render_to_file output_path, context
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
                assert_equal 'Could not find w:abstractNum definition for style: "ListNumber"', e.message
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                skip 'implement default styles'
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              private
         
     | 
| 
      
 40 
     | 
    
         
            +
              def content
         
     | 
| 
      
 41 
     | 
    
         
            +
                <<-HTML
         
     | 
| 
      
 42 
     | 
    
         
            +
            <div>Lorem <strong>ipsum</strong> <em>dolor</em> <strong>sit</strong> <em>amet</em>, <strong>consectetur adipiscing elit</strong>. <em>Suspendisse a tempus turpis</em>. Duis urna justo, vehicula vitae ultricies vel, congue at sem. Fusce turpis turpis, aliquet id pulvinar aliquam, iaculis non elit. Nulla feugiat lectus nulla, in dictum ipsum cursus ac. Quisque at odio neque. Sed ac tortor iaculis, bibendum leo ut, malesuada velit. Donec iaculis sed urna eget pharetra. Praesent ornare fermentum turpis, placerat iaculis urna bibendum vitae. Nunc in quam consequat, tristique tellus in, commodo turpis. Curabitur ullamcorper odio purus, lobortis egestas magna laoreet vitae. Nunc fringilla velit ante, eu aliquam nisi cursus vitae. Suspendisse sit amet dui egestas, volutpat nisi vel, mattis justo. Nullam pellentesque, ipsum eget blandit pharetra, augue elit aliquam mauris, vel mollis nisl augue ut ipsum.</div><ol><li>Vestibulum <ol><li>ante ipsum primis </li></ol></li><li>in faucibus orci luctus <ol><li>et ultrices posuere cubilia Curae; <ol><li>Aliquam vel dolor </li><li>sed sem maximus </li></ol></li><li>fermentum in non odio. <ol><li>Fusce hendrerit ornare mollis. </li></ol></li><li>Nunc scelerisque nibh nec turpis tempor pulvinar. </li></ol></li><li>Donec eros turpis, </li><li>aliquet vel volutpat sit amet, <ol><li>semper eu purus. </li><li>Proin ac erat nec urna efficitur vulputate. <ol><li>Quisque varius convallis ultricies. </li><li>Nullam vel fermentum eros. </li></ol></li></ol></li></ol><div>Pellentesque nulla leo, auctor ornare erat sed, rhoncus congue diam. Duis non porttitor nulla, ut eleifend enim. Pellentesque non tempor sem.</div><div>Mauris auctor egestas arcu, </div><ol><li>id venenatis nibh dignissim id. </li><li>In non placerat metus. </li></ol><ul><li>Nunc sed consequat metus. </li><li>Nulla consectetur lorem consequat, </li><li>malesuada dui at, lacinia lectus. </li></ul><ol><li>Aliquam efficitur </li><li>lorem a mauris feugiat, </li><li>at semper eros pellentesque. </li></ol><div>Nunc lacus diam, consectetur ut odio sit amet, placerat pharetra erat. Sed commodo ut sem id congue. Sed eget neque elit. Curabitur at erat tortor. Maecenas eget sapien vitae est sagittis accumsan et nec orci. Integer luctus at nisl eget venenatis. Nunc nunc eros, consectetur at tortor et, tristique ultrices elit. Nulla in turpis nibh.</div><ul><li>Nam consectetur <ul><li>venenatis tempor. </li></ul></li><li>Aenean <ul><li>blandit<ul><li>porttitor massa, <ul><li>non efficitur <ul><li>metus. </li></ul></li></ul></li></ul></li></ul></li><li>Duis faucibus nunc nec venenatis faucibus. </li><li>Aliquam erat volutpat. </li></ul><div><strong>Quisque non neque ut lacus eleifend volutpat quis sed lacus. Praesent ultrices purus eu quam elementum, sit amet faucibus elit interdum. In lectus orci, elementum quis dictum ac, porta ac ante. Fusce tempus ac mauris id cursus. Phasellus a erat nulla. Mauris dolor orci, malesuada auctor dignissim non, posuere nec odio. Etiam hendrerit justo nec diam ullamcorper, nec blandit elit sodales.</strong></div>
         
     | 
| 
      
 43 
     | 
    
         
            +
            HTML
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -3,13 +3,13 @@ require "test_helper" 
     | 
|
| 
       3 
3 
     | 
    
         
             
            require "support/document_xml_helper"
         
     | 
| 
       4 
4 
     | 
    
         
             
            require "support/xml_snippets"
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
            class  
     | 
| 
      
 6 
     | 
    
         
            +
            class ProcessorDocumentTest < Sablon::TestCase
         
     | 
| 
       7 
7 
     | 
    
         
             
              include DocumentXMLHelper
         
     | 
| 
       8 
8 
     | 
    
         
             
              include XMLSnippets
         
     | 
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
              def setup
         
     | 
| 
       11 
11 
     | 
    
         
             
                super
         
     | 
| 
       12 
     | 
    
         
            -
                @processor = Sablon::Processor
         
     | 
| 
      
 12 
     | 
    
         
            +
                @processor = Sablon::Processor::Document
         
     | 
| 
       13 
13 
     | 
    
         
             
              end
         
     | 
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
              def test_simple_field_replacement
         
     | 
    
        data/test/test_helper.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: sablon
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.0.19.beta1
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Yves Senn
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2016-01-06 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: nokogiri
         
     | 
| 
         @@ -129,9 +129,14 @@ files: 
     | 
|
| 
       129 
129 
     | 
    
         
             
            - lib/sablon.rb
         
     | 
| 
       130 
130 
     | 
    
         
             
            - lib/sablon/content.rb
         
     | 
| 
       131 
131 
     | 
    
         
             
            - lib/sablon/context.rb
         
     | 
| 
      
 132 
     | 
    
         
            +
            - lib/sablon/html/ast.rb
         
     | 
| 
      
 133 
     | 
    
         
            +
            - lib/sablon/html/converter.rb
         
     | 
| 
      
 134 
     | 
    
         
            +
            - lib/sablon/html/visitor.rb
         
     | 
| 
      
 135 
     | 
    
         
            +
            - lib/sablon/numbering.rb
         
     | 
| 
       132 
136 
     | 
    
         
             
            - lib/sablon/operations.rb
         
     | 
| 
       133 
137 
     | 
    
         
             
            - lib/sablon/parser/mail_merge.rb
         
     | 
| 
       134 
     | 
    
         
            -
            - lib/sablon/processor.rb
         
     | 
| 
      
 138 
     | 
    
         
            +
            - lib/sablon/processor/document.rb
         
     | 
| 
      
 139 
     | 
    
         
            +
            - lib/sablon/processor/numbering.rb
         
     | 
| 
       135 
140 
     | 
    
         
             
            - lib/sablon/processor/section_properties.rb
         
     | 
| 
       136 
141 
     | 
    
         
             
            - lib/sablon/redcarpet/render/word_ml.rb
         
     | 
| 
       137 
142 
     | 
    
         
             
            - lib/sablon/template.rb
         
     | 
| 
         @@ -151,6 +156,9 @@ files: 
     | 
|
| 
       151 
156 
     | 
    
         
             
            - test/fixtures/conditionals_template.docx
         
     | 
| 
       152 
157 
     | 
    
         
             
            - test/fixtures/cv_sample.docx
         
     | 
| 
       153 
158 
     | 
    
         
             
            - test/fixtures/cv_template.docx
         
     | 
| 
      
 159 
     | 
    
         
            +
            - test/fixtures/html_sample.docx
         
     | 
| 
      
 160 
     | 
    
         
            +
            - test/fixtures/insertion_template.docx
         
     | 
| 
      
 161 
     | 
    
         
            +
            - test/fixtures/insertion_template_no_styles.docx
         
     | 
| 
       154 
162 
     | 
    
         
             
            - test/fixtures/recipe_context.json
         
     | 
| 
       155 
163 
     | 
    
         
             
            - test/fixtures/recipe_sample.docx
         
     | 
| 
       156 
164 
     | 
    
         
             
            - test/fixtures/recipe_template.docx
         
     | 
| 
         @@ -169,8 +177,10 @@ files: 
     | 
|
| 
       169 
177 
     | 
    
         
             
            - test/fixtures/xml/simple_fields.xml
         
     | 
| 
       170 
178 
     | 
    
         
             
            - test/fixtures/xml/table_multi_row_loop.xml
         
     | 
| 
       171 
179 
     | 
    
         
             
            - test/fixtures/xml/table_row_loop.xml
         
     | 
| 
      
 180 
     | 
    
         
            +
            - test/html/converter_test.rb
         
     | 
| 
      
 181 
     | 
    
         
            +
            - test/html_test.rb
         
     | 
| 
       172 
182 
     | 
    
         
             
            - test/mail_merge_parser_test.rb
         
     | 
| 
       173 
     | 
    
         
            -
            - test/ 
     | 
| 
      
 183 
     | 
    
         
            +
            - test/processor/document_test.rb
         
     | 
| 
       174 
184 
     | 
    
         
             
            - test/redcarpet_render_word_ml_test.rb
         
     | 
| 
       175 
185 
     | 
    
         
             
            - test/sablon_test.rb
         
     | 
| 
       176 
186 
     | 
    
         
             
            - test/sandbox/.gitkeep
         
     | 
| 
         @@ -193,9 +203,9 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       193 
203 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       194 
204 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       195 
205 
     | 
    
         
             
              requirements:
         
     | 
| 
       196 
     | 
    
         
            -
              - - " 
     | 
| 
      
 206 
     | 
    
         
            +
              - - ">"
         
     | 
| 
       197 
207 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       198 
     | 
    
         
            -
                  version:  
     | 
| 
      
 208 
     | 
    
         
            +
                  version: 1.3.1
         
     | 
| 
       199 
209 
     | 
    
         
             
            requirements: []
         
     | 
| 
       200 
210 
     | 
    
         
             
            rubyforge_project: 
         
     | 
| 
       201 
211 
     | 
    
         
             
            rubygems_version: 2.4.5.1
         
     | 
| 
         @@ -211,6 +221,9 @@ test_files: 
     | 
|
| 
       211 
221 
     | 
    
         
             
            - test/fixtures/conditionals_template.docx
         
     | 
| 
       212 
222 
     | 
    
         
             
            - test/fixtures/cv_sample.docx
         
     | 
| 
       213 
223 
     | 
    
         
             
            - test/fixtures/cv_template.docx
         
     | 
| 
      
 224 
     | 
    
         
            +
            - test/fixtures/html_sample.docx
         
     | 
| 
      
 225 
     | 
    
         
            +
            - test/fixtures/insertion_template.docx
         
     | 
| 
      
 226 
     | 
    
         
            +
            - test/fixtures/insertion_template_no_styles.docx
         
     | 
| 
       214 
227 
     | 
    
         
             
            - test/fixtures/recipe_context.json
         
     | 
| 
       215 
228 
     | 
    
         
             
            - test/fixtures/recipe_sample.docx
         
     | 
| 
       216 
229 
     | 
    
         
             
            - test/fixtures/recipe_template.docx
         
     | 
| 
         @@ -229,8 +242,10 @@ test_files: 
     | 
|
| 
       229 
242 
     | 
    
         
             
            - test/fixtures/xml/simple_fields.xml
         
     | 
| 
       230 
243 
     | 
    
         
             
            - test/fixtures/xml/table_multi_row_loop.xml
         
     | 
| 
       231 
244 
     | 
    
         
             
            - test/fixtures/xml/table_row_loop.xml
         
     | 
| 
      
 245 
     | 
    
         
            +
            - test/html/converter_test.rb
         
     | 
| 
      
 246 
     | 
    
         
            +
            - test/html_test.rb
         
     | 
| 
       232 
247 
     | 
    
         
             
            - test/mail_merge_parser_test.rb
         
     | 
| 
       233 
     | 
    
         
            -
            - test/ 
     | 
| 
      
 248 
     | 
    
         
            +
            - test/processor/document_test.rb
         
     | 
| 
       234 
249 
     | 
    
         
             
            - test/redcarpet_render_word_ml_test.rb
         
     | 
| 
       235 
250 
     | 
    
         
             
            - test/sablon_test.rb
         
     | 
| 
       236 
251 
     | 
    
         
             
            - test/sandbox/.gitkeep
         
     | 
    
        data/lib/sablon/processor.rb
    DELETED
    
    | 
         @@ -1,191 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # -*- coding: utf-8 -*-
         
     | 
| 
       2 
     | 
    
         
            -
            module Sablon
         
     | 
| 
       3 
     | 
    
         
            -
              class Processor
         
     | 
| 
       4 
     | 
    
         
            -
                def self.process(xml_node, context, properties = {})
         
     | 
| 
       5 
     | 
    
         
            -
                  processor = new(parser)
         
     | 
| 
       6 
     | 
    
         
            -
                  processor.manipulate xml_node, Sablon::Context.transform(context)
         
     | 
| 
       7 
     | 
    
         
            -
                  processor.write_properties xml_node, properties if properties.any?
         
     | 
| 
       8 
     | 
    
         
            -
                  xml_node
         
     | 
| 
       9 
     | 
    
         
            -
                end
         
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
                def self.parser
         
     | 
| 
       12 
     | 
    
         
            -
                  @parser ||= Sablon::Parser::MailMerge.new
         
     | 
| 
       13 
     | 
    
         
            -
                end
         
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
                def initialize(parser)
         
     | 
| 
       16 
     | 
    
         
            -
                  @parser = parser
         
     | 
| 
       17 
     | 
    
         
            -
                end
         
     | 
| 
       18 
     | 
    
         
            -
             
     | 
| 
       19 
     | 
    
         
            -
                def manipulate(xml_node, context)
         
     | 
| 
       20 
     | 
    
         
            -
                  operations = build_operations(@parser.parse_fields(xml_node))
         
     | 
| 
       21 
     | 
    
         
            -
                  operations.each do |step|
         
     | 
| 
       22 
     | 
    
         
            -
                    step.evaluate context
         
     | 
| 
       23 
     | 
    
         
            -
                  end
         
     | 
| 
       24 
     | 
    
         
            -
                  cleanup(xml_node)
         
     | 
| 
       25 
     | 
    
         
            -
                  xml_node
         
     | 
| 
       26 
     | 
    
         
            -
                end
         
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
                def write_properties(xml_node, properties)
         
     | 
| 
       29 
     | 
    
         
            -
                  if start_page_number = properties[:start_page_number] || properties["start_page_number"]
         
     | 
| 
       30 
     | 
    
         
            -
                    section_properties = SectionProperties.from_document(xml_node)
         
     | 
| 
       31 
     | 
    
         
            -
                    section_properties.start_page_number = start_page_number
         
     | 
| 
       32 
     | 
    
         
            -
                  end
         
     | 
| 
       33 
     | 
    
         
            -
                end
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                private
         
     | 
| 
       36 
     | 
    
         
            -
                def build_operations(fields)
         
     | 
| 
       37 
     | 
    
         
            -
                  OperationConstruction.new(fields).operations
         
     | 
| 
       38 
     | 
    
         
            -
                end
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
                def cleanup(xml_node)
         
     | 
| 
       41 
     | 
    
         
            -
                  fill_empty_table_cells xml_node
         
     | 
| 
       42 
     | 
    
         
            -
                end
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                def fill_empty_table_cells(xml_node)
         
     | 
| 
       45 
     | 
    
         
            -
                  xml_node.xpath("//w:tc[count(*[name() = 'w:p'])=0 or not(*)]").each do |blank_cell|
         
     | 
| 
       46 
     | 
    
         
            -
                    filler = Nokogiri::XML::Node.new("w:p", xml_node.document)
         
     | 
| 
       47 
     | 
    
         
            -
                    blank_cell.add_child filler
         
     | 
| 
       48 
     | 
    
         
            -
                  end
         
     | 
| 
       49 
     | 
    
         
            -
                end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                class Block < Struct.new(:start_field, :end_field)
         
     | 
| 
       52 
     | 
    
         
            -
                  def self.enclosed_by(start_field, end_field)
         
     | 
| 
       53 
     | 
    
         
            -
                    @blocks ||= [RowBlock, ParagraphBlock, InlineParagraphBlock]
         
     | 
| 
       54 
     | 
    
         
            -
                    block_class = @blocks.detect { |klass| klass.encloses?(start_field, end_field) }
         
     | 
| 
       55 
     | 
    
         
            -
                    block_class.new start_field, end_field
         
     | 
| 
       56 
     | 
    
         
            -
                  end
         
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                  def process(context)
         
     | 
| 
       59 
     | 
    
         
            -
                    replaced_node = Nokogiri::XML::Node.new("tmp", start_node.document)
         
     | 
| 
       60 
     | 
    
         
            -
                    replaced_node.children = Nokogiri::XML::NodeSet.new(start_node.document, body.map(&:dup))
         
     | 
| 
       61 
     | 
    
         
            -
                    Processor.process replaced_node, context
         
     | 
| 
       62 
     | 
    
         
            -
                    replaced_node.children
         
     | 
| 
       63 
     | 
    
         
            -
                  end
         
     | 
| 
       64 
     | 
    
         
            -
             
     | 
| 
       65 
     | 
    
         
            -
                  def replace(content)
         
     | 
| 
       66 
     | 
    
         
            -
                    content.each { |n| start_node.add_next_sibling n }
         
     | 
| 
       67 
     | 
    
         
            -
                    remove_control_elements
         
     | 
| 
       68 
     | 
    
         
            -
                  end
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
                  def remove_control_elements
         
     | 
| 
       71 
     | 
    
         
            -
                    body.each &:remove
         
     | 
| 
       72 
     | 
    
         
            -
                    start_node.remove
         
     | 
| 
       73 
     | 
    
         
            -
                    end_node.remove
         
     | 
| 
       74 
     | 
    
         
            -
                  end
         
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
                  def body
         
     | 
| 
       77 
     | 
    
         
            -
                    return @body if defined?(@body)
         
     | 
| 
       78 
     | 
    
         
            -
                    @body = []
         
     | 
| 
       79 
     | 
    
         
            -
                    node = start_node
         
     | 
| 
       80 
     | 
    
         
            -
                    while (node = node.next_element) && node != end_node
         
     | 
| 
       81 
     | 
    
         
            -
                      @body << node
         
     | 
| 
       82 
     | 
    
         
            -
                    end
         
     | 
| 
       83 
     | 
    
         
            -
                    @body
         
     | 
| 
       84 
     | 
    
         
            -
                  end
         
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
                  def start_node
         
     | 
| 
       87 
     | 
    
         
            -
                    @start_node ||= self.class.parent(start_field).first
         
     | 
| 
       88 
     | 
    
         
            -
                  end
         
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
                  def end_node
         
     | 
| 
       91 
     | 
    
         
            -
                    @end_node ||= self.class.parent(end_field).first
         
     | 
| 
       92 
     | 
    
         
            -
                  end
         
     | 
| 
       93 
     | 
    
         
            -
             
     | 
| 
       94 
     | 
    
         
            -
                  def self.encloses?(start_field, end_field)
         
     | 
| 
       95 
     | 
    
         
            -
                    parent(start_field).any? && parent(end_field).any?
         
     | 
| 
       96 
     | 
    
         
            -
                  end
         
     | 
| 
       97 
     | 
    
         
            -
                end
         
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
                class RowBlock < Block
         
     | 
| 
       100 
     | 
    
         
            -
                  def self.parent(node)
         
     | 
| 
       101 
     | 
    
         
            -
                    node.ancestors ".//w:tr"
         
     | 
| 
       102 
     | 
    
         
            -
                  end
         
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
                  def self.encloses?(start_field, end_field)
         
     | 
| 
       105 
     | 
    
         
            -
                    super && parent(start_field) != parent(end_field)
         
     | 
| 
       106 
     | 
    
         
            -
                  end
         
     | 
| 
       107 
     | 
    
         
            -
                end
         
     | 
| 
       108 
     | 
    
         
            -
             
     | 
| 
       109 
     | 
    
         
            -
                class ParagraphBlock < Block
         
     | 
| 
       110 
     | 
    
         
            -
                  def self.parent(node)
         
     | 
| 
       111 
     | 
    
         
            -
                    node.ancestors ".//w:p"
         
     | 
| 
       112 
     | 
    
         
            -
                  end
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
     | 
    
         
            -
                  def self.encloses?(start_field, end_field)
         
     | 
| 
       115 
     | 
    
         
            -
                    super && parent(start_field) != parent(end_field)
         
     | 
| 
       116 
     | 
    
         
            -
                  end
         
     | 
| 
       117 
     | 
    
         
            -
                end
         
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
                class InlineParagraphBlock < Block
         
     | 
| 
       120 
     | 
    
         
            -
                  def self.parent(node)
         
     | 
| 
       121 
     | 
    
         
            -
                    node.ancestors ".//w:p"
         
     | 
| 
       122 
     | 
    
         
            -
                  end
         
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
                  def remove_control_elements
         
     | 
| 
       125 
     | 
    
         
            -
                    body.each &:remove
         
     | 
| 
       126 
     | 
    
         
            -
                    start_field.remove
         
     | 
| 
       127 
     | 
    
         
            -
                    end_field.remove
         
     | 
| 
       128 
     | 
    
         
            -
                  end
         
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
     | 
    
         
            -
                  def start_node
         
     | 
| 
       131 
     | 
    
         
            -
                    @start_node ||= start_field.end_node
         
     | 
| 
       132 
     | 
    
         
            -
                  end
         
     | 
| 
       133 
     | 
    
         
            -
             
     | 
| 
       134 
     | 
    
         
            -
                  def end_node
         
     | 
| 
       135 
     | 
    
         
            -
                    @end_node ||= end_field.start_node
         
     | 
| 
       136 
     | 
    
         
            -
                  end
         
     | 
| 
       137 
     | 
    
         
            -
             
     | 
| 
       138 
     | 
    
         
            -
                  def self.encloses?(start_field, end_field)
         
     | 
| 
       139 
     | 
    
         
            -
                    super && parent(start_field) == parent(end_field)
         
     | 
| 
       140 
     | 
    
         
            -
                  end
         
     | 
| 
       141 
     | 
    
         
            -
                end
         
     | 
| 
       142 
     | 
    
         
            -
             
     | 
| 
       143 
     | 
    
         
            -
                class OperationConstruction
         
     | 
| 
       144 
     | 
    
         
            -
                  def initialize(fields)
         
     | 
| 
       145 
     | 
    
         
            -
                    @fields = fields
         
     | 
| 
       146 
     | 
    
         
            -
                    @operations = []
         
     | 
| 
       147 
     | 
    
         
            -
                  end
         
     | 
| 
       148 
     | 
    
         
            -
             
     | 
| 
       149 
     | 
    
         
            -
                  def operations
         
     | 
| 
       150 
     | 
    
         
            -
                    while @fields.any?
         
     | 
| 
       151 
     | 
    
         
            -
                      @operations << consume(true)
         
     | 
| 
       152 
     | 
    
         
            -
                    end
         
     | 
| 
       153 
     | 
    
         
            -
                    @operations.compact
         
     | 
| 
       154 
     | 
    
         
            -
                  end
         
     | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
       156 
     | 
    
         
            -
                  def consume(allow_insertion)
         
     | 
| 
       157 
     | 
    
         
            -
                    @field = @fields.shift
         
     | 
| 
       158 
     | 
    
         
            -
                    return unless @field
         
     | 
| 
       159 
     | 
    
         
            -
                    case @field.expression
         
     | 
| 
       160 
     | 
    
         
            -
                    when /^=/
         
     | 
| 
       161 
     | 
    
         
            -
                      if allow_insertion
         
     | 
| 
       162 
     | 
    
         
            -
                        Statement::Insertion.new(Expression.parse(@field.expression[1..-1]), @field)
         
     | 
| 
       163 
     | 
    
         
            -
                      end
         
     | 
| 
       164 
     | 
    
         
            -
                    when /([^ ]+):each\(([^ ]+)\)/
         
     | 
| 
       165 
     | 
    
         
            -
                      block = consume_block("#{$1}:endEach")
         
     | 
| 
       166 
     | 
    
         
            -
                      Statement::Loop.new(Expression.parse($1), $2, block)
         
     | 
| 
       167 
     | 
    
         
            -
                    when /([^ ]+):if\(([^)]+)\)/
         
     | 
| 
       168 
     | 
    
         
            -
                      block = consume_block("#{$1}:endIf")
         
     | 
| 
       169 
     | 
    
         
            -
                      Statement::Condition.new(Expression.parse($1), block, $2)
         
     | 
| 
       170 
     | 
    
         
            -
                    when /([^ ]+):if/
         
     | 
| 
       171 
     | 
    
         
            -
                      block = consume_block("#{$1}:endIf")
         
     | 
| 
       172 
     | 
    
         
            -
                      Statement::Condition.new(Expression.parse($1), block)
         
     | 
| 
       173 
     | 
    
         
            -
                    end
         
     | 
| 
       174 
     | 
    
         
            -
                  end
         
     | 
| 
       175 
     | 
    
         
            -
             
     | 
| 
       176 
     | 
    
         
            -
                  def consume_block(end_expression)
         
     | 
| 
       177 
     | 
    
         
            -
                    start_field = end_field = @field
         
     | 
| 
       178 
     | 
    
         
            -
                    while end_field && end_field.expression != end_expression
         
     | 
| 
       179 
     | 
    
         
            -
                      consume(false)
         
     | 
| 
       180 
     | 
    
         
            -
                      end_field = @field
         
     | 
| 
       181 
     | 
    
         
            -
                    end
         
     | 
| 
       182 
     | 
    
         
            -
             
     | 
| 
       183 
     | 
    
         
            -
                    if end_field
         
     | 
| 
       184 
     | 
    
         
            -
                      Block.enclosed_by start_field, end_field
         
     | 
| 
       185 
     | 
    
         
            -
                    else
         
     | 
| 
       186 
     | 
    
         
            -
                      raise TemplateError, "Could not find end field for «#{start_field.expression}». Was looking for «#{end_expression}»"
         
     | 
| 
       187 
     | 
    
         
            -
                    end
         
     | 
| 
       188 
     | 
    
         
            -
                  end
         
     | 
| 
       189 
     | 
    
         
            -
                end
         
     | 
| 
       190 
     | 
    
         
            -
              end
         
     | 
| 
       191 
     | 
    
         
            -
            end
         
     |