22
33namespace Phug \Formatter \Format ;
44
5+ use Closure ;
56use Generator ;
7+ use InvalidArgumentException ;
68use Phug \Formatter ;
79use Phug \Formatter \AbstractFormat ;
810use Phug \Formatter \AssignmentContainerInterface ;
2022use Phug \FormatterException ;
2123use Phug \Util \AttributesInterface ;
2224use Phug \Util \Joiner ;
25+ use Phug \Util \OrderedValue ;
2326use SplObjectStorage ;
2427
2528class XmlFormat extends AbstractFormat
@@ -268,17 +271,66 @@ protected function yieldAssignmentElement(AssignmentElement $element)
268271
269272 /* @var MarkupElement $markup */
270273 $ markup = $ element ->getContainer ();
274+ $ attributeOrder = $ this ->hasOption ('attribute_precedence ' )
275+ ? $ this ->getOption ('attribute_precedence ' )
276+ : 'assignment ' ;
277+
278+ switch ($ attributeOrder ) {
279+ case 'assignment ' :
280+ case 'assignments ' :
281+ $ arguments = array_merge (
282+ $ markup instanceof AttributesInterface
283+ ? $ this ->formatMarkupAttributes ($ markup )
284+ : [],
285+ $ markup instanceof AssignmentContainerInterface
286+ ? $ this ->formatAttributeAssignments ($ markup )
287+ : []
288+ );
289+ break ;
271290
272- $ arguments = $ markup instanceof AssignmentContainerInterface
273- ? $ this ->formatAttributeAssignments ($ markup )
274- : [];
291+ case 'attribute ' :
292+ case 'attributes ' :
293+ $ arguments = array_merge (
294+ $ markup instanceof AssignmentContainerInterface
295+ ? $ this ->formatAttributeAssignments ($ markup )
296+ : [],
297+ $ markup instanceof AttributesInterface
298+ ? $ this ->formatMarkupAttributes ($ markup )
299+ : []
300+ );
301+ break ;
275302
276- $ arguments = array_merge (
277- $ markup instanceof AttributesInterface
278- ? $ this ->formatMarkupAttributes ($ markup )
279- : [],
280- $ arguments
281- );
303+ case 'left ' :
304+ $ arguments = $ this ->getSortedAttributes ($ markup , static function (OrderedValue $ a , OrderedValue $ b ) {
305+ return $ b ->getOrder () - $ a ->getOrder ();
306+ });
307+ break ;
308+
309+ case 'right ' :
310+ $ arguments = $ this ->getSortedAttributes ($ markup , static function (OrderedValue $ a , OrderedValue $ b ) {
311+ return $ a ->getOrder () - $ b ->getOrder ();
312+ });
313+ break ;
314+
315+ default :
316+ if (!is_callable ($ attributeOrder )) {
317+ throw new InvalidArgumentException (
318+ 'Option attribute_precedence must be ' .
319+ '"assignment" (default), "attribute", "left", "right" or a callable. '
320+ );
321+ }
322+
323+ $ arguments = array_map (static function ($ argument ) {
324+ return $ argument instanceof OrderedValue ? $ argument ->getValue () : $ argument ;
325+ }, $ attributeOrder (
326+ $ markup instanceof AssignmentContainerInterface
327+ ? $ this ->formatOrderedAttributeAssignments ($ markup )
328+ : [],
329+ $ markup instanceof AttributesInterface
330+ ? $ this ->formatOrderedMarkupAttributes ($ markup )
331+ : []
332+ ));
333+ }
282334
283335 foreach ($ markup ->getAssignments () as $ assignment ) {
284336 /* @var AssignmentElement $assignment */
@@ -303,22 +355,48 @@ protected function formatAttributeAssignments(AssignmentContainerInterface $mark
303355 $ arguments = [];
304356
305357 foreach ($ this ->yieldAssignmentAttributes ($ markup ) as $ attribute ) {
306- $ checked = method_exists ($ attribute , 'isChecked ' ) && $ attribute ->isChecked ();
358+ $ arguments [] = $ this ->formatInnerCodeValue ($ attribute );
359+ }
307360
308- while (method_exists ($ attribute , 'getValue ' )) {
309- $ attribute = $ attribute ->getValue ();
310- }
361+ return $ arguments ;
362+ }
311363
312- $ arguments [] = $ this ->formatCode ($ attribute , $ checked );
364+ /**
365+ * @param AssignmentContainerInterface $markup
366+ *
367+ * @return list<OrderedValue<string>>
368+ */
369+ protected function formatOrderedAttributeAssignments (AssignmentContainerInterface $ markup )
370+ {
371+ $ arguments = [];
372+
373+ foreach ($ this ->yieldAssignmentOrderedAttributes ($ markup ) as $ attribute => $ order ) {
374+ $ arguments [] = new OrderedValue ($ this ->formatInnerCodeValue ($ attribute ), $ order );
313375 }
314376
315377 return $ arguments ;
316378 }
317379
380+ /**
381+ * @param AbstractValueElement|mixed $value
382+ *
383+ * @return string
384+ */
385+ protected function formatInnerCodeValue ($ value )
386+ {
387+ $ checked = method_exists ($ value , 'isChecked ' ) && $ value ->isChecked ();
388+
389+ while (method_exists ($ value , 'getValue ' )) {
390+ $ value = $ value ->getValue ();
391+ }
392+
393+ return $ this ->formatCode ($ value , $ checked );
394+ }
395+
318396 /**
319397 * @param AssignmentContainerInterface $markup
320398 *
321- * @return Generator| AbstractValueElement[]
399+ * @return Generator< AbstractValueElement>
322400 */
323401 protected function yieldAssignmentAttributes (AssignmentContainerInterface $ markup )
324402 {
@@ -333,10 +411,28 @@ protected function yieldAssignmentAttributes(AssignmentContainerInterface $marku
333411 }
334412 }
335413
414+ /**
415+ * @param AssignmentContainerInterface $markup
416+ *
417+ * @return Generator<AbstractValueElement, int|null>
418+ */
419+ protected function yieldAssignmentOrderedAttributes (AssignmentContainerInterface $ markup )
420+ {
421+ foreach ($ markup ->getAssignmentsByName ('attributes ' ) as $ attributesAssignment ) {
422+ /* @var AssignmentElement $attributesAssignment */
423+ foreach ($ attributesAssignment ->getAttributes () as $ attribute ) {
424+ /* @var AbstractValueElement $attribute */
425+ yield $ attribute => $ attributesAssignment ->getOrder ();
426+ }
427+
428+ $ markup ->removedAssignment ($ attributesAssignment );
429+ }
430+ }
431+
336432 /**
337433 * @param AttributesInterface $markup
338434 *
339- * @return array <string>
435+ * @return list <string>
340436 */
341437 protected function formatMarkupAttributes (AttributesInterface $ markup )
342438 {
@@ -353,6 +449,26 @@ protected function formatMarkupAttributes(AttributesInterface $markup)
353449 return $ arguments ;
354450 }
355451
452+ /**
453+ * @param AttributesInterface $markup
454+ *
455+ * @return list<OrderedValue<string>>
456+ */
457+ protected function formatOrderedMarkupAttributes (AttributesInterface $ markup )
458+ {
459+ $ arguments = [];
460+ $ attributes = $ markup ->getAttributes ();
461+
462+ foreach ($ attributes as $ attribute ) {
463+ /* @var AttributeElement $attribute */
464+ $ arguments [] = new OrderedValue ($ this ->formatAttributeAsArrayItem ($ attribute ), $ attribute ->getOrder ());
465+ }
466+
467+ $ attributes ->removeAll ($ attributes );
468+
469+ return $ arguments ;
470+ }
471+
356472 /**
357473 * @param AssignmentElement $element
358474 *
@@ -441,4 +557,27 @@ protected function formatMarkupElement(MarkupElement $element)
441557 ? $ this ->getIndent ().$ tag .$ this ->getNewLine ()
442558 : $ tag ;
443559 }
560+
561+ /**
562+ * @param AssignmentContainerInterface|AttributesInterface|mixed $markup
563+ * @param Closure(OrderedValue, OrderedValue): int $sorter
564+ *
565+ * @return list<string>
566+ */
567+ private function getSortedAttributes ($ markup , Closure $ sorter )
568+ {
569+ $ arguments = array_merge (
570+ $ markup instanceof AssignmentContainerInterface
571+ ? $ this ->formatOrderedAttributeAssignments ($ markup )
572+ : [],
573+ $ markup instanceof AttributesInterface
574+ ? $ this ->formatOrderedMarkupAttributes ($ markup )
575+ : []
576+ );
577+ usort ($ arguments , $ sorter );
578+
579+ return array_map (static function (OrderedValue $ value ) {
580+ return $ value ->getValue ();
581+ }, $ arguments );
582+ }
444583}
0 commit comments