Module TemplateParse
In: app/controllers/template_parse.rb

Mixin module: Parse component, of the template controller.

Note that the web server must be restarted to recognize changes within this file.

Methods

Public Instance methods

@output processing.

This method populates all template element references into the input page template, recursing as necessary through nested levels of template element references, returning the fully parsed template.

Template elements must be parsed in a specific order to generate a valid page:

  • host level templates, for example headers and footers
  • content indices
  • content groups
  • content items
  • links
  • project data
  • constants
  • variables
  • methods

Note that the order in which content items and groups are parsed has no impact on performance; both content items and groups use the same low level methods to obtain content items and thus the same caching logic - accordingly, a content item will always be cached after it‘s first access, whether this is the result of parsing a group or an item.

[Source]

    # File app/controllers/template_parse.rb, line 30
30:   def parse
31:     # Recursion safety net; perform a depth check to catch endless loops,
32:     # for example a template that contains a reference to itself.
33:     if (!@parse_depth)
34:       @parse_depth = 1
35:     else
36:       @parse_depth += 1
37: 
38:       # If there is a custom recursion limit defined then use it.
39:       if o('template:parse:max_depth')
40:         max_parse_depth = o('template:parse:max_depth').to_i
41:       # Otherwise, use the application default.
42:       else
43:         max_parse_depth = MAX_PARSE_DEPTH
44:       end
45: 
46:       # Terminate parsing if the limit is reached.
47:       if (@parse_depth > max_parse_depth)
48:         l(m(1, max_parse_depth, 
49:             reference_extract(REFERENCE, @output).sort.uniq.join(' ')))
50:         return
51:       end
52:     end
53: 
54: 
55:     # Parse all template elements into the page template. Operations must
56:     # be performed in the following order:
57:     # * parse_templates()      must be executed first
58:     # * parse_content_index()  must be executed prior to parse_content_items()
59:     # * parse_content_groups() must be executed prior to parse_content_items()
60:     # * parse_content_items()  must be executed prior to all remaining methods
61:     # * parse_content_links()  must be executed prior all remaining methods
62:     # * parse_page_data(), parse_constants() and parse_variables() may be 
63:     #   executed in any order
64:     # * parse_methods() must be executed last
65:     parse_templates
66:     parse_content_index
67:     parse_content_groups
68:     parse_content_items
69:     parse_links
70:     parse_page_data
71:     parse_constants
72:     parse_variables
73:     parse_methods
74: 
75: 
76:     # The elements that have been parsed into this template may have inserted
77:     # template element references. As a result, the template must be scanned 
78:     # for references, and if any are found then parse() must be applied 
79:     # recursively.
80:     parse if (reference_exists(REFERENCE, @output))
81:   end

@output processing.

Parse all constant references within a template.

[Source]

     # File app/controllers/template_parse.rb, line 89
 89:   def parse_constants
 90:     if (reference_exists(CONSTANT, @output))
 91:       for reference in reference_extract(CONSTANT, @output)
 92:         constant_name = reference_split(reference)
 93: 
 94:         # Construct a variable containing the value of this constant.
 95:         begin 
 96:           constant = ''
 97:           eval "constant = #{constant_name}"
 98:         # Catch NameError exceptions.
 99:         rescue
100:           constant = constant_name
101:           l(m(2, constant_name))
102:         end
103: 
104:         # Parse the value of this constant into the template.
105:         parse_reference(reference, constant)
106:       end
107:     end
108:   end

@output processing.

Parse all content group references within a template.

Content group definitions are found within the table ‘content_groups’.

The ids of content items that are contained within a group are stored in the table ‘content_item_to_group_map’. This table facilitates the mapping of items to groups, and groups to items.

[Source]

     # File app/controllers/template_parse.rb, line 122
