|
1
|
|
|
<?php |
|
2
|
|
|
/* |
|
3
|
|
|
* This file is part of the trefoil application. |
|
4
|
|
|
* |
|
5
|
|
|
* (c) Miguel Angel Gabriel <[email protected]> |
|
6
|
|
|
* |
|
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
8
|
|
|
* file that was distributed with this source code. |
|
9
|
|
|
*/ |
|
10
|
|
|
namespace Trefoil\Plugins\Optional; |
|
11
|
|
|
|
|
12
|
|
|
use Easybook\Events\BaseEvent; |
|
13
|
|
|
use Easybook\Events\EasybookEvents; |
|
14
|
|
|
use Easybook\Events\ParseEvent; |
|
15
|
|
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface; |
|
16
|
|
|
use Symfony\Component\Yaml\Yaml; |
|
17
|
|
|
use Trefoil\Events\TrefoilEvents; |
|
18
|
|
|
use Trefoil\Plugins\BasePlugin; |
|
19
|
|
|
use Trefoil\Util\Toolkit; |
|
20
|
|
|
|
|
21
|
|
|
/** |
|
22
|
|
|
* Several tweaks to the final HTML of the ebook. |
|
23
|
|
|
* It allows modifying the HTML produced by the Markdown processor or by the |
|
24
|
|
|
* templates to follow certain rules or comply with certain requirements. |
|
25
|
|
|
* |
|
26
|
|
|
* Examples: |
|
27
|
|
|
* - Surround all '<pre>...</pre>' tags with '<div class="box">...</div>' |
|
28
|
|
|
* - Append '<hr>' tag to each '<h2>' tag. |
|
29
|
|
|
* |
|
30
|
|
|
* The changes are read from a 'html-tweaks.yml' file, that could be located: |
|
31
|
|
|
* - In the book /Contents directory |
|
32
|
|
|
* - In the theme <format>/Config directory |
|
33
|
|
|
* - In the theme Common/Config directory |
|
34
|
|
|
* The first one found will be used. |
|
35
|
|
|
* |
|
36
|
|
|
* Expected definition: |
|
37
|
|
|
* |
|
38
|
|
|
* tweaks: |
|
39
|
|
|
* onPreParse: # replacements to be made at onPreParse time |
|
40
|
|
|
* tweak-name-free: # just a name |
|
41
|
|
|
* tag: 'div' # tag name to find |
|
42
|
|
|
* class: 'class-name' # OPTIONAL class name assigned to tag |
|
43
|
|
|
* |
|
44
|
|
|
* # either one of 'insert', 'surround' or 'replace' operations |
|
45
|
|
|
* |
|
46
|
|
|
* insert: # insert HTML inside the tag (surrounding its contents) |
|
47
|
|
|
* open: '<span class="my-class">===</span>' # opening text |
|
48
|
|
|
* close: '<span class="my-class">####</span>' # closing text |
|
49
|
|
|
* |
|
50
|
|
|
* surround: # surround the tag (not only its contents) with HTML code |
|
51
|
|
|
* open: '<div class="other-class">' # opening text |
|
52
|
|
|
* close: '</div>' # closing text |
|
53
|
|
|
* |
|
54
|
|
|
* replace: # replace tag with another one |
|
55
|
|
|
* tag: 'h2' # replacement tag |
|
56
|
|
|
* |
|
57
|
|
|
* onPostParse: # replacements to be made at onPostParse time |
|
58
|
|
|
* another-tweak-name: |
|
59
|
|
|
* tag: 'span' |
|
60
|
|
|
* ---- |
|
61
|
|
|
* |
|
62
|
|
|
* The 'onPreParse' tweaks will be made before the Markdown parser has processed the item |
|
63
|
|
|
* (so they can easyly pick any raw HTML embedded into the Markdown text), while the 'onPostParse' |
|
64
|
|
|
* tweaks will work on the HTML produced by the Markdown processor. |
|
65
|
|
|
*/ |
|
66
|
|
|
class HtmlTweaksPlugin extends BasePlugin implements EventSubscriberInterface |
|
67
|
|
|
{ |
|
68
|
|
|
protected $tweaks = array(); |
|
69
|
|
|
|
|
70
|
1 |
View Code Duplication |
public static function getSubscribedEvents() |
|
|
|
|
|
|
71
|
|
|
{ |
|
72
|
|
|
return array( |
|
73
|
1 |
|
TrefoilEvents::PRE_PUBLISH_AND_READY => 'onPrePublishAndReady', |
|
74
|
1 |
|
EasybookEvents::PRE_PARSE => array('onItemPreParse', -1000), |
|
75
|
1 |
|
EasybookEvents::POST_PARSE => array('onItemPostParse', -1000) |
|
76
|
1 |
|
); |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
1 |
|
public function onPrePublishAndReady(BaseEvent $event) |
|
80
|
|
|
{ |
|
81
|
1 |
|
$this->init($event); |
|
82
|
|
|
|
|
83
|
|
|
// read tweaks defined in file |
|
84
|
1 |
|
$this->readTweaksFile(); |
|
85
|
1 |
|
} |
|
86
|
|
|
|
|
87
|
1 |
View Code Duplication |
public function onItemPreParse(ParseEvent $event) |
|
88
|
|
|
{ |
|
89
|
1 |
|
$this->init($event); |
|
90
|
|
|
|
|
91
|
1 |
|
$content = $event->getItemProperty('original'); |
|
92
|
|
|
|
|
93
|
1 |
|
if (isset($this->tweaks['tweaks']['onPreParse'])) { |
|
94
|
1 |
|
$content = $this->processTweaks($content, $this->tweaks['tweaks']['onPreParse']); |
|
95
|
1 |
|
} |
|
96
|
|
|
|
|
97
|
1 |
|
$event->setItemProperty('original', $content); |
|
98
|
1 |
|
} |
|
99
|
|
|
|
|
100
|
1 |
View Code Duplication |
public function onItemPostParse(ParseEvent $event) |
|
101
|
|
|
{ |
|
102
|
1 |
|
$this->init($event); |
|
103
|
|
|
|
|
104
|
1 |
|
$content = $event->getItemProperty('content'); |
|
105
|
|
|
|
|
106
|
1 |
|
if (isset($this->tweaks['tweaks']['onPostParse'])) { |
|
107
|
1 |
|
$content = $this->processTweaks($content, $this->tweaks['tweaks']['onPostParse']); |
|
108
|
1 |
|
} |
|
109
|
|
|
|
|
110
|
1 |
|
$event->setItemProperty('content', $content); |
|
111
|
1 |
|
} |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* Read the Yml file with the tweaks' options |
|
115
|
|
|
*/ |
|
116
|
1 |
|
protected function readTweaksFile() |
|
117
|
|
|
{ |
|
118
|
1 |
|
$bookDir = $this->app['publishing.dir.book']; |
|
119
|
1 |
|
$themeDir = Toolkit::getCurrentThemeDir($this->app); |
|
120
|
|
|
|
|
121
|
|
|
// first path is the book Contents dir |
|
122
|
1 |
|
$contentsDir = $bookDir . '/Contents'; |
|
123
|
|
|
|
|
124
|
|
|
// second path is the theme "<format>/Config" dir |
|
125
|
1 |
|
$configFormatDir = sprintf('%s/%s/Config', $themeDir, $this->format); |
|
126
|
|
|
|
|
127
|
|
|
// third path is the theme "Common/Config" dir |
|
128
|
1 |
|
$configCommonDir = sprintf('%s/Common/Config', $themeDir); |
|
129
|
|
|
|
|
130
|
|
|
// look for either one |
|
131
|
|
|
$dirs = array( |
|
132
|
1 |
|
$contentsDir, |
|
133
|
1 |
|
$configFormatDir, |
|
134
|
|
|
$configCommonDir |
|
135
|
1 |
|
); |
|
136
|
|
|
|
|
137
|
1 |
|
$file = $this->app->getFirstExistingFile('html-tweaks.yml', $dirs); |
|
138
|
|
|
|
|
139
|
1 |
|
if (!$file) { |
|
140
|
|
|
$this->writeLn('No html-tweaks.yml file found. Looked up directories:', 'error'); |
|
141
|
|
|
foreach ($dirs as $dir) { |
|
142
|
|
|
$this->writeLn('- ' . $dir); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
return; |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
1 |
|
$this->tweaks = Yaml::parse($file); |
|
149
|
1 |
|
} |
|
150
|
|
|
|
|
151
|
|
|
/** |
|
152
|
|
|
* Process all tweaks |
|
153
|
|
|
* |
|
154
|
|
|
* @param string $content |
|
155
|
|
|
* @param array $tweaks |
|
156
|
|
|
* |
|
157
|
|
|
* @return string |
|
158
|
|
|
*/ |
|
159
|
1 |
|
protected function processTweaks($content, array $tweaks) |
|
160
|
|
|
{ |
|
161
|
1 |
|
foreach ($tweaks as $tweakName => $tweak) { |
|
162
|
|
|
|
|
163
|
1 |
|
$tweak['tweak-name'] = $tweakName; |
|
164
|
|
|
|
|
165
|
1 |
|
if (isset($tweak['tag'])) { |
|
166
|
1 |
|
$content = $this->processTweakTag($content, $tweak); |
|
167
|
1 |
|
} elseif (isset($tweak['regex'])) { |
|
168
|
1 |
|
$content = $this->processTweakRegex($content, $tweak); |
|
169
|
1 |
|
} |
|
170
|
1 |
|
} |
|
171
|
|
|
|
|
172
|
1 |
|
return $content; |
|
173
|
|
|
} |
|
174
|
|
|
|
|
175
|
|
|
/** |
|
176
|
|
|
* Process one tweak |
|
177
|
|
|
* |
|
178
|
|
|
* @param string $content |
|
179
|
|
|
* @param array $tweak |
|
180
|
|
|
* |
|
181
|
|
|
* @return string |
|
182
|
|
|
*/ |
|
183
|
1 |
|
protected function processTweakTag($content, $tweak) |
|
184
|
|
|
{ |
|
185
|
1 |
|
$noclose = false; |
|
186
|
|
|
|
|
187
|
1 |
|
if ('hr' == $tweak['tag']) { |
|
188
|
|
|
|
|
189
|
|
|
// <hr> tags are special (no closing tag) |
|
190
|
|
|
$noclose = true; |
|
191
|
|
|
|
|
192
|
|
|
$pattern = '/'; |
|
193
|
|
|
$pattern .= '<' . $tweak['tag'] . ' *(?<attrs>.*) *\/>'; |
|
194
|
|
|
$pattern .= '/Ums'; |
|
195
|
|
|
} else { |
|
196
|
1 |
|
$pattern = '/'; |
|
197
|
1 |
|
$pattern .= '<' . $tweak['tag'] . ' *(?<attrs>.*)>'; |
|
198
|
1 |
|
$pattern .= '(?<inner>.*)'; |
|
199
|
1 |
|
$pattern .= '<\/' . $tweak['tag'] . '>'; |
|
200
|
1 |
|
$pattern .= '/Ums'; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
1 |
|
$content = preg_replace_callback( |
|
204
|
1 |
|
$pattern, |
|
205
|
1 |
|
function ($matches) use ($tweak, $noclose) { |
|
206
|
1 |
|
$found = true; |
|
207
|
|
|
|
|
208
|
|
|
// lookup with or without class? |
|
209
|
1 |
|
if (isset($tweak['class'])) { |
|
210
|
1 |
|
if (preg_match('/class="(?<classes>.*)"/Ums', $matches['attrs'], $matches2)) { |
|
211
|
1 |
|
$classes = explode(' ', $matches2['classes']); |
|
212
|
1 |
|
if (!in_array($tweak['class'], $classes)) { |
|
213
|
1 |
|
$found = false; |
|
214
|
1 |
|
} |
|
215
|
1 |
|
} else { |
|
216
|
|
|
$found = false; |
|
217
|
|
|
} |
|
218
|
1 |
|
} |
|
219
|
|
|
|
|
220
|
1 |
|
$newTag = sprintf( |
|
221
|
1 |
|
'<%s %s>%s</%s>', |
|
222
|
1 |
|
$tweak['tag'], |
|
223
|
1 |
|
$matches['attrs'], |
|
224
|
1 |
|
$matches['inner'], |
|
225
|
1 |
|
$tweak['tag'] |
|
226
|
1 |
|
); |
|
227
|
|
|
|
|
228
|
1 |
|
if ($found) { |
|
229
|
1 |
|
if (isset($tweak['insert'])) { |
|
230
|
1 |
|
$newTag = sprintf( |
|
231
|
1 |
|
'<%s %s>%s%s%s</%s>', |
|
232
|
1 |
|
$tweak['tag'], |
|
233
|
1 |
|
$matches['attrs'], |
|
234
|
1 |
|
str_replace('\n', "\n", $tweak['insert']['open']), |
|
235
|
1 |
|
$matches['inner'], |
|
236
|
1 |
|
str_replace('\n', "\n", $tweak['insert']['close']), |
|
237
|
1 |
|
$tweak['tag'] |
|
238
|
1 |
|
); |
|
239
|
|
|
|
|
240
|
1 |
|
} elseif (isset($tweak['surround'])) { |
|
241
|
1 |
|
$newTag = sprintf( |
|
242
|
1 |
|
'%s<%s %s>%s</%s>%s', |
|
243
|
1 |
|
str_replace('\n', "\n", $tweak['surround']['open']), |
|
244
|
1 |
|
$tweak['tag'], |
|
245
|
1 |
|
$matches['attrs'], |
|
246
|
1 |
|
$matches['inner'], |
|
247
|
1 |
|
$tweak['tag'], |
|
248
|
1 |
|
str_replace('\n', "\n", $tweak['surround']['close']) |
|
249
|
1 |
|
); |
|
250
|
|
|
|
|
251
|
1 |
|
} elseif (isset($tweak['replace'])) { |
|
252
|
1 |
|
if ($noclose) { |
|
253
|
|
|
$newTag = sprintf( |
|
254
|
|
|
'<%s %s />', |
|
255
|
|
|
str_replace('\n', "\n", $tweak['replace']['tag']), |
|
256
|
|
|
$matches['attrs'] |
|
257
|
|
|
); |
|
258
|
|
|
} else { |
|
259
|
1 |
|
$newTag = sprintf( |
|
260
|
1 |
|
'<%s %s>%s</%s>', |
|
261
|
1 |
|
str_replace('\n', "\n", $tweak['replace']['tag']), |
|
262
|
1 |
|
$matches['attrs'], |
|
263
|
1 |
|
$matches['inner'], |
|
264
|
1 |
|
str_replace('\n', "\n", $tweak['replace']['tag']) |
|
265
|
1 |
|
); |
|
266
|
|
|
} |
|
267
|
1 |
|
} |
|
268
|
1 |
|
} |
|
269
|
|
|
|
|
270
|
1 |
|
return $newTag; |
|
271
|
1 |
|
}, |
|
272
|
|
|
$content |
|
273
|
1 |
|
); |
|
274
|
|
|
|
|
275
|
1 |
|
return $content; |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* Process a regular expression tweak |
|
280
|
|
|
* |
|
281
|
|
|
* @param $content |
|
282
|
|
|
* @param $tweak |
|
283
|
|
|
* |
|
284
|
|
|
* @return string |
|
285
|
|
|
*/ |
|
286
|
1 |
|
protected function processTweakRegex($content, $tweak) |
|
287
|
|
|
{ |
|
288
|
1 |
|
if (!isset($tweak['replace'])) { |
|
289
|
|
|
|
|
290
|
|
|
$this->writeLn(sprintf('Tweak "%s": missing "replace" expression.', $tweak['tweak-name']), 'error'); |
|
291
|
|
|
|
|
292
|
|
|
return $content; |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
1 |
|
$content = preg_replace($tweak['regex'], $tweak['replace'], $content); |
|
296
|
|
|
|
|
297
|
1 |
|
return $content; |
|
298
|
|
|
} |
|
299
|
|
|
} |
|
300
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.