PageRenderTime 55ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/csscomb.php

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

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