122:   def parse_content_groups 
123:     if (reference_exists(CONTENT_GROUP, @output))
124:       for reference in reference_extract(CONTENT_GROUP, @output)
125:         if (cache_data = ApplicationCache::get(reference))
126:           content = cache_data
127:         else
128:           type_id, id, template_id = reference_split(reference)
129:   
130:           content = ''
131:   
132:           # Obtain this content group.
133:           if (group = @content.group(@page.project_id,@page.host_id,type_id,id))
134:             # This group contains no items.
135:             if (0 == group.item_ids.size)
136:               content = variable_text('empty_result_set')
137:             else
138:               # Construct a string containing content item references for every 
139:               # item within this group.
140:               item_references = ''
141:               for item_id in group.item_ids
142:                 if (template_id)
143:                   item_reference = reference_assemble_tag(CONTENT_ITEM, 
144:                                                           type_id,
145:                                                           item_id,
146:                                                           template_id)
147:                 elsif (group.item_template_id)
148:                   item_reference = reference_assemble_tag(CONTENT_ITEM, 
149:                                                          type_id,
150:                                                          item_id,
151:                                                          group.item_template_id)
152:                 else
153:                   item_reference = reference_assemble_tag(CONTENT_ITEM, 
154:                                                           type_id,
155:                                                           item_id)
156:                 end
157:   
158:                 # Collect content references for all items in this group.
159:                 item_references << item_reference
160:               end
161:     
162:               # Parse the items into this group's template.
163:               if (group.master_template_id)
164:                 content = Template.get(@page.host_id, 
165:                            group.master_template_id).parse_data(item_references)
166:               # These items will be displayed directly.
167:               else
168:                 content = item_references
169:               end
170:             end
171:           end
172:         end
173: 
174:         # Insert the content into the template.
175:         parse_reference(reference, content, TRUE)
176:       end
177:     end
178:   end

@output processing.

URL content mapping.

Parse a content index reference within a template.

[Source]

     # File app/controllers/template_parse.rb, line 188
188:   def parse_content_index
189:     # Proceed iff:
190:     # * this page is a content index
191:     # * content index processing has not yet been performed (established by
192:     #   examining the value of @parse_depth)
193:     if (@page.content_index && (1 == @parse_depth))
194:       # Initialize a string containing HTML for this index.
195:       @index_html = ''
196: 
197:       # Generate an index only if there are items of this content type.
198:       if (@content.index['map'].size > 0)
199:         # If there is only one content item then insert it's reference into 
200:         # the template.
201:         if (1 == @content.index['type'].items.size)
202:           @index_html = reference_assemble_tag(CONTENT_ITEM, 
203:                                               @content.index['type'].id,
204:                                               @content.index['type'].items.keys)
205:         # If there are multiple items then generate an index containing 
206:         # individual references for each value.
207:         else
208:           parse_content_index_data
209:         end
210: 
211:         # Expose content index/map data in template variables.
212:         TemplateVariable.set('content:name',  @content.index['type'].name)
213:         TemplateVariable.set('content:type',  @content.index['type'].id)
214:         TemplateVariable.set('content:count', @content.index['map'].size)
215:         if e(@content.index['field'])
216:           TemplateVariable.set('content:field', @content.index['field'])
217:         end
218: 
219:       # There were no items found for this index.
220:       else
221:         TemplateVariable.set('content:count', "0")
222:         @index_html = variable_text('empty_result_set')
223:       end
224: 
225:       parse_reference(DATA_MARKER, @index_html)
226:     end
227:   end

URL content mapping.

Generate the HTML for all data in the content index map.

A content index may display one of the following:

  • a list of all fields for the current content type
  • a list of items for the current content type

[Source]

     # File app/controllers/template_parse.rb, line 239
239:   def parse_content_index_data
240:     # Descriptive field indices for the arrays in @content.index['map'].
241:     data      = 0
242:     path      = 1
243:     id        = 2
244:     title     = 3
245:     append_id = 4
246:     
247: 
248:     for item in @content.index['map']
249:       # Display all available items for a content type on one page.
250:       if (URL_ALL_ITEMS == @content.index['all'])
251:         # Determine the template that individual content items will be parsed 
252:         # in to:
253:         # * if there is a template defined for this content type, use it
254:         # * else, if there is a template defined for this project host, use it
255:         #   (via application module option inheritance)
256:         # * else, use the template for the default project host
257:         if (@content.index['type'].index_all_item_template_id)
258:           template_id = @content.index['type'].index_all_item_template_id
259:         else
260:           template_id = o('content:index:all_item_template_id')
261:         end
262: 
263:         # Append a content item template element reference for the current 
264:         # item to the content index template.
265:         @index_html << reference_assemble_tag(CONTENT_ITEM, 
266:                                               @content.index['type'].id,
267:                                               item[id],
268:                                               template_id)
269: 
270:       # Construct all other URL content index pages.
271:       else
272:         # Set the title for this link:
273:         # * Use the title field if it is available
274:         # * Use the data value if no title is available, converting underscores 
275:         #   into spaces
276:         link_title = item[title] || convert_to_title(item[data])
277: 
278:         # If the title is missing then link to this item's id.
279:         if (link_title.to_s !~ /[\w]/)
280:           field      = "#{item[path]}/id/#{item[id]}"
281:           link_title = v('text:missing_data')
282:         # If the title is available then link to it.
283:         else
284:           # Target path for links.
285:           target = url_encode(item[data])
286: 
287:           # When on a 'view all' page, links point to the special value 'all'.
288:           if (URL_ALL_INDEX == @content.index['all'])
289:             target += '/all'
290:           elsif (item[append_id])
291:             target += '/' + item[id].to_s
292:           end
293: 
294:           field      = "#{item[path]}/#{target}"
295:           link_title = CGI.escape(link_title)
296:         end
297: 
298:         # Determine the template that individual content items will be parsed 
299:         # in to:
300:         # * if there is a template defined for this content type, use it
301:         # * else, if there is a template defined for this project host, use it
302:         #   (via application module option inheritance)
303:         # * else, use the template for the default project host
304:         if (@content.index['type'].index_item_template_id)
305:           template_id = @content.index['type'].index_item_template_id
306:         else
307:           template_id = o('content:index:item_template_id')
308:         end
309: 
310:         # Retrieve the item template, construct a link to this field, and parse
311:         # the link into the item template.
312:         frame       =  Template.get(@page.host_id, template_id)
313:         @index_html << frame.parse_data(reference_assemble_link('page', 
314:                                                                 'view',
315:                                                                 field, 
316:                                                                 link_title))
317:       end
318:     end
319:   end

