| Path: | libraries/redcloth_for_tex.rb |
| Last Update: | Fri May 21 20:30:15 GMT+2:00 2004 |
mb_decode_numericentity($text, $cmap, $charset);
| A_HLGN | = | /(?:(?:<>|<|>|\=|[()]+)+)/ |
| Regular expressions to convert to HTML. | ||
| A_VLGN | = | /[\-^~]/ |
| C_CLAS | = | '(?:\([^)]+\))' |
| C_LNGE | = | '(?:\[[^\]]+\])' |
| C_STYL | = | '(?:\{[^}]+\})' |
| S_CSPN | = | '(?:\\\\\d+)' |
| S_RSPN | = | '(?:/\d+)' |
| A | = | "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)" |
| S | = | "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)" |
| C | = | "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)" |
| PUNCT | = | Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' ) |
| PUNCT = Regexp::quote( ’!"#$%&\’()*+,-./:;<=>?@[\]^_`{|}~’ ) | ||
| HYPERLINK | = | '(\S+?)([^\w\s/;=\?]*?)(\s|$)' |
| GLYPHS | = | [
# [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing
[ /([^\s\[{(>])\'/, '\1’' ], # single closing
[ /\'(?=\s|s\b|[#{PUNCT}])/, '’' ], # single closing
[ /\'/, '‘' ], # single opening
# [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing
[ /([^\s\[{(>])"/, '\1”' ], # double closing
[ /"(?=\s|[#{PUNCT}])/, '”' ], # double closing
[ /"/, '“' ], # double opening
[ /\b( )?\.{3}/, '\1…' ], # ellipsis
[ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '\1' ], # 3+ uppercase acronym
[ /(^|[^"][>\s])([A-Z][A-Z0-9 ]{2,})([^ |
| I_ALGN_VALS | = | { '<' => 'left', '=' => 'center', '>' => 'right' |
| H_ALGN_VALS | = | { '<' => 'left', '=' => 'center', '>' => 'right', '<>' => 'justify' |
| V_ALGN_VALS | = | { '^' => 'top', '-' => 'middle', '~' => 'bottom' |
| QTAGS | = | [ ['**', 'bf'], ['*', 'bf'], ['??', 'cite'], ['-', 'del'], ['__', 'underline'], ['_', 'em'], ['%', 'span'], ['+', 'ins'], ['^', 'sup'], ['~', 'sub'] |
| CMAP | = | [ 160, 255, 0, 0xffff, 402, 402, 0, 0xffff, 913, 929, 0, 0xffff, 931, 937, 0, 0xffff, 945, 969, 0, 0xffff, 977, 978, 0, 0xffff, 982, 982, 0, 0xffff, 8226, 8226, 0, 0xffff, 8230, 8230, 0, 0xffff, 8242, 8243, 0, 0xffff, 8254, 8254, 0, 0xffff, 8260, 8260, 0, 0xffff, 8465, 8465, 0, 0xffff, 8472, 8472, 0, 0xffff, 8476, 8476, 0, 0xffff, 8482, 8482, 0, 0xffff, 8501, 8501, 0, 0xffff, 8592, 8596, 0, 0xffff, 8629, 8629, 0, 0xffff, 8656, 8660, 0, 0xffff, 8704, 8704, 0, 0xffff, 8706, 8707, 0, 0xffff, 8709, 8709, 0, 0xffff, 8711, 8713, 0, 0xffff, 8715, 8715, 0, 0xffff, 8719, 8719, 0, 0xffff, 8721, 8722, 0, 0xffff, 8727, 8727, 0, 0xffff, 8730, 8730, 0, 0xffff, 8733, 8734, 0, 0xffff, 8736, 8736, 0, 0xffff, 8743, 8747, 0, 0xffff, 8756, 8756, 0, 0xffff, 8764, 8764, 0, 0xffff, 8773, 8773, 0, 0xffff, 8776, 8776, 0, 0xffff, 8800, 8801, 0, 0xffff, 8804, 8805, 0, 0xffff, 8834, 8836, 0, 0xffff, 8838, 8839, 0, 0xffff, 8853, 8853, 0, 0xffff, 8855, 8855, 0, 0xffff, 8869, 8869, 0, 0xffff, 8901, 8901, 0, 0xffff, 8968, 8971, 0, 0xffff, 9001, 9002, 0, 0xffff, 9674, 9674, 0, 0xffff, 9824, 9824, 0, 0xffff, 9827, 9827, 0, 0xffff, 9829, 9830, 0, 0xffff, 338, 339, 0, 0xffff, 352, 353, 0, 0xffff, 376, 376, 0, 0xffff, 710, 710, 0, 0xffff, 732, 732, 0, 0xffff, 8194, 8195, 0, 0xffff, 8201, 8201, 0, 0xffff, 8204, 8207, 0, 0xffff, 8211, 8212, 0, 0xffff, 8216, 8218, 0, 0xffff, 8218, 8218, 0, 0xffff, 8220, 8222, 0, 0xffff, 8224, 8225, 0, 0xffff, 8240, 8240, 0, 0xffff, 8249, 8250, 0, 0xffff, 8364, 8364, 0, 0xffff |
# File libraries/redcloth_for_tex.rb, line 323
323: def initialize( string, restrictions = [] )
324: restrictions.each { |r| method( "#{ r }=" ).call( true ) }
325: super( string )
326: end
# File libraries/redcloth_for_tex.rb, line 505
505: def block( text )
506: pre = false
507: find = ['bq','h[1-6]','fn\d+']
508:
509: regexp_cue = []
510:
511: lines = text.split( /\n/ ) + [' ']
512: new_text =
513: lines.collect do |line|
514: pre = true if line =~ /<(pre|notextile)>/i
515: find.each do |tag|
516: line.gsub!( /^(#{ tag })(#{A}#{C})\.(?::(\S+))? (.*)$/ ) do |m|
517: tag,atts,cite,content = $~[1..4]
518:
519: atts = pba( atts )
520:
521: if tag =~ /fn(\d+)/
522: # tag = 'p';
523: # atts << " id=\"fn#{ $1 }\""
524: regexp_cue << [ /footnote\{#{$1}}/, "footnote{#{content}}" ]
525: content = ""
526: end
527:
528: if tag =~ /h([1-6])/
529: section_type = "sub" * [$1.to_i - 1, 2].min
530: start = "\t\\#{section_type}section*{"
531: tend = "}"
532: end
533:
534: if tag == "bq"
535: cite = check_refs( cite )
536: cite = " cite=\"#{ cite }\"" if cite
537: start = "\t\\begin{quotation}\n\\noindent {\\em ";
538: tend = "}\n\t\\end{quotation}";
539: end
540:
541: "#{ start }#{ content }#{ tend }"
542: end unless pre
543: end
544:
545: #line.gsub!( /^(?!\t|<\/?pre|<\/?notextile|<\/?code|$| )(.*)/, "\t<p>\\1</p>" )
546:
547: #line.gsub!( "<br />", "\n" ) if pre
548: # pre = false if line =~ /<\/(pre|notextile)>/i
549:
550: line
551: end.join( "\n" )
552: text.replace( new_text )
553: regexp_cue.each { |pair| text.gsub!(pair.first, pair.last) }
554: end
# File libraries/redcloth_for_tex.rb, line 615
615: def check_refs( text )
616: @urlrefs[text] || text
617: end
# File libraries/redcloth_for_tex.rb, line 702
702: def clean_white_space( text )
703: text.gsub!( /\r\n/, "\n" )
704: text.gsub!( /\t/, '' )
705: text.gsub!( /\n{3,}/, "\n\n" )
706: text.gsub!( /\n *\n/, "\n\n" )
707: text.gsub!( /"$/, "\" " )
708: end
# File libraries/redcloth_for_tex.rb, line 651
651: def code( text )
652: text.gsub!( /
653: (?:^|([\s\(\[{])) # 1 open bracket?
654: @ # opening
655: (?:\|(\w+?)\|)? # 2 language
656: (\S(?:[^\n]|\n(?!\n))*?) # 3 code
657: @ # closing
658: (?:$|([\]})])|
659: (?=[#{PUNCT}]{1,2}|
660: \s)) # 4 closing bracket?
661: /x ) do |m|
662: before,lang,code,after = $~[1..4]
663: lang = " language=\"#{ lang }\"" if lang
664: "#{ before }<code#{ lang }>#{ code }</code>#{ after }"
665: end
666: end
# File libraries/redcloth_for_tex.rb, line 792
792: def decode_high( text )
793: ## mb_decode_numericentity($text, $cmap, $charset);
794: end
# File libraries/redcloth_for_tex.rb, line 687
687: def encode_entities( text )
688: ## Convert high and low ascii to entities.
689: # if $-K == "UTF-8"
690: # encode_high( text )
691: # else
692: text.texesc!( :NoQuotes )
693: # end
694: end
# File libraries/redcloth_for_tex.rb, line 788
788: def encode_high( text )
789: ## mb_encode_numericentity($text, $cmap, $charset);
790: end
# File libraries/redcloth_for_tex.rb, line 696
696: def fix_entities( text )
697: ## de-entify any remaining angle brackets or ampersands
698: text.gsub!( "\&", "&" )
699: text.gsub!( "\%", "%" )
700: end
# File libraries/redcloth_for_tex.rb, line 500
500: def fold( text )
501: text.gsub!( /(.+)\n(?![#*\s|])/, "\\1\\\\\\\\" )
502: # text.gsub!( /(.+)\n(?![#*\s|])/, "\\1#{ @fold_lines ? ' ' : '<br />' }" )
503: end
# File libraries/redcloth_for_tex.rb, line 715
715: def footnote_ref( text )
716: text.gsub!( /\[([0-9]+?)\](\s)?/,
717: '\footnote{\1}\2')
718: #'<sup><a href="#fn\1">\1</a></sup>\2' )
719: end
# File libraries/redcloth_for_tex.rb, line 608
608: def get_refs( text )
609: text.gsub!( /(^|\s)\[(.+?)\]((?:http:\/\/|javascript:|ftp:\/\/|\/)\S+?)(?=\s|$)/ ) do |m|
610: flag, url = $~[1..2]
611: @urlrefs[flag] = url
612: end
613: end
# File libraries/redcloth_for_tex.rb, line 767
767: def glyphs( text )
768: text.gsub!( /"\z/, "\" " )
769: ## if no html, do a simple search and replace...
770: if text !~ /<.*>/
771: inline text
772: end
773: glyphs_deep text
774: end
# File libraries/redcloth_for_tex.rb, line 728
728: def glyphs_deep( text )
729: codepre = 0
730: offtags = /(?:code|pre|kbd|notextile)/
731: if text !~ /<.*>/
732: # pgl text
733: footnote_ref text
734: else
735: used_offtags = {}
736: text.gsub!( /(?:[^<].*?(?=<[^\n]*?>|$)|<[^\n]*?>+)/m ) do |line|
737: tagline = ( line =~ /^<.*>/ )
738:
739: ## matches are off if we're between <code>, <pre> etc.
740: if tagline
741: if line =~ /<(#{ offtags })>/i
742: codepre += 1
743: used_offtags[$1] = true
744: line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
745: elsif line =~ /<\/(#{ offtags })>/i
746: line.texesc!( :NoQuotes ) if codepre - used_offtags.length > 0
747: codepre -= 1 unless codepre.zero?
748: used_offtags = {} if codepre.zero?
749: elsif @filter_html or codepre > 0
750: line.texesc!( :NoQuotes )
751: ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
752: end
753: ## do htmlspecial if between <code>
754: elsif codepre > 0
755: line.texesc!( :NoQuotes )
756: ## line.gsub!( /<(\/?#{ offtags })>/, '<\1>' )
757: elsif not tagline
758: inline line
759: glyphs_deep line
760: end
761:
762: line
763: end
764: end
765: end
# File libraries/redcloth_for_tex.rb, line 780
780: def h_align( text )
781: H_ALGN_VALS[text]
782: end
# File libraries/redcloth_for_tex.rb, line 776
776: def i_align( text )
777: I_ALGN_VALS[text]
778: end
# File libraries/redcloth_for_tex.rb, line 619
619: def image( text )
620: text.gsub!( /
621: \! # opening
622: (\<|\=|\>)? # optional alignment atts
623: (#{C}) # optional style,class atts
624: (?:\. )? # optional dot-space
625: ([^\s(!]+?) # presume this is the src
626: \s? # optional space
627: (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
628: \! # closing
629: (?::#{ HYPERLINK })? # optional href
630: /x ) do |m|
631: algn,atts,url,title,href,href_a1,href_a2 = $~[1..7]
632: atts = pba( atts )
633: atts << " align=\"#{ i_align( algn ) }\"" if algn
634: atts << " title=\"#{ title }\"" if title
635: atts << " alt=\"#{ title }\""
636: # size = @getimagesize($url);
637: # if($size) $atts.= " $size[3]";
638:
639: href = check_refs( href ) if href
640: url = check_refs( url )
641:
642: out = ''
643: out << "<a href=\"#{ href }\">" if href
644: out << "<img src=\"#{ url }\"#{ atts } />"
645: out << "</a>#{ href_a1 }#{ href_a2 }" if href
646:
647: out
648: end
649: end
# File libraries/redcloth_for_tex.rb, line 679
679: def incoming_entities( text )
680: ## turn any incoming ampersands into a dummy character for now.
681: ## This uses a negative lookahead for alphanumerics followed by a semicolon,
682: ## implying an incoming html entity, to be skipped
683:
684: text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
685: end
# File libraries/redcloth_for_tex.rb, line 721
721: def inline( text )
722: image text
723: links text
724: code text
725: span text
726: end
# File libraries/redcloth_for_tex.rb, line 496
496: def lT( text )
497: text =~ /\#$/ ? 'enumerate' : 'itemize'
498: end
# File libraries/redcloth_for_tex.rb, line 582
582: def links( text )
583: text.gsub!( /
584: ([\s\[{(]|[#{PUNCT}])? # $pre
585: " # start
586: (#{C}) # $atts
587: ([^"]+?) # $text
588: \s?
589: (?:\(([^)]+?)\)(?="))? # $title
590: ":
591: (\S+?) # $url
592: (\/)? # $slash
593: ([^\w\/;]*?) # $post
594: (?=\s|$)
595: /x ) do |m|
596: pre,atts,text,title,url,slash,post = $~[1..7]
597:
598: url = check_refs( url )
599:
600: atts = pba( atts )
601: atts << " title=\"#{ title }\"" if title
602: atts = shelve( atts ) if atts
603:
604: "#{ pre }<a href=\"#{ url }#{ slash }\"#{ atts }>#{ text }</a>#{ post }"
605: end
606: end
# File libraries/redcloth_for_tex.rb, line 452
452: def lists( text )
453: text.gsub!( /^([#*]+?#{C} .*?)$(?![^#*])/m ) do |match|
454: lines = match.split( /\n/ )
455: last_line = -1
456: depth = []
457: lines.each_with_index do |line, line_id|
458: if line =~ /^([#*]+)(#{A}#{C}) (.*)$/m
459: tl,atts,content = $~[1..3]
460: if depth.last
461: if depth.last.length > tl.length
462: (depth.length - 1).downto(0) do |i|
463: break if depth[i].length == tl.length
464: lines[line_id - 1] << "\n\t\\end{#{ lT( depth[i] ) }}\n\t"
465: depth.pop
466: end
467: end
468: if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length
469: lines[line_id - 1] << ''
470: end
471: end
472: unless depth.last == tl
473: depth << tl
474: atts = pba( atts )
475: lines[line_id] = "\t\\begin{#{ lT(tl) }}\n\t\\item #{ content }"
476: else
477: lines[line_id] = "\t\t\\item #{ content }"
478: end
479: last_line = line_id
480:
481: elsif line =~ /^\s+\S/
482: last_line = line_id
483: elsif line_id - last_line < 2 and line =~ /^\S/
484: last_line = line_id
485: end
486: if line_id - last_line > 1 or line_id == lines.length - 1
487: depth.delete_if do |v|
488: lines[last_line] << "\n\t\\end{#{ lT( v ) }}"
489: end
490: end
491: end
492: lines.join( "\n" )
493: end
494: end
# File libraries/redcloth_for_tex.rb, line 710
710: def no_textile( text )
711: text.gsub!( /(^|\s)==(.*?)==(\s|$)?/,
712: '\1<notextile>\2</notextile>\3' )
713: end
# File libraries/redcloth_for_tex.rb, line 378
378: def pba( text_in, element = "" )
379:
380: return '' unless text_in
381:
382: style = []
383: text = text_in.dup
384: if element == 'td'
385: colspan = $1 if text =~ /\\(\d+)/
386: rowspan = $1 if text =~ /\/(\d+)/
387: style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
388: end
389:
390: style << "#{ $1 };" if not @filter_styles and
391: text.sub!( /\{([^}]*)\}/, '' )
392:
393: lang = $1 if
394: text.sub!( /\[([^)]+?)\]/, '' )
395:
396: cls = $1 if
397: text.sub!( /\(([^()]+?)\)/, '' )
398:
399: style << "padding-left:#{ $1.length }em;" if
400: text.sub!( /([(]+)/, '' )
401:
402: style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
403:
404: style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
405:
406: cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
407:
408: atts = ''
409: atts << " style=\"#{ style.join }\"" unless style.empty?
410: atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
411: atts << " lang=\"#{ lang }\"" if lang
412: atts << " id=\"#{ id }\"" if id
413: atts << " colspan=\"#{ colspan }\"" if colspan
414: atts << " rowspan=\"#{ rowspan }\"" if rowspan
415:
416: atts
417: end
# File libraries/redcloth_for_tex.rb, line 372
372: def pgl( text )
373: GLYPHS.each do |re, resub|
374: text.gsub! re, resub
375: end
376: end
# File libraries/redcloth_for_tex.rb, line 673
673: def retrieve( text )
674: @shelf.each_with_index do |r, i|
675: text.gsub!( " <#{ i + 1 }>", r )
676: end
677: end
# File libraries/redcloth_for_tex.rb, line 668
668: def shelve( val )
669: @shelf << val
670: " <#{ @shelf.length }>"
671: end
# File libraries/redcloth_for_tex.rb, line 556
556: def span( text )
557: QTAGS.each do |tt, ht|
558: ttr = Regexp::quote( tt )
559: text.gsub!(
560:
561: /(^|\s|\>|[#{PUNCT}{(\[])
562: #{ttr}
563: (#{C})
564: (?::(\S+?))?
565: ([^\s#{ttr}]+?(?:[^\n]|\n(?!\n))*?)
566: ([#{PUNCT}]*?)
567: #{ttr}
568: (?=[\])}]|[#{PUNCT}]+?|<|\s|$)/xm
569:
570: ) do |m|
571:
572: start,atts,cite,content,tend = $~[1..5]
573: atts = pba( atts )
574: atts << " cite=\"#{ cite }\"" if cite
575:
576: "#{ start }{\\#{ ht } #{ content }#{ tend }}"
577:
578: end
579: end
580: end
# File libraries/redcloth_for_tex.rb, line 419
419: def table( text )
420: text << "\n\n"
421: text.gsub!( /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)\n\n/m ) do |matches|
422:
423: tatts, fullrow = $~[1..2]
424: tatts = pba( tatts, 'table' )
425: rows = []
426:
427: fullrow.
428: split( /\|$/m ).
429: delete_if { |x| x.empty? }.
430: each do |row|
431:
432: ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
433:
434: cells = []
435: row.split( '|' ).each do |cell|
436: ctyp = 'd'
437: ctyp = 'h' if cell =~ /^_/
438:
439: catts = ''
440: catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. )(.*)/
441:
442: unless cell.strip.empty?
443: cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
444: end
445: end
446: rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
447: end
448: "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
449: end
450: end
# File libraries/redcloth_for_tex.rb, line 160
160: def table_of_contents(text, pages)
161: text.gsub!( /^([#*]+? .*?)$(?![^#*])/m ) do |match|
162: lines = match.split( /\n/ )
163: last_line = -1
164: depth = []
165: lines.each_with_index do |line, line_id|
166: if line =~ /^([#*]+) (.*)$/m
167: tl,content = $~[1..2]
168: content.gsub! /[\[\]]/, ""
169: content.strip!
170:
171: if depth.last
172: if depth.last.length > tl.length
173: (depth.length - 1).downto(0) do |i|
174: break if depth[i].length == tl.length
175: lines[line_id - 1] << "" # "\n\t\\end{#{ lT( depth[i] ) }}\n\t"
176: depth.pop
177: end
178: end
179: if !depth.last.nil? && !tl.length.nil? && depth.last.length == tl.length
180: lines[line_id - 1] << ''
181: end
182: end
183:
184: depth << tl unless depth.last == tl
185:
186: subsection_depth = [depth.length - 1, 2].min
187:
188: lines[line_id] = "\n\\#{ "sub" * subsection_depth }section{#{ content }}"
189: lines[line_id] += "\n#{pages[content]}" if pages.keys.include?(content)
190:
191: lines[line_id] = "\\pagebreak\n#{lines[line_id]}" if subsection_depth == 0
192:
193: last_line = line_id
194:
195: elsif line =~ /^\s+\S/
196: last_line = line_id
197: elsif line_id - last_line < 2 and line =~ /^\S/
198: last_line = line_id
199: end
200: if line_id - last_line > 1 or line_id == lines.length - 1
201: depth.delete_if do |v|
202: lines[last_line] << "" # "\n\t\\end{#{ lT( v ) }}"
203: end
204: end
205: end
206: lines.join( "\n" )
207: end
208: end
# File libraries/redcloth_for_tex.rb, line 796
796: def textile_popup_help( name, helpvar, windowW, windowH )
797: ' <a target="_blank" href="http://www.textpattern.com/help/?item=' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
798: end
Generate tex.
# File libraries/redcloth_for_tex.rb, line 331
331: def to_tex( lite = false )
332:
333: # make our working copy
334: text = self.dup
335:
336: @urlrefs = {}
337: @shelf = []
338:
339: # incoming_entities text
340: fix_entities text
341: clean_white_space text
342:
343: get_refs text
344:
345: no_textile text
346:
347: unless lite
348: lists text
349: table text
350: end
351:
352: glyphs text
353:
354: unless lite
355: fold text
356: block text
357: end
358:
359: retrieve text
360: encode_entities text
361:
362: text.gsub!(/\[\[(.*?)\]\]/, "\\1")
363: text.gsub!(/_/, "\\_")
364: text.gsub!( /<\/?notextile>/, '' )
365: # text.gsub!( /x%x%/, '&' )
366: # text.gsub!( /<br \/>/, "<br />\n" )
367: text.strip!
368: text
369:
370: end