guard-mthaml 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -1
  3. data/lib/guard/mthaml/version.rb +1 -1
  4. data/vendor/autoload.php +1 -1
  5. data/vendor/composer/autoload_real.php +4 -4
  6. data/vendor/composer/installed.json +68 -60
  7. data/vendor/michelf/php-markdown/License.md +2 -2
  8. data/vendor/michelf/php-markdown/Michelf/Markdown.php +130 -1638
  9. data/vendor/michelf/php-markdown/Michelf/MarkdownExtra.php +1592 -16
  10. data/vendor/michelf/php-markdown/Michelf/MarkdownInterface.php +2 -5
  11. data/vendor/michelf/php-markdown/Readme.md +32 -10
  12. data/vendor/michelf/php-markdown/composer.json +2 -2
  13. data/vendor/mthaml/mthaml/CHANGELOG +6 -0
  14. data/vendor/mthaml/mthaml/composer.json +11 -3
  15. data/vendor/mthaml/mthaml/lib/MtHaml/Filter/Markdown.php +1 -52
  16. data/vendor/mthaml/mthaml/lib/MtHaml/Filter/Markdown/CommonMark.php +25 -0
  17. data/vendor/mthaml/mthaml/lib/MtHaml/Filter/Markdown/FluxBBMarkdown.php +25 -0
  18. data/vendor/mthaml/mthaml/lib/MtHaml/Filter/OptimizableFilter.php +58 -0
  19. data/vendor/mthaml/mthaml/lib/MtHaml/Filter/ReST.php +21 -0
  20. data/vendor/mthaml/mthaml/test/MtHaml/Tests/TestCase.php +1 -1
  21. data/vendor/mthaml/mthaml/test/MtHaml/Tests/fixtures/environment/markdown_filter_commonmark.test +33 -0
  22. data/vendor/mthaml/mthaml/test/MtHaml/Tests/fixtures/environment/markdown_filter_fluxbb.test +32 -0
  23. data/vendor/mthaml/mthaml/test/MtHaml/Tests/fixtures/environment/rest_filter.test +42 -0
  24. data/vendor/mthaml/mthaml/test/MtHaml/Tests/fixtures/environment/rest_optimization_filter.test +39 -0
  25. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5fc482e482c713ad7b3b3af866becbce06ab71e6
4
- data.tar.gz: f066f53727625cb3d455fc066d39125c0834dfed
3
+ metadata.gz: 79e04479b2beda05ed1c51f2b48dd1cfadc74c26
4
+ data.tar.gz: 24ee42938388f97c2ebf624b2d7e97a436a4d45c
5
5
  SHA512:
6
- metadata.gz: d4a645d0ce3628c912b7abe905fc9724cf578ea6788ddc693199948366538c05eb89d6a84c1d01c8df1426a50741c3926999dd2b44c6352d87e237a30ae83015
7
- data.tar.gz: 15c99ce864cc3a34241384ec35c6985be36637eb009d67eb86e57f13610a22dcf29c31eb7c296e707c5299e9f2d6b1c4e027bf5e8cf7fb5f41c48279e6156501
6
+ metadata.gz: 00f7e760e66b8bde132d5e4a15e66229edd2fb9fdae0ede76134defdddd1e3b1f40daded45de01889a098d72d856971197ae354c5ec35f0a871fdc25cb697217
7
+ data.tar.gz: 6573c3a3357ad92d198f9f4c7903384c5ec16b51e282f56f0149c61b7f73bd91679d237597f5d5fd8e5a0a18488bf33b65a5d2904872d8b64ac0df0c2190d8be
data/README.md CHANGED
@@ -4,7 +4,7 @@ This is a Guard wrapper for [MtHaml](https://github.com/arnaud-lb/MtHaml) to com
4
4
  ## Installation
5
5
  Add to your `Gemfile`:
6
6
  ```ruby
7
- gem 'guard-mthaml'
7
+ gem "guard-mthaml"
8
8
  ```
9
9
 
10
10
  Require in your `Guardfile`:
@@ -27,6 +27,7 @@ Requires that `php` be executable via command line.
27
27
  # :input ("views/src") set input directory with haml files
28
28
  # :output ("views") set output directory for compiled files
29
29
  # :environment ("php") haml environment
30
+ # :extension (nil) output file extension, uses environment if nil
30
31
  # :notifications (true) toggle guard notifications
31
32
  # :compress_output (false) compress compiled haml files
32
33
  # :static_files (false) compile haml to static html
@@ -1,5 +1,5 @@
1
1
  module ::Guard
2
2
  class MtHamlVersion
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -4,4 +4,4 @@
4
4
 
5
5
  require_once __DIR__ . '/composer' . '/autoload_real.php';
6
6
 
7
- return ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37::getLoader();
7
+ return ComposerAutoloaderInit70d5564cb90f02ffa7babc3c9a2c2a90::getLoader();
@@ -2,7 +2,7 @@
2
2
 
3
3
  // autoload_real.php @generated by Composer
4
4
 
5
- class ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37
5
+ class ComposerAutoloaderInit70d5564cb90f02ffa7babc3c9a2c2a90
6
6
  {
7
7
  private static $loader;
8
8
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37
19
19
  return self::$loader;
20
20
  }
21
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37', 'loadClassLoader'), true, true);
22
+ spl_autoload_register(array('ComposerAutoloaderInit70d5564cb90f02ffa7babc3c9a2c2a90', 'loadClassLoader'), true, true);
23
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37', 'loadClassLoader'));
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit70d5564cb90f02ffa7babc3c9a2c2a90', 'loadClassLoader'));
25
25
 
26
26
  $map = require __DIR__ . '/autoload_namespaces.php';
27
27
  foreach ($map as $namespace => $path) {
@@ -44,7 +44,7 @@ class ComposerAutoloaderInit7c72a395feca2c9cf06b47db0e557a37
44
44
  }
45
45
  }
46
46
 
47
- function composerRequire7c72a395feca2c9cf06b47db0e557a37($file)
47
+ function composerRequire70d5564cb90f02ffa7babc3c9a2c2a90($file)
48
48
  {
49
49
  require $file;
50
50
  }
@@ -45,85 +45,37 @@
45
45
  "javascript"
46
46
  ]
47
47
  },
48
- {
49
- "name": "michelf/php-markdown",
50
- "version": "1.4.1",
51
- "version_normalized": "1.4.1.0",
52
- "source": {
53
- "type": "git",
54
- "url": "https://github.com/michelf/php-markdown.git",
55
- "reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6"
56
- },
57
- "dist": {
58
- "type": "zip",
59
- "url": "https://api.github.com/repos/michelf/php-markdown/zipball/de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
60
- "reference": "de9a19c7bf352d41cc99ed86c3c0ef17e87394b6",
61
- "shasum": ""
62
- },
63
- "require": {
64
- "php": ">=5.3.0"
65
- },
66
- "time": "2014-05-05 02:43:50",
67
- "type": "library",
68
- "extra": {
69
- "branch-alias": {
70
- "dev-lib": "1.4.x-dev"
71
- }
72
- },
73
- "installation-source": "dist",
74
- "autoload": {
75
- "psr-0": {
76
- "Michelf": ""
77
- }
78
- },
79
- "notification-url": "https://packagist.org/downloads/",
80
- "license": [
81
- "BSD-3-Clause"
82
- ],
83
- "authors": [
84
- {
85
- "name": "Michel Fortin",
86
- "email": "michel.fortin@michelf.ca",
87
- "homepage": "http://michelf.ca/",
88
- "role": "Developer"
89
- },
90
- {
91
- "name": "John Gruber",
92
- "homepage": "http://daringfireball.net/"
93
- }
94
- ],
95
- "description": "PHP Markdown",
96
- "homepage": "http://michelf.ca/projects/php-markdown/",
97
- "keywords": [
98
- "markdown"
99
- ]
100
- },
101
48
  {
102
49
  "name": "mthaml/mthaml",
103
- "version": "dev-master",
104
- "version_normalized": "9999999-dev",
50
+ "version": "1.7.0",
51
+ "version_normalized": "1.7.0.0",
105
52
  "source": {
106
53
  "type": "git",
107
54
  "url": "https://github.com/arnaud-lb/MtHaml.git",
108
- "reference": "86f094aa6e5036a349d59bb0c30c3088a3ee7b1b"
55
+ "reference": "d14c9860240710763226c4b8c09f80c80c4911dd"
109
56
  },
110
57
  "dist": {
111
58
  "type": "zip",
112
- "url": "https://api.github.com/repos/arnaud-lb/MtHaml/zipball/86f094aa6e5036a349d59bb0c30c3088a3ee7b1b",
113
- "reference": "86f094aa6e5036a349d59bb0c30c3088a3ee7b1b",
59
+ "url": "https://api.github.com/repos/arnaud-lb/MtHaml/zipball/d14c9860240710763226c4b8c09f80c80c4911dd",
60
+ "reference": "d14c9860240710763226c4b8c09f80c80c4911dd",
114
61
  "shasum": ""
115
62
  },
116
63
  "require": {
117
- "php": ">=5.3.0"
64
+ "php": ">=5.4.0"
118
65
  },
119
66
  "conflict": {
120
67
  "mthaml/mthaml-bundle": "<1.1.0"
121
68
  },
122
69
  "require-dev": {
70
+ "cebe/markdown": "~1",
123
71
  "coffeescript/coffeescript": "~1",
124
72
  "erusev/parsedown": "*",
73
+ "fluxbb/commonmark": "~1@dev",
74
+ "gregwar/rst": "~1",
75
+ "kzykhys/ciconia": "~1",
125
76
  "leafo/lessphp": "*",
126
77
  "leafo/scssphp": "*",
78
+ "league/commonmark": ">=0.5",
127
79
  "michelf/php-markdown": "~1.3",
128
80
  "oyejorge/less.php": "*",
129
81
  "twig/twig": "~1.11"
@@ -132,14 +84,17 @@
132
84
  "cebe/markdown": "If you want to use :markdown filter",
133
85
  "coffeescript/coffeescript": "If you want to use :coffee or :coffeescript filter",
134
86
  "erusev/parsedown": "If you want to use :markdown filter",
87
+ "fluxbb/commonmark": "If you want to use :markdown filter",
88
+ "gregwar/rst": "If you want to use :rest filter",
135
89
  "kzykhys/ciconia": "If you want to use :markdown filter",
136
90
  "leafo/lessphp": "If you want to use :less filter",
137
91
  "leafo/scssphp": "If you want to use :scss filter",
92
+ "league/commonmark": "If you want to use :markdown filter",
138
93
  "michelf/php-markdown": "If you want to use :markdown filter",
139
94
  "oyejorge/less.php": "If you want to use :less filter",
140
95
  "twig/twig": "If you want to use Twig templating engine"
141
96
  },
142
- "time": "2014-10-10 20:23:22",
97
+ "time": "2015-01-23 15:23:28",
143
98
  "type": "library",
144
99
  "installation-source": "source",
145
100
  "autoload": {
@@ -162,5 +117,58 @@
162
117
  "keywords": [
163
118
  "HAML"
164
119
  ]
120
+ },
121
+ {
122
+ "name": "michelf/php-markdown",
123
+ "version": "1.5.0",
124
+ "version_normalized": "1.5.0.0",
125
+ "source": {
126
+ "type": "git",
127
+ "url": "https://github.com/michelf/php-markdown.git",
128
+ "reference": "e1aabe18173231ebcefc90e615565742fc1c7fd9"
129
+ },
130
+ "dist": {
131
+ "type": "zip",
132
+ "url": "https://api.github.com/repos/michelf/php-markdown/zipball/e1aabe18173231ebcefc90e615565742fc1c7fd9",
133
+ "reference": "e1aabe18173231ebcefc90e615565742fc1c7fd9",
134
+ "shasum": ""
135
+ },
136
+ "require": {
137
+ "php": ">=5.3.0"
138
+ },
139
+ "time": "2015-03-01 12:03:08",
140
+ "type": "library",
141
+ "extra": {
142
+ "branch-alias": {
143
+ "dev-lib": "1.4.x-dev"
144
+ }
145
+ },
146
+ "installation-source": "dist",
147
+ "autoload": {
148
+ "psr-0": {
149
+ "Michelf": ""
150
+ }
151
+ },
152
+ "notification-url": "https://packagist.org/downloads/",
153
+ "license": [
154
+ "BSD-3-Clause"
155
+ ],
156
+ "authors": [
157
+ {
158
+ "name": "John Gruber",
159
+ "homepage": "http://daringfireball.net/"
160
+ },
161
+ {
162
+ "name": "Michel Fortin",
163
+ "email": "michel.fortin@michelf.ca",
164
+ "homepage": "https://michelf.ca/",
165
+ "role": "Developer"
166
+ }
167
+ ],
168
+ "description": "PHP Markdown",
169
+ "homepage": "https://michelf.ca/projects/php-markdown/",
170
+ "keywords": [
171
+ "markdown"
172
+ ]
165
173
  }
166
174
  ]
@@ -1,6 +1,6 @@
1
1
  PHP Markdown Lib
2
- Copyright (c) 2004-2014 Michel Fortin
3
- <http://michelf.ca/>
2
+ Copyright (c) 2004-2015 Michel Fortin
3
+ <https://michelf.ca/>
4
4
  All rights reserved.
5
5
 
6
6
  Based on Markdown
@@ -3,8 +3,8 @@
3
3
  # Markdown - A text-to-HTML conversion tool for web writers
4
4
  #
5
5
  # PHP Markdown
6
- # Copyright (c) 2004-2014 Michel Fortin
7
- # <http://michelf.com/projects/php-markdown/>
6
+ # Copyright (c) 2004-2015 Michel Fortin
7
+ # <https://michelf.ca/projects/php-markdown/>
8
8
  #
9
9
  # Original Markdown
10
10
  # Copyright (c) 2004-2006 John Gruber
@@ -21,7 +21,7 @@ class Markdown implements MarkdownInterface {
21
21
 
22
22
  ### Version ###
23
23
 
24
- const MARKDOWNLIB_VERSION = "1.4.1";
24
+ const MARKDOWNLIB_VERSION = "1.5.0";
25
25
 
26
26
  ### Simple Function Interface ###
27
27
 
@@ -59,6 +59,24 @@ class Markdown implements MarkdownInterface {
59
59
  public $predef_urls = array();
60
60
  public $predef_titles = array();
61
61
 
62
+ # Optional filter function for URLs
63
+ public $url_filter_func = null;
64
+
65
+ # Optional header id="" generation callback function.
66
+ public $header_id_func = null;
67
+
68
+ # Class attribute to toggle "enhanced ordered list" behaviour
69
+ # setting this to true will allow ordered lists to start from the index
70
+ # number that is defined first. For example:
71
+ # 2. List item two
72
+ # 3. List item three
73
+ #
74
+ # becomes
75
+ # <ol start="2">
76
+ # <li>List item two</li>
77
+ # <li>List item three</li>
78
+ # </ol>
79
+ public $enhanced_ordered_list = false;
62
80
 
63
81
  ### Parser Implementation ###
64
82
 
@@ -593,7 +611,7 @@ class Markdown implements MarkdownInterface {
593
611
 
594
612
  if (isset($this->urls[$link_id])) {
595
613
  $url = $this->urls[$link_id];
596
- $url = $this->encodeAttribute($url);
614
+ $url = $this->encodeURLAttribute($url);
597
615
 
598
616
  $result = "<a href=\"$url\"";
599
617
  if ( isset( $this->titles[$link_id] ) ) {
@@ -623,7 +641,7 @@ class Markdown implements MarkdownInterface {
623
641
  if ($unhashed != $url)
624
642
  $url = preg_replace('/^<(.*)>$/', '\1', $unhashed);
625
643
 
626
- $url = $this->encodeAttribute($url);
644
+ $url = $this->encodeURLAttribute($url);
627
645
 
628
646
  $result = "<a href=\"$url\"";
629
647
  if (isset($title)) {
@@ -704,7 +722,7 @@ class Markdown implements MarkdownInterface {
704
722
 
705
723
  $alt_text = $this->encodeAttribute($alt_text);
706
724
  if (isset($this->urls[$link_id])) {
707
- $url = $this->encodeAttribute($this->urls[$link_id]);
725
+ $url = $this->encodeURLAttribute($this->urls[$link_id]);
708
726
  $result = "<img src=\"$url\" alt=\"$alt_text\"";
709
727
  if (isset($this->titles[$link_id])) {
710
728
  $title = $this->titles[$link_id];
@@ -728,7 +746,7 @@ class Markdown implements MarkdownInterface {
728
746
  $title =& $matches[7];
729
747
 
730
748
  $alt_text = $this->encodeAttribute($alt_text);
731
- $url = $this->encodeAttribute($url);
749
+ $url = $this->encodeURLAttribute($url);
732
750
  $result = "<img src=\"$url\" alt=\"$alt_text\"";
733
751
  if (isset($title)) {
734
752
  $title = $this->encodeAttribute($title);
@@ -770,21 +788,46 @@ class Markdown implements MarkdownInterface {
770
788
 
771
789
  return $text;
772
790
  }
791
+
773
792
  protected function _doHeaders_callback_setext($matches) {
774
793
  # Terrible hack to check we haven't found an empty list item.
775
794
  if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))
776
795
  return $matches[0];
777
796
 
778
797
  $level = $matches[2]{0} == '=' ? 1 : 2;
779
- $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>";
798
+
799
+ # id attribute generation
800
+ $idAtt = $this->_generateIdFromHeaderValue($matches[1]);
801
+
802
+ $block = "<h$level$idAtt>".$this->runSpanGamut($matches[1])."</h$level>";
780
803
  return "\n" . $this->hashBlock($block) . "\n\n";
781
804
  }
782
805
  protected function _doHeaders_callback_atx($matches) {
806
+
807
+ # id attribute generation
808
+ $idAtt = $this->_generateIdFromHeaderValue($matches[2]);
809
+
783
810
  $level = strlen($matches[1]);
784
- $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>";
811
+ $block = "<h$level$idAtt>".$this->runSpanGamut($matches[2])."</h$level>";
785
812
  return "\n" . $this->hashBlock($block) . "\n\n";
786
813
  }
787
814
 
815
+ protected function _generateIdFromHeaderValue($headerValue) {
816
+
817
+ # if a header_id_func property is set, we can use it to automatically
818
+ # generate an id attribute.
819
+ #
820
+ # This method returns a string in the form id="foo", or an empty string
821
+ # otherwise.
822
+ if (!is_callable($this->header_id_func)) {
823
+ return "";
824
+ }
825
+ $idValue = call_user_func($this->header_id_func, $headerValue);
826
+ if (!$idValue) return "";
827
+
828
+ return ' id="' . $this->encodeAttribute($idValue) . '"';
829
+
830
+ }
788
831
 
789
832
  protected function doLists($text) {
790
833
  #
@@ -856,16 +899,33 @@ class Markdown implements MarkdownInterface {
856
899
  $marker_ul_re = '[*+-]';
857
900
  $marker_ol_re = '\d+[\.]';
858
901
  $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
859
-
902
+ $marker_ol_start_re = '[0-9]+';
903
+
860
904
  $list = $matches[1];
861
905
  $list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";
862
-
906
+
863
907
  $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );
864
-
908
+
865
909
  $list .= "\n";
866
910
  $result = $this->processListItems($list, $marker_any_re);
867
-
868
- $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
911
+
912
+ $ol_start = 1;
913
+ if ($this->enhanced_ordered_list) {
914
+ # Get the start number for ordered list.
915
+ if ($list_type == 'ol') {
916
+ $ol_start_array = array();
917
+ $ol_start_check = preg_match("/$marker_ol_start_re/", $matches[4], $ol_start_array);
918
+ if ($ol_start_check){
919
+ $ol_start = $ol_start_array[0];
920
+ }
921
+ }
922
+ }
923
+
924
+ if ($ol_start > 1 && $list_type == 'ol'){
925
+ $result = $this->hashBlock("<$list_type start=\"$ol_start\">\n" . $result . "</$list_type>");
926
+ } else {
927
+ $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
928
+ }
869
929
  return "\n". $result ."\n\n";
870
930
  }
871
931
 
@@ -1260,6 +1320,33 @@ class Markdown implements MarkdownInterface {
1260
1320
  $text = str_replace('"', '&quot;', $text);
1261
1321
  return $text;
1262
1322
  }
1323
+
1324
+
1325
+ protected function encodeURLAttribute($url, &$text = null) {
1326
+ #
1327
+ # Encode text for a double-quoted HTML attribute containing a URL,
1328
+ # applying the URL filter if set. Also generates the textual
1329
+ # representation for the URL (removing mailto: or tel:) storing it in $text.
1330
+ # This function is *not* suitable for attributes enclosed in single quotes.
1331
+ #
1332
+ if ($this->url_filter_func)
1333
+ $url = call_user_func($this->url_filter_func, $url);
1334
+
1335
+ if (preg_match('{^mailto:}i', $url))
1336
+ $url = $this->encodeEntityObfuscatedAttribute($url, $text, 7);
1337
+ else if (preg_match('{^tel:}i', $url))
1338
+ {
1339
+ $url = $this->encodeAttribute($url);
1340
+ $text = substr($url, 4);
1341
+ }
1342
+ else
1343
+ {
1344
+ $url = $this->encodeAttribute($url);
1345
+ $text = $url;
1346
+ }
1347
+
1348
+ return $url;
1349
+ }
1263
1350
 
1264
1351
 
1265
1352
  protected function encodeAmpsAndAngles($text) {
@@ -1284,7 +1371,7 @@ class Markdown implements MarkdownInterface {
1284
1371
 
1285
1372
 
1286
1373
  protected function doAutoLinks($text) {
1287
- $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i',
1374
+ $text = preg_replace_callback('{<((https?|ftp|dict|tel):[^\'">\s]+)>}i',
1288
1375
  array($this, '_doAutoLinks_url_callback'), $text);
1289
1376
 
1290
1377
  # Email addresses: <address@domain.foo>
@@ -1307,48 +1394,46 @@ class Markdown implements MarkdownInterface {
1307
1394
  >
1308
1395
  }xi',
1309
1396
  array($this, '_doAutoLinks_email_callback'), $text);
1310
- $text = preg_replace_callback('{<(tel:([^\'">\s]+))>}i',array($this, '_doAutoLinks_tel_callback'), $text);
1311
1397
 
1312
1398
  return $text;
1313
1399
  }
1314
- protected function _doAutoLinks_tel_callback($matches) {
1315
- $url = $this->encodeAttribute($matches[1]);
1316
- $tel = $this->encodeAttribute($matches[2]);
1317
- $link = "<a href=\"$url\">$tel</a>";
1318
- return $this->hashPart($link);
1319
- }
1320
1400
  protected function _doAutoLinks_url_callback($matches) {
1321
- $url = $this->encodeAttribute($matches[1]);
1322
- $link = "<a href=\"$url\">$url</a>";
1401
+ $url = $this->encodeURLAttribute($matches[1], $text);
1402
+ $link = "<a href=\"$url\">$text</a>";
1323
1403
  return $this->hashPart($link);
1324
1404
  }
1325
1405
  protected function _doAutoLinks_email_callback($matches) {
1326
- $address = $matches[1];
1327
- $link = $this->encodeEmailAddress($address);
1406
+ $addr = $matches[1];
1407
+ $url = $this->encodeURLAttribute("mailto:$addr", $text);
1408
+ $link = "<a href=\"$url\">$text</a>";
1328
1409
  return $this->hashPart($link);
1329
1410
  }
1330
1411
 
1331
1412
 
1332
- protected function encodeEmailAddress($addr) {
1413
+ protected function encodeEntityObfuscatedAttribute($text, &$tail = null, $head_length = 0) {
1333
1414
  #
1334
- # Input: an email address, e.g. "foo@example.com"
1415
+ # Input: some text to obfuscate, e.g. "mailto:foo@example.com"
1335
1416
  #
1336
- # Output: the email address as a mailto link, with each character
1337
- # of the address encoded as either a decimal or hex entity, in
1338
- # the hopes of foiling most address harvesting spam bots. E.g.:
1417
+ # Output: the same text but with most characters encoded as either a
1418
+ # decimal or hex entity, in the hopes of foiling most address
1419
+ # harvesting spam bots. E.g.:
1339
1420
  #
1340
- # <p><a href="&#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1421
+ # &#109;&#x61;&#105;&#x6c;&#116;&#x6f;&#58;&#x66;o&#111;
1341
1422
  # &#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;&#101;&#46;&#x63;&#111;
1342
- # &#x6d;">&#x66;o&#111;&#x40;&#101;&#x78;&#97;&#x6d;&#112;&#x6c;
1343
- # &#101;&#46;&#x63;&#111;&#x6d;</a></p>
1423
+ # &#x6d;
1424
+ #
1425
+ # Note: the additional output $tail is assigned the same value as the
1426
+ # ouput, minus the number of characters specified by $head_length.
1344
1427
  #
1345
1428
  # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
1346
- # With some optimizations by Milian Wolff.
1429
+ # With some optimizations by Milian Wolff. Forced encoding of HTML
1430
+ # attribute special characters by Allan Odgaard.
1347
1431
  #
1348
- $addr = "mailto:" . $addr;
1349
- $chars = preg_split('/(?<!^)(?!$)/', $addr);
1350
- $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.
1351
-
1432
+ if ($text == "") return $tail = "";
1433
+
1434
+ $chars = preg_split('/(?<!^)(?!$)/', $text);
1435
+ $seed = (int)abs(crc32($text) / strlen($text)); # Deterministic seed.
1436
+
1352
1437
  foreach ($chars as $key => $char) {
1353
1438
  $ord = ord($char);
1354
1439
  # Ignore non-ascii chars.
@@ -1356,18 +1441,17 @@ class Markdown implements MarkdownInterface {
1356
1441
  $r = ($seed * (1 + $key)) % 100; # Pseudo-random function.
1357
1442
  # roughly 10% raw, 45% hex, 45% dec
1358
1443
  # '@' *must* be encoded. I insist.
1359
- # '"' has to be encoded inside the attribute
1360
- if ($r > 90 && $char != '@' && $char != '"') /* do nothing */;
1444
+ # '"' and '>' have to be encoded inside the attribute
1445
+ if ($r > 90 && strpos('@"&>', $char) === false) /* do nothing */;
1361
1446
  else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';';
1362
1447
  else $chars[$key] = '&#'.$ord.';';
1363
1448
  }
1364
1449
  }
1365
-
1366
- $addr = implode('', $chars);
1367
- $text = implode('', array_slice($chars, 7)); # text without `mailto:`
1368
- $addr = "<a href=\"$addr\">$text</a>";
1369
1450
 
1370
- return $addr;
1451
+ $text = implode('', $chars);
1452
+ $tail = $head_length ? implode('', array_slice($chars, $head_length)) : $text;
1453
+
1454
+ return $text;
1371
1455
  }
1372
1456
 
1373
1457
 
@@ -1523,1595 +1607,3 @@ class Markdown implements MarkdownInterface {
1523
1607
  }
1524
1608
 
1525
1609
  }
1526
-
1527
-
1528
- #
1529
- # Temporary Markdown Extra Parser Implementation Class
1530
- #
1531
- # NOTE: DON'T USE THIS CLASS
1532
- # Currently the implementation of of Extra resides here in this temporary class.
1533
- # This makes it easier to propagate the changes between the three different
1534
- # packaging styles of PHP Markdown. When this issue is resolved, this
1535
- # MarkdownExtra_TmpImpl class here will disappear and \Michelf\MarkdownExtra
1536
- # will contain the code. So please use \Michelf\MarkdownExtra and ignore this
1537
- # one.
1538
- #
1539
-
1540
- abstract class _MarkdownExtra_TmpImpl extends \Michelf\Markdown {
1541
-
1542
- ### Configuration Variables ###
1543
-
1544
- # Prefix for footnote ids.
1545
- public $fn_id_prefix = "";
1546
-
1547
- # Optional title attribute for footnote links and backlinks.
1548
- public $fn_link_title = "";
1549
- public $fn_backlink_title = "";
1550
-
1551
- # Optional class attribute for footnote links and backlinks.
1552
- public $fn_link_class = "footnote-ref";
1553
- public $fn_backlink_class = "footnote-backref";
1554
-
1555
- # Class name for table cell alignment (%% replaced left/center/right)
1556
- # For instance: 'go-%%' becomes 'go-left' or 'go-right' or 'go-center'
1557
- # If empty, the align attribute is used instead of a class name.
1558
- public $table_align_class_tmpl = '';
1559
-
1560
- # Optional class prefix for fenced code block.
1561
- public $code_class_prefix = "";
1562
- # Class attribute for code blocks goes on the `code` tag;
1563
- # setting this to true will put attributes on the `pre` tag instead.
1564
- public $code_attr_on_pre = false;
1565
-
1566
- # Predefined abbreviations.
1567
- public $predef_abbr = array();
1568
-
1569
-
1570
- ### Parser Implementation ###
1571
-
1572
- public function __construct() {
1573
- #
1574
- # Constructor function. Initialize the parser object.
1575
- #
1576
- # Add extra escapable characters before parent constructor
1577
- # initialize the table.
1578
- $this->escape_chars .= ':|';
1579
-
1580
- # Insert extra document, block, and span transformations.
1581
- # Parent constructor will do the sorting.
1582
- $this->document_gamut += array(
1583
- "doFencedCodeBlocks" => 5,
1584
- "stripFootnotes" => 15,
1585
- "stripAbbreviations" => 25,
1586
- "appendFootnotes" => 50,
1587
- );
1588
- $this->block_gamut += array(
1589
- "doFencedCodeBlocks" => 5,
1590
- "doTables" => 15,
1591
- "doDefLists" => 45,
1592
- );
1593
- $this->span_gamut += array(
1594
- "doFootnotes" => 5,
1595
- "doAbbreviations" => 70,
1596
- );
1597
-
1598
- parent::__construct();
1599
- }
1600
-
1601
-
1602
- # Extra variables used during extra transformations.
1603
- protected $footnotes = array();
1604
- protected $footnotes_ordered = array();
1605
- protected $footnotes_ref_count = array();
1606
- protected $footnotes_numbers = array();
1607
- protected $abbr_desciptions = array();
1608
- protected $abbr_word_re = '';
1609
-
1610
- # Give the current footnote number.
1611
- protected $footnote_counter = 1;
1612
-
1613
-
1614
- protected function setup() {
1615
- #
1616
- # Setting up Extra-specific variables.
1617
- #
1618
- parent::setup();
1619
-
1620
- $this->footnotes = array();
1621
- $this->footnotes_ordered = array();
1622
- $this->footnotes_ref_count = array();
1623
- $this->footnotes_numbers = array();
1624
- $this->abbr_desciptions = array();
1625
- $this->abbr_word_re = '';
1626
- $this->footnote_counter = 1;
1627
-
1628
- foreach ($this->predef_abbr as $abbr_word => $abbr_desc) {
1629
- if ($this->abbr_word_re)
1630
- $this->abbr_word_re .= '|';
1631
- $this->abbr_word_re .= preg_quote($abbr_word);
1632
- $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
1633
- }
1634
- }
1635
-
1636
- protected function teardown() {
1637
- #
1638
- # Clearing Extra-specific variables.
1639
- #
1640
- $this->footnotes = array();
1641
- $this->footnotes_ordered = array();
1642
- $this->footnotes_ref_count = array();
1643
- $this->footnotes_numbers = array();
1644
- $this->abbr_desciptions = array();
1645
- $this->abbr_word_re = '';
1646
-
1647
- parent::teardown();
1648
- }
1649
-
1650
-
1651
- ### Extra Attribute Parser ###
1652
-
1653
- # Expression to use to catch attributes (includes the braces)
1654
- protected $id_class_attr_catch_re = '\{((?:[ ]*[#.][-_:a-zA-Z0-9]+){1,})[ ]*\}';
1655
- # Expression to use when parsing in a context when no capture is desired
1656
- protected $id_class_attr_nocatch_re = '\{(?:[ ]*[#.][-_:a-zA-Z0-9]+){1,}[ ]*\}';
1657
-
1658
- protected function doExtraAttributes($tag_name, $attr) {
1659
- #
1660
- # Parse attributes caught by the $this->id_class_attr_catch_re expression
1661
- # and return the HTML-formatted list of attributes.
1662
- #
1663
- # Currently supported attributes are .class and #id.
1664
- #
1665
- if (empty($attr)) return "";
1666
-
1667
- # Split on components
1668
- preg_match_all('/[#.][-_:a-zA-Z0-9]+/', $attr, $matches);
1669
- $elements = $matches[0];
1670
-
1671
- # handle classes and ids (only first id taken into account)
1672
- $classes = array();
1673
- $id = false;
1674
- foreach ($elements as $element) {
1675
- if ($element{0} == '.') {
1676
- $classes[] = substr($element, 1);
1677
- } else if ($element{0} == '#') {
1678
- if ($id === false) $id = substr($element, 1);
1679
- }
1680
- }
1681
-
1682
- # compose attributes as string
1683
- $attr_str = "";
1684
- if (!empty($id)) {
1685
- $attr_str .= ' id="'.$id.'"';
1686
- }
1687
- if (!empty($classes)) {
1688
- $attr_str .= ' class="'.implode(" ", $classes).'"';
1689
- }
1690
- return $attr_str;
1691
- }
1692
-
1693
-
1694
- protected function stripLinkDefinitions($text) {
1695
- #
1696
- # Strips link definitions from text, stores the URLs and titles in
1697
- # hash references.
1698
- #
1699
- $less_than_tab = $this->tab_width - 1;
1700
-
1701
- # Link defs are in the form: ^[id]: url "optional title"
1702
- $text = preg_replace_callback('{
1703
- ^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?: # id = $1
1704
- [ ]*
1705
- \n? # maybe *one* newline
1706
- [ ]*
1707
- (?:
1708
- <(.+?)> # url = $2
1709
- |
1710
- (\S+?) # url = $3
1711
- )
1712
- [ ]*
1713
- \n? # maybe one newline
1714
- [ ]*
1715
- (?:
1716
- (?<=\s) # lookbehind for whitespace
1717
- ["(]
1718
- (.*?) # title = $4
1719
- [")]
1720
- [ ]*
1721
- )? # title is optional
1722
- (?:[ ]* '.$this->id_class_attr_catch_re.' )? # $5 = extra id & class attr
1723
- (?:\n+|\Z)
1724
- }xm',
1725
- array($this, '_stripLinkDefinitions_callback'),
1726
- $text);
1727
- return $text;
1728
- }
1729
- protected function _stripLinkDefinitions_callback($matches) {
1730
- $link_id = strtolower($matches[1]);
1731
- $url = $matches[2] == '' ? $matches[3] : $matches[2];
1732
- $this->urls[$link_id] = $url;
1733
- $this->titles[$link_id] =& $matches[4];
1734
- $this->ref_attr[$link_id] = $this->doExtraAttributes("", $dummy =& $matches[5]);
1735
- return ''; # String that will replace the block
1736
- }
1737
-
1738
-
1739
- ### HTML Block Parser ###
1740
-
1741
- # Tags that are always treated as block tags:
1742
- protected $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend|article|section|nav|aside|hgroup|header|footer|figcaption|figure';
1743
-
1744
- # Tags treated as block tags only if the opening tag is alone on its line:
1745
- protected $context_block_tags_re = 'script|noscript|style|ins|del|iframe|object|source|track|param|math|svg|canvas|audio|video';
1746
-
1747
- # Tags where markdown="1" default to span mode:
1748
- protected $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address';
1749
-
1750
- # Tags which must not have their contents modified, no matter where
1751
- # they appear:
1752
- protected $clean_tags_re = 'script|style|math|svg';
1753
-
1754
- # Tags that do not need to be closed.
1755
- protected $auto_close_tags_re = 'hr|img|param|source|track';
1756
-
1757
-
1758
- protected function hashHTMLBlocks($text) {
1759
- #
1760
- # Hashify HTML Blocks and "clean tags".
1761
- #
1762
- # We only want to do this for block-level HTML tags, such as headers,
1763
- # lists, and tables. That's because we still want to wrap <p>s around
1764
- # "paragraphs" that are wrapped in non-block-level tags, such as anchors,
1765
- # phrase emphasis, and spans. The list of tags we're looking for is
1766
- # hard-coded.
1767
- #
1768
- # This works by calling _HashHTMLBlocks_InMarkdown, which then calls
1769
- # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1"
1770
- # attribute is found within a tag, _HashHTMLBlocks_InHTML calls back
1771
- # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag.
1772
- # These two functions are calling each other. It's recursive!
1773
- #
1774
- if ($this->no_markup) return $text;
1775
-
1776
- #
1777
- # Call the HTML-in-Markdown hasher.
1778
- #
1779
- list($text, ) = $this->_hashHTMLBlocks_inMarkdown($text);
1780
-
1781
- return $text;
1782
- }
1783
- protected function _hashHTMLBlocks_inMarkdown($text, $indent = 0,
1784
- $enclosing_tag_re = '', $span = false)
1785
- {
1786
- #
1787
- # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags.
1788
- #
1789
- # * $indent is the number of space to be ignored when checking for code
1790
- # blocks. This is important because if we don't take the indent into
1791
- # account, something like this (which looks right) won't work as expected:
1792
- #
1793
- # <div>
1794
- # <div markdown="1">
1795
- # Hello World. <-- Is this a Markdown code block or text?
1796
- # </div> <-- Is this a Markdown code block or a real tag?
1797
- # <div>
1798
- #
1799
- # If you don't like this, just don't indent the tag on which
1800
- # you apply the markdown="1" attribute.
1801
- #
1802
- # * If $enclosing_tag_re is not empty, stops at the first unmatched closing
1803
- # tag with that name. Nested tags supported.
1804
- #
1805
- # * If $span is true, text inside must treated as span. So any double
1806
- # newline will be replaced by a single newline so that it does not create
1807
- # paragraphs.
1808
- #
1809
- # Returns an array of that form: ( processed text , remaining text )
1810
- #
1811
- if ($text === '') return array('', '');
1812
-
1813
- # Regex to check for the presense of newlines around a block tag.
1814
- $newline_before_re = '/(?:^\n?|\n\n)*$/';
1815
- $newline_after_re =
1816
- '{
1817
- ^ # Start of text following the tag.
1818
- (?>[ ]*<!--.*?-->)? # Optional comment.
1819
- [ ]*\n # Must be followed by newline.
1820
- }xs';
1821
-
1822
- # Regex to match any tag.
1823
- $block_tag_re =
1824
- '{
1825
- ( # $2: Capture whole tag.
1826
- </? # Any opening or closing tag.
1827
- (?> # Tag name.
1828
- '.$this->block_tags_re.' |
1829
- '.$this->context_block_tags_re.' |
1830
- '.$this->clean_tags_re.' |
1831
- (?!\s)'.$enclosing_tag_re.'
1832
- )
1833
- (?:
1834
- (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
1835
- (?>
1836
- ".*?" | # Double quotes (can contain `>`)
1837
- \'.*?\' | # Single quotes (can contain `>`)
1838
- .+? # Anything but quotes and `>`.
1839
- )*?
1840
- )?
1841
- > # End of tag.
1842
- |
1843
- <!-- .*? --> # HTML Comment
1844
- |
1845
- <\?.*?\?> | <%.*?%> # Processing instruction
1846
- |
1847
- <!\[CDATA\[.*?\]\]> # CData Block
1848
- '. ( !$span ? ' # If not in span.
1849
- |
1850
- # Indented code block
1851
- (?: ^[ ]*\n | ^ | \n[ ]*\n )
1852
- [ ]{'.($indent+4).'}[^\n]* \n
1853
- (?>
1854
- (?: [ ]{'.($indent+4).'}[^\n]* | [ ]* ) \n
1855
- )*
1856
- |
1857
- # Fenced code block marker
1858
- (?<= ^ | \n )
1859
- [ ]{0,'.($indent+3).'}(?:~{3,}|`{3,})
1860
- [ ]*
1861
- (?:
1862
- \.?[-_:a-zA-Z0-9]+ # standalone class name
1863
- |
1864
- '.$this->id_class_attr_nocatch_re.' # extra attributes
1865
- )?
1866
- [ ]*
1867
- (?= \n )
1868
- ' : '' ). ' # End (if not is span).
1869
- |
1870
- # Code span marker
1871
- # Note, this regex needs to go after backtick fenced
1872
- # code blocks but it should also be kept outside of the
1873
- # "if not in span" condition adding backticks to the parser
1874
- `+
1875
- )
1876
- }xs';
1877
-
1878
-
1879
- $depth = 0; # Current depth inside the tag tree.
1880
- $parsed = ""; # Parsed text that will be returned.
1881
-
1882
- #
1883
- # Loop through every tag until we find the closing tag of the parent
1884
- # or loop until reaching the end of text if no parent tag specified.
1885
- #
1886
- do {
1887
- #
1888
- # Split the text using the first $tag_match pattern found.
1889
- # Text before pattern will be first in the array, text after
1890
- # pattern will be at the end, and between will be any catches made
1891
- # by the pattern.
1892
- #
1893
- $parts = preg_split($block_tag_re, $text, 2,
1894
- PREG_SPLIT_DELIM_CAPTURE);
1895
-
1896
- # If in Markdown span mode, add a empty-string span-level hash
1897
- # after each newline to prevent triggering any block element.
1898
- if ($span) {
1899
- $void = $this->hashPart("", ':');
1900
- $newline = "$void\n";
1901
- $parts[0] = $void . str_replace("\n", $newline, $parts[0]) . $void;
1902
- }
1903
-
1904
- $parsed .= $parts[0]; # Text before current tag.
1905
-
1906
- # If end of $text has been reached. Stop loop.
1907
- if (count($parts) < 3) {
1908
- $text = "";
1909
- break;
1910
- }
1911
-
1912
- $tag = $parts[1]; # Tag to handle.
1913
- $text = $parts[2]; # Remaining text after current tag.
1914
- $tag_re = preg_quote($tag); # For use in a regular expression.
1915
-
1916
- #
1917
- # Check for: Fenced code block marker.
1918
- # Note: need to recheck the whole tag to disambiguate backtick
1919
- # fences from code spans
1920
- #
1921
- if (preg_match('{^\n?([ ]{0,'.($indent+3).'})(~{3,}|`{3,})[ ]*(?:\.?[-_:a-zA-Z0-9]+|'.$this->id_class_attr_nocatch_re.')?[ ]*\n?$}', $tag, $capture)) {
1922
- # Fenced code block marker: find matching end marker.
1923
- $fence_indent = strlen($capture[1]); # use captured indent in re
1924
- $fence_re = $capture[2]; # use captured fence in re
1925
- if (preg_match('{^(?>.*\n)*?[ ]{'.($fence_indent).'}'.$fence_re.'[ ]*(?:\n|$)}', $text,
1926
- $matches))
1927
- {
1928
- # End marker found: pass text unchanged until marker.
1929
- $parsed .= $tag . $matches[0];
1930
- $text = substr($text, strlen($matches[0]));
1931
- }
1932
- else {
1933
- # No end marker: just skip it.
1934
- $parsed .= $tag;
1935
- }
1936
- }
1937
- #
1938
- # Check for: Indented code block.
1939
- #
1940
- else if ($tag{0} == "\n" || $tag{0} == " ") {
1941
- # Indented code block: pass it unchanged, will be handled
1942
- # later.
1943
- $parsed .= $tag;
1944
- }
1945
- #
1946
- # Check for: Code span marker
1947
- # Note: need to check this after backtick fenced code blocks
1948
- #
1949
- else if ($tag{0} == "`") {
1950
- # Find corresponding end marker.
1951
- $tag_re = preg_quote($tag);
1952
- if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}',
1953
- $text, $matches))
1954
- {
1955
- # End marker found: pass text unchanged until marker.
1956
- $parsed .= $tag . $matches[0];
1957
- $text = substr($text, strlen($matches[0]));
1958
- }
1959
- else {
1960
- # Unmatched marker: just skip it.
1961
- $parsed .= $tag;
1962
- }
1963
- }
1964
- #
1965
- # Check for: Opening Block level tag or
1966
- # Opening Context Block tag (like ins and del)
1967
- # used as a block tag (tag is alone on it's line).
1968
- #
1969
- else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) ||
1970
- ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) &&
1971
- preg_match($newline_before_re, $parsed) &&
1972
- preg_match($newline_after_re, $text) )
1973
- )
1974
- {
1975
- # Need to parse tag and following text using the HTML parser.
1976
- list($block_text, $text) =
1977
- $this->_hashHTMLBlocks_inHTML($tag . $text, "hashBlock", true);
1978
-
1979
- # Make sure it stays outside of any paragraph by adding newlines.
1980
- $parsed .= "\n\n$block_text\n\n";
1981
- }
1982
- #
1983
- # Check for: Clean tag (like script, math)
1984
- # HTML Comments, processing instructions.
1985
- #
1986
- else if (preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||
1987
- $tag{1} == '!' || $tag{1} == '?')
1988
- {
1989
- # Need to parse tag and following text using the HTML parser.
1990
- # (don't check for markdown attribute)
1991
- list($block_text, $text) =
1992
- $this->_hashHTMLBlocks_inHTML($tag . $text, "hashClean", false);
1993
-
1994
- $parsed .= $block_text;
1995
- }
1996
- #
1997
- # Check for: Tag with same name as enclosing tag.
1998
- #
1999
- else if ($enclosing_tag_re !== '' &&
2000
- # Same name as enclosing tag.
2001
- preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))
2002
- {
2003
- #
2004
- # Increase/decrease nested tag count.
2005
- #
2006
- if ($tag{1} == '/') $depth--;
2007
- else if ($tag{strlen($tag)-2} != '/') $depth++;
2008
-
2009
- if ($depth < 0) {
2010
- #
2011
- # Going out of parent element. Clean up and break so we
2012
- # return to the calling function.
2013
- #
2014
- $text = $tag . $text;
2015
- break;
2016
- }
2017
-
2018
- $parsed .= $tag;
2019
- }
2020
- else {
2021
- $parsed .= $tag;
2022
- }
2023
- } while ($depth >= 0);
2024
-
2025
- return array($parsed, $text);
2026
- }
2027
- protected function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) {
2028
- #
2029
- # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags.
2030
- #
2031
- # * Calls $hash_method to convert any blocks.
2032
- # * Stops when the first opening tag closes.
2033
- # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed.
2034
- # (it is not inside clean tags)
2035
- #
2036
- # Returns an array of that form: ( processed text , remaining text )
2037
- #
2038
- if ($text === '') return array('', '');
2039
-
2040
- # Regex to match `markdown` attribute inside of a tag.
2041
- $markdown_attr_re = '
2042
- {
2043
- \s* # Eat whitespace before the `markdown` attribute
2044
- markdown
2045
- \s*=\s*
2046
- (?>
2047
- (["\']) # $1: quote delimiter
2048
- (.*?) # $2: attribute value
2049
- \1 # matching delimiter
2050
- |
2051
- ([^\s>]*) # $3: unquoted attribute value
2052
- )
2053
- () # $4: make $3 always defined (avoid warnings)
2054
- }xs';
2055
-
2056
- # Regex to match any tag.
2057
- $tag_re = '{
2058
- ( # $2: Capture whole tag.
2059
- </? # Any opening or closing tag.
2060
- [\w:$]+ # Tag name.
2061
- (?:
2062
- (?=[\s"\'/a-zA-Z0-9]) # Allowed characters after tag name.
2063
- (?>
2064
- ".*?" | # Double quotes (can contain `>`)
2065
- \'.*?\' | # Single quotes (can contain `>`)
2066
- .+? # Anything but quotes and `>`.
2067
- )*?
2068
- )?
2069
- > # End of tag.
2070
- |
2071
- <!-- .*? --> # HTML Comment
2072
- |
2073
- <\?.*?\?> | <%.*?%> # Processing instruction
2074
- |
2075
- <!\[CDATA\[.*?\]\]> # CData Block
2076
- )
2077
- }xs';
2078
-
2079
- $original_text = $text; # Save original text in case of faliure.
2080
-
2081
- $depth = 0; # Current depth inside the tag tree.
2082
- $block_text = ""; # Temporary text holder for current text.
2083
- $parsed = ""; # Parsed text that will be returned.
2084
-
2085
- #
2086
- # Get the name of the starting tag.
2087
- # (This pattern makes $base_tag_name_re safe without quoting.)
2088
- #
2089
- if (preg_match('/^<([\w:$]*)\b/', $text, $matches))
2090
- $base_tag_name_re = $matches[1];
2091
-
2092
- #
2093
- # Loop through every tag until we find the corresponding closing tag.
2094
- #
2095
- do {
2096
- #
2097
- # Split the text using the first $tag_match pattern found.
2098
- # Text before pattern will be first in the array, text after
2099
- # pattern will be at the end, and between will be any catches made
2100
- # by the pattern.
2101
- #
2102
- $parts = preg_split($tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
2103
-
2104
- if (count($parts) < 3) {
2105
- #
2106
- # End of $text reached with unbalenced tag(s).
2107
- # In that case, we return original text unchanged and pass the
2108
- # first character as filtered to prevent an infinite loop in the
2109
- # parent function.
2110
- #
2111
- return array($original_text{0}, substr($original_text, 1));
2112
- }
2113
-
2114
- $block_text .= $parts[0]; # Text before current tag.
2115
- $tag = $parts[1]; # Tag to handle.
2116
- $text = $parts[2]; # Remaining text after current tag.
2117
-
2118
- #
2119
- # Check for: Auto-close tag (like <hr/>)
2120
- # Comments and Processing Instructions.
2121
- #
2122
- if (preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||
2123
- $tag{1} == '!' || $tag{1} == '?')
2124
- {
2125
- # Just add the tag to the block as if it was text.
2126
- $block_text .= $tag;
2127
- }
2128
- else {
2129
- #
2130
- # Increase/decrease nested tag count. Only do so if
2131
- # the tag's name match base tag's.
2132
- #
2133
- if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) {
2134
- if ($tag{1} == '/') $depth--;
2135
- else if ($tag{strlen($tag)-2} != '/') $depth++;
2136
- }
2137
-
2138
- #
2139
- # Check for `markdown="1"` attribute and handle it.
2140
- #
2141
- if ($md_attr &&
2142
- preg_match($markdown_attr_re, $tag, $attr_m) &&
2143
- preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))
2144
- {
2145
- # Remove `markdown` attribute from opening tag.
2146
- $tag = preg_replace($markdown_attr_re, '', $tag);
2147
-
2148
- # Check if text inside this tag must be parsed in span mode.
2149
- $this->mode = $attr_m[2] . $attr_m[3];
2150
- $span_mode = $this->mode == 'span' || $this->mode != 'block' &&
2151
- preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);
2152
-
2153
- # Calculate indent before tag.
2154
- if (preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {
2155
- $strlen = $this->utf8_strlen;
2156
- $indent = $strlen($matches[1], 'UTF-8');
2157
- } else {
2158
- $indent = 0;
2159
- }
2160
-
2161
- # End preceding block with this tag.
2162
- $block_text .= $tag;
2163
- $parsed .= $this->$hash_method($block_text);
2164
-
2165
- # Get enclosing tag name for the ParseMarkdown function.
2166
- # (This pattern makes $tag_name_re safe without quoting.)
2167
- preg_match('/^<([\w:$]*)\b/', $tag, $matches);
2168
- $tag_name_re = $matches[1];
2169
-
2170
- # Parse the content using the HTML-in-Markdown parser.
2171
- list ($block_text, $text)
2172
- = $this->_hashHTMLBlocks_inMarkdown($text, $indent,
2173
- $tag_name_re, $span_mode);
2174
-
2175
- # Outdent markdown text.
2176
- if ($indent > 0) {
2177
- $block_text = preg_replace("/^[ ]{1,$indent}/m", "",
2178
- $block_text);
2179
- }
2180
-
2181
- # Append tag content to parsed text.
2182
- if (!$span_mode) $parsed .= "\n\n$block_text\n\n";
2183
- else $parsed .= "$block_text";
2184
-
2185
- # Start over with a new block.
2186
- $block_text = "";
2187
- }
2188
- else $block_text .= $tag;
2189
- }
2190
-
2191
- } while ($depth > 0);
2192
-
2193
- #
2194
- # Hash last block text that wasn't processed inside the loop.
2195
- #
2196
- $parsed .= $this->$hash_method($block_text);
2197
-
2198
- return array($parsed, $text);
2199
- }
2200
-
2201
-
2202
- protected function hashClean($text) {
2203
- #
2204
- # Called whenever a tag must be hashed when a function inserts a "clean" tag
2205
- # in $text, it passes through this function and is automaticaly escaped,
2206
- # blocking invalid nested overlap.
2207
- #
2208
- return $this->hashPart($text, 'C');
2209
- }
2210
-
2211
-
2212
- protected function doAnchors($text) {
2213
- #
2214
- # Turn Markdown link shortcuts into XHTML <a> tags.
2215
- #
2216
- if ($this->in_anchor) return $text;
2217
- $this->in_anchor = true;
2218
-
2219
- #
2220
- # First, handle reference-style links: [link text] [id]
2221
- #
2222
- $text = preg_replace_callback('{
2223
- ( # wrap whole match in $1
2224
- \[
2225
- ('.$this->nested_brackets_re.') # link text = $2
2226
- \]
2227
-
2228
- [ ]? # one optional space
2229
- (?:\n[ ]*)? # one optional newline followed by spaces
2230
-
2231
- \[
2232
- (.*?) # id = $3
2233
- \]
2234
- )
2235
- }xs',
2236
- array($this, '_doAnchors_reference_callback'), $text);
2237
-
2238
- #
2239
- # Next, inline-style links: [link text](url "optional title")
2240
- #
2241
- $text = preg_replace_callback('{
2242
- ( # wrap whole match in $1
2243
- \[
2244
- ('.$this->nested_brackets_re.') # link text = $2
2245
- \]
2246
- \( # literal paren
2247
- [ \n]*
2248
- (?:
2249
- <(.+?)> # href = $3
2250
- |
2251
- ('.$this->nested_url_parenthesis_re.') # href = $4
2252
- )
2253
- [ \n]*
2254
- ( # $5
2255
- ([\'"]) # quote char = $6
2256
- (.*?) # Title = $7
2257
- \6 # matching quote
2258
- [ \n]* # ignore any spaces/tabs between closing quote and )
2259
- )? # title is optional
2260
- \)
2261
- (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes
2262
- )
2263
- }xs',
2264
- array($this, '_doAnchors_inline_callback'), $text);
2265
-
2266
- #
2267
- # Last, handle reference-style shortcuts: [link text]
2268
- # These must come last in case you've also got [link text][1]
2269
- # or [link text](/foo)
2270
- #
2271
- $text = preg_replace_callback('{
2272
- ( # wrap whole match in $1
2273
- \[
2274
- ([^\[\]]+) # link text = $2; can\'t contain [ or ]
2275
- \]
2276
- )
2277
- }xs',
2278
- array($this, '_doAnchors_reference_callback'), $text);
2279
-
2280
- $this->in_anchor = false;
2281
- return $text;
2282
- }
2283
- protected function _doAnchors_reference_callback($matches) {
2284
- $whole_match = $matches[1];
2285
- $link_text = $matches[2];
2286
- $link_id =& $matches[3];
2287
-
2288
- if ($link_id == "") {
2289
- # for shortcut links like [this][] or [this].
2290
- $link_id = $link_text;
2291
- }
2292
-
2293
- # lower-case and turn embedded newlines into spaces
2294
- $link_id = strtolower($link_id);
2295
- $link_id = preg_replace('{[ ]?\n}', ' ', $link_id);
2296
-
2297
- if (isset($this->urls[$link_id])) {
2298
- $url = $this->urls[$link_id];
2299
- $url = $this->encodeAttribute($url);
2300
-
2301
- $result = "<a href=\"$url\"";
2302
- if ( isset( $this->titles[$link_id] ) ) {
2303
- $title = $this->titles[$link_id];
2304
- $title = $this->encodeAttribute($title);
2305
- $result .= " title=\"$title\"";
2306
- }
2307
- if (isset($this->ref_attr[$link_id]))
2308
- $result .= $this->ref_attr[$link_id];
2309
-
2310
- $link_text = $this->runSpanGamut($link_text);
2311
- $result .= ">$link_text</a>";
2312
- $result = $this->hashPart($result);
2313
- }
2314
- else {
2315
- $result = $whole_match;
2316
- }
2317
- return $result;
2318
- }
2319
- protected function _doAnchors_inline_callback($matches) {
2320
- $whole_match = $matches[1];
2321
- $link_text = $this->runSpanGamut($matches[2]);
2322
- $url = $matches[3] == '' ? $matches[4] : $matches[3];
2323
- $title =& $matches[7];
2324
- $attr = $this->doExtraAttributes("a", $dummy =& $matches[8]);
2325
-
2326
- // if the URL was of the form <s p a c e s> it got caught by the HTML
2327
- // tag parser and hashed. Need to reverse the process before using the URL.
2328
- $unhashed = $this->unhash($url);
2329
- if ($unhashed != $url)
2330
- $url = preg_replace('/^<(.*)>$/', '\1', $unhashed);
2331
-
2332
- $url = $this->encodeAttribute($url);
2333
-
2334
- $result = "<a href=\"$url\"";
2335
- if (isset($title)) {
2336
- $title = $this->encodeAttribute($title);
2337
- $result .= " title=\"$title\"";
2338
- }
2339
- $result .= $attr;
2340
-
2341
- $link_text = $this->runSpanGamut($link_text);
2342
- $result .= ">$link_text</a>";
2343
-
2344
- return $this->hashPart($result);
2345
- }
2346
-
2347
-
2348
- protected function doImages($text) {
2349
- #
2350
- # Turn Markdown image shortcuts into <img> tags.
2351
- #
2352
- #
2353
- # First, handle reference-style labeled images: ![alt text][id]
2354
- #
2355
- $text = preg_replace_callback('{
2356
- ( # wrap whole match in $1
2357
- !\[
2358
- ('.$this->nested_brackets_re.') # alt text = $2
2359
- \]
2360
-
2361
- [ ]? # one optional space
2362
- (?:\n[ ]*)? # one optional newline followed by spaces
2363
-
2364
- \[
2365
- (.*?) # id = $3
2366
- \]
2367
-
2368
- )
2369
- }xs',
2370
- array($this, '_doImages_reference_callback'), $text);
2371
-
2372
- #
2373
- # Next, handle inline images: ![alt text](url "optional title")
2374
- # Don't forget: encode * and _
2375
- #
2376
- $text = preg_replace_callback('{
2377
- ( # wrap whole match in $1
2378
- !\[
2379
- ('.$this->nested_brackets_re.') # alt text = $2
2380
- \]
2381
- \s? # One optional whitespace character
2382
- \( # literal paren
2383
- [ \n]*
2384
- (?:
2385
- <(\S*)> # src url = $3
2386
- |
2387
- ('.$this->nested_url_parenthesis_re.') # src url = $4
2388
- )
2389
- [ \n]*
2390
- ( # $5
2391
- ([\'"]) # quote char = $6
2392
- (.*?) # title = $7
2393
- \6 # matching quote
2394
- [ \n]*
2395
- )? # title is optional
2396
- \)
2397
- (?:[ ]? '.$this->id_class_attr_catch_re.' )? # $8 = id/class attributes
2398
- )
2399
- }xs',
2400
- array($this, '_doImages_inline_callback'), $text);
2401
-
2402
- return $text;
2403
- }
2404
- protected function _doImages_reference_callback($matches) {
2405
- $whole_match = $matches[1];
2406
- $alt_text = $matches[2];
2407
- $link_id = strtolower($matches[3]);
2408
-
2409
- if ($link_id == "") {
2410
- $link_id = strtolower($alt_text); # for shortcut links like ![this][].
2411
- }
2412
-
2413
- $alt_text = $this->encodeAttribute($alt_text);
2414
- if (isset($this->urls[$link_id])) {
2415
- $url = $this->encodeAttribute($this->urls[$link_id]);
2416
- $result = "<img src=\"$url\" alt=\"$alt_text\"";
2417
- if (isset($this->titles[$link_id])) {
2418
- $title = $this->titles[$link_id];
2419
- $title = $this->encodeAttribute($title);
2420
- $result .= " title=\"$title\"";
2421
- }
2422
- if (isset($this->ref_attr[$link_id]))
2423
- $result .= $this->ref_attr[$link_id];
2424
- $result .= $this->empty_element_suffix;
2425
- $result = $this->hashPart($result);
2426
- }
2427
- else {
2428
- # If there's no such link ID, leave intact:
2429
- $result = $whole_match;
2430
- }
2431
-
2432
- return $result;
2433
- }
2434
- protected function _doImages_inline_callback($matches) {
2435
- $whole_match = $matches[1];
2436
- $alt_text = $matches[2];
2437
- $url = $matches[3] == '' ? $matches[4] : $matches[3];
2438
- $title =& $matches[7];
2439
- $attr = $this->doExtraAttributes("img", $dummy =& $matches[8]);
2440
-
2441
- $alt_text = $this->encodeAttribute($alt_text);
2442
- $url = $this->encodeAttribute($url);
2443
- $result = "<img src=\"$url\" alt=\"$alt_text\"";
2444
- if (isset($title)) {
2445
- $title = $this->encodeAttribute($title);
2446
- $result .= " title=\"$title\""; # $title already quoted
2447
- }
2448
- $result .= $attr;
2449
- $result .= $this->empty_element_suffix;
2450
-
2451
- return $this->hashPart($result);
2452
- }
2453
-
2454
-
2455
- protected function doHeaders($text) {
2456
- #
2457
- # Redefined to add id and class attribute support.
2458
- #
2459
- # Setext-style headers:
2460
- # Header 1 {#header1}
2461
- # ========
2462
- #
2463
- # Header 2 {#header2 .class1 .class2}
2464
- # --------
2465
- #
2466
- $text = preg_replace_callback(
2467
- '{
2468
- (^.+?) # $1: Header text
2469
- (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes
2470
- [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer
2471
- }mx',
2472
- array($this, '_doHeaders_callback_setext'), $text);
2473
-
2474
- # atx-style headers:
2475
- # # Header 1 {#header1}
2476
- # ## Header 2 {#header2}
2477
- # ## Header 2 with closing hashes ## {#header3.class1.class2}
2478
- # ...
2479
- # ###### Header 6 {.class2}
2480
- #
2481
- $text = preg_replace_callback('{
2482
- ^(\#{1,6}) # $1 = string of #\'s
2483
- [ ]*
2484
- (.+?) # $2 = Header text
2485
- [ ]*
2486
- \#* # optional closing #\'s (not counted)
2487
- (?:[ ]+ '.$this->id_class_attr_catch_re.' )? # $3 = id/class attributes
2488
- [ ]*
2489
- \n+
2490
- }xm',
2491
- array($this, '_doHeaders_callback_atx'), $text);
2492
-
2493
- return $text;
2494
- }
2495
- protected function _doHeaders_callback_setext($matches) {
2496
- if ($matches[3] == '-' && preg_match('{^- }', $matches[1]))
2497
- return $matches[0];
2498
- $level = $matches[3]{0} == '=' ? 1 : 2;
2499
- $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[2]);
2500
- $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>";
2501
- return "\n" . $this->hashBlock($block) . "\n\n";
2502
- }
2503
- protected function _doHeaders_callback_atx($matches) {
2504
- $level = strlen($matches[1]);
2505
- $attr = $this->doExtraAttributes("h$level", $dummy =& $matches[3]);
2506
- $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>";
2507
- return "\n" . $this->hashBlock($block) . "\n\n";
2508
- }
2509
-
2510
-
2511
- protected function doTables($text) {
2512
- #
2513
- # Form HTML tables.
2514
- #
2515
- $less_than_tab = $this->tab_width - 1;
2516
- #
2517
- # Find tables with leading pipe.
2518
- #
2519
- # | Header 1 | Header 2
2520
- # | -------- | --------
2521
- # | Cell 1 | Cell 2
2522
- # | Cell 3 | Cell 4
2523
- #
2524
- $text = preg_replace_callback('
2525
- {
2526
- ^ # Start of a line
2527
- [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2528
- [|] # Optional leading pipe (present)
2529
- (.+) \n # $1: Header row (at least one pipe)
2530
-
2531
- [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2532
- [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline
2533
-
2534
- ( # $3: Cells
2535
- (?>
2536
- [ ]* # Allowed whitespace.
2537
- [|] .* \n # Row content.
2538
- )*
2539
- )
2540
- (?=\n|\Z) # Stop at final double newline.
2541
- }xm',
2542
- array($this, '_doTable_leadingPipe_callback'), $text);
2543
-
2544
- #
2545
- # Find tables without leading pipe.
2546
- #
2547
- # Header 1 | Header 2
2548
- # -------- | --------
2549
- # Cell 1 | Cell 2
2550
- # Cell 3 | Cell 4
2551
- #
2552
- $text = preg_replace_callback('
2553
- {
2554
- ^ # Start of a line
2555
- [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2556
- (\S.*[|].*) \n # $1: Header row (at least one pipe)
2557
-
2558
- [ ]{0,'.$less_than_tab.'} # Allowed whitespace.
2559
- ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline
2560
-
2561
- ( # $3: Cells
2562
- (?>
2563
- .* [|] .* \n # Row content
2564
- )*
2565
- )
2566
- (?=\n|\Z) # Stop at final double newline.
2567
- }xm',
2568
- array($this, '_DoTable_callback'), $text);
2569
-
2570
- return $text;
2571
- }
2572
- protected function _doTable_leadingPipe_callback($matches) {
2573
- $head = $matches[1];
2574
- $underline = $matches[2];
2575
- $content = $matches[3];
2576
-
2577
- # Remove leading pipe for each row.
2578
- $content = preg_replace('/^ *[|]/m', '', $content);
2579
-
2580
- return $this->_doTable_callback(array($matches[0], $head, $underline, $content));
2581
- }
2582
- protected function _doTable_makeAlignAttr($alignname)
2583
- {
2584
- if (empty($this->table_align_class_tmpl))
2585
- return " align=\"$alignname\"";
2586
-
2587
- $classname = str_replace('%%', $alignname, $this->table_align_class_tmpl);
2588
- return " class=\"$classname\"";
2589
- }
2590
- protected function _doTable_callback($matches) {
2591
- $head = $matches[1];
2592
- $underline = $matches[2];
2593
- $content = $matches[3];
2594
-
2595
- # Remove any tailing pipes for each line.
2596
- $head = preg_replace('/[|] *$/m', '', $head);
2597
- $underline = preg_replace('/[|] *$/m', '', $underline);
2598
- $content = preg_replace('/[|] *$/m', '', $content);
2599
-
2600
- # Reading alignement from header underline.
2601
- $separators = preg_split('/ *[|] */', $underline);
2602
- foreach ($separators as $n => $s) {
2603
- if (preg_match('/^ *-+: *$/', $s))
2604
- $attr[$n] = $this->_doTable_makeAlignAttr('right');
2605
- else if (preg_match('/^ *:-+: *$/', $s))
2606
- $attr[$n] = $this->_doTable_makeAlignAttr('center');
2607
- else if (preg_match('/^ *:-+ *$/', $s))
2608
- $attr[$n] = $this->_doTable_makeAlignAttr('left');
2609
- else
2610
- $attr[$n] = '';
2611
- }
2612
-
2613
- # Parsing span elements, including code spans, character escapes,
2614
- # and inline HTML tags, so that pipes inside those gets ignored.
2615
- $head = $this->parseSpan($head);
2616
- $headers = preg_split('/ *[|] */', $head);
2617
- $col_count = count($headers);
2618
- $attr = array_pad($attr, $col_count, '');
2619
-
2620
- # Write column headers.
2621
- $text = "<table>\n";
2622
- $text .= "<thead>\n";
2623
- $text .= "<tr>\n";
2624
- foreach ($headers as $n => $header)
2625
- $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n";
2626
- $text .= "</tr>\n";
2627
- $text .= "</thead>\n";
2628
-
2629
- # Split content by row.
2630
- $rows = explode("\n", trim($content, "\n"));
2631
-
2632
- $text .= "<tbody>\n";
2633
- foreach ($rows as $row) {
2634
- # Parsing span elements, including code spans, character escapes,
2635
- # and inline HTML tags, so that pipes inside those gets ignored.
2636
- $row = $this->parseSpan($row);
2637
-
2638
- # Split row by cell.
2639
- $row_cells = preg_split('/ *[|] */', $row, $col_count);
2640
- $row_cells = array_pad($row_cells, $col_count, '');
2641
-
2642
- $text .= "<tr>\n";
2643
- foreach ($row_cells as $n => $cell)
2644
- $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n";
2645
- $text .= "</tr>\n";
2646
- }
2647
- $text .= "</tbody>\n";
2648
- $text .= "</table>";
2649
-
2650
- return $this->hashBlock($text) . "\n";
2651
- }
2652
-
2653
-
2654
- protected function doDefLists($text) {
2655
- #
2656
- # Form HTML definition lists.
2657
- #
2658
- $less_than_tab = $this->tab_width - 1;
2659
-
2660
- # Re-usable pattern to match any entire dl list:
2661
- $whole_list_re = '(?>
2662
- ( # $1 = whole list
2663
- ( # $2
2664
- [ ]{0,'.$less_than_tab.'}
2665
- ((?>.*\S.*\n)+) # $3 = defined term
2666
- \n?
2667
- [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2668
- )
2669
- (?s:.+?)
2670
- ( # $4
2671
- \z
2672
- |
2673
- \n{2,}
2674
- (?=\S)
2675
- (?! # Negative lookahead for another term
2676
- [ ]{0,'.$less_than_tab.'}
2677
- (?: \S.*\n )+? # defined term
2678
- \n?
2679
- [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2680
- )
2681
- (?! # Negative lookahead for another definition
2682
- [ ]{0,'.$less_than_tab.'}:[ ]+ # colon starting definition
2683
- )
2684
- )
2685
- )
2686
- )'; // mx
2687
-
2688
- $text = preg_replace_callback('{
2689
- (?>\A\n?|(?<=\n\n))
2690
- '.$whole_list_re.'
2691
- }mx',
2692
- array($this, '_doDefLists_callback'), $text);
2693
-
2694
- return $text;
2695
- }
2696
- protected function _doDefLists_callback($matches) {
2697
- # Re-usable patterns to match list item bullets and number markers:
2698
- $list = $matches[1];
2699
-
2700
- # Turn double returns into triple returns, so that we can make a
2701
- # paragraph for the last item in a list, if necessary:
2702
- $result = trim($this->processDefListItems($list));
2703
- $result = "<dl>\n" . $result . "\n</dl>";
2704
- return $this->hashBlock($result) . "\n\n";
2705
- }
2706
-
2707
-
2708
- protected function processDefListItems($list_str) {
2709
- #
2710
- # Process the contents of a single definition list, splitting it
2711
- # into individual term and definition list items.
2712
- #
2713
- $less_than_tab = $this->tab_width - 1;
2714
-
2715
- # trim trailing blank lines:
2716
- $list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);
2717
-
2718
- # Process definition terms.
2719
- $list_str = preg_replace_callback('{
2720
- (?>\A\n?|\n\n+) # leading line
2721
- ( # definition terms = $1
2722
- [ ]{0,'.$less_than_tab.'} # leading whitespace
2723
- (?!\:[ ]|[ ]) # negative lookahead for a definition
2724
- # mark (colon) or more whitespace.
2725
- (?> \S.* \n)+? # actual term (not whitespace).
2726
- )
2727
- (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed
2728
- # with a definition mark.
2729
- }xm',
2730
- array($this, '_processDefListItems_callback_dt'), $list_str);
2731
-
2732
- # Process actual definitions.
2733
- $list_str = preg_replace_callback('{
2734
- \n(\n+)? # leading line = $1
2735
- ( # marker space = $2
2736
- [ ]{0,'.$less_than_tab.'} # whitespace before colon
2737
- \:[ ]+ # definition mark (colon)
2738
- )
2739
- ((?s:.+?)) # definition text = $3
2740
- (?= \n+ # stop at next definition mark,
2741
- (?: # next term or end of text
2742
- [ ]{0,'.$less_than_tab.'} \:[ ] |
2743
- <dt> | \z
2744
- )
2745
- )
2746
- }xm',
2747
- array($this, '_processDefListItems_callback_dd'), $list_str);
2748
-
2749
- return $list_str;
2750
- }
2751
- protected function _processDefListItems_callback_dt($matches) {
2752
- $terms = explode("\n", trim($matches[1]));
2753
- $text = '';
2754
- foreach ($terms as $term) {
2755
- $term = $this->runSpanGamut(trim($term));
2756
- $text .= "\n<dt>" . $term . "</dt>";
2757
- }
2758
- return $text . "\n";
2759
- }
2760
- protected function _processDefListItems_callback_dd($matches) {
2761
- $leading_line = $matches[1];
2762
- $marker_space = $matches[2];
2763
- $def = $matches[3];
2764
-
2765
- if ($leading_line || preg_match('/\n{2,}/', $def)) {
2766
- # Replace marker with the appropriate whitespace indentation
2767
- $def = str_repeat(' ', strlen($marker_space)) . $def;
2768
- $def = $this->runBlockGamut($this->outdent($def . "\n\n"));
2769
- $def = "\n". $def ."\n";
2770
- }
2771
- else {
2772
- $def = rtrim($def);
2773
- $def = $this->runSpanGamut($this->outdent($def));
2774
- }
2775
-
2776
- return "\n<dd>" . $def . "</dd>\n";
2777
- }
2778
-
2779
-
2780
- protected function doFencedCodeBlocks($text) {
2781
- #
2782
- # Adding the fenced code block syntax to regular Markdown:
2783
- #
2784
- # ~~~
2785
- # Code block
2786
- # ~~~
2787
- #
2788
- $less_than_tab = $this->tab_width;
2789
-
2790
- $text = preg_replace_callback('{
2791
- (?:\n|\A)
2792
- # 1: Opening marker
2793
- (
2794
- (?:~{3,}|`{3,}) # 3 or more tildes/backticks.
2795
- )
2796
- [ ]*
2797
- (?:
2798
- \.?([-_:a-zA-Z0-9]+) # 2: standalone class name
2799
- |
2800
- '.$this->id_class_attr_catch_re.' # 3: Extra attributes
2801
- )?
2802
- [ ]* \n # Whitespace and newline following marker.
2803
-
2804
- # 4: Content
2805
- (
2806
- (?>
2807
- (?!\1 [ ]* \n) # Not a closing marker.
2808
- .*\n+
2809
- )+
2810
- )
2811
-
2812
- # Closing marker.
2813
- \1 [ ]* (?= \n )
2814
- }xm',
2815
- array($this, '_doFencedCodeBlocks_callback'), $text);
2816
-
2817
- return $text;
2818
- }
2819
- protected function _doFencedCodeBlocks_callback($matches) {
2820
- $classname =& $matches[2];
2821
- $attrs =& $matches[3];
2822
- $codeblock = $matches[4];
2823
- $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
2824
- $codeblock = preg_replace_callback('/^\n+/',
2825
- array($this, '_doFencedCodeBlocks_newlines'), $codeblock);
2826
-
2827
- if ($classname != "") {
2828
- if ($classname{0} == '.')
2829
- $classname = substr($classname, 1);
2830
- $attr_str = ' class="'.$this->code_class_prefix.$classname.'"';
2831
- } else {
2832
- $attr_str = $this->doExtraAttributes($this->code_attr_on_pre ? "pre" : "code", $attrs);
2833
- }
2834
- $pre_attr_str = $this->code_attr_on_pre ? $attr_str : '';
2835
- $code_attr_str = $this->code_attr_on_pre ? '' : $attr_str;
2836
- $codeblock = "<pre$pre_attr_str><code$code_attr_str>$codeblock</code></pre>";
2837
-
2838
- return "\n\n".$this->hashBlock($codeblock)."\n\n";
2839
- }
2840
- protected function _doFencedCodeBlocks_newlines($matches) {
2841
- return str_repeat("<br$this->empty_element_suffix",
2842
- strlen($matches[0]));
2843
- }
2844
-
2845
-
2846
- #
2847
- # Redefining emphasis markers so that emphasis by underscore does not
2848
- # work in the middle of a word.
2849
- #
2850
- protected $em_relist = array(
2851
- '' => '(?:(?<!\*)\*(?!\*)|(?<![a-zA-Z0-9_])_(?!_))(?![\.,:;]?\s)',
2852
- '*' => '(?<![\s*])\*(?!\*)',
2853
- '_' => '(?<![\s_])_(?![a-zA-Z0-9_])',
2854
- );
2855
- protected $strong_relist = array(
2856
- '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?![\.,:;]?\s)',
2857
- '**' => '(?<![\s*])\*\*(?!\*)',
2858
- '__' => '(?<![\s_])__(?![a-zA-Z0-9_])',
2859
- );
2860
- protected $em_strong_relist = array(
2861
- '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?![\.,:;]?\s)',
2862
- '***' => '(?<![\s*])\*\*\*(?!\*)',
2863
- '___' => '(?<![\s_])___(?![a-zA-Z0-9_])',
2864
- );
2865
-
2866
-
2867
- protected function formParagraphs($text) {
2868
- #
2869
- # Params:
2870
- # $text - string to process with html <p> tags
2871
- #
2872
- # Strip leading and trailing lines:
2873
- $text = preg_replace('/\A\n+|\n+\z/', '', $text);
2874
-
2875
- $grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);
2876
-
2877
- #
2878
- # Wrap <p> tags and unhashify HTML blocks
2879
- #
2880
- foreach ($grafs as $key => $value) {
2881
- $value = trim($this->runSpanGamut($value));
2882
-
2883
- # Check if this should be enclosed in a paragraph.
2884
- # Clean tag hashes & block tag hashes are left alone.
2885
- $is_p = !preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);
2886
-
2887
- if ($is_p) {
2888
- $value = "<p>$value</p>";
2889
- }
2890
- $grafs[$key] = $value;
2891
- }
2892
-
2893
- # Join grafs in one text, then unhash HTML tags.
2894
- $text = implode("\n\n", $grafs);
2895
-
2896
- # Finish by removing any tag hashes still present in $text.
2897
- $text = $this->unhash($text);
2898
-
2899
- return $text;
2900
- }
2901
-
2902
-
2903
- ### Footnotes
2904
-
2905
- protected function stripFootnotes($text) {
2906
- #
2907
- # Strips link definitions from text, stores the URLs and titles in
2908
- # hash references.
2909
- #
2910
- $less_than_tab = $this->tab_width - 1;
2911
-
2912
- # Link defs are in the form: [^id]: url "optional title"
2913
- $text = preg_replace_callback('{
2914
- ^[ ]{0,'.$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $1
2915
- [ ]*
2916
- \n? # maybe *one* newline
2917
- ( # text = $2 (no blank lines allowed)
2918
- (?:
2919
- .+ # actual text
2920
- |
2921
- \n # newlines but
2922
- (?!\[.+?\][ ]?:\s)# negative lookahead for footnote or link definition marker.
2923
- (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed
2924
- # by non-indented content
2925
- )*
2926
- )
2927
- }xm',
2928
- array($this, '_stripFootnotes_callback'),
2929
- $text);
2930
- return $text;
2931
- }
2932
- protected function _stripFootnotes_callback($matches) {
2933
- $note_id = $this->fn_id_prefix . $matches[1];
2934
- $this->footnotes[$note_id] = $this->outdent($matches[2]);
2935
- return ''; # String that will replace the block
2936
- }
2937
-
2938
-
2939
- protected function doFootnotes($text) {
2940
- #
2941
- # Replace footnote references in $text [^id] with a special text-token
2942
- # which will be replaced by the actual footnote marker in appendFootnotes.
2943
- #
2944
- if (!$this->in_anchor) {
2945
- $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);
2946
- }
2947
- return $text;
2948
- }
2949
-
2950
-
2951
- protected function appendFootnotes($text) {
2952
- #
2953
- # Append footnote list to text.
2954
- #
2955
- $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2956
- array($this, '_appendFootnotes_callback'), $text);
2957
-
2958
- if (!empty($this->footnotes_ordered)) {
2959
- $text .= "\n\n";
2960
- $text .= "<div class=\"footnotes\">\n";
2961
- $text .= "<hr". $this->empty_element_suffix ."\n";
2962
- $text .= "<ol>\n\n";
2963
-
2964
- $attr = "";
2965
- if ($this->fn_backlink_class != "") {
2966
- $class = $this->fn_backlink_class;
2967
- $class = $this->encodeAttribute($class);
2968
- $attr .= " class=\"$class\"";
2969
- }
2970
- if ($this->fn_backlink_title != "") {
2971
- $title = $this->fn_backlink_title;
2972
- $title = $this->encodeAttribute($title);
2973
- $attr .= " title=\"$title\"";
2974
- }
2975
- $num = 0;
2976
-
2977
- while (!empty($this->footnotes_ordered)) {
2978
- $footnote = reset($this->footnotes_ordered);
2979
- $note_id = key($this->footnotes_ordered);
2980
- unset($this->footnotes_ordered[$note_id]);
2981
- $ref_count = $this->footnotes_ref_count[$note_id];
2982
- unset($this->footnotes_ref_count[$note_id]);
2983
- unset($this->footnotes[$note_id]);
2984
-
2985
- $footnote .= "\n"; # Need to append newline before parsing.
2986
- $footnote = $this->runBlockGamut("$footnote\n");
2987
- $footnote = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}',
2988
- array($this, '_appendFootnotes_callback'), $footnote);
2989
-
2990
- $attr = str_replace("%%", ++$num, $attr);
2991
- $note_id = $this->encodeAttribute($note_id);
2992
-
2993
- # Prepare backlink, multiple backlinks if multiple references
2994
- $backlink = "<a href=\"#fnref:$note_id\"$attr>&#8617;</a>";
2995
- for ($ref_num = 2; $ref_num <= $ref_count; ++$ref_num) {
2996
- $backlink .= " <a href=\"#fnref$ref_num:$note_id\"$attr>&#8617;</a>";
2997
- }
2998
- # Add backlink to last paragraph; create new paragraph if needed.
2999
- if (preg_match('{</p>$}', $footnote)) {
3000
- $footnote = substr($footnote, 0, -4) . "&#160;$backlink</p>";
3001
- } else {
3002
- $footnote .= "\n\n<p>$backlink</p>";
3003
- }
3004
-
3005
- $text .= "<li id=\"fn:$note_id\">\n";
3006
- $text .= $footnote . "\n";
3007
- $text .= "</li>\n\n";
3008
- }
3009
-
3010
- $text .= "</ol>\n";
3011
- $text .= "</div>";
3012
- }
3013
- return $text;
3014
- }
3015
- protected function _appendFootnotes_callback($matches) {
3016
- $node_id = $this->fn_id_prefix . $matches[1];
3017
-
3018
- # Create footnote marker only if it has a corresponding footnote *and*
3019
- # the footnote hasn't been used by another marker.
3020
- if (isset($this->footnotes[$node_id])) {
3021
- $num =& $this->footnotes_numbers[$node_id];
3022
- if (!isset($num)) {
3023
- # Transfer footnote content to the ordered list and give it its
3024
- # number
3025
- $this->footnotes_ordered[$node_id] = $this->footnotes[$node_id];
3026
- $this->footnotes_ref_count[$node_id] = 1;
3027
- $num = $this->footnote_counter++;
3028
- $ref_count_mark = '';
3029
- } else {
3030
- $ref_count_mark = $this->footnotes_ref_count[$node_id] += 1;
3031
- }
3032
-
3033
- $attr = "";
3034
- if ($this->fn_link_class != "") {
3035
- $class = $this->fn_link_class;
3036
- $class = $this->encodeAttribute($class);
3037
- $attr .= " class=\"$class\"";
3038
- }
3039
- if ($this->fn_link_title != "") {
3040
- $title = $this->fn_link_title;
3041
- $title = $this->encodeAttribute($title);
3042
- $attr .= " title=\"$title\"";
3043
- }
3044
-
3045
- $attr = str_replace("%%", $num, $attr);
3046
- $node_id = $this->encodeAttribute($node_id);
3047
-
3048
- return
3049
- "<sup id=\"fnref$ref_count_mark:$node_id\">".
3050
- "<a href=\"#fn:$node_id\"$attr>$num</a>".
3051
- "</sup>";
3052
- }
3053
-
3054
- return "[^".$matches[1]."]";
3055
- }
3056
-
3057
-
3058
- ### Abbreviations ###
3059
-
3060
- protected function stripAbbreviations($text) {
3061
- #
3062
- # Strips abbreviations from text, stores titles in hash references.
3063
- #
3064
- $less_than_tab = $this->tab_width - 1;
3065
-
3066
- # Link defs are in the form: [id]*: url "optional title"
3067
- $text = preg_replace_callback('{
3068
- ^[ ]{0,'.$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $1
3069
- (.*) # text = $2 (no blank lines allowed)
3070
- }xm',
3071
- array($this, '_stripAbbreviations_callback'),
3072
- $text);
3073
- return $text;
3074
- }
3075
- protected function _stripAbbreviations_callback($matches) {
3076
- $abbr_word = $matches[1];
3077
- $abbr_desc = $matches[2];
3078
- if ($this->abbr_word_re)
3079
- $this->abbr_word_re .= '|';
3080
- $this->abbr_word_re .= preg_quote($abbr_word);
3081
- $this->abbr_desciptions[$abbr_word] = trim($abbr_desc);
3082
- return ''; # String that will replace the block
3083
- }
3084
-
3085
-
3086
- protected function doAbbreviations($text) {
3087
- #
3088
- # Find defined abbreviations in text and wrap them in <abbr> elements.
3089
- #
3090
- if ($this->abbr_word_re) {
3091
- // cannot use the /x modifier because abbr_word_re may
3092
- // contain significant spaces:
3093
- $text = preg_replace_callback('{'.
3094
- '(?<![\w\x1A])'.
3095
- '(?:'.$this->abbr_word_re.')'.
3096
- '(?![\w\x1A])'.
3097
- '}',
3098
- array($this, '_doAbbreviations_callback'), $text);
3099
- }
3100
- return $text;
3101
- }
3102
- protected function _doAbbreviations_callback($matches) {
3103
- $abbr = $matches[0];
3104
- if (isset($this->abbr_desciptions[$abbr])) {
3105
- $desc = $this->abbr_desciptions[$abbr];
3106
- if (empty($desc)) {
3107
- return $this->hashPart("<abbr>$abbr</abbr>");
3108
- } else {
3109
- $desc = $this->encodeAttribute($desc);
3110
- return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>");
3111
- }
3112
- } else {
3113
- return $matches[0];
3114
- }
3115
- }
3116
-
3117
- }