Parse each field of a content item into it‘s template.

[Source]

     # File app/controllers/template_parse.rb, line 325
325:   def parse_content_item_fields (
326:     content_item_reference
327:   )
328:     type_id, id, template_id = reference_split(content_item_reference)
329: 
330: 
331:     # Set the content table containing items of this content type,
332:     # process dynamic content item id's, and initialize the template for this
333:     # content item.
334:     ContentItem.set_table(type_id)
335:     id       = ContentItem.get_dynamic_id(id)
336:     template = nil
337: 
338: 
339:     # Proceed iff the requested item is available.
340:     if (content_item = @content.item(@page.project_id,@page.host_id,type_id,id))
341:       # Retrieve the content item's template.
342:       if (template = Template.get_content_type_template(@page.host_id,
343:                                                       template_id,
344:                                                       @content.types(
345:                                                                @page.project_id,
346:                                                                @page.host_id,
347:                                                                type_id)))
348:         # Parse each of this content item's field values into their 
349:         # field references within the template.
350:         if (reference_exists(DATABASE_FIELD_REFERENCE, template))
351:           for reference in reference_extract(DATABASE_FIELD, template)
352:             field = reference_split(reference)[0].to_s
353:   
354:             # Obtain the class and value of the requested field.
355:             type  = FALSE
356:             value = FALSE
357:             begin
358:               eval "type  = content_item.#{field}.get_variable_class"
359:               eval "value = content_item.#{field}.to_s"
360:             rescue
361:               l(m(7, field))
362:             end
363:   
364:             # Insert all non-empty, non-binary data into the template.
365:             if (!BINARY_COLUMN_TYPES.has(type) && e(value))
366:               template = template.gsub(reference_assemble_tag(DATABASE_FIELD, 
367:                                                               field), value)
368:             end
369:           end
370:         end
371:   
372:         # Loop through each attribute of the current content item, and  
373:         # if the attribute has no value then check if the template possesses 
374:         # a boundary tag for that attribute and if so purge it.
375:         for attribute in content_item.attributes
376:           field = attribute[0]
377:           value = attribute[1]
378:           template = purge_unparsed_field(field, template) if !(value)
379:         end
380:       end
381: 
382:     # If no content was found for this reference then return the reference.
383:     else
384:       template = REFERENCE_TAG_TEMPLATE.parse_data(content_item_reference)
385:     end
386:     
387: 
388:     (template) ? (template) : ('')
389:   end

@output processing.

Parse all content item references within a template.

Template Reference

The template reference for a content item is of the format:

  • {<reference type>:<content type>:<content item id>:<template id>}

An example content item reference is:

  • {i:1:99:4}

Using the example content item reference above, ‘i:1:99:4’, this reference refers to content item 99 for the content type 1, which is ‘blog’. The value 1 maps to the row with id = 1 in the table ‘content_types’, and 99 corresponds to the row with id = 99 in the table ‘content_type_1’.

Content Type

A content type describes the category of this content, for example a blog entry or image file. A content type id maps directly to a row in the table ‘content_types’. This table provides full information on all available types.

Content Item ID

A content item id maps to a row in it‘s content table. Content tables are named using the format ‘content_type_<content type id>’. For example, the content type ‘blog’ has content type id = 1, correspondingly all blog data is stored within the database table ‘content_type_1’.

Static and Dynamic ID Specification

Content item ids may be specified statically or dynamically.

A static item id directly specifies a content item‘s database row id, for example the item with id 5 of content type 1 would be:

  • {i:1:5}

A dynamic item id specifies the type of content item to be shown, using one of the following options:

  • oldest
    • For example {i:9:oldest}
  • newest
    • For example {i:72:newest}
  • random
    • For example {i:23:random}

Template ID

The id of the template into which this content item will be parsed.

This parameter is optional. If no template id is specified then the value of the field ‘template_id’ for this content type is used, as found within the table ‘content_types’.

[Source]

     # File app/controllers/template_parse.rb, line 449
449:   def parse_content_items
450:     if (reference_exists(CONTENT_ITEM, @output))
451:       for reference in reference_extract(CONTENT_ITEM, @output)
452:         if (cache_data = ApplicationCache::get(reference))
453:           content = cache_data
454:         else
455:           content = parse_content_item_fields(reference)
456:         end
457: 
458:         parse_reference(reference, content, TRUE)
459:       end
460:     end
461:   end

@output processing.

Convert link references into <a> tags.

The Structure of a Link Reference

Link references are of the following format:

  • {l:<category>:<action>:<data>:<title>:<arguments>:<inline modifiers>}

Category

There are two categories of link references, each providing link actions particular to the category:

  • Content
    • Actions for this category:
      • download: generate a link to download this item
      • next by <field>: link to item after this item, sorted by <field>
      • previous by <field>: link to item before this item, sorted by <field>
      • view: link to view this item‘s project page
  • Page
    • Actions for this category:
      • children: generate links to all active child pages of this page
      • parent: link to this page‘s parent page
      • view: link to view this project page

Data

The data segment is used to provide a parameter for the action.

Only content items may be specified in link references. Content item data segments use the following format:

  • "<content type id> <content item id>"

For example, the content item with id = 92 and content type id = 1:

  • "1 92"

Arguments

The arguments segment is string data that is appended to the link‘s href target.

Inline Modifiers

This is a string segment that may be used to insert CSS or JavaScript data into a link. This segment is inserted into the <a> tag directly after the opening of the tag, and prior to the href segment.

Special Characters

In order to use the ’:’ character within any segment of a link reference it must be escaped:

  • \:

Any segment of a link reference may contain any character that is not in the following list:

  • }
  • \n
  • \r

Examples

  • Generate an <a> tag linking to the project page with id = 15, if it is active:
    • {l:page:view:15}
  • Generate an <a> tag linking to the project page with id = 92, if it is active, with the POST parameters sort_field = date, sort_order = asc:
    • {l:page:view:92:sort_field=date&sort_order=asc}
  • Generate an <a> tag, linking to the parent page of the project page with id = 32:
    • {l:page:parent:32}
  • Generate an <a> tag linking to the content item with id = 83 and content type with id = 3, if it is active:
    • {l:content:download:3 83}
  • Generate an <a> tag linking to the content item with id = 42, content type id = 17:
    • {l:content:view:17 42}
  • Generate an <a> tag linking to the content item that occurs immediately after the item with id = 8 and content type = 1, sorting items on the date field:
    • {l:content:next by date:1 8}

[Source]

     # File app/controllers/template_parse.rb, line 552
552:   def parse_links 
553:     if (reference_exists(LINK, @output))
554:       for reference in reference_extract(LINK, @output)
555:         if (!reference_exists(REFERENCE, reference))
556:           # Reset loop variables.
557:           title     = FALSE
558:           target    = FALSE
559:           titles    = []
560:           targets   = []
561:           content   = FALSE
562:           permalink = FALSE
563:   
564:           category, action, data, title, arguments, modifiers = reference_split(
565:                                                                       reference)
566:           # Proceed according to this link reference's category.
567:           case category
568:   
569:             # Content.
570:             when 'content'
571:               type_id, item_id = data.split(' ')
572:               ContentItem.set_table(type_id)
573:               item_id = ContentItem.get_dynamic_id(item_id)
574:   
575:               # The name of this content type.
576:               if ('type name' == action)
577:                 name = @content.types(@page.project_id, @page.host_id, 
578:                                       type_id).name
579:   
580:                 # If no title has been specified then use the content type name.
581:                 title  = name if !e(title)
582:                 target = '/' + name
583:     
584:               # Download an item.
585:               elsif ('download' == action)
586:                 title  = 'download' if !e(title)
587:                 target = DOWNLOAD_PATH + '/' + 
588:                          @content.types(@page.project_id, @page.host_id, 
589:                                         type_id).name + '/' + item_id.to_s
590:   
591:               # Logic below requires that the requested item be available.
592:               elsif (item = @content.item(@page.project_id, 
593:                                           @page.host_id, 
594:                                           type_id,
595:                                           item_id))
596:   
597:                 # NOTE:
598:                 # The 'view' action requires no special processing logic.
599:     
600:                 # Link to this item using a specific field.
601:                 if ((action =~ /#{VIEW_BY_FIELD_REGEX}/) ||
602:                     (action =~ /#{PERMALINK_REGEX}/))
603:   
604:                   # The field name extracted from the action, and the URL base 
605:                   # for content item's of this content type.
606:                   field          = $1
607:                   content_target = '/' + @content.types(@page.project_id, 
608:                                                         @page.host_id, 
609:                                                         type_id).name
610:   
611:                   table_name = ContentItem.set_table(type_id)
612:   
613:                   # Flag permalinks.
614:                   permalink = TRUE if (action =~ /#{PERMALINK_REGEX}/)
615:     
616:                   # Links using the 'date' field are constructed using the 
617:                   # URL date format: 
618:                   #   /<content type name>/date/<year>/<month>/<day>
619:                   if ('date' == field)
620:                     day    = item.date.day
621:                     month  = item.date.month
622:                     year   = item.date.year
623:                     target = content_target + "/date/#{year}/#{month}/#{day}"
624:     
625:                     # In the case that no title has been specified construct a
626:                     # human readable title using the full month name, day, 
627:                     # and year.
628:                     title = item.date.to_readable_date if !e(title)
629:     
630:                   # All other fields use the URL format: 
631:                   #   /<content type name>/<field>/<value>
632:                   elsif (value = item["#{field}""#{field}"].to_s)
633:                     # If this field contains SCSV then separate and process them
634:                     # individually.
635:                     if (value =~ /#{SCSV}/)
636:                       for v in value.split("#{SCSV}").sort.uniq
637:                         c = ContentItem.count_by_sql(["SELECT count(*) 
638:                                                        FROM   #{table_name}
639:                                                        WHERE  #{field} LIKE ?
640:                                                        AND    active = 'TRUE'",
641:                                                       "%#{v}%"])
642:                         if (c > 1)
643:                           encoded_value = url_encode(v)
644:                         else
645:                           encoded_value = url_encode(v, item.id)
646:                         end
647:                         
648:                         titles  << v
649:                         targets << "#{content_target}/#{field}/#{encoded_value}"
650:                                    
651:                       end
652:                     # Otherwise this a regular, non-SCSV, value.
653:                     else
654:                       c = ContentItem.count_by_sql(["SELECT count(*) 
655:                                                      FROM   #{table_name}
656:                                                      WHERE  #{field} LIKE ?
657:                                                      AND    active = 'TRUE'",
658:                                                     "%#{value}%"])
659:                       if (permalink)
660:                         encoded_value = url_encode(value, item.id, TRUE)
661:                       elsif (c > 1)
662:                         encoded_value = url_encode(value)
663:                       else
664:                         encoded_value = url_encode(value, item.id)
665:                       end
666:   
667:                       title  = value if !e(title)
668:                       target = "#{content_target}/#{field}/#{encoded_value}"
669:                     end
670:                   end
671:       
672:                 # Next/previous by database field.
673:                 elsif (action =~ /#{LINK_ACTION_REGEX}/)
674:                   direction = $1
675:                   field     = $2
676:                   if (item = @content.item(@page.project_id, 
677:                                            @page.host_id, 
678:                                            type_id, 
679:                                            ContentItem.get_adjacent_id(type_id, 
680:                                                               item["#{field}""#{field}"],
681:                                                               direction, 
682:                                                               field)))
683:                     if (!e(title))
684:                       if ('date' == field)
685:                         title  = item.date.to_readable_date
686:                         day    = item.date.day
687:                         month  = item.date.month
688:                         year   = item.date.year
689:                         target = '/' + @content.types(@page.project_id, 
690:                                                       @page.host_id,
691:                                                       type_id).name + 
692:                                  "/date/#{year}/#{month}/#{day}"
693:                       else
694:                         title = item.title
695:                       end
696:                     end
697:   
698:                     if (!target)
699:                       table_name = ContentItem.set_table(type_id)
700:                       c = ContentItem.count_by_sql(["SELECT count(*) 
701:                                                      FROM   #{table_name}
702:                                                      WHERE  title = ?
703:                                                      AND    active = 'TRUE'",
704:                                                     item.title])
705:   
706:                       # If there is more than one content item with this title,
707:                       # then do not include the database row ID in the URL, so 
708:                       # an index page linking to each individual item with this
709:                       # title will then be generated.
710:                       if (c > 1)
711:                         value = url_encode(item.title)
712:                       else
713:                         value = url_encode(item.title, item.id)
714:                       end
715:   
716:                       target = '/' + @content.types(@page.project_id, 
717:                                                @page.host_id,
718:                                                type_id).name + "/title/#{value}"
719:                     end
720:                   end
721:                 end
722:               end
723:   
724:             # Project page.
725:             when 'page'
726:               case action
727:                 # Children project pages.
728:                 when 'children'
729:   
730:                 # Parent project page.
731:                 when 'parent'
732:                   # Obtain the requested page.
733:                   page = ProjectPage.fetch({'project_id' => @page.project_id, 
734:                                             'id'         => data.to_i})
735:   
736:                   # Proceed iff the page and it's parent are available.
737:                   if (page && parent = ProjectPage.fetch(
738:                                  {'project_id' => @page.project_id, 
739:                                   'id'         => page.parent_project_page_id}))
740:                     target = parent.path
741:                     title  = parent.name
742:                   end
743:   
744:                 # Default: 'view'.
745:                 else
746:                   # The input is a Fixnum id.
747:                   if (data =~ /#{ID}/ &&
748:                       page = ProjectPage.fetch({'project_id' =>@page.project_id,
749:                                                 'id'         =>data.to_i}))
750:                     target   = page.path
751:                     title  ||= page.name
752:                   # The input is a URL path string.
753:                   else
754:                     target = data
755:                   end
756:   
757:                   title ||= data
758:               end
759:           end
760:   
761:   
762:           # Continue only if valid data exists.
763:           if (target || (targets.size > 0))
764:             # Convert the Ruby value FALSE into an empty string.
765:             modifiers = '' if !e(modifiers)
766:   
767:             # If arguments exist then prepend the GET indicator.
768:             e(arguments) ? (arguments = "?#{arguments}") : (arguments = '')
769:   
770:             # Create multiple links to SCSV.
771:             if (e(titles) && e(targets) && (targets.size == titles.size))
772:               links = []
773:   
774:               # Create a link for each individual value.
775:               for c in 0..titles.size-1
776:                 links << LINK_TEMPLATE.parse_data(targets[c],
777:                                                   CGI.unescape(titles[c]),
778:                                                   arguments, modifiers)
779:               end
780:   
781:               content = links.join(', ')
782:             # Parse a single link reference into the link template.
783:             else
784:               content = LINK_TEMPLATE.parse_data(target, 
785:                                                  CGI.unescape(title),
786:                                                  arguments,
787:                                                  modifiers)
788:             end
789:           end
790:         
791: 
792:           # Insert this link into the template.
793:           parse_reference(reference, content) if (content)
794:         end
795:       end
796:     end
797:   end

@output processing.

Parse all method references.

[Source]

     # File app/controllers/template_parse.rb, line 805
805:   def parse_methods
806:     if (reference_exists(METHOD, @output))
807:       for reference in reference_extract(METHOD, @output)
808:         method_name, value = reference_split(reference)
809:         result = ''
810: 
811:         # If a method has been defined and exists then apply it to the value 
812:         # of this field.
813:         if (method_name && !defined?(method_name))
814:           l(m(6, method_name))
815:         elsif ((method_name != '') && value)
816:           begin
817:             # eval() will fail if #{value} resolves to a string containing
818:             # any single quote characters, i.e. '. To ensure safe operation, 
819:             # every single quote within #{value} is replaced with a single quote
820:             # marker, eval() is executed, then single quote markers are 
821:             # reverted into the single quote character.
822:             #
823:             # Note that if the method specified in #{method_name} performs any
824:             # string operations, such as truncation, then #{method_name} must 
825:             # perform the single quote reversion for itself.
826:             value = value.gsub('\'', SINGLE_QUOTE_CHARACTER)
827:             eval "result = #{method_name}('#{value}').to_s"
828:             result = result.gsub(SINGLE_QUOTE_CHARACTER, "'")
829:           rescue
830:             l(m(5, method_name, value))
831:           end
832:         end
833: 
834:         parse_reference(reference, result)
835:       end
836:     end
837:   end

@output processing.

Parse all project data references.

[Source]

     # File app/controllers/template_parse.rb, line 867
867:   def parse_page_data
868:     if (reference_exists(PAGE_DATA, @output))
869:       for reference in reference_extract(PAGE_DATA, @output)
870:         field = reference_split(reference)
871: 
872:         # Obtain the value of the requested field.
873:         value = FALSE
874:         begin
875:           eval "value = @page.#{field}.to_s"
876:           parse_reference(reference, value) if (value)
877:         rescue
878:           l(m(8, field))
879:         end
880:       end
881:     end
882:   end

@output processing.

Insert the page template into it‘s master template.

[Source]

     # File app/controllers/template_parse.rb, line 845
845:   def parse_page_into_master (
846:     printer_friendly
847:   )
848:     if ('TRUE' == @page.use_master_template && @host.master_template_id)
849:       # In the case that this request is for a printable page, use the 'printer
850:       # friendly' master template.
851:       if (printer_friendly)
852:         master = o('template:parse:printer_friendly_master_template_id')
853:       else
854:         master = @host.master_template_id
855:       end
856: 
857:       @output = Template.get(@page.host_id, master).parse_data(@output)
858:     end
859:   end

@output processing.

Replace a template element reference with it‘s data.

[Source]

     # File app/controllers/template_parse.rb, line 890
890:   def parse_reference (
891:     *arguments
892:   )
893:     # Process the parameters.
894:     reference = arguments[0]
895:     data      = arguments[1]
896:     (arguments[2]) ? (do_cache = TRUE) : (do_cache = FALSE)
897:     
898:     # Prepare the HTML and data for this template reference.
899:     reference_tag = REFERENCE_TAG_TEMPLATE.parse_data(reference)
900:     data_string   = data.to_s
901: 
902:     # Parse the data into the template reference for this page request.
903:     @output = @output.gsub(reference_tag, data_string)
904: 
905:     # If this template reference is not embedded within a nocache tag, then:
906:     # * store this reference and its data in the cache
907:     # * parse its data into the cached version of this page
908:     if (@output_cache !~ /#{NOCACHE_REGEX.parse_data(r(reference_tag))}/)
909:       @output_cache = @output_cache.gsub(reference_tag, data_string)
910: 
911:       # Cache this data iff instructed to do so, and content level caching
912:       # is enabled.
913:       if (do_cache && o('application:cache:content'))
914:         ApplicationCache::set(reference.gsubs([[WHITESPACE_EXTRA,'' ]]), data)
915:       end
916:     end
917:   end

@output processing.

Insert the server comment into the page.

The page comment is added last, just prior to returning to the ApplicationController, in order to allow for the most accurate collection of page build time data.

[Source]

     # File app/controllers/template_parse.rb, line 929
929:   def parse_server_comment (
930:     build_start
931:   )
932:     if (HTML_HTTP_CONTENT_TYPE == @page.http_content_type)
933:       defined?(SERVER_COMMENT)?(comment=SERVER_COMMENT):(comment=PAGE_COMMENT)
934: 
935:       # NOTE:
936:       # gsub() is used directly on @output rather than parse_reference() 
937:       # in order to avoid processing [and alteration] of @output_cache.
938:       @output = @output.gsub(/#{BODY_TAG_REGEX}/, 
939:                              comment.parse_data(Time.now.to_f - build_start) + 
940:                              BODY_TAG)
941:     end
942:   end

@output processing.

Parse all template references.

[Source]

     # File app/controllers/template_parse.rb, line 950
950:   def parse_templates 
951:     if (reference_exists(TEMPLATE, @output))
952:       for reference in reference_extract(TEMPLATE, @output)
953:         parse_reference(reference, 
954:                         Template.get(@page.host_id, reference_split(reference)))
955:       end
956:     end
957:   end

@output processing.

Parse all template variable references.

[Source]

     # File app/controllers/template_parse.rb, line 965
965:   def parse_variables
966:     # Populate the template variable containing the last modified information 
967:     # for the current page, if it does not already exist.
968:     if (!v('text:host_last_modified'))
969:       @host_last_modified = ProjectController::last_modified(@page.project_id, 
970:                                                              @host.id,
971:                                                              @host.updated_on, 
972:                                                              @page.updated_on)
973:       TemplateVariable.set('text:host_last_modified', 
974:                            @host_last_modified.to_mysql_datetime)
975:     end
976: 
977:     if (reference_exists(VARIABLE, @output))
978:       for reference in reference_extract(VARIABLE, @output)
979:         parse_reference(reference, v(reference_split(reference)))
980:       end
981:     end
982:   end

@output processing.

Remove boundary tags from a finalized template.

Note that boundary tags are similar in structure to HTML tags, but are not valid HTML, thus their removal is requisite for HTML validation:

[Source]

      # File app/controllers/template_parse.rb, line 1019
1019:   def purge_boundary_tags
1020:     if o('template:parse:purge_boundary_tags')
1021:       @output       = @output.gsub(      /#{REFERENCE_TAG_REGEX}/, '')
1022:       @output       = @output.gsub(      /#{NOCACHE_TAG_REGEX}/,   '')
1023:       @output_cache = @output_cache.gsub(/#{REFERENCE_TAG_REGEX}/, '')
1024:     end
1025:   end

Remove database field element references from a content item template, using a field‘s boundary tags.

Opening boundary tags may use one of two formats:

  • <f:field_name>
  • <f:field_name:template_id>

In the second format listed above: if the database field ‘field_name’ is empty, and the template specified by template_id is available, then the template with template_id will be displayed as a replacement value for the missing data of ‘field_name’.

[Source]

      # File app/controllers/template_parse.rb, line 1041
1041:   def purge_unparsed_field (
1042:     field,
1043:     template
1044:   )
1045:     # Compose this field's boundary tags.
1046:     f_start = reference_assemble_boundary_tag('open',  field)
1047:     f_end   = reference_assemble_boundary_tag('close', field)
1048: 
1049: 
1050:     # Proceed iff the template contains boundary tags for this field.
1051:     if (template =~ /#{f_end}/ && open_tag = template.match(/#{f_start}/).to_a)
1052:       # Obtain the individual data segments of the opening boundary tag.
1053:       type, name, template_id = reference_split_boundary_tag(open_tag[0])
1054: 
1055:       # If the opening boundary tag specified a replacement template then
1056:       # that template will be used to replace the HTML within this empty 
1057:       # boundary tag.
1058:       if (template_id) 
1059:         replacement = Template.get(@page.host_id, template_id)
1060:       # Otherwise, the HTML within this empty boundary tag will be removed
1061:       # entirely.
1062:       else 
1063:         replacement = ''
1064:       end
1065: 
1066:       # Define the data to be removed from the template. 
1067:       #
1068:       # New line and line return characters are removed from the template 
1069:       # and replaced with placeholders in order to facilitate and simplify 
1070:       # the regular expression that is applied for removal of a field. 
1071:       # Once a field has been removed all new line and line return 
1072:       # characters are reverted to their original form.
1073:       subs =  []
1074:       subs << ['[\n]',                   NEW_LINE_CHARACTER   ]
1075:       subs << ['[\r]',                   LINE_RETURN_CHARACTER]
1076:       subs << [f_start + '(.+)' + f_end, replacement]
1077:       subs << [NEW_LINE_CHARACTER,       "\n"]
1078:       subs << [LINE_RETURN_CHARACTER,    "\r"]
1079: 
1080:       # Purge unwanted data from the template.
1081:       template = template.gsubs(subs) 
1082:     end
1083: 
1084: 
1085:     template
1086:   end

@output processing.

Remove unnecessary whitespace from a finalized template.

Caveat: Prior to setting the purge_whitespace application module option for a host ensure that it‘s graphical layout does not depend upon whitespace, for example as used within <pre> tags.

[Source]

      # File app/controllers/template_parse.rb, line 995
 995:   def purge_whitespace
 996:     if o('template:parse:purge_whitespace')
 997:       # Process the output for this request.
 998:       @output = @output.gsubs([[WHITESPACE,      ' ']])
 999:       @output = @output.gsubs([[WHITESPACE_EXTRA,'' ]]) if (@output !~ /<pre>/i)
1000: 
1001:       # Process the cached version of the output for this request.
1002:       @output_cache = @output_cache.gsubs([[WHITESPACE, ' ']])
1003:       if (@output_cache !~ /<pre>/i)
1004:         @output_cache = @output_cache.gsubs([[WHITESPACE_EXTRA, '' ]])
1005:       end
1006:     end
1007:   end

[Validate]