├── .gitignore
├── .phpcs.xml.dist
├── composer.json
├── composer.lock
├── phpstan.neon.dist
├── readme.md
└── src
├── Exceptions
└── InvalidHtmlException.php
├── Full.php
├── Quick.php
└── functions.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
--------------------------------------------------------------------------------
/.phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sniffs for WordPress plugins
4 |
5 |
6 | src
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "code-atlantic/chophper",
3 | "description": "HTML text truncation library for PHP",
4 | "version": "1.0.1",
5 | "minimum-stability": "dev",
6 | "require": {
7 | "masterminds/html5": "^2.8.1"
8 | },
9 | "require-dev": {
10 | "code-atlantic/coding-standards": "^1.1.0"
11 | },
12 | "license": "MIT",
13 | "config": {
14 | "allow-plugins": {
15 | "dealerdirect/phpcodesniffer-composer-installer": true
16 | }
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "Chophper\\": "src/"
21 | }
22 | },
23 | "scripts": {
24 | "format": "vendor/bin/phpcbf --standard=.phpcs.xml.dist --report-summary --report-source",
25 | "lint": "vendor/bin/phpcs --standard=.phpcs.xml.dist"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "74ccbe36af039e7a02f69c2bd78ee76f",
8 | "packages": [
9 | {
10 | "name": "masterminds/html5",
11 | "version": "2.8.1",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/Masterminds/html5-php.git",
15 | "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf",
20 | "reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-dom": "*",
25 | "php": ">=5.3.0"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8"
29 | },
30 | "type": "library",
31 | "extra": {
32 | "branch-alias": {
33 | "dev-master": "2.7-dev"
34 | }
35 | },
36 | "autoload": {
37 | "psr-4": {
38 | "Masterminds\\": "src"
39 | }
40 | },
41 | "notification-url": "https://packagist.org/downloads/",
42 | "license": [
43 | "MIT"
44 | ],
45 | "authors": [
46 | {
47 | "name": "Matt Butcher",
48 | "email": "technosophos@gmail.com"
49 | },
50 | {
51 | "name": "Matt Farina",
52 | "email": "matt@mattfarina.com"
53 | },
54 | {
55 | "name": "Asmir Mustafic",
56 | "email": "goetas@gmail.com"
57 | }
58 | ],
59 | "description": "An HTML5 parser and serializer.",
60 | "homepage": "http://masterminds.github.io/html5-php",
61 | "keywords": [
62 | "HTML5",
63 | "dom",
64 | "html",
65 | "parser",
66 | "querypath",
67 | "serializer",
68 | "xml"
69 | ],
70 | "support": {
71 | "issues": "https://github.com/Masterminds/html5-php/issues",
72 | "source": "https://github.com/Masterminds/html5-php/tree/2.8.1"
73 | },
74 | "time": "2023-05-10T11:58:31+00:00"
75 | }
76 | ],
77 | "packages-dev": [
78 | {
79 | "name": "code-atlantic/coding-standards",
80 | "version": "1.1.0",
81 | "source": {
82 | "type": "git",
83 | "url": "https://github.com/code-atlantic/coding-standards.git",
84 | "reference": "953457fb0334f49ddfbf48ae4782bc4d91896ff7"
85 | },
86 | "dist": {
87 | "type": "zip",
88 | "url": "https://api.github.com/repos/code-atlantic/coding-standards/zipball/953457fb0334f49ddfbf48ae4782bc4d91896ff7",
89 | "reference": "953457fb0334f49ddfbf48ae4782bc4d91896ff7",
90 | "shasum": ""
91 | },
92 | "require": {
93 | "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0",
94 | "phpcompatibility/phpcompatibility-wp": "^2.1.4",
95 | "wp-coding-standards/wpcs": "^3.0.0"
96 | },
97 | "type": "phpcodesniffer-standard",
98 | "notification-url": "https://packagist.org/downloads/",
99 | "license": [
100 | "MIT"
101 | ],
102 | "description": "Code Atlantic Coding Standards",
103 | "support": {
104 | "issues": "https://github.com/code-atlantic/coding-standards/issues",
105 | "source": "https://github.com/code-atlantic/coding-standards/tree/v1.1.0"
106 | },
107 | "time": "2023-09-04T06:24:59+00:00"
108 | },
109 | {
110 | "name": "dealerdirect/phpcodesniffer-composer-installer",
111 | "version": "v1.0.0",
112 | "source": {
113 | "type": "git",
114 | "url": "https://github.com/PHPCSStandards/composer-installer.git",
115 | "reference": "4be43904336affa5c2f70744a348312336afd0da"
116 | },
117 | "dist": {
118 | "type": "zip",
119 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da",
120 | "reference": "4be43904336affa5c2f70744a348312336afd0da",
121 | "shasum": ""
122 | },
123 | "require": {
124 | "composer-plugin-api": "^1.0 || ^2.0",
125 | "php": ">=5.4",
126 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0"
127 | },
128 | "require-dev": {
129 | "composer/composer": "*",
130 | "ext-json": "*",
131 | "ext-zip": "*",
132 | "php-parallel-lint/php-parallel-lint": "^1.3.1",
133 | "phpcompatibility/php-compatibility": "^9.0",
134 | "yoast/phpunit-polyfills": "^1.0"
135 | },
136 | "type": "composer-plugin",
137 | "extra": {
138 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin"
139 | },
140 | "autoload": {
141 | "psr-4": {
142 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/"
143 | }
144 | },
145 | "notification-url": "https://packagist.org/downloads/",
146 | "license": [
147 | "MIT"
148 | ],
149 | "authors": [
150 | {
151 | "name": "Franck Nijhof",
152 | "email": "franck.nijhof@dealerdirect.com",
153 | "homepage": "http://www.frenck.nl",
154 | "role": "Developer / IT Manager"
155 | },
156 | {
157 | "name": "Contributors",
158 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors"
159 | }
160 | ],
161 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin",
162 | "homepage": "http://www.dealerdirect.com",
163 | "keywords": [
164 | "PHPCodeSniffer",
165 | "PHP_CodeSniffer",
166 | "code quality",
167 | "codesniffer",
168 | "composer",
169 | "installer",
170 | "phpcbf",
171 | "phpcs",
172 | "plugin",
173 | "qa",
174 | "quality",
175 | "standard",
176 | "standards",
177 | "style guide",
178 | "stylecheck",
179 | "tests"
180 | ],
181 | "support": {
182 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues",
183 | "source": "https://github.com/PHPCSStandards/composer-installer"
184 | },
185 | "time": "2023-01-05T11:28:13+00:00"
186 | },
187 | {
188 | "name": "phpcompatibility/php-compatibility",
189 | "version": "9.3.5",
190 | "source": {
191 | "type": "git",
192 | "url": "https://github.com/PHPCompatibility/PHPCompatibility.git",
193 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243"
194 | },
195 | "dist": {
196 | "type": "zip",
197 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/9fb324479acf6f39452e0655d2429cc0d3914243",
198 | "reference": "9fb324479acf6f39452e0655d2429cc0d3914243",
199 | "shasum": ""
200 | },
201 | "require": {
202 | "php": ">=5.3",
203 | "squizlabs/php_codesniffer": "^2.3 || ^3.0.2"
204 | },
205 | "conflict": {
206 | "squizlabs/php_codesniffer": "2.6.2"
207 | },
208 | "require-dev": {
209 | "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0"
210 | },
211 | "suggest": {
212 | "dealerdirect/phpcodesniffer-composer-installer": "^0.5 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.",
213 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
214 | },
215 | "type": "phpcodesniffer-standard",
216 | "notification-url": "https://packagist.org/downloads/",
217 | "license": [
218 | "LGPL-3.0-or-later"
219 | ],
220 | "authors": [
221 | {
222 | "name": "Wim Godden",
223 | "homepage": "https://github.com/wimg",
224 | "role": "lead"
225 | },
226 | {
227 | "name": "Juliette Reinders Folmer",
228 | "homepage": "https://github.com/jrfnl",
229 | "role": "lead"
230 | },
231 | {
232 | "name": "Contributors",
233 | "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors"
234 | }
235 | ],
236 | "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.",
237 | "homepage": "http://techblog.wimgodden.be/tag/codesniffer/",
238 | "keywords": [
239 | "compatibility",
240 | "phpcs",
241 | "standards"
242 | ],
243 | "support": {
244 | "issues": "https://github.com/PHPCompatibility/PHPCompatibility/issues",
245 | "source": "https://github.com/PHPCompatibility/PHPCompatibility"
246 | },
247 | "time": "2019-12-27T09:44:58+00:00"
248 | },
249 | {
250 | "name": "phpcompatibility/phpcompatibility-paragonie",
251 | "version": "1.3.2",
252 | "source": {
253 | "type": "git",
254 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie.git",
255 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26"
256 | },
257 | "dist": {
258 | "type": "zip",
259 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityParagonie/zipball/bba5a9dfec7fcfbd679cfaf611d86b4d3759da26",
260 | "reference": "bba5a9dfec7fcfbd679cfaf611d86b4d3759da26",
261 | "shasum": ""
262 | },
263 | "require": {
264 | "phpcompatibility/php-compatibility": "^9.0"
265 | },
266 | "require-dev": {
267 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7",
268 | "paragonie/random_compat": "dev-master",
269 | "paragonie/sodium_compat": "dev-master"
270 | },
271 | "suggest": {
272 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
273 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
274 | },
275 | "type": "phpcodesniffer-standard",
276 | "notification-url": "https://packagist.org/downloads/",
277 | "license": [
278 | "LGPL-3.0-or-later"
279 | ],
280 | "authors": [
281 | {
282 | "name": "Wim Godden",
283 | "role": "lead"
284 | },
285 | {
286 | "name": "Juliette Reinders Folmer",
287 | "role": "lead"
288 | }
289 | ],
290 | "description": "A set of rulesets for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by the Paragonie polyfill libraries.",
291 | "homepage": "http://phpcompatibility.com/",
292 | "keywords": [
293 | "compatibility",
294 | "paragonie",
295 | "phpcs",
296 | "polyfill",
297 | "standards",
298 | "static analysis"
299 | ],
300 | "support": {
301 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie/issues",
302 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityParagonie"
303 | },
304 | "time": "2022-10-25T01:46:02+00:00"
305 | },
306 | {
307 | "name": "phpcompatibility/phpcompatibility-wp",
308 | "version": "2.1.4",
309 | "source": {
310 | "type": "git",
311 | "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git",
312 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5"
313 | },
314 | "dist": {
315 | "type": "zip",
316 | "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5",
317 | "reference": "b6c1e3ee1c35de6c41a511d5eb9bd03e447480a5",
318 | "shasum": ""
319 | },
320 | "require": {
321 | "phpcompatibility/php-compatibility": "^9.0",
322 | "phpcompatibility/phpcompatibility-paragonie": "^1.0"
323 | },
324 | "require-dev": {
325 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7"
326 | },
327 | "suggest": {
328 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || This Composer plugin will sort out the PHP_CodeSniffer 'installed_paths' automatically.",
329 | "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues."
330 | },
331 | "type": "phpcodesniffer-standard",
332 | "notification-url": "https://packagist.org/downloads/",
333 | "license": [
334 | "LGPL-3.0-or-later"
335 | ],
336 | "authors": [
337 | {
338 | "name": "Wim Godden",
339 | "role": "lead"
340 | },
341 | {
342 | "name": "Juliette Reinders Folmer",
343 | "role": "lead"
344 | }
345 | ],
346 | "description": "A ruleset for PHP_CodeSniffer to check for PHP cross-version compatibility issues in projects, while accounting for polyfills provided by WordPress.",
347 | "homepage": "http://phpcompatibility.com/",
348 | "keywords": [
349 | "compatibility",
350 | "phpcs",
351 | "standards",
352 | "static analysis",
353 | "wordpress"
354 | ],
355 | "support": {
356 | "issues": "https://github.com/PHPCompatibility/PHPCompatibilityWP/issues",
357 | "source": "https://github.com/PHPCompatibility/PHPCompatibilityWP"
358 | },
359 | "time": "2022-10-24T09:00:36+00:00"
360 | },
361 | {
362 | "name": "phpcsstandards/phpcsextra",
363 | "version": "dev-develop",
364 | "source": {
365 | "type": "git",
366 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git",
367 | "reference": "7ef54bb093e935e7b530049b5696b6f74c33eaa8"
368 | },
369 | "dist": {
370 | "type": "zip",
371 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/7ef54bb093e935e7b530049b5696b6f74c33eaa8",
372 | "reference": "7ef54bb093e935e7b530049b5696b6f74c33eaa8",
373 | "shasum": ""
374 | },
375 | "require": {
376 | "php": ">=5.4",
377 | "phpcsstandards/phpcsutils": "^1.0.8",
378 | "squizlabs/php_codesniffer": "^3.7.1"
379 | },
380 | "require-dev": {
381 | "php-parallel-lint/php-console-highlighter": "^1.0",
382 | "php-parallel-lint/php-parallel-lint": "^1.3.2",
383 | "phpcsstandards/phpcsdevcs": "^1.1.6",
384 | "phpcsstandards/phpcsdevtools": "^1.2.1",
385 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0"
386 | },
387 | "default-branch": true,
388 | "type": "phpcodesniffer-standard",
389 | "extra": {
390 | "branch-alias": {
391 | "dev-stable": "1.x-dev",
392 | "dev-develop": "1.x-dev"
393 | }
394 | },
395 | "notification-url": "https://packagist.org/downloads/",
396 | "license": [
397 | "LGPL-3.0-or-later"
398 | ],
399 | "authors": [
400 | {
401 | "name": "Juliette Reinders Folmer",
402 | "homepage": "https://github.com/jrfnl",
403 | "role": "lead"
404 | },
405 | {
406 | "name": "Contributors",
407 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors"
408 | }
409 | ],
410 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.",
411 | "keywords": [
412 | "PHP_CodeSniffer",
413 | "phpcbf",
414 | "phpcodesniffer-standard",
415 | "phpcs",
416 | "standards",
417 | "static analysis"
418 | ],
419 | "support": {
420 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues",
421 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy",
422 | "source": "https://github.com/PHPCSStandards/PHPCSExtra"
423 | },
424 | "time": "2023-11-23T05:37:34+00:00"
425 | },
426 | {
427 | "name": "phpcsstandards/phpcsutils",
428 | "version": "dev-develop",
429 | "source": {
430 | "type": "git",
431 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git",
432 | "reference": "f76bab4ac55d559a22c24dafe1f35c0b940b5c02"
433 | },
434 | "dist": {
435 | "type": "zip",
436 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/f76bab4ac55d559a22c24dafe1f35c0b940b5c02",
437 | "reference": "f76bab4ac55d559a22c24dafe1f35c0b940b5c02",
438 | "shasum": ""
439 | },
440 | "require": {
441 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0",
442 | "php": ">=5.4",
443 | "squizlabs/php_codesniffer": "^3.7.1 || 4.0.x-dev@dev"
444 | },
445 | "require-dev": {
446 | "ext-filter": "*",
447 | "php-parallel-lint/php-console-highlighter": "^1.0",
448 | "php-parallel-lint/php-parallel-lint": "^1.3.2",
449 | "phpcsstandards/phpcsdevcs": "^1.1.6",
450 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0"
451 | },
452 | "default-branch": true,
453 | "type": "phpcodesniffer-standard",
454 | "extra": {
455 | "branch-alias": {
456 | "dev-stable": "1.x-dev",
457 | "dev-develop": "1.x-dev"
458 | }
459 | },
460 | "autoload": {
461 | "classmap": [
462 | "PHPCSUtils/"
463 | ]
464 | },
465 | "notification-url": "https://packagist.org/downloads/",
466 | "license": [
467 | "LGPL-3.0-or-later"
468 | ],
469 | "authors": [
470 | {
471 | "name": "Juliette Reinders Folmer",
472 | "homepage": "https://github.com/jrfnl",
473 | "role": "lead"
474 | },
475 | {
476 | "name": "Contributors",
477 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors"
478 | }
479 | ],
480 | "description": "A suite of utility functions for use with PHP_CodeSniffer",
481 | "homepage": "https://phpcsutils.com/",
482 | "keywords": [
483 | "PHP_CodeSniffer",
484 | "phpcbf",
485 | "phpcodesniffer-standard",
486 | "phpcs",
487 | "phpcs3",
488 | "standards",
489 | "static analysis",
490 | "tokens",
491 | "utility"
492 | ],
493 | "support": {
494 | "docs": "https://phpcsutils.com/",
495 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues",
496 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy",
497 | "source": "https://github.com/PHPCSStandards/PHPCSUtils"
498 | },
499 | "time": "2023-11-24T21:05:41+00:00"
500 | },
501 | {
502 | "name": "squizlabs/php_codesniffer",
503 | "version": "dev-master",
504 | "source": {
505 | "type": "git",
506 | "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
507 | "reference": "7763e2e1f773cb0615ed8afa133189fc804f583d"
508 | },
509 | "dist": {
510 | "type": "zip",
511 | "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/7763e2e1f773cb0615ed8afa133189fc804f583d",
512 | "reference": "7763e2e1f773cb0615ed8afa133189fc804f583d",
513 | "shasum": ""
514 | },
515 | "require": {
516 | "ext-simplexml": "*",
517 | "ext-tokenizer": "*",
518 | "ext-xmlwriter": "*",
519 | "php": ">=5.4.0"
520 | },
521 | "require-dev": {
522 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
523 | },
524 | "default-branch": true,
525 | "bin": [
526 | "bin/phpcs",
527 | "bin/phpcbf"
528 | ],
529 | "type": "library",
530 | "extra": {
531 | "branch-alias": {
532 | "dev-master": "3.x-dev"
533 | }
534 | },
535 | "notification-url": "https://packagist.org/downloads/",
536 | "license": [
537 | "BSD-3-Clause"
538 | ],
539 | "authors": [
540 | {
541 | "name": "Greg Sherwood",
542 | "role": "lead"
543 | }
544 | ],
545 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
546 | "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
547 | "keywords": [
548 | "phpcs",
549 | "standards",
550 | "static analysis"
551 | ],
552 | "support": {
553 | "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
554 | "source": "https://github.com/squizlabs/PHP_CodeSniffer",
555 | "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
556 | },
557 | "time": "2023-11-02T00:47:31+00:00"
558 | },
559 | {
560 | "name": "wp-coding-standards/wpcs",
561 | "version": "3.0.1",
562 | "source": {
563 | "type": "git",
564 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git",
565 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1"
566 | },
567 | "dist": {
568 | "type": "zip",
569 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
570 | "reference": "b4caf9689f1a0e4a4c632679a44e638c1c67aff1",
571 | "shasum": ""
572 | },
573 | "require": {
574 | "ext-filter": "*",
575 | "ext-libxml": "*",
576 | "ext-tokenizer": "*",
577 | "ext-xmlreader": "*",
578 | "php": ">=5.4",
579 | "phpcsstandards/phpcsextra": "^1.1.0",
580 | "phpcsstandards/phpcsutils": "^1.0.8",
581 | "squizlabs/php_codesniffer": "^3.7.2"
582 | },
583 | "require-dev": {
584 | "php-parallel-lint/php-console-highlighter": "^1.0.0",
585 | "php-parallel-lint/php-parallel-lint": "^1.3.2",
586 | "phpcompatibility/php-compatibility": "^9.0",
587 | "phpcsstandards/phpcsdevtools": "^1.2.0",
588 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
589 | },
590 | "suggest": {
591 | "ext-iconv": "For improved results",
592 | "ext-mbstring": "For improved results"
593 | },
594 | "type": "phpcodesniffer-standard",
595 | "notification-url": "https://packagist.org/downloads/",
596 | "license": [
597 | "MIT"
598 | ],
599 | "authors": [
600 | {
601 | "name": "Contributors",
602 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors"
603 | }
604 | ],
605 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions",
606 | "keywords": [
607 | "phpcs",
608 | "standards",
609 | "static analysis",
610 | "wordpress"
611 | ],
612 | "support": {
613 | "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues",
614 | "source": "https://github.com/WordPress/WordPress-Coding-Standards",
615 | "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki"
616 | },
617 | "funding": [
618 | {
619 | "url": "https://opencollective.com/thewpcc/contribute/wp-php-63406",
620 | "type": "custom"
621 | }
622 | ],
623 | "time": "2023-09-14T07:06:09+00:00"
624 | }
625 | ],
626 | "aliases": [],
627 | "minimum-stability": "dev",
628 | "stability-flags": [],
629 | "prefer-stable": false,
630 | "prefer-lowest": false,
631 | "platform": [],
632 | "platform-dev": [],
633 | "plugin-api-version": "2.6.0"
634 | }
635 |
--------------------------------------------------------------------------------
/phpstan.neon.dist:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 6
3 | paths:
4 | - src/
5 | bootstrapFiles:
6 |
7 | scanDirectories:
8 |
9 | excludePaths:
10 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Chophper - A simple text truncation utility for HTML
2 |
3 | Chophper is a PHP utility for truncating text within HTML to a given length without breaking the HTML tags.
4 |
5 | Support for:
6 |
7 | - Truncate chars, optionally respecting word boundaries
8 | - Truncate words, optionally respecting sentence boundaries
9 | - Truncate sentences, optionally respecting block boundaries
10 | - Truncate blocks (paragraphs, lists, etc.)
11 | - Preserving HTML tags
12 | - Preserving HTML entities
13 |
14 | **Note: This is an alpha version. Use at your own risk, and expect API changes towards simple and more flexible usage before the first stable release.**
15 |
16 | ## Installation
17 |
18 | Install via composer:
19 |
20 | ```bash
21 | composer require code-atlantic/chophper
22 | ```
23 |
24 | ## Usage
25 |
26 | ```php
27 | // Full is built to fully support HTML5 without breaking the HTML structure.
28 | use Chophper\Full as Chophper;
29 |
30 | $options [
31 | // ... see options below.
32 | ];
33 |
34 | Chophper::truncate($html, $length, $options);
35 | ```
36 |
37 | ## Options ( current, very subject to change )
38 |
39 | | Option | Type | Default | Description |
40 | | --- | --- | --- | --- |
41 | | `ellipsis` | string | `…` | The string to append to the truncated text. |
42 | | `truncateBy` | string | `words` | Whether to break the text by chars, words, sentences or blocks |
43 | | `preserveWords` | boolean | `false` | Whether to preserve words when using chars truncation. |
44 |
45 | ## Options (wisthlist)
46 |
47 | | Option | Type | Default | Description |
48 | | --- | --- | --- | --- |
49 | | `wordBreak` | boolean | `false` | Whether to break the text at word boundaries. |
50 | | `preserveTags` | boolean | `false` | Whether to preserve HTML tags. |
51 | | `tagsWhitelist` | array | `[]` | A list of HTML tags to preserve. |
52 | | `tagsBlacklist` | array | `[]` | A list of HTML tags to remove. |
53 | | `tagsIngoreLength` | array | `[]` | A list of HTML tags to not count towards the length. |
54 | | `preserveEntities` | boolean | `false` | Whether to preserve HTML entities. |
55 | | `entitiesWhitelist` | array | `[]` | A list of HTML entities to preserve. |
56 | | `entitiesBlacklist` | array | `[]` | A list of HTML entities to remove. |
57 | | `preserveImages` | boolean | `false` | Whether to preserve images. |
58 |
--------------------------------------------------------------------------------
/src/Exceptions/InvalidHtmlException.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace Chophper\Exceptions;
11 |
12 | /**
13 | * Exception for invalid HTML.
14 | */
15 | class InvalidHtmlException extends \Exception {
16 | }
17 |
--------------------------------------------------------------------------------
/src/Full.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace Chophper;
11 |
12 | require_once __DIR__ . '/functions.php';
13 |
14 | use DOMDocument;
15 | use Masterminds\HTML5;
16 | use Chophper\Exceptions\InvalidHtmlException;
17 |
18 | use function Chophper\ht_strlen;
19 | use function Chophper\ht_substr;
20 | use function Chophper\ht_strtolower;
21 |
22 | /**
23 | * Truncate HTML using full parser.
24 | */
25 | class Full {
26 |
27 | /**
28 | * These tags can be truncated.
29 | *
30 | * @var array
31 | */
32 | public static $ellipsable_tags = [
33 | 'p',
34 | 'ol',
35 | 'ul',
36 | 'li',
37 | 'div',
38 | 'header',
39 | 'article',
40 | 'nav',
41 | 'section',
42 | 'footer',
43 | 'aside',
44 | 'dd',
45 | 'dt',
46 | 'dl',
47 | ];
48 |
49 | /**
50 | * These tags are self-closing.
51 | *
52 | * @var array
53 | */
54 | public static $self_closing_tags = [
55 | 'br',
56 | 'hr',
57 | 'img',
58 | ];
59 |
60 | /**
61 | * Parse options.
62 | *
63 | * @param string|array $opts Options.
64 | *
65 | * @return array
66 | */
67 | public static function parse_options( $opts ) {
68 | $opts = array_merge( [
69 | 'ellipsis' => '…',
70 | 'truncateBy' => 'words', // words, chars, sentences, blocks.
71 | 'preserveWords' => false,
72 | ], $opts );
73 |
74 | return $opts;
75 | }
76 |
77 | /**
78 | * Get the root node of an HTML string.
79 | *
80 | * @param string $html HTML string.
81 | *
82 | * @return \DOMNode|null
83 | *
84 | * @throws InvalidHtmlException If the HTML is invalid.
85 | */
86 | public static function get_root_node( $html ) {
87 | $root_node = null;
88 |
89 | // Parse using HTML5Lib if it's available.
90 | if ( class_exists( 'Masterminds\HTML5' ) ) {
91 | try {
92 | $html5 = new HTML5();
93 | $doc = $html5->loadHTML( $html );
94 | $root_node = $doc->documentElement->lastChild;
95 | } catch ( \Exception $e ) {
96 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
97 | error_log( 'HTML5Lib failed to parse HTML: ' . $e->getMessage() );
98 | }
99 | }
100 |
101 | if ( null === $root_node ) {
102 | // HTML5Lib not available so we'll have to use DOMDocument
103 | // We'll only be able to parse HTML5 if it's valid XML.
104 | $doc = new DOMDocument();
105 | $doc->formatOutput = false;
106 | $doc->preserveWhitespace = true;
107 | // loadHTML will fail with HTML5 tags (article, nav, etc)
108 | // so we need to suppress errors and if it fails to parse we
109 | // retry with the XML parser instead.
110 | $prev_use_errors = libxml_use_internal_errors( true );
111 | if ( $doc->loadHTML( $html ) ) {
112 | $root_node = $doc->documentElement->lastChild->lastChild;
113 | } elseif ( $doc->loadXML( $html ) ) {
114 | $root_node = $doc->documentElement;
115 | } else {
116 | libxml_use_internal_errors( $prev_use_errors );
117 | throw new InvalidHtmlException();
118 | }
119 | libxml_use_internal_errors( $prev_use_errors );
120 | }
121 |
122 | return $root_node;
123 | }
124 |
125 | /**
126 | * Truncate given HTML string to specified length.
127 | * If length_in_chars is false it's trimmed by number
128 | * of words, otherwise by number of characters.
129 | *
130 | * @param string $str string HTML string to truncate.
131 | * @param integer $length Length to truncate to.
132 | * @param string|array $opts Options.
133 | *
134 | * @return string
135 | *
136 | * @throws InvalidHtmlException If the HTML is invalid.
137 | */
138 | public static function truncate( $str, $length, $opts = [] ) {
139 | $opts = static::parse_options( $opts );
140 |
141 | $node = self::get_root_node( '
' . static::utf8_for_xml( $str ) . '
' );
142 |
143 | if ( ! $node ) {
144 | return '';
145 | }
146 |
147 | switch ( $opts['truncateBy'] ) {
148 | default:
149 | case 'words':
150 | case 'chars':
151 | case 'sentences':
152 | // Truncate by traversing the DOM tree, counting words as we go through each nodea recursively and truncating when we reach the desired length.
153 | $results = static::truncate_node( $node, $length, $opts );
154 | break;
155 |
156 | case 'blocks':
157 | $results = static::truncateBy_blocks( $node, $length, $opts );
158 | break;
159 | }
160 |
161 | list( $str ) = $results;
162 |
163 | // Strip off the root node div added before.
164 | $str = ht_substr( ht_substr( $str, 0, -6 ), 5 );
165 |
166 | return $str;
167 | }
168 |
169 | /**
170 | * Truncate given HTML string to specified number of words.
171 | *
172 | * Truncate by traversing the DOM tree, counting words as we go through each
173 | * nodea recursively and truncating when we reach the desired length.
174 | *
175 | * @param \DOMNode $node Node to truncate.
176 | * @param integer $length Length to truncate to.
177 | * @param array $opts Options.
178 | *
179 | * @return array[string,integer,array} [0] Truncated inner contents. [1] Number of words remaining. [2] Options.
180 | */
181 | protected static function truncate_node( $node, $length, $opts ) {
182 | $doc = $node->ownerDocument;
183 |
184 | // If the length is 0, return an empty string.
185 | if ( 0 === $length && ! static::is_ellipsable( $node ) ) {
186 | return [
187 | '',
188 | 0, // TODO Why is this 1 and not 0?
189 | $opts,
190 | ];
191 | }
192 |
193 | // Truncate the nodes inner contents recursively.
194 | list( $inner, $remaining, $opts ) = static::inner_truncate( $node, $length, $opts );
195 |
196 | // If the inner contents are empty, return an empty string.
197 | if ( 0 === ht_strlen( $inner ) ) {
198 | return [
199 | in_array( ht_strtolower( $node->nodeName ), static::$self_closing_tags, true )
200 | // Self-closing tags should be returned as-is.
201 | ? $doc->saveXML( $node )
202 | // Other tags should be returned as an empty string.
203 | : '',
204 | // Return the number of words remaining.
205 | // $length - $remaining, // TODO Review why is this not simply $remaining?
206 | $remaining < 0 ? 0 : $remaining,
207 | $opts,
208 | ];
209 | }
210 |
211 | // Remove all child nodes from the node.
212 | while ( $node->firstChild ) {
213 | $node->removeChild( $node->firstChild );
214 | }
215 |
216 | // Create a new document fragment to hold our truncated content.
217 | $new_node = $doc->createDocumentFragment();
218 |
219 | // Append the inner contents to the fragment.
220 | $new_node->appendXml( $inner );
221 |
222 | // Append the fragment to the node.
223 | $node->appendChild( $new_node );
224 |
225 | // Return the truncated node.
226 | return [
227 | $doc->saveXML( $node ),
228 | // $length - $remaining, // TODO Review why is this not simply $remaining?
229 | $remaining < 0 ? 0 : $remaining,
230 | $opts,
231 | ];
232 | }
233 |
234 | /**
235 | * Truncate the inner contents of a node.
236 | *
237 | * @param \DOMNode $node Node to truncate.
238 | * @param integer $length Length to truncate to.
239 | * @param array $opts Options.
240 | *
241 | * @return array[string,integer,array} [0] Truncated inner contents. [1] Number of words remaining. [2] Options.
242 | */
243 | protected static function inner_truncate( $node, $length, $opts ) {
244 | $inner = '';
245 | $remaining = $length;
246 |
247 | foreach ( $node->childNodes as $child_node ) {
248 | if ( XML_ELEMENT_NODE === $child_node->nodeType ) {
249 | // Truncate nodes recursively.
250 | list( $text, $remaining, $opts ) = static::truncate_node( $child_node, $remaining, $opts );
251 | } elseif ( XML_TEXT_NODE === $child_node->nodeType ) {
252 | // Process the child node, checking if it needs to be truncated, returning the truncated node and the number of words remaining.
253 | list( $text, $remaining, $opts ) = static::truncate_text( $child_node, $remaining, $opts );
254 | } else {
255 | // If the node is not a text or element node, set the text to an empty string and the number of words to 0.
256 | $text = '';
257 | }
258 |
259 | $inner .= $text;
260 |
261 | if ( $remaining <= 0 ) {
262 | if ( static::is_ellipsable( $node ) ) {
263 | $inner = preg_replace( '/(?:[\s\pP]+|(?:&(?:[a-z]+|#[0-9]+);?))*$/u', '', $inner ) . $opts['ellipsis'];
264 | $opts['ellipsis'] = ''; // TODO Find a better way to clear this, maybe based on $remaining.
265 | }
266 | break;
267 | }
268 | }
269 |
270 | return [
271 | $inner,
272 | $remaining < 0 ? 0 : $remaining,
273 | $opts,
274 | ];
275 | }
276 |
277 | /**
278 | * Truncate by root-level block elements like p, ul, ol, etc.
279 | *
280 | * @param \DOMNode $node Node to truncate.
281 | * @param integer $length Length to truncate to.
282 | * @param array $opts Options.
283 | *
284 | * @return array[string,integer,array} [0] Truncated inner contents. [1] Number of words remaining. [2] Options.
285 | */
286 | protected static function truncateBy_blocks( $node, $length, $opts ) {
287 | $doc = $node->ownerDocument;
288 |
289 | $block_tags = [ 'p', 'ul', 'ol', 'div', 'header', 'article', 'nav', 'section', 'footer', 'aside', 'dd', 'dt', 'dl' ];
290 | $remaining = $length;
291 |
292 | $nodes_to_keep = [];
293 |
294 | foreach ( $node->childNodes as $child_node ) {
295 | if ( $remaining <= 0 ) {
296 | break;
297 | }
298 |
299 | if ( XML_ELEMENT_NODE === $child_node->nodeType && in_array( ht_strtolower( $child_node->nodeName ), $block_tags, true ) ) {
300 | // If the node is a block element, add it to the fragment.
301 | $nodes_to_keep[] = $child_node;
302 | --$remaining;
303 | } elseif ( XML_TEXT_NODE === $child_node->nodeType && 0 !== ht_strlen( $child_node->textContent ) ) {
304 | // If the node is a non-empty text node, add it to the fragment.
305 | $nodes_to_keep[] = $child_node;
306 | }
307 | }
308 |
309 | $new_nodes = count( $nodes_to_keep );
310 |
311 | // Remove all child nodes from the node.
312 | while ( $node->firstChild ) {
313 | $node->removeChild( $node->firstChild );
314 | }
315 |
316 | // Loop over the nodes to process.
317 | foreach ( $nodes_to_keep as $i => $child_node ) {
318 | if ( 0 === $remaining && $i === $new_nodes - 1 ) {
319 | $child_node->appendChild( $doc->createTextNode( $opts['ellipsis'] ) );
320 | $opts['ellipsis'] = ''; // TODO Find a better way to clear this, maybe based on $remaining.
321 | }
322 |
323 | // If the node is a block element, add it to the fragment.
324 | if ( XML_ELEMENT_NODE === $child_node->nodeType && in_array( ht_strtolower( $child_node->nodeName ), $block_tags, true ) ) {
325 | $node->appendChild( $child_node );
326 | } elseif ( XML_TEXT_NODE === $child_node->nodeType ) {
327 | // If the node is a non-empty text node, add it to the fragment.
328 | $node->appendChild( $child_node );
329 | }
330 | }
331 |
332 | // Return the truncated node.
333 | return [
334 | $doc->saveXML( $node ),
335 | $remaining < 0 ? 0 : $remaining,
336 | $opts,
337 | ];
338 | }
339 |
340 | /**
341 | * Truncate a text node.
342 | *
343 | * @param \DOMText $node Text node to truncate.
344 | * @param integer $length Length to truncate to.
345 | * @param array $opts Options.
346 | *
347 | * @return array[string,integer,array} [0] Truncated inner contents. [1] Number of words remaining. [2] Options.
348 | */
349 | protected static function truncate_text( $node, $length, $opts ) {
350 | $doc = $node->ownerDocument;
351 | $xhtml = $doc->saveXML( $node );
352 |
353 | switch ( $opts['truncateBy'] ) {
354 | default:
355 | case 'words':
356 | // Split the text into words.
357 | preg_match_all( '/\s*\S+/', $xhtml, $words );
358 |
359 | // Get the words.
360 | $words = $words[0];
361 |
362 | // Count the words and get the number of words remaining after truncation.
363 | $word_count = count( $words );
364 | $remaining = $length - $word_count > 0 ? $length - $word_count : 0;
365 |
366 | // If the number of words is less than or equal to the length, return the text in full.
367 | if ( $length > $word_count ) {
368 | return [
369 | // Return the full text.
370 | $xhtml,
371 | // Return the number of words remaining.
372 | $remaining,
373 | $opts,
374 | ];
375 | }
376 |
377 | return [
378 | // Slice the words array to the desired length and implode it back into a string.
379 | implode( '', array_slice( $words, 0, $length ) ),
380 | // Return the number of words remaining.
381 | $remaining,
382 | $opts,
383 | ];
384 |
385 | case 'chars':
386 | // Split the text into words, excluding whitespace.
387 | preg_match_all( '/\s*\S+/', $xhtml, $words );
388 |
389 | // Get the words.
390 | $words = $words[0];
391 |
392 | // Count the words and get the number of words remaining after truncation.
393 | $char_count = ht_strlen( trim( $xhtml ) ); // Trim to prevent empty text nodes from counting as a character.
394 |
395 | // If the number of chars is less than or equal to the length, return the text in full.
396 | if ( $length > $char_count ) {
397 | return [
398 | // Return the full text.
399 | $xhtml,
400 | // Return the number of chars remaining.
401 | $length - $char_count,
402 | $opts,
403 | ];
404 | }
405 |
406 | if ( count( $words ) > 1 ) {
407 | $content = '';
408 |
409 | $remaining = $length;
410 |
411 | // Loop through the words and add them to the content until we reach the desired length.
412 | foreach ( $words as $word ) {
413 | if ( $remaining <= 0 ) {
414 | break;
415 | }
416 |
417 | // If the length of the content plus the length of the word is greater than the desired length, break the loop.
418 | if ( ht_strlen( $content ) + ht_strlen( $word ) > $length ) {
419 | // If option for keeping words whole is set, break on the last word.
420 | if ( $opts['preserveWords'] ) {
421 | break;
422 | } else {
423 | // Trim the word to the remaining length.
424 | $word = ht_substr( $word, 0, $remaining );
425 | }
426 | }
427 |
428 | // Add the word to the content.
429 | $content .= $word;
430 | $remaining -= ht_strlen( $word );
431 | }
432 |
433 | return [
434 | // Return the truncated content.
435 | $content,
436 | // Return the number of words remaining.
437 | $remaining < 0 ? 0 : $remaining,
438 | $opts,
439 | ];
440 | } else {
441 | // If there is only one word, truncate it to the desired length.
442 | return [
443 | // Truncate the text to the desired length.
444 | ht_substr( $node->textContent, 0, $length ),
445 | // Return the number of words remaining.
446 | $length - $char_count,
447 | $opts,
448 | ];
449 | }
450 | break;
451 |
452 | case 'sentences':
453 | // Split the text into sentences.
454 | preg_match_all( '/(.*?[.!?]+)(?:\s|$)/us', $xhtml, $sentences );
455 |
456 | // Get the sentences.
457 | $sentences = $sentences[0];
458 |
459 | // Count the sentences.
460 | $sentence_count = count( $sentences );
461 | $remaining = $length - $sentence_count;
462 |
463 | // If the number of sentences is less than or equal to the length, return the text in full.
464 | if ( $length >= $sentence_count ) {
465 | $remaining = $length - $sentence_count;
466 |
467 | return [
468 | // Return the full text.
469 | $xhtml,
470 | // Return the number of sentences remaining.
471 | $remaining < 0 ? 0 : $remaining,
472 | $opts,
473 | ];
474 | }
475 |
476 | return [
477 | // Implode the sentences array back into a string.
478 | implode( '', array_slice( $sentences, 0, $length ) ),
479 | // Return the number of sentences remaining.
480 | 0,
481 | $opts,
482 | ];
483 | }
484 | }
485 |
486 | /**
487 | * Check if a node is ellipsable.
488 | *
489 | * @param \DOMNode|\DOMDocument $node Node to truncate.
490 | *
491 | * @return boolean
492 | */
493 | protected static function is_ellipsable( $node ) {
494 | return ( $node instanceof DOMDocument )
495 | || in_array( ht_strtolower( $node->nodeName ), static::$ellipsable_tags, true );
496 | }
497 |
498 | /**
499 | * Convert a string to UTF-8 for XML.
500 | *
501 | * @param string $str String to convert.
502 | *
503 | * @return string
504 | */
505 | protected static function utf8_for_xml( $str ) {
506 | return preg_replace( '/[^\x{0009}\x{000a}\x{000d}\x{0020}-\x{D7FF}\x{E000}-\x{FFFD}]+/u', ' ', $str );
507 | }
508 | }
509 |
--------------------------------------------------------------------------------
/src/Quick.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace Chophper;
11 |
12 | use Masterminds\HTML5;
13 |
14 | /**
15 | * Class to handle HTML truncation.
16 | */
17 | class Quick {
18 | /**
19 | * HTML parser.
20 | *
21 | * @var HTML5
22 | */
23 | public $parser;
24 |
25 | /**
26 | * Constructor.
27 | */
28 | public function __construct() {
29 | $this->parser = new HTML5();
30 | }
31 |
32 | /**
33 | * Strip HTML tags from a string.
34 | *
35 | * @param string $text Text to strip tags from.
36 | * @param bool $remove_breaks Whether to remove line breaks.
37 | *
38 | * @return string
39 | */
40 | public function safe_strip_tags( $text, $remove_breaks = false ) {
41 | if ( is_null( $text ) ) {
42 | return '';
43 | }
44 |
45 | if ( ! is_scalar( $text ) ) {
46 | return '';
47 | }
48 |
49 | $text = preg_replace( '@<(script|style)[^>]*?>.*?\\1>@si', '', $text );
50 | $text = strip_tags( $text );
51 |
52 | if ( $remove_breaks ) {
53 | $text = preg_replace( '/[\r\n\t ]+/', ' ', $text );
54 | }
55 |
56 | return trim( $text );
57 | }
58 |
59 | /**
60 | * Truncate a string of HTML to a certain number of words while preserving HTML tags.
61 | *
62 | * This function breaks the HTML into words and tags, truncates the words, and then
63 | * reassembles the HTML.
64 | *
65 | * @param string $html HTML string to truncate.
66 | * @param int $words Number of words to truncate to.
67 | *
68 | * @return string
69 | */
70 | public function truncate_words( $html, $words ) {
71 | // First lets check if we need to truncate at all.
72 | $stripped_html_word_count = str_word_count( $this->safe_strip_tags( $html ) );
73 |
74 | if ( $stripped_html_word_count <= $words ) {
75 | return $html;
76 | }
77 |
78 | $parsed = $this->parser->loadHTML( $html );
79 |
80 | $truncated_html = '';
81 | $word_count = 0;
82 |
83 | foreach ( $parsed->childNodes as $node ) {
84 | if ( XML_TEXT_NODE === $node->nodeType ) {
85 | $words_in_node = preg_split( '/\s+/', $node->textContent, -1, PREG_SPLIT_NO_EMPTY );
86 |
87 | foreach ( $words_in_node as $word ) {
88 | if ( $word_count < $words ) {
89 | $truncated_html .= $word . ' ';
90 | }
91 |
92 | ++$word_count;
93 | }
94 | } else {
95 | $truncated_html .= $this->parser->saveHTML( $node );
96 | }
97 |
98 | if ( $word_count >= $words ) {
99 | break;
100 | }
101 | }
102 |
103 | $truncated_html = trim( $truncated_html );
104 |
105 | if ( '>' === substr( $truncated_html, -1 ) ) {
106 | $truncated_html = substr( $truncated_html, 0, strrpos( $truncated_html, '<' ) );
107 | }
108 |
109 | return $truncated_html;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/src/functions.php:
--------------------------------------------------------------------------------
1 |
8 | */
9 |
10 | namespace Chophper;
11 |
12 | if ( function_exists( 'grapheme_strlen' ) ) {
13 | /**
14 | * Get the length of a string
15 | *
16 | * @param string $str The string being measured for length.
17 | *
18 | * @return int
19 | */
20 | function ht_strlen( $str ) {
21 | return grapheme_strlen( $str );
22 | }
23 |
24 | /**
25 | * Get part of string
26 | *
27 | * @param string $str The input string. Must be one character or longer.
28 | * @param int $from Start position in $str. If $from is non-negative, the returned string will start at the $from'th position in $str, counting from zero. If $from is negative, the returned string will start at the $from'th character from the end of string.
29 | * @param int $to If $to is given, the string returned will contain at most $to characters beginning from $from (depending on the length of $str).
30 | *
31 | * @return string
32 | */
33 | function ht_substr( $str, $from, $to = 2147483647 ) {
34 | return grapheme_substr( $str, $from, $to );
35 | }
36 | } elseif ( function_exists( 'mb_strlen' ) ) {
37 | /**
38 | * Get the length of a string
39 | *
40 | * @param string $str The string being measured for length.
41 | *
42 | * @return int
43 | */
44 | function ht_strlen( $str ) {
45 | return mb_strlen( $str );
46 | }
47 |
48 | /**
49 | * Get part of string
50 | *
51 | * @param string $str The input string. Must be one character or longer.
52 | * @param int $from Start position in $str. If $from is non-negative, the returned string will start at the $from'th position in $str, counting from zero. If $from is negative, the returned string will start at the $from'th character from the end of string.
53 | * @param int $to If $to is given, the string returned will contain at most $to characters beginning from $from (depending on the length of $str).
54 | *
55 | * @return string
56 | */
57 | function ht_substr( $str, $from, $to = 2147483647 ) {
58 | return mb_substr( $str, $from, $to );
59 | }
60 | } elseif ( function_exists( 'iconv_strlen' ) ) {
61 | /**
62 | * Get the length of a string
63 | *
64 | * @param string $str The string being measured for length.
65 | *
66 | * @return int
67 | */
68 | function ht_strlen( $str ) {
69 | return iconv_strlen( $str );
70 | }
71 |
72 | /**
73 | * Get part of string
74 | *
75 | * @param string $str The input string. Must be one character or longer.
76 | * @param int $from Start position in $str. If $from is non-negative, the returned string will start at the $from'th position in $str, counting from zero. If $from is negative, the returned string will start at the $from'th character from the end of string.
77 | * @param int $to If $to is given, the string returned will contain at most $to characters beginning from $from (depending on the length of $str).
78 | *
79 | * @return string
80 | */
81 | function ht_substr( $str, $from, $to = 2147483647 ) {
82 | return iconv_substr( $str, $from, $to );
83 | }
84 | } else {
85 | /**
86 | * Get the length of a string
87 | *
88 | * @param string $str The string being measured for length.
89 | *
90 | * @return int
91 | */
92 | function ht_strlen( $str ) {
93 | return strlen( $str );
94 | }
95 |
96 | /**
97 | * Get part of string
98 | *
99 | * @param string $str The input string. Must be one character or longer.
100 | * @param int $from Start position in $str. If $from is non-negative, the returned string will start at the $from'th position in $str, counting from zero. If $from is negative, the returned string will start at the $from'th character from the end of string.
101 | * @param int $to If $to is given, the string returned will contain at most $to characters beginning from $from (depending on the length of $str).
102 | *
103 | * @return string
104 | */
105 | function ht_substr( $str, $from, $to = 2147483647 ) {
106 | return substr( $str, $from, $to );
107 | }
108 | }
109 |
110 | if ( function_exists( 'mb_strtolower' ) ) {
111 | /**
112 | * Make a string lowercase
113 | *
114 | * @param string $str The string being lowercased.
115 | *
116 | * @return string
117 | */
118 | function ht_strtolower( $str ) {
119 | return mb_strtolower( $str );
120 | }
121 |
122 | /**
123 | * Make a string uppercase
124 | *
125 | * @param string $str The string being uppercased.
126 | *
127 | * @return string
128 | */
129 | function ht_strtoupper( $str ) {
130 | return mb_strtoupper( $str );
131 | }
132 | } else {
133 | /**
134 | * Make a string lowercase
135 | *
136 | * @param string $str The string being lowercased.
137 | *
138 | * @return string
139 | */
140 | function ht_strtolower( $str ) {
141 | return strtolower( $str );
142 | }
143 |
144 | /**
145 | * Make a string uppercase
146 | *
147 | * @param string $str The string being uppercased.
148 | *
149 | * @return string
150 | */
151 | function ht_strtoupper( $str ) {
152 | return strtoupper( $str );
153 | }
154 | }
155 |
--------------------------------------------------------------------------------