cello
JUCE ValueTrees for Humans
Loading...
Searching...
No Matches
cello_path.cpp
1/*
2 Copyright (c) 2023 Brett g Porter
3 Permission is hereby granted, free of charge, to any person obtaining a copy
4 of this software and associated documentation files (the "Software"), to deal
5 in the Software without restriction, including without limitation the rights
6 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 copies of the Software, and to permit persons to whom the Software is
8 furnished to do so, subject to the following conditions:
9 The above copyright notice and this permission notice shall be included in all
10 copies or substantial portions of the Software.
11 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
16 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
17 SOFTWARE.
18*/
19
20#include "cello_path.h"
21
22namespace
23{
24juce::ValueTree findRoot (juce::ValueTree& origin)
25{
26 auto current { origin };
27 for (;;)
28 {
29 auto parent { current.getParent () };
30 if (!parent.isValid ())
31 return current;
32 current = parent;
33 }
34}
35
36juce::ValueTree findAncestor (juce::ValueTree& origin,
37 const juce::Identifier ancestorType)
38{
39 auto current { origin };
40 for (;;)
41 {
42 // pop up a level.
43 auto parent { current.getParent () };
44 // we hit the root without finding that ancestor; bail out.
45 if (!parent.isValid ())
46 return {};
47 // found it!
48 if (parent.hasType (ancestorType))
49 return parent;
50 // keep looking up a level.
51 current = parent;
52 }
53}
54
55} // namespace
56
57namespace cello
58{
59juce::ValueTree Path::findValueTree (juce::ValueTree& origin, Path::SearchType searchType,
60 juce::UndoManager* undo)
61{
62 // nothing to look for!
63 if (pathSegments.size () == 0)
64 return {};
65
66 if (!origin.isValid ())
67 {
68 // can't query an empty tree
69 if (searchType == SearchType::query)
70 return {};
71 // can't create a hierarchy starting without a root.
72 if (pathSegments.size () > 1)
73 return {};
74 // We need a real type name, not a relative path character (or garbage)
75 if (!juce::Identifier::isValidIdentifier (pathSegments[0]))
76 return {};
77 // create and return the new root tree.
78 searchResult = SearchResult::created;
79 return juce::ValueTree (pathSegments[0]);
80 }
81
82 auto currentTree { origin };
83
84 // special case: if there's only 1 segment and it matches the type of the current
85 // tree, treat it the same as "." (current tree) and just return it directly. If
86 // it's a different type, fall into the code below that will look for a child
87 // tree of the requested type.
88 if (pathSegments.size () == 1 &&
89 (pathSegments[0] == currentTree.getType ().toString ()))
90 return currentTree;
91
92 for (int i { 0 }; i < pathSegments.size () && currentTree.isValid (); ++i)
93 {
94 const auto& segment { pathSegments[i] };
95 const auto isLastSegment { i == (pathSegments.size () - 1) };
96 if (segment == sep)
97 {
98 currentTree = findRoot (origin);
99 }
100 else if (segment == parent)
101 {
102 currentTree = currentTree.getParent ();
103 }
104 else if ((segment == current) || (segment.isEmpty ()))
105 {
106 // do nothing -- current tree remains the same
107 }
108 else if (segment.startsWith (ancestor))
109 {
110 currentTree = findAncestor (
111 currentTree, segment.trimCharactersAtStart (juce::String { ancestor }));
112 }
113 else
114 {
115 // next segment is a child of the current tree
116 auto childTree { currentTree.getChildWithName (segment) };
117 if (searchType == SearchType::query)
118 {
119 currentTree = childTree;
120 }
121 else
122 {
123 if (!childTree.isValid ())
124 {
125 // doesn't exist...yet. Create and add to the current tree?
126 if (isLastSegment || (searchType == SearchType::createAll))
127 {
128 childTree = juce::ValueTree (segment);
129 currentTree.appendChild (childTree, undo);
130 searchResult = SearchResult::created;
131 }
132 }
133 currentTree = childTree;
134 }
135 };
136 }
137 if (searchResult != SearchResult::created)
138 searchResult =
139 currentTree.isValid () ? SearchResult::found : SearchResult::notFound;
140
141 return currentTree;
142}
143
144juce::StringArray Path::parsePathSegments (const juce::String& pathString)
145{
146 auto segments { juce::StringArray::fromTokens (pathString, sep, "") };
147 for (auto& segment : segments)
148 {
149 // get rid of any internal whitespace
150 segment = segment.trim ();
151 }
152 // if the path started with our separator '/', re-insert it at the beginning of the
153 // list so our find function works correctly.
154 if (pathString.startsWith (sep))
155 segments.insert (0, sep);
156 return segments;
157}
158
159} // namespace cello
160#if RUN_UNIT_TESTS
161#include "test/test_cello_path.inl"
162#endif
SearchType
Definition cello_path.h:64
@ query
only search, do not create anything.
Definition cello_path.h:65
@ createAll
create final tree and all intermediate trees needed to reach it.
Definition cello_path.h:67
juce::ValueTree findValueTree(juce::ValueTree &origin, SearchType searchType, juce::UndoManager *undo=nullptr)
Navigate the path from origin to a tree that is expected at the end of the current path specification...
Definition cello_path.cpp:59
@ created
performing a search created a new tree
Definition cello_path.h:74
@ notFound
unable to find the requested tree
Definition cello_path.h:72
@ found
the sought tree existed already and was found
Definition cello_path.h:73
static const juce::String sep
Path separator.
Definition cello_path.h:53