From 7f72bab06af2751987ff1aae1afa956ca6c9bd6b Mon Sep 17 00:00:00 2001
From: bit4bit <bit4bit@noemail.net>
Date: Thu, 4 Nov 2021 02:31:45 +0000
Subject: [PATCH] facho placeholder mantiene orden de elementos

FossilOrigin-Name: c535b486c8f956a25f8e88bf9053aab312ed441b1d217699a36924e24b46ab21
---
 facho/facho.py      | 27 ++++++++++++++++++++++++---
 tests/test_facho.py | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 60 insertions(+), 3 deletions(-)

diff --git a/facho/facho.py b/facho/facho.py
index e0f1bf7..6304f8d 100644
--- a/facho/facho.py
+++ b/facho/facho.py
@@ -5,6 +5,7 @@ from lxml import etree
 from lxml.etree import Element, SubElement, tostring
 import re
 from collections import defaultdict
+from copy import deepcopy
 from pprint import pprint
 
 class FachoValueInvalid(Exception):
@@ -114,9 +115,18 @@ class LXMLBuilder:
         elem.attrib[key] = value
 
     @classmethod
-    def tostring(self, elem, **attrs):
+    def tostring(self, oelem, **attrs):
+        elem = deepcopy(oelem)
+
         attrs['pretty_print'] = attrs.pop('pretty_print', False)
         attrs['encoding'] = attrs.pop('encoding', 'UTF-8')
+
+        for el in elem.getiterator():
+            is_optional = el.get('facho_optional', 'False') == 'True'
+            if is_optional and el.getchildren() == []:
+                print(tostring(el))
+                el.getparent().remove(el)
+
         return tostring(elem, **attrs).decode('utf-8')
 
 
@@ -182,8 +192,11 @@ class FachoXML:
     def _path_xpath_for(self, xpath):
         return self._normalize_xpath(self._translate_xpath_for(xpath))
 
-    def placeholder_for(self, xpath, append=False):
-        return self.find_or_create_element(xpath, append)
+    def placeholder_for(self, xpath, append=False, optional=False):
+        elem = self.find_or_create_element(xpath, append)
+        if optional:
+            elem.set('facho_optional', 'True')
+        return elem
 
     def replacement_for(self, xpath, new_xpath, content, **attrs):
         elem = self.get_element(xpath)
@@ -217,6 +230,7 @@ class FachoXML:
         for node_path in node_paths:
             node_expr = self.builder.match_expression(node_path)
             node = self.builder.build_from_expression(node_path)
+
             child = self.builder.find_relative(current_elem, node_expr['path'], self.nsmap)
 
             parent = current_elem
@@ -240,6 +254,12 @@ class FachoXML:
             self.builder.append_next(last_slibing, node)
             return node
 
+        try:
+            # TODO(bit4bit) acoplamiento indirecto a placeholder
+            del current_elem.attrib['facho_optional']
+        except KeyError:
+            pass
+
         return current_elem
 
     def set_element_validator(self, xpath, validator = False):
@@ -267,6 +287,7 @@ class FachoXML:
         format_ = attrs.pop('format_', '%s')
         append_ = attrs.pop('append_', False)
         elem = self.find_or_create_element(xpath, append=append_)
+
         validator = self._validators[xpath]
 
         if not validator(content, attrs):
diff --git a/tests/test_facho.py b/tests/test_facho.py
index 1ddfb48..19d13d7 100644
--- a/tests/test_facho.py
+++ b/tests/test_facho.py
@@ -219,3 +219,39 @@ def test_facho_xml_keep_orden_slibing():
     xml.find_or_create_element('./A', append=True)
 
     assert xml.tostring() == '<root><A/><A/><B/><B/><C/></root>'
+
+def test_facho_xml_placeholder_optional():
+    xml = facho.FachoXML('root')
+    xml.placeholder_for('./A')
+    xml.placeholder_for('./B', optional=True)
+    xml.placeholder_for('./C')
+    
+    assert xml.tostring() == '<root><A/><C/></root>'
+
+def test_facho_xml_placeholder_append_to_optional():
+    xml = facho.FachoXML('root')
+    xml.placeholder_for('./A')
+    xml.placeholder_for('./B', optional=True)
+    xml.placeholder_for('./C')
+
+    xml.find_or_create_element('./B')
+    assert xml.tostring() == '<root><A/><B/><C/></root>'
+
+def test_facho_xml_placeholder_set_element_to_optional():
+    xml = facho.FachoXML('root')
+    xml.placeholder_for('./A')
+    xml.placeholder_for('./B', optional=True)
+    xml.placeholder_for('./C')
+
+    xml.set_element('./B', '2')
+    assert xml.tostring() == '<root><A/><B>2</B><C/></root>'
+
+def test_facho_xml_placeholder_set_element_to_optional_with_append():
+    xml = facho.FachoXML('root')
+    xml.placeholder_for('./A')
+    xml.placeholder_for('./B', optional=True)
+    xml.placeholder_for('./C')
+
+    xml.set_element('./B', '2')
+    xml.set_element('./B', '3', append_=True)
+    assert xml.tostring() == '<root><A/><B>2</B><B>3</B><C/></root>'