PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/www/src/csscomb.php

https://github.com/mdunbavan/CSScomb
PHP | 1620 lines | 1325 code | 111 blank | 184 comment | 67 complexity | d09e3b3db3a1a68c1aaf5bbe700975bb MD5 | raw file
Possible License(s): MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * CSScomb
  4. *
  5. * Tool for sorting CSS properties in specific order
  6. *
  7. * @version 2.11 (build c6abf31-1211171628)
  8. * @author Vyacheslav Oliyanchuk (miripiruni) <mail@csscomb.com>
  9. * @license MIT
  10. * @web http://csscomb.com/
  11. */
  12. error_reporting(E_ALL);
  13. class csscomb{
  14. var $sort_order = Array(),
  15. $code = Array(
  16. // оригинальный код, без изменений, то, что пришло на вход
  17. 'original' => null,
  18. // код, который может меняться в процессе выполнения алгоритма пересортировки
  19. 'edited' => null,
  20. // TODO: избавиться от resorted
  21. // конечный, пересортированный CSS-код
  22. 'resorted' => null,
  23. // если найдены expression, то эта переменная станет массивом, ячейки которого
  24. // будут содержать код каждого найденного expression
  25. 'expressions' => null,
  26. // если найдены data uri, то эта переменная станет массивом...
  27. 'datauri' => null,
  28. // если найдены интерполированные переменные, то эта переменная станет массивом
  29. 'interpolations' => null,
  30. // если найдены CSS-хаки мешающие парсить, то эта переменная станет массивом...
  31. 'hacks' => null,
  32. // если найдены комментарии содержащие { или } мешающие парсить,
  33. // то эта переменная станет массивом.
  34. 'braces' => null,
  35. // если найдены entities мешающие парсить, то эта переменная станет массивом.
  36. 'entities' => null
  37. ),
  38. // В переменной $mode лежит режим работы с CSS-кодом. Возможны следующие значения:
  39. // css-file - только CSS-код
  40. // style-attribute - найден атрибут style="..."
  41. // properties - не найдено фигурных скобок, зато присутствуют точки с запятой и двоеточия.
  42. $mode = 'properties',
  43. $default_sort_order = '[
  44. "position",
  45. "top",
  46. "right",
  47. "bottom",
  48. "left",
  49. "z-index",
  50. "display",
  51. "visibility",
  52. "-webkit-flex-direction",
  53. "-moz-flex-direction",
  54. "-ms-flex-direction",
  55. "-o-flex-direction",
  56. "flex-direction",
  57. "-webkit-flex-order",
  58. "-moz-flex-order",
  59. "-ms-flex-order",
  60. "-o-flex-order",
  61. "flex-order",
  62. "-webkit-flex-pack",
  63. "-moz-flex-pack",
  64. "-ms-flex-pack",
  65. "-o-flex-pack",
  66. "flex-pack",
  67. "float",
  68. "clear",
  69. "-webkit-flex-align",
  70. "-moz-flex-align",
  71. "-ms-flex-align",
  72. "-o-flex-align",
  73. "flex-align",
  74. "overflow",
  75. "-ms-overflow-x",
  76. "-ms-overflow-y",
  77. "overflow-x",
  78. "overflow-y",
  79. "clip",
  80. "-webkit-box-sizing",
  81. "-moz-box-sizing",
  82. "box-sizing",
  83. "margin",
  84. "margin-top",
  85. "margin-right",
  86. "margin-bottom",
  87. "margin-left",
  88. "padding",
  89. "padding-top",
  90. "padding-right",
  91. "padding-bottom",
  92. "padding-left",
  93. "min-width",
  94. "min-height",
  95. "max-width",
  96. "max-height",
  97. "width",
  98. "height",
  99. "outline",
  100. "outline-width",
  101. "outline-style",
  102. "outline-color",
  103. "outline-offset",
  104. "border",
  105. "border-spacing",
  106. "border-collapse",
  107. "border-width",
  108. "border-style",
  109. "border-color",
  110. "border-top",
  111. "border-top-width",
  112. "border-top-style",
  113. "border-top-color",
  114. "border-right",
  115. "border-right-width",
  116. "border-right-style",
  117. "border-right-color",
  118. "border-bottom",
  119. "border-bottom-width",
  120. "border-bottom-style",
  121. "border-bottom-color",
  122. "border-left",
  123. "border-left-width",
  124. "border-left-style",
  125. "border-left-color",
  126. "-webkit-border-radius",
  127. "-moz-border-radius",
  128. "border-radius",
  129. "-webkit-border-top-left-radius",
  130. "-moz-border-radius-topleft",
  131. "border-top-left-radius",
  132. "-webkit-border-top-right-radius",
  133. "-moz-border-radius-topright",
  134. "border-top-right-radius",
  135. "-webkit-border-bottom-right-radius",
  136. "-moz-border-radius-bottomright",
  137. "border-bottom-right-radius",
  138. "-webkit-border-bottom-left-radius",
  139. "-moz-border-radius-bottomleft",
  140. "border-bottom-left-radius",
  141. "-webkit-border-image",
  142. "-moz-border-image",
  143. "-o-border-image",
  144. "border-image",
  145. "-webkit-border-image-source",
  146. "-moz-border-image-source",
  147. "-o-border-image-source",
  148. "border-image-source",
  149. "-webkit-border-image-slice",
  150. "-moz-border-image-slice",
  151. "-o-border-image-slice",
  152. "border-image-slice",
  153. "-webkit-border-image-width",
  154. "-moz-border-image-width",
  155. "-o-border-image-width",
  156. "border-image-width",
  157. "-webkit-border-image-outset",
  158. "-moz-border-image-outset",
  159. "-o-border-image-outset",
  160. "border-image-outset",
  161. "-webkit-border-image-repeat",
  162. "-moz-border-image-repeat",
  163. "-o-border-image-repeat",
  164. "border-image-repeat",
  165. "-webkit-border-top-image",
  166. "-moz-border-top-image",
  167. "-o-border-top-image",
  168. "border-top-image",
  169. "-webkit-border-right-image",
  170. "-moz-border-right-image",
  171. "-o-border-right-image",
  172. "border-right-image",
  173. "-webkit-border-bottom-image",
  174. "-moz-border-bottom-image",
  175. "-o-border-bottom-image",
  176. "border-bottom-image",
  177. "-webkit-border-left-image",
  178. "-moz-border-left-image",
  179. "-o-border-left-image",
  180. "border-left-image",
  181. "-webkit-border-corner-image",
  182. "-moz-border-corner-image",
  183. "-o-border-corner-image",
  184. "border-corner-image",
  185. "-webkit-border-top-left-image",
  186. "-moz-border-top-left-image",
  187. "-o-border-top-left-image",
  188. "border-top-left-image",
  189. "-webkit-border-top-right-image",
  190. "-moz-border-top-right-image",
  191. "-o-border-top-right-image",
  192. "border-top-right-image",
  193. "-webkit-border-bottom-right-image",
  194. "-moz-border-bottom-right-image",
  195. "-o-border-bottom-right-image",
  196. "border-bottom-right-image",
  197. "-webkit-border-bottom-left-image",
  198. "-moz-border-bottom-left-image",
  199. "-o-border-bottom-left-image",
  200. "border-bottom-left-image",
  201. "background",
  202. "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
  203. "background-color",
  204. "background-image",
  205. "background-attachment",
  206. "background-position",
  207. "-ms-background-position-x",
  208. "-ms-background-position-y",
  209. "background-position-x",
  210. "background-position-y",
  211. "background-clip",
  212. "background-origin",
  213. "background-size",
  214. "background-repeat",
  215. "box-decoration-break",
  216. "-webkit-box-shadow",
  217. "-moz-box-shadow",
  218. "box-shadow",
  219. "color",
  220. "table-layout",
  221. "caption-side",
  222. "empty-cells",
  223. "list-style",
  224. "list-style-position",
  225. "list-style-type",
  226. "list-style-image",
  227. "quotes",
  228. "content",
  229. "counter-increment",
  230. "counter-reset",
  231. "-ms-writing-mode",
  232. "vertical-align",
  233. "text-align",
  234. "-webkit-text-align-last",
  235. "-moz-text-align-last",
  236. "-ms-text-align-last",
  237. "text-align-last",
  238. "text-decoration",
  239. "text-emphasis",
  240. "text-emphasis-position",
  241. "text-emphasis-style",
  242. "text-emphasis-color",
  243. "text-indent",
  244. "-ms-text-justify",
  245. "text-justify",
  246. "text-outline",
  247. "text-transform",
  248. "text-wrap",
  249. "-ms-text-overflow",
  250. "text-overflow",
  251. "text-overflow-ellipsis",
  252. "text-overflow-mode",
  253. "text-shadow",
  254. "white-space",
  255. "word-spacing",
  256. "-ms-word-wrap",
  257. "word-wrap",
  258. "-ms-word-break",
  259. "word-break",
  260. "-moz-tab-size",
  261. "-o-tab-size",
  262. "tab-size",
  263. "-webkit-hyphens",
  264. "-moz-hyphens",
  265. "hyphens",
  266. "letter-spacing",
  267. "font",
  268. "font-weight",
  269. "font-style",
  270. "font-variant",
  271. "font-size-adjust",
  272. "font-stretch",
  273. "font-size",
  274. "font-family",
  275. "src",
  276. "line-height",
  277. "opacity",
  278. "-ms-filter:\'progid:DXImageTransform.Microsoft.Alpha",
  279. "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
  280. "-ms-interpolation-mode",
  281. "-webkit-filter",
  282. "-ms-filter",
  283. "filter",
  284. "resize",
  285. "cursor",
  286. "nav-index",
  287. "nav-up",
  288. "nav-right",
  289. "nav-down",
  290. "nav-left",
  291. "-webkit-transition",
  292. "-moz-transition",
  293. "-ms-transition",
  294. "-o-transition",
  295. "transition",
  296. "-webkit-transition-delay",
  297. "-moz-transition-delay",
  298. "-ms-transition-delay",
  299. "-o-transition-delay",
  300. "transition-delay",
  301. "-webkit-transition-timing-function",
  302. "-moz-transition-timing-function",
  303. "-ms-transition-timing-function",
  304. "-o-transition-timing-function",
  305. "transition-timing-function",
  306. "-webkit-transition-duration",
  307. "-moz-transition-duration",
  308. "-ms-transition-duration",
  309. "-o-transition-duration",
  310. "transition-duration",
  311. "-webkit-transition-property",
  312. "-moz-transition-property",
  313. "-ms-transition-property",
  314. "-o-transition-property",
  315. "transition-property",
  316. "-webkit-transform",
  317. "-moz-transform",
  318. "-ms-transform",
  319. "-o-transform",
  320. "transform",
  321. "-webkit-transform-origin",
  322. "-moz-transform-origin",
  323. "-ms-transform-origin",
  324. "-o-transform-origin",
  325. "transform-origin",
  326. "-webkit-animation",
  327. "-moz-animation",
  328. "-ms-animation",
  329. "-o-animation",
  330. "animation",
  331. "-webkit-animation-name",
  332. "-moz-animation-name",
  333. "-ms-animation-name",
  334. "-o-animation-name",
  335. "animation-name",
  336. "-webkit-animation-duration",
  337. "-moz-animation-duration",
  338. "-ms-animation-duration",
  339. "-o-animation-duration",
  340. "animation-duration",
  341. "-webkit-animation-play-state",
  342. "-moz-animation-play-state",
  343. "-ms-animation-play-state",
  344. "-o-animation-play-state",
  345. "animation-play-state",
  346. "-webkit-animation-timing-function",
  347. "-moz-animation-timing-function",
  348. "-ms-animation-timing-function",
  349. "-o-animation-timing-function",
  350. "animation-timing-function",
  351. "-webkit-animation-delay",
  352. "-moz-animation-delay",
  353. "-ms-animation-delay",
  354. "-o-animation-delay",
  355. "animation-delay",
  356. "-webkit-animation-iteration-count",
  357. "-moz-animation-iteration-count",
  358. "-ms-animation-iteration-count",
  359. "-o-animation-iteration-count",
  360. "animation-iteration-count",
  361. "-webkit-animation-direction",
  362. "-moz-animation-direction",
  363. "-ms-animation-direction",
  364. "-o-animation-direction",
  365. "animation-direction",
  366. "pointer-events",
  367. "unicode-bidi",
  368. "direction",
  369. "-webkit-columns",
  370. "-moz-columns",
  371. "columns",
  372. "-webkit-column-span",
  373. "-moz-column-span",
  374. "column-span",
  375. "-webkit-column-width",
  376. "-moz-column-width",
  377. "column-width",
  378. "-webkit-column-count",
  379. "-moz-column-count",
  380. "column-count",
  381. "-webkit-column-fill",
  382. "-moz-column-fill",
  383. "column-fill",
  384. "-webkit-column-gap",
  385. "-moz-column-gap",
  386. "column-gap",
  387. "-webkit-column-rule",
  388. "-moz-column-rule",
  389. "column-rule",
  390. "-webkit-column-rule-width",
  391. "-moz-column-rule-width",
  392. "column-rule-width",
  393. "-webkit-column-rule-style",
  394. "-moz-column-rule-style",
  395. "column-rule-style",
  396. "-webkit-column-rule-color",
  397. "-moz-column-rule-color",
  398. "column-rule-color",
  399. "break-before",
  400. "break-inside",
  401. "break-after",
  402. "page-break-before",
  403. "page-break-inside",
  404. "page-break-after",
  405. "orphans",
  406. "widows",
  407. "-ms-zoom",
  408. "zoom",
  409. "max-zoom",
  410. "min-zoom",
  411. "user-zoom",
  412. "orientation"
  413. ]',
  414. $yandex_sort_order = '[
  415. [
  416. "position",
  417. "z-index",
  418. "top",
  419. "right",
  420. "bottom",
  421. "left"
  422. ],
  423. [
  424. "display",
  425. "visibility",
  426. "float",
  427. "clear",
  428. "overflow",
  429. "overflow-x",
  430. "overflow-y",
  431. "-ms-overflow-x",
  432. "-ms-overflow-y",
  433. "clip",
  434. "zoom",
  435. "flex-direction",
  436. "flex-order",
  437. "flex-pack",
  438. "flex-align"
  439. ],
  440. [
  441. "-webkit-box-sizing",
  442. "-moz-box-sizing",
  443. "box-sizing",
  444. "width",
  445. "min-width",
  446. "max-width",
  447. "height",
  448. "min-height",
  449. "max-height",
  450. "margin",
  451. "margin-top",
  452. "margin-right",
  453. "margin-bottom",
  454. "margin-left",
  455. "padding",
  456. "padding-top",
  457. "padding-right",
  458. "padding-bottom",
  459. "padding-left"
  460. ],
  461. [
  462. "table-layout",
  463. "empty-cells",
  464. "caption-side",
  465. "border-spacing",
  466. "border-collapse",
  467. "list-style",
  468. "list-style-position",
  469. "list-style-type",
  470. "list-style-image"
  471. ],
  472. [
  473. "content",
  474. "quotes",
  475. "counter-reset",
  476. "counter-increment",
  477. "resize",
  478. "cursor",
  479. "nav-index",
  480. "nav-up",
  481. "nav-right",
  482. "nav-down",
  483. "nav-left",
  484. "-webkit-transition",
  485. "-moz-transition",
  486. "-ms-transition",
  487. "-o-transition",
  488. "transition",
  489. "-webkit-transition-delay",
  490. "-moz-transition-delay",
  491. "-ms-transition-delay",
  492. "-o-transition-delay",
  493. "transition-delay",
  494. "-webkit-transition-timing-function",
  495. "-moz-transition-timing-function",
  496. "-ms-transition-timing-function",
  497. "-o-transition-timing-function",
  498. "transition-timing-function",
  499. "-webkit-transition-duration",
  500. "-moz-transition-duration",
  501. "-ms-transition-duration",
  502. "-o-transition-duration",
  503. "transition-duration",
  504. "-webkit-transition-property",
  505. "-moz-transition-property",
  506. "-ms-transition-property",
  507. "-o-transition-property",
  508. "transition-property",
  509. "-webkit-transform",
  510. "-moz-transform",
  511. "-ms-transform",
  512. "-o-transform",
  513. "transform",
  514. "-webkit-transform-origin",
  515. "-moz-transform-origin",
  516. "-ms-transform-origin",
  517. "-o-transform-origin",
  518. "transform-origin",
  519. "-webkit-animation",
  520. "-moz-animation",
  521. "-ms-animation",
  522. "-o-animation",
  523. "animation",
  524. "-webkit-animation-name",
  525. "-moz-animation-name",
  526. "-ms-animation-name",
  527. "-o-animation-name",
  528. "animation-name",
  529. "-webkit-animation-duration",
  530. "-moz-animation-duration",
  531. "-ms-animation-duration",
  532. "-o-animation-duration",
  533. "animation-duration",
  534. "-webkit-animation-play-state",
  535. "-moz-animation-play-state",
  536. "-ms-animation-play-state",
  537. "-o-animation-play-state",
  538. "animation-play-state",
  539. "-webkit-animation-timing-function",
  540. "-moz-animation-timing-function",
  541. "-ms-animation-timing-function",
  542. "-o-animation-timing-function",
  543. "animation-timing-function",
  544. "-webkit-animation-delay",
  545. "-moz-animation-delay",
  546. "-ms-animation-delay",
  547. "-o-animation-delay",
  548. "animation-delay",
  549. "-webkit-animation-iteration-count",
  550. "-moz-animation-iteration-count",
  551. "-ms-animation-iteration-count",
  552. "-o-animation-iteration-count",
  553. "animation-iteration-count",
  554. "-webkit-animation-iteration-count",
  555. "-moz-animation-iteration-count",
  556. "-ms-animation-iteration-count",
  557. "-o-animation-iteration-count",
  558. "animation-iteration-count",
  559. "-webkit-animation-direction",
  560. "-moz-animation-direction",
  561. "-ms-animation-direction",
  562. "-o-animation-direction",
  563. "animation-direction",
  564. "text-align",
  565. "-webkit-text-align-last",
  566. "-moz-text-align-last",
  567. "-ms-text-align-last",
  568. "text-align-last",
  569. "vertical-align",
  570. "white-space",
  571. "text-decoration",
  572. "text-emphasis",
  573. "text-emphasis-color",
  574. "text-emphasis-style",
  575. "text-emphasis-position",
  576. "text-indent",
  577. "-ms-text-justify",
  578. "text-justify",
  579. "text-transform",
  580. "letter-spacing",
  581. "word-spacing",
  582. "-ms-writing-mode",
  583. "text-outline",
  584. "text-transform",
  585. "text-wrap",
  586. "text-overflow",
  587. "-ms-text-overflow",
  588. "text-overflow-ellipsis",
  589. "text-overflow-mode",
  590. "-ms-word-wrap",
  591. "word-wrap",
  592. "word-break",
  593. "-ms-word-break",
  594. "-moz-tab-size",
  595. "-o-tab-size",
  596. "tab-size",
  597. "-webkit-hyphens",
  598. "-moz-hyphens",
  599. "hyphens",
  600. "pointer-events"
  601. ],
  602. [
  603. "opacity",
  604. "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
  605. "-ms-filter:\'progid:DXImageTransform.Microsoft.Alpha",
  606. "-ms-interpolation-mode",
  607. "color",
  608. "border",
  609. "border-collapse",
  610. "border-width",
  611. "border-style",
  612. "border-color",
  613. "border-top",
  614. "border-top-width",
  615. "border-top-style",
  616. "border-top-color",
  617. "border-right",
  618. "border-right-width",
  619. "border-right-style",
  620. "border-right-color",
  621. "border-bottom",
  622. "border-bottom-width",
  623. "border-bottom-style",
  624. "border-bottom-color",
  625. "border-left",
  626. "border-left-width",
  627. "border-left-style",
  628. "border-left-color",
  629. "-webkit-border-radius",
  630. "-moz-border-radius",
  631. "border-radius",
  632. "-webkit-border-top-left-radius",
  633. "-moz-border-radius-topleft",
  634. "border-top-left-radius",
  635. "-webkit-border-top-right-radius",
  636. "-moz-border-radius-topright",
  637. "border-top-right-radius",
  638. "-webkit-border-bottom-right-radius",
  639. "-moz-border-radius-bottomright",
  640. "border-bottom-right-radius",
  641. "-webkit-border-bottom-left-radius",
  642. "-moz-border-radius-bottomleft",
  643. "border-bottom-left-radius",
  644. "-webkit-border-image",
  645. "-moz-border-image",
  646. "-o-border-image",
  647. "border-image",
  648. "-webkit-border-image-source",
  649. "-moz-border-image-source",
  650. "-o-border-image-source",
  651. "border-image-source",
  652. "-webkit-border-image-slice",
  653. "-moz-border-image-slice",
  654. "-o-border-image-slice",
  655. "border-image-slice",
  656. "-webkit-border-image-width",
  657. "-moz-border-image-width",
  658. "-o-border-image-width",
  659. "border-image-width",
  660. "-webkit-border-image-outset",
  661. "-moz-border-image-outset",
  662. "-o-border-image-outset",
  663. "border-image-outset",
  664. "-webkit-border-image-repeat",
  665. "-moz-border-image-repeat",
  666. "-o-border-image-repeat",
  667. "border-image-repeat",
  668. "outline",
  669. "outline-width",
  670. "outline-style",
  671. "outline-color",
  672. "outline-offset",
  673. "background",
  674. "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
  675. "background-color",
  676. "background-image",
  677. "background-repeat",
  678. "background-attachment",
  679. "background-position",
  680. "background-position-x",
  681. "-ms-background-position-x",
  682. "background-position-y",
  683. "-ms-background-position-y",
  684. "background-clip",
  685. "background-origin",
  686. "background-size",
  687. "box-decoration-break",
  688. "-webkit-box-shadow",
  689. "-moz-box-shadow",
  690. "box-shadow",
  691. "-webkit-box-shadow",
  692. "-moz-box-shadow",
  693. "box-shadow",
  694. "-webkit-box-shadow",
  695. "-moz-box-shadow",
  696. "box-shadow",
  697. "-webkit-box-shadow",
  698. "-moz-box-shadow",
  699. "box-shadow",
  700. "filter:progid:DXImageTransform.Microsoft.gradient",
  701. "-ms-filter:\'progid:DXImageTransform.Microsoft.gradient",
  702. "text-shadow"
  703. ],
  704. [
  705. "font",
  706. "font-family",
  707. "font-size",
  708. "font-weight",
  709. "font-style",
  710. "font-variant",
  711. "font-size-adjust",
  712. "font-stretch",
  713. "font-effect",
  714. "font-emphasize",
  715. "font-emphasize-position",
  716. "font-emphasize-style",
  717. "font-smooth",
  718. "line-height"
  719. ]
  720. ]';
  721. /**
  722. * @param string css
  723. * @param boolean debug, OPTIONAL
  724. * @param json custom_sort_order JSON expected, OPTIONAL
  725. * @return string|false
  726. *
  727. * @TODO: https://github.com/miripiruni/CSScomb/issues/21
  728. *
  729. * Example:
  730. *
  731. * <code>
  732. * require_once 'PATH_TO_CSScomb/csscomb.php';
  733. *
  734. * $c = new csscomb();
  735. * $result_code = $c->csscomb(
  736. * 'div {margin-top:0; color: red; display: inline;}',
  737. * false,
  738. * $MY_JSON_SORT_ORDER
  739. * );
  740. * </code>
  741. *
  742. */
  743. function csscomb($css = '', $debug = false, $custom_sort_order = null) {
  744. $this->output = $debug ? true : false;
  745. if ($css && is_string($css)) {
  746. $this->code['original'] = $this->code['edited'] = $css;
  747. $this->set_mode();
  748. $this->set_sort_order($custom_sort_order);
  749. $this->preprocess();
  750. $this->parse_rules();
  751. $this->postprocess();
  752. return $this->end_of_process();
  753. } else {
  754. return false;
  755. }
  756. }
  757. /**
  758. * Функция устанавливает $this->sort_order
  759. *
  760. * @param json_array {string/JSON}
  761. *
  762. */
  763. function set_sort_order($json_array = null) {
  764. $this->sort_order = json_decode($this->default_sort_order);
  765. if ($json_array !== null) {
  766. $custom_sort_order = json_decode($json_array);
  767. if (is_array($custom_sort_order) AND count($custom_sort_order) > 0) {
  768. $this->sort_order = $custom_sort_order;
  769. }
  770. }
  771. if ($json_array === 'yandex') {
  772. $this->sort_order = json_decode($this->yandex_sort_order);
  773. //switch(json_last_error()) {
  774. //case JSON_ERROR_DEPTH:
  775. //echo 'JSON parse error: Достигнута максимальная глубина стека';
  776. //break;
  777. //case JSON_ERROR_STATE_MISMATCH:
  778. //echo 'JSON parse error: Некорректные разряды или не совпадение режимов';
  779. //break;
  780. //case JSON_ERROR_CTRL_CHAR:
  781. //echo 'JSON parse error: Некорректный управляющий символ';
  782. //break;
  783. //case JSON_ERROR_SYNTAX:
  784. //echo 'JSON parse error: Синтаксическая ошибка, не корректный JSON';
  785. //break;
  786. //}
  787. }
  788. }
  789. /**
  790. * Функция устанавливает $this->mode
  791. *
  792. * @TODO: а если и тег <style> и несколько style="..." в HTML?
  793. * https://github.com/miripiruni/CSScomb/issues/9
  794. */
  795. function set_mode() {
  796. if (strpos($this->code['original'], '{')) { // если есть фигурные скобки
  797. $this->mode = 'css-file';
  798. }
  799. else { // если нет фигурных скобок
  800. // если есть атрибут
  801. if (strpos($this->code['original'], "style='") OR strpos($this->code['original'], 'style="')) {
  802. $this->mode = 'style-attribute';
  803. }
  804. // если есть двоеточия и точки с запятой то это набор свойств
  805. else if (strpos($this->code['original'], ':') AND strpos($this->code['original'], ';')) {
  806. $this->mode = 'properties';
  807. }
  808. }
  809. }
  810. /**
  811. * @TODO: почему нигде не используется? Убрать?
  812. */
  813. function get_sort_order($order_name = null) {
  814. $order = '';
  815. if ($order_name !== null) {
  816. if ($order_name == 'zen') {
  817. $this->set_sort_order($this->default_sort_order);
  818. foreach ($this->sort_order as $k => $prop) {
  819. $order .= $prop."
  820. ";
  821. }
  822. }
  823. if ($order_name === 'yandex') {
  824. $this->set_sort_order($this->yandex_sort_order);
  825. foreach ($this->sort_order as $group) {
  826. foreach ($group as $prop) {
  827. $order .= $prop."
  828. ";
  829. }
  830. $order .= "
  831. ";
  832. }
  833. $order = trim($order);
  834. }
  835. }
  836. return $order;
  837. }
  838. function preprocess() {
  839. // 1. экранирование хаков, которые мешают парсить
  840. if (strpos($this->code['edited'], '"\\"}\\""')) { // разбираемся со страшным хаком "\"}\""
  841. $i = 0;
  842. $this->code['hacks'] = array();
  843. while(strpos($this->code['edited'], '"\\"}\\""')):
  844. $this->code['hacks'][] = '"\\"}\\""';
  845. $this->code['edited'] = str_replace('"\\"}\\""', 'hack'.$i++.'__', $this->code['edited']);
  846. endwhile;
  847. }
  848. // 2. expressions
  849. if (strpos($this->code['edited'], 'expression(')) { // разбираемся с expression если они присутствуют
  850. $i = 0;
  851. $this->code['expressions'] = array();
  852. while(strpos($this->code['edited'], 'expression(')):
  853. // вылавливаем expression
  854. preg_match_all('@(.*)expression\((.*?)\)@ism', $this->code['edited'], $match, PREG_SET_ORDER);
  855. $this->code['expressions'][] = $match[0][2]; // собираем значения expression(...)
  856. $this->code['edited'] = str_replace(
  857. 'expression('.$match[0][2].')',
  858. 'exp'.$i++.'__',
  859. $this->code['edited']);
  860. endwhile;
  861. }
  862. // 3. data uri
  863. if (strpos($this->code['edited'], ';base64,')) {
  864. $i = 0;
  865. $this->code['datauri'] = array();
  866. while(strpos($this->code['edited'], ';base64,')):
  867. // вылавливаем data uri
  868. preg_match_all(
  869. '@(url\(["\']?data:.[^\)]*["\']?\))@ism',
  870. $this->code['edited'],
  871. $match,
  872. PREG_SET_ORDER);
  873. $this->code['datauri'][] = $match[0][1]; // собираем значения
  874. $this->code['edited'] = str_replace($match[0][1], 'datauri'.$i++.'__', $this->code['edited']);
  875. endwhile;
  876. }
  877. // 4. Interpolated variables
  878. preg_match_all('@(\#|\@){.*?}@ismx', $this->code['edited'], $this->code['interpolations']);
  879. foreach ($this->code['interpolations'][0] as $key => $value) {
  880. $pos = strpos($this->code['edited'], $value);
  881. if ($pos !== false) {
  882. $this->code['edited'] = substr_replace($this->code['edited'],"interpolation".$key.'__',$pos,strlen($value));
  883. }
  884. }
  885. // 5. Закрываем сложности парсинга {}
  886. $this->code['edited'] = str_replace('{}', '{ }', $this->code['edited']);
  887. // 6. Закрываем сложности с отсутствующей последней ; перед }
  888. $this->code['edited'] = preg_replace('@(.*?[^\s;\{\}\/\*])(\s*?})@', '$1;$2', $this->code['edited']);
  889. // Убираем ; у последнего инлайнового комментария
  890. // Инлайновый комментарий может идти только после фигурной скобки или ;
  891. $this->code['edited'] = preg_replace('@([;\{\}]+\s*?//.*?);(\s*?})@', '$1$2', $this->code['edited']);
  892. // Убираем ; у интерполированных переменных
  893. $this->code['edited'] = preg_replace('/((#\{\$|\@\{).*?)[;](\s*?\})/', '$1$3', $this->code['edited']);
  894. // 7. Комментарии
  895. if (preg_match_all('@
  896. (
  897. \s*
  898. /\*
  899. .*?[^\*/]
  900. \*/
  901. (\s/\*\*/)?
  902. )
  903. @ismx', $this->code['edited'], $test)) {
  904. // 7.1. Закомментировано одно или несколько свойств: повторяющийся паттерн *:*; \s*?
  905. if (preg_match_all('@
  906. (\s*)
  907. /\*
  908. (.*?[^\*/])
  909. \*+/
  910. (\ {0,1}/\*\*/)?
  911. @ismx', $this->code['edited'], $comments)) {
  912. $new_comments = Array();
  913. $old_comments = $comments[0];
  914. foreach ($comments[2] as $key => $comment) {
  915. if ( // если комментарий содержит ; и :
  916. strpos($comment, ':') !== FALSE AND
  917. strpos($comment, ';') !== FALSE
  918. ) {
  919. preg_match_all('@
  920. (\s*)
  921. (
  922. .+?[^;]
  923. ;
  924. )
  925. @ismx', $comment, $properties);
  926. $new_comment = '';
  927. foreach ($properties[2] as $property) {
  928. $new_comment .= $comments[1][$key]."commented__".$property;
  929. }
  930. $new_comments[] = $new_comment;
  931. }
  932. else {
  933. // если нет : или ;, то считаем что это текстовый комментарий
  934. // и копируем его в том виде, в каком он был.
  935. $new_comments[] = $comments[0][$key];
  936. }
  937. }
  938. foreach ($old_comments as $key => $old_comment) {
  939. $this->code['edited'] = str_replace(
  940. $old_comments[$key],
  941. $new_comments[$key],
  942. $this->code['edited']);
  943. }
  944. }
  945. // 7.2. Обрывки закомментированных деклараций: присутствует { или }
  946. if (preg_match_all('@
  947. \s*?
  948. /\*
  949. (
  950. .*?[^\*/]
  951. )*?
  952. \*+/
  953. @ismx', $this->code['edited'], $comments)) {
  954. $new_comments = Array();
  955. $old_comments = $comments[0];
  956. foreach ($comments[0] as $key => $comment) {
  957. if (strpos($comment, '}') !== FALSE OR strpos($comment, '{') !== FALSE) {
  958. $new_comment = '';
  959. if (strpos($comment, '}') !== FALSE) { $new_comment .= '}'; }
  960. $new_comment .= "brace__".$key;
  961. if (strpos($comment, '{') !== FALSE) { $new_comment .= '{'; }
  962. $new_comments[$key] = $new_comment;
  963. $this->code['braces'][$key] = $comment;
  964. }
  965. }
  966. foreach ($new_comments as $key => $new_comment) {
  967. if (strlen($new_comment) > 0) {
  968. $this->code['edited'] = str_replace(
  969. $old_comments[$key],
  970. $new_comment,
  971. $this->code['edited']);
  972. }
  973. }
  974. }
  975. }
  976. // 8. Entities
  977. if (preg_match_all('@
  978. \&
  979. \#?
  980. [\d\w]*?[^;]
  981. \;
  982. @ismx', $this->code['edited'], $entities)) {
  983. $this->code['entities'] = array();
  984. foreach ($entities[0] as $key => $val) {
  985. $this->code['entities'][$key] = $val;
  986. $this->code['edited'] = str_replace($val, 'entity__'.$key, $this->code['edited']);
  987. }
  988. }
  989. }
  990. /**
  991. *
  992. * Зависит от $this->mode
  993. * Из $this->code['edited'] получает массив разбитый по }
  994. *
  995. */
  996. function parse_rules() {
  997. if ($this->mode === 'css-file') {
  998. // Отделяем всё после последней }
  999. // Например, @import и комментарии
  1000. preg_match('@
  1001. (
  1002. .*[^}]
  1003. }
  1004. )
  1005. (.*)
  1006. @ismx', $this->code['edited'], $matches);
  1007. $code_without_end = $matches[1];
  1008. // Если что-то нашлось, выносим в отдельную строку
  1009. $end_of_code = '';
  1010. if($matches[2]) {
  1011. $end_of_code = $matches[2];
  1012. }
  1013. // Обрабатываем всё до последней }
  1014. $code_without_end = $this->parse_root($code_without_end);
  1015. // Склеиваем обратно
  1016. $this->code['resorted'] = $code_without_end.$end_of_code;
  1017. }
  1018. // TODO: Написать тесты для этой части и переписать код
  1019. if ($this->mode === 'style-attribute') {
  1020. $this->code['resorted'] = $this->code['edited'];
  1021. preg_match_all('@
  1022. .*?[^"\']
  1023. style=
  1024. ["\']
  1025. (.*?)
  1026. ["\']
  1027. @ismx', $this->code['edited'], $matches);
  1028. $properties = $matches[1];
  1029. //TODO: вынести вызов parse_prop в csscomb(), сделать чтобы parse_rules возвращала результат своей работы в виде $rules
  1030. foreach ($properties as $props) {
  1031. $r = $this->parse_properties($props);
  1032. $this->code['resorted'] = str_replace($props, $r, $this->code['resorted']).$end_of_code;
  1033. }
  1034. }
  1035. if ($this->mode === 'properties') {
  1036. $this->code['edited'] = "\n".$this->code['edited'];
  1037. $this->code['resorted'] = $this->parse_child($this->code['edited']);
  1038. $this->code['resorted'] = substr($this->code['resorted'], 1);
  1039. }
  1040. }
  1041. /**
  1042. * Ищем парные {} первого уровня
  1043. *
  1044. */
  1045. function parse_root($css = '') {
  1046. preg_match_all('@
  1047. \{(((?>[^\{\}]+)|(?R))*)\}
  1048. @ismx', $css, $matches);
  1049. // Парсим содержимое каждой пары {}
  1050. foreach ($matches[1] as &$value) {
  1051. $old_value = $value;
  1052. $value = $this->parse_child($value);
  1053. $css = str_replace($old_value, $value, $css);
  1054. }
  1055. return $css;
  1056. }
  1057. /**
  1058. * Разбиваем код на группы:
  1059. * - вложенные {}
  1060. * - переменные ($tomato, @tomato)
  1061. * - включения (@import, @include, @extend)
  1062. * - простые свойства (color: white;)
  1063. * TODO: добавить поддержку сложных свойств (border: {...})
  1064. *
  1065. */
  1066. function parse_child($value = '') {
  1067. $block_imports = array();
  1068. // 1. Ищем «детей» (вложенные селекторы)
  1069. preg_match_all('@
  1070. [^};]*?
  1071. {
  1072. (
  1073. (
  1074. ([^\{\}]+)|(?R)
  1075. )*
  1076. )
  1077. }
  1078. @ismx', $value, $nested);
  1079. // TODO: возможно, вынести отдельной функцией, т.к. часто повторяется
  1080. foreach ($nested[0] as $key => &$nest) {
  1081. $value = str_replace($nest, '', $value);
  1082. if(strpos(trim($nest), '@include') === 0) {
  1083. $value = str_replace($nest, '', $value);
  1084. $old_nest = $nested[1][$key];
  1085. $new_nest = $this->parse_child($nested[1][$key]);
  1086. $nest = str_replace($old_nest, $new_nest, $nest);
  1087. $block_imports[] = $nest;
  1088. unset($nested[0][$key]);
  1089. unset($nested[1][$key]);
  1090. }
  1091. }
  1092. // Сохраняем всех «детей» в строку для последующей замены
  1093. // TODO: убрать, если без этого можно обойтись
  1094. $nested_string = implode('', $nested[0]);
  1095. // Рекурсия, ahoj!
  1096. // Сортируем содержимое «детей»
  1097. foreach ($nested[1] as &$child) {
  1098. $old_child = $child;
  1099. $new_child = $this->parse_child($child);
  1100. $nested_string = str_replace($old_child, $new_child, $nested_string);
  1101. }
  1102. // Остались без «детей»
  1103. // 2. Выносим переменные в отдельный массив $vars
  1104. preg_match_all('@
  1105. (\s*/\*[^\*/]*?\*/)?
  1106. (\s*//.*?)?
  1107. \s*(\$|\@)[^;\}]+?:[^;]+?;
  1108. @ismx', $value, $vars);
  1109. // Удаляем их из общей строки
  1110. foreach ($vars[0] as $var) {
  1111. $value = str_replace($var, '', $value);
  1112. }
  1113. // 3. Выносим импорты в отдельный массив $imports
  1114. // TODO: объединить в одно выражение
  1115. // Включения, следующие сразу за {
  1116. preg_match_all('@
  1117. (^\s*\@[^;]+?[;])|(^\s*\.[^;:]+?[;])
  1118. @isx', $value, $first_imports);
  1119. foreach ($first_imports[0] as &$first_import) {
  1120. $value = str_replace($first_import, '', $value);
  1121. }
  1122. // Все остальные
  1123. preg_match_all('@
  1124. (?<=[;}])(\s*\@[^;]+?[;])|(?<=[;}])(\s*\.[^;:]+?[;])
  1125. @ismx', $value, $imports);
  1126. // Удаляем их из общей строки
  1127. foreach ($imports[1] as &$import) {
  1128. $value = str_replace($import, '', $value);
  1129. }
  1130. foreach ($imports[2] as &$import) {
  1131. $value = str_replace($import, '', $value);
  1132. }
  1133. // 4. Выносим простые свойства в массив $properties
  1134. preg_match_all('@
  1135. \s*[^;]+?:[^;]+?;
  1136. (\s*/\*.*?[^\*/]\*/)?
  1137. (\s{0,1}/\*\*/)?
  1138. @ismx', $value, $properties);
  1139. // Удаляем их из общей строки
  1140. foreach ($properties[0] as &$property) {
  1141. $value = str_replace($property, '', $value);
  1142. }
  1143. // Сортируем свойства
  1144. $props = $properties[0];
  1145. $props = $this->resort_properties($props);
  1146. // 5. Если осталось ещё что-то, оставляем «как есть»
  1147. // 6. Склеиваем всё обратно в следующем порядке:
  1148. // переменные, включения, простые свойства, вложенные {}
  1149. $value = implode('', $vars[0]).implode('', $first_imports[0]).implode('', $imports[1]).implode('', $imports[2]).implode('', $block_imports).implode('', $props).$nested_string.$value;
  1150. return $value;
  1151. }
  1152. /**
  1153. * Сильно зависит от $this->mode
  1154. *
  1155. * Парсит CSS-декларации из строки
  1156. * @param css {string}
  1157. *
  1158. */
  1159. function parse_properties($css = '') {
  1160. if ($this->mode === 'css-file') {
  1161. // отделяем фигурную скобку
  1162. $matches = null;
  1163. preg_match('@
  1164. ^
  1165. (.*?)
  1166. (
  1167. #(\s*/\*.*\*/;)*?
  1168. \s*?
  1169. }
  1170. )
  1171. @ismx', $css, $matches);
  1172. $all = null;
  1173. preg_match_all('@
  1174. ^
  1175. (
  1176. \s*
  1177. /\*.*\*/
  1178. )
  1179. ;
  1180. (
  1181. \s*
  1182. }
  1183. )
  1184. @ismx', $css, $all);
  1185. if (count($all[0]) > 0 and $all[0][0] !== null and $all[0][0] === $css) { // Если в этом участке кода ничего нет кроме одиногоко /* ... */ и закрывающей }
  1186. $all[0][0] = '';
  1187. return $all[1][0].$all[2][0];
  1188. }
  1189. if (sizeof($matches) > 0 and strlen($matches[1]) > 0) { // если есть и свойства и скобка и хотя бы одно :
  1190. $properties = $matches[1];
  1191. $brace = $matches[2];
  1192. if (is_array($this->sort_order[0])) { // Если порядок сортировки разбит на группы свойств
  1193. /**
  1194. * Если CSS-свойства уже были разделены на группы пустой
  1195. * строкой, то нужно поудалять это разделение, чтобы сделать
  1196. * новое.
  1197. */
  1198. $properties = preg_replace('/
  1199. \r?\n
  1200. \ *?
  1201. \r?\n
  1202. /ismx', "\n", $properties);
  1203. }
  1204. /* отделяем первый комментарий, который находится на той же строке, где и была скобка */
  1205. $matches = null;
  1206. $first_spaces = $first_comment = '';
  1207. preg_match('@
  1208. ^
  1209. (.*?)
  1210. (\s*?)
  1211. (/\* .* \*/)
  1212. (.*)
  1213. $
  1214. @ismx', $properties, $matches);
  1215. if (
  1216. count($matches) === 5 and // все распарсилось как надо
  1217. strlen($matches[1]) === 0 and // комментарий действительно идет первым
  1218. strpos($matches[2], "\n") !== 0 // перед комментарием нет переноса строки, следовательно предпологаем, что он относится к скобке с селектором
  1219. ) {
  1220. $first_spaces = $matches[2];
  1221. $first_comment = $matches[3];
  1222. $properties = $matches[4];
  1223. }
  1224. $matches = null;
  1225. preg_match_all('@
  1226. \s*
  1227. (
  1228. .[^:]*
  1229. [:>]
  1230. .[^;]*
  1231. ;
  1232. ( # На этой же строке (после ;) может быть комментарий. Он тоже пригодится.
  1233. \s*
  1234. /\*
  1235. .*?[^\*/]
  1236. \*/
  1237. )
  1238. ?
  1239. (\s{0,1}/\*\*/)?
  1240. )
  1241. @ismx', $properties, $matches);
  1242. $props = $matches[0];
  1243. $props = $this->resort_properties($props);
  1244. $props = $first_spaces.$first_comment.implode($props).$brace;
  1245. }
  1246. else $props = $css;
  1247. }
  1248. if ($this->mode === 'properties' OR $this->mode === 'style-attribute') {
  1249. preg_match_all('@
  1250. \s*
  1251. (
  1252. .[^:]* # все что угодно, но не :
  1253. :
  1254. .[^;]* # все что угодно, но не ;
  1255. ;
  1256. ( # На этой же строке (после ;) может быть комментарий. Он тоже пригодится.
  1257. \s*
  1258. /\* .* \*/
  1259. )
  1260. *?
  1261. )
  1262. @ismx', $css, $matches);
  1263. $props = $matches[0];
  1264. if (sizeof($props) > 0) { // если есть и свойства и скобка и хотя бы одно :
  1265. $props = $this->resort_properties($props);
  1266. $props = implode($props);
  1267. }
  1268. else {
  1269. $props = $css;
  1270. }
  1271. }
  1272. return $props;
  1273. }
  1274. /**
  1275. * Функция выполняет сортировку свойств
  1276. *
  1277. */
  1278. function resort_properties($prop) {
  1279. $resorted = $undefined = array();
  1280. foreach ($prop as $k => $val) {
  1281. $index = null; // Дефолтное значение индекса порядка для свойства. Если свойство не знакомо, то index так и останется null.
  1282. preg_match_all('@\s*?(.*?[^:]:).*@ism', $val, $matches, PREG_SET_ORDER);
  1283. // Решаем проблему с пробелами перед :
  1284. $property = preg_replace('@\s*:@ism', ':', (trim($matches[0][1])));
  1285. if (is_array($this->sort_order[0])) { // Если порядок сортировки разбит на группы свойств
  1286. foreach ($this->sort_order as $pos => $key) { // для каждой группы свойств
  1287. foreach ($this->sort_order[$pos] as $p => $k) { // для каждого свойства
  1288. if (
  1289. /**
  1290. * Пробел в начале добавляется специально, чтобы избежать совпадений по вхождению
  1291. * одной строки в другую. Например: top не должно совпадать с border-top
  1292. */
  1293. strpos(' '.trim($property), ' '.$k.':') !== FALSE OR
  1294. strpos(' '.trim($property), ' commented__'.$k.':') !== FALSE
  1295. ) {
  1296. $through_number = $this->get_through_number($k); // определяем "сквозной" порядковый номер
  1297. if ($through_number !== false) $index = $through_number;
  1298. }
  1299. }
  1300. }
  1301. }
  1302. else {
  1303. foreach ($this->sort_order as $pos => $key) {
  1304. if (
  1305. // пробел в начале добавляется специально.
  1306. strpos(' '.trim($property), ' '.$key.':') !== FALSE OR
  1307. strpos(' '.trim($property), ' commente…

Large files files are truncated, but you can click here to view the full file