@@ -24,6 +24,103 @@ namespace yup
2424
2525// ==============================================================================
2626
27+ namespace
28+ {
29+ var coerceAttributeValue (const Identifier& nodeType,
30+ const Identifier& propertyName,
31+ const String& rawValue,
32+ const ReferenceCountedObjectPtr<DataTreeSchema>& schema)
33+ {
34+ if (schema == nullptr )
35+ return var (rawValue);
36+
37+ auto info = schema->getPropertyInfo (nodeType, propertyName);
38+ if (info.type .isEmpty ())
39+ return var (rawValue);
40+
41+ const auto trimmed = rawValue.trim ();
42+
43+ const auto looksLikeInteger = [] (const String& text)
44+ {
45+ if (text.isEmpty ())
46+ return false ;
47+
48+ int start = 0 ;
49+ if (text.startsWithChar (' -' ) || text.startsWithChar (' +' ))
50+ start = 1 ;
51+
52+ if (start == text.length ())
53+ return false ;
54+
55+ for (int i = start; i < text.length (); ++i)
56+ {
57+ if (! CharacterFunctions::isDigit (text[i]))
58+ return false ;
59+ }
60+
61+ return true ;
62+ };
63+
64+ const auto looksLikeNumber = [] (const String& text)
65+ {
66+ bool hasDigit = false ;
67+
68+ for (int i = 0 ; i < text.length (); ++i)
69+ {
70+ const auto c = text[i];
71+ if (CharacterFunctions::isDigit (c))
72+ {
73+ hasDigit = true ;
74+ continue ;
75+ }
76+
77+ if (c == ' .' || c == ' -' || c == ' +' || c == ' e' || c == ' E' )
78+ continue ;
79+
80+ return false ;
81+ }
82+
83+ return hasDigit;
84+ };
85+
86+ if (info.type == " boolean" )
87+ {
88+ if (trimmed.equalsIgnoreCase (" true" ) || trimmed == " 1" || trimmed.equalsIgnoreCase (" yes" ))
89+ return var (true );
90+
91+ if (trimmed.equalsIgnoreCase (" false" ) || trimmed == " 0" || trimmed.equalsIgnoreCase (" no" ))
92+ return var (false );
93+
94+ return var (rawValue);
95+ }
96+
97+ if (info.type == " number" && looksLikeNumber (trimmed))
98+ {
99+ if (looksLikeInteger (trimmed))
100+ return var (trimmed.getLargeIntValue ());
101+
102+ return var (trimmed.getDoubleValue ());
103+ }
104+
105+ if ((info.type == " array" || info.type == " object" ) && trimmed.isNotEmpty ())
106+ {
107+ var parsed;
108+ if (JSON::parse (trimmed, parsed))
109+ {
110+ if (info.type == " array" && parsed.isArray ())
111+ return parsed;
112+
113+ if (info.type == " object" && parsed.isObject ())
114+ return parsed;
115+ }
116+ }
117+
118+ return var (rawValue);
119+ }
120+ } // namespace
121+
122+ // ==============================================================================
123+
27124class PropertySetAction : public UndoableAction
28125{
29126public:
@@ -911,13 +1008,7 @@ std::unique_ptr<XmlElement> DataTree::createXml() const
9111008 auto element = std::make_unique<XmlElement> (object->type .toString ());
9121009
9131010 // Add properties as attributes
914- for (int i = 0 ; i < object->properties .size (); ++i)
915- {
916- const auto name = object->properties .getName (i);
917- const auto value = object->properties .getValueAt (i);
918-
919- element->setAttribute (name.toString (), value.toString ());
920- }
1011+ object->properties .copyToXmlAttributes (*element);
9211012
9221013 // Add children as child elements
9231014 for (const auto & child : object->children )
@@ -930,21 +1021,28 @@ std::unique_ptr<XmlElement> DataTree::createXml() const
9301021}
9311022
9321023DataTree DataTree::fromXml (const XmlElement& xml)
1024+ {
1025+ return fromXml (xml, nullptr );
1026+ }
1027+
1028+ DataTree DataTree::fromXml (const XmlElement& xml, ReferenceCountedObjectPtr<DataTreeSchema> schema)
9331029{
9341030 DataTree tree (xml.getTagName ());
1031+ const auto nodeType = tree.getType ();
9351032
9361033 // Load properties from attributes
9371034 for (int i = 0 ; i < xml.getNumAttributes (); ++i)
9381035 {
939- const auto name = xml.getAttributeName (i);
940- const auto value = xml.getAttributeValue (i);
941- tree.setProperty (name, value);
1036+ auto name = xml.getAttributeName (i);
1037+ auto value = xml.getAttributeValue (i);
1038+
1039+ tree.setProperty (name, coerceAttributeValue (nodeType, name, value, schema));
9421040 }
9431041
9441042 // Load children from child elements
9451043 for (const auto * childXml : xml.getChildIterator ())
9461044 {
947- auto child = fromXml (*childXml);
1045+ auto child = fromXml (*childXml, schema );
9481046 tree.addChild (child);
9491047 }
9501048
@@ -1475,6 +1573,38 @@ void DataTree::Transaction::moveChild (int currentIndex, int newIndex)
14751573 childChanges.push_back (change);
14761574}
14771575
1576+ int DataTree::Transaction::getEffectiveChildCount () const
1577+ {
1578+ if (dataTree.object == nullptr )
1579+ return 0 ;
1580+
1581+ int count = dataTree.getNumChildren ();
1582+
1583+ for (const auto & change : childChanges)
1584+ {
1585+ switch (change.type )
1586+ {
1587+ case ChildChange::Add:
1588+ ++count;
1589+ break ;
1590+
1591+ case ChildChange::Remove:
1592+ if (count > 0 )
1593+ --count;
1594+ break ;
1595+
1596+ case ChildChange::RemoveAll:
1597+ count = 0 ;
1598+ break ;
1599+
1600+ case ChildChange::Move:
1601+ break ; // No change in count
1602+ }
1603+ }
1604+
1605+ return std::max (0 , count);
1606+ }
1607+
14781608// ==============================================================================
14791609
14801610DataTree::ValidatedTransaction::ValidatedTransaction (DataTree& tree, ReferenceCountedObjectPtr<DataTreeSchema> schema, UndoManager* undoManager)
@@ -1555,8 +1685,8 @@ Result DataTree::ValidatedTransaction::addChild (const DataTree& child, int inde
15551685 if (! child.isValid ())
15561686 return Result::fail (" Cannot add invalid child" );
15571687
1558- // TODO: Get current child count from the transaction's target tree
1559- auto validationResult = schema->validateChildAddition (nodeType, child.getType (), 0 );
1688+ const int effectiveChildCount = transaction-> getEffectiveChildCount ();
1689+ auto validationResult = schema->validateChildAddition (nodeType, child.getType (), effectiveChildCount );
15601690 if (validationResult.failed ())
15611691 {
15621692 hasValidationErrors = true ;
@@ -1588,7 +1718,18 @@ Result DataTree::ValidatedTransaction::removeChild (const DataTree& child)
15881718 if (! transaction || ! transaction->isActive () || ! schema)
15891719 return Result::fail (" Transaction is not active" );
15901720
1591- // TODO: Check minimum child count constraints
1721+ if (! schema->hasNodeType (nodeType))
1722+ return Result::fail (" Unknown node type: " + nodeType.toString ());
1723+
1724+ const auto constraints = schema->getChildConstraints (nodeType);
1725+ const int currentCount = transaction->getEffectiveChildCount ();
1726+ const int resultingCount = std::max (0 , currentCount - 1 );
1727+
1728+ if (resultingCount < constraints.minCount )
1729+ {
1730+ hasValidationErrors = true ;
1731+ return Result::fail (" Cannot remove child: would violate minimum child count (" + String (constraints.minCount ) + " )" );
1732+ }
15921733
15931734 transaction->removeChild (child);
15941735 return Result::ok ();
0 commit comments