manyfest 1.0.13 → 1.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "optOut": false,
3
- "lastUpdateCheck": 1666359250724
3
+ "lastUpdateCheck": 1668357514653
4
4
  }
package/README.md CHANGED
@@ -581,4 +581,6 @@ let _Schema = new libManyfest(schemaArchiveOrg);
581
581
 
582
582
  console.log(`The URL for "${_Schema.getValueByHash(dataArchiveOrg,'Title')}" is: ${_Schema.getValueByHash(dataArchiveOrg,'Server')}${_Schema.getValueByHash(dataArchiveOrg,'Path')}`);
583
583
  ```
584
+ # Architectural TODO:
584
585
 
586
+ Change the complex address resolution functions to leverage a single resolver that returns both `container` and `entry`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "manyfest",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "JSON Object Manifest for Data Description and Parsing",
5
5
  "main": "source/Manyfest.js",
6
6
  "scripts": {
@@ -0,0 +1,28 @@
1
+ // When a boxed property is passed in, it should have quotes of some
2
+ // kind around it.
3
+ //
4
+ // For instance:
5
+ // MyValues['Name']
6
+ // MyValues["Age"]
7
+ // MyValues[`Cost`]
8
+ //
9
+ // This function removes the wrapping quotes.
10
+ //
11
+ // Please note it *DOES NOT PARSE* template literals, so backticks just
12
+ // end up doing the same thing as other quote types.
13
+ //
14
+ // TODO: Should template literals be processed? If so what state do they have access to? That should happen here if so.
15
+ // TODO: Make a simple class include library with these
16
+ let cleanWrapCharacters = (pCharacter, pString) =>
17
+ {
18
+ if (pString.startsWith(pCharacter) && pString.endsWith(pCharacter))
19
+ {
20
+ return pString.substring(1, pString.length - 1);
21
+ }
22
+ else
23
+ {
24
+ return pString;
25
+ }
26
+ };
27
+
28
+ module.exports = cleanWrapCharacters;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @license MIT
3
+ * @author <steven@velozo.com>
4
+ */
5
+ let libSimpleLog = require('./Manyfest-LogToConsole.js');
6
+
7
+ /**
8
+ * Object Address Resolver
9
+ *
10
+ * IMPORTANT NOTE: This code is intentionally more verbose than necessary, to
11
+ * be extremely clear what is going on in the recursion for
12
+ * each of the three address resolution functions.
13
+ *
14
+ * Although there is some opportunity to repeat ourselves a
15
+ * bit less in this codebase (e.g. with detection of arrays
16
+ * versus objects versus direct properties), it can make
17
+ * debugging.. challenging. The minified version of the code
18
+ * optimizes out almost anything repeated in here. So please
19
+ * be kind and rewind... meaning please keep the codebase less
20
+ * terse and more verbose so humans can comprehend it.
21
+ *
22
+ *
23
+ * @class ManyfestObjectAddressResolverCheckAddressExists
24
+ */
25
+ class ManyfestObjectAddressResolverCheckAddressExists
26
+ {
27
+ constructor(pInfoLog, pErrorLog)
28
+ {
29
+ // Wire in logging
30
+ this.logInfo = (typeof(pInfoLog) == 'function') ? pInfoLog : libSimpleLog;
31
+ this.logError = (typeof(pErrorLog) == 'function') ? pErrorLog : libSimpleLog;
32
+
33
+ this.elucidatorSolver = false;
34
+ this.elucidatorSolverState = {};
35
+ }
36
+
37
+ // Check if an address exists.
38
+ //
39
+ // This is necessary because the getValueAtAddress function is ambiguous on
40
+ // whether the element/property is actually there or not (it returns
41
+ // undefined whether the property exists or not). This function checks for
42
+ // existance and returns true or false dependent.
43
+ checkAddressExists (pObject, pAddress)
44
+ {
45
+ // TODO: Should these throw an error?
46
+ // Make sure pObject is an object
47
+ if (typeof(pObject) != 'object') return false;
48
+ // Make sure pAddress is a string
49
+ if (typeof(pAddress) != 'string') return false;
50
+
51
+ // TODO: Make this work for things like SomeRootObject.Metadata["Some.People.Use.Bad.Object.Property.Names"]
52
+ let tmpSeparatorIndex = pAddress.indexOf('.');
53
+
54
+ // This is the terminal address string (no more dots so the RECUSION ENDS IN HERE somehow)
55
+ if (tmpSeparatorIndex == -1)
56
+ {
57
+ // Check if the address refers to a boxed property
58
+ let tmpBracketStartIndex = pAddress.indexOf('[');
59
+ let tmpBracketStopIndex = pAddress.indexOf(']');
60
+ // Boxed elements look like this:
61
+ // MyValues[10]
62
+ // MyValues['Name']
63
+ // MyValues["Age"]
64
+ // MyValues[`Cost`]
65
+ //
66
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
67
+ // The requirements to detect a boxed element are:
68
+ // 1) The start bracket is after character 0
69
+ if ((tmpBracketStartIndex > 0)
70
+ // 2) The end bracket has something between them
71
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
72
+ // 3) There is data
73
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
74
+ {
75
+ // The "Name" of the Object contained too the left of the bracket
76
+ let tmpBoxedPropertyName = pAddress.substring(0, tmpBracketStartIndex).trim();
77
+
78
+ // If the subproperty doesn't test as a proper Object, none of the rest of this is possible.
79
+ // This is a rare case where Arrays testing as Objects is useful
80
+ if (typeof(pObject[tmpBoxedPropertyName]) !== 'object')
81
+ {
82
+ return false;
83
+ }
84
+
85
+ // The "Reference" to the property within it, either an array element or object property
86
+ let tmpBoxedPropertyReference = pAddress.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
87
+ // Attempt to parse the reference as a number, which will be used as an array element
88
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
89
+
90
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
91
+ // This seems confusing to me at first read, so explaination:
92
+ // Is the Boxed Object an Array? TRUE
93
+ // And is the Reference inside the boxed Object not a number? TRUE
94
+ // --> So when these are in agreement, it's an impossible access state
95
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
96
+ {
97
+ return false;
98
+ }
99
+
100
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
101
+ // otherwise we will try to treat it as a dynamic object property.
102
+ if (isNaN(tmpBoxedPropertyNumber))
103
+ {
104
+ // This isn't a number ... let's treat it as a dynamic object property.
105
+ // We would expect the property to be wrapped in some kind of quotes so strip them
106
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
107
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
108
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
109
+
110
+ // Check if the property exists.
111
+ return pObject[tmpBoxedPropertyName].hasOwnProperty(tmpBoxedPropertyReference);
112
+ }
113
+ else
114
+ {
115
+ // Use the new in operator to see if the element is in the array
116
+ return (tmpBoxedPropertyNumber in pObject[tmpBoxedPropertyName]);
117
+ }
118
+ }
119
+ else
120
+ {
121
+ // Check if the property exists
122
+ return pObject.hasOwnProperty(pAddress);
123
+ }
124
+ }
125
+ else
126
+ {
127
+ let tmpSubObjectName = pAddress.substring(0, tmpSeparatorIndex);
128
+ let tmpNewAddress = pAddress.substring(tmpSeparatorIndex+1);
129
+
130
+ // Test if the tmpNewAddress is an array or object
131
+ // Check if it's a boxed property
132
+ let tmpBracketStartIndex = tmpSubObjectName.indexOf('[');
133
+ let tmpBracketStopIndex = tmpSubObjectName.indexOf(']');
134
+ // Boxed elements look like this:
135
+ // MyValues[42]
136
+ // MyValues['Color']
137
+ // MyValues["Weight"]
138
+ // MyValues[`Diameter`]
139
+ //
140
+ // When we are passed SomeObject["Name"] this code below recurses as if it were SomeObject.Name
141
+ // The requirements to detect a boxed element are:
142
+ // 1) The start bracket is after character 0
143
+ if ((tmpBracketStartIndex > 0)
144
+ // 2) The end bracket has something between them
145
+ && (tmpBracketStopIndex > tmpBracketStartIndex)
146
+ // 3) There is data
147
+ && (tmpBracketStopIndex - tmpBracketStartIndex > 1))
148
+ {
149
+ let tmpBoxedPropertyName = tmpSubObjectName.substring(0, tmpBracketStartIndex).trim();
150
+
151
+ let tmpBoxedPropertyReference = tmpSubObjectName.substring(tmpBracketStartIndex+1, tmpBracketStopIndex).trim();
152
+
153
+ let tmpBoxedPropertyNumber = parseInt(tmpBoxedPropertyReference, 10);
154
+
155
+ // Guard: If the referrant is a number and the boxed property is not an array, or vice versa, return undefined.
156
+ // This seems confusing to me at first read, so explaination:
157
+ // Is the Boxed Object an Array? TRUE
158
+ // And is the Reference inside the boxed Object not a number? TRUE
159
+ // --> So when these are in agreement, it's an impossible access state
160
+ // This could be a failure in the recursion chain because they passed something like this in:
161
+ // StudentData.Sections.Algebra.Students[1].Tardy
162
+ // BUT
163
+ // StudentData.Sections.Algebra.Students is an object, so the [1].Tardy is not possible to access
164
+ // This could be a failure in the recursion chain because they passed something like this in:
165
+ // StudentData.Sections.Algebra.Students["JaneDoe"].Grade
166
+ // BUT
167
+ // StudentData.Sections.Algebra.Students is an array, so the ["JaneDoe"].Grade is not possible to access
168
+ // TODO: Should this be an error or something? Should we keep a log of failures like this?
169
+ if (Array.isArray(pObject[tmpBoxedPropertyName]) == isNaN(tmpBoxedPropertyNumber))
170
+ {
171
+ // Because this is an impossible address, the property doesn't exist
172
+ // TODO: Should we throw an error in this condition?
173
+ return false;
174
+ }
175
+
176
+ //This is a bracketed value
177
+ // 4) If the middle part is *only* a number (no single, double or backtick quotes) it is an array element,
178
+ // otherwise we will try to reat it as a dynamic object property.
179
+ if (isNaN(tmpBoxedPropertyNumber))
180
+ {
181
+ // This isn't a number ... let's treat it as a dynanmic object property.
182
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('"', tmpBoxedPropertyReference);
183
+ tmpBoxedPropertyReference = this.cleanWrapCharacters('`', tmpBoxedPropertyReference);
184
+ tmpBoxedPropertyReference = this.cleanWrapCharacters("'", tmpBoxedPropertyReference);
185
+
186
+ // Recurse directly into the subobject
187
+ return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyReference], tmpNewAddress);
188
+ }
189
+ else
190
+ {
191
+ // We parsed a valid number out of the boxed property name, so recurse into the array
192
+ return this.checkAddressExists(pObject[tmpBoxedPropertyName][tmpBoxedPropertyNumber], tmpNewAddress);
193
+ }
194
+ }
195
+
196
+ // If there is an object property already named for the sub object, but it isn't an object
197
+ // then the system can't set the value in there. Error and abort!
198
+ if (pObject.hasOwnProperty(tmpSubObjectName) && typeof(pObject[tmpSubObjectName]) !== 'object')
199
+ {
200
+ return false;
201
+ }
202
+ else if (pObject.hasOwnProperty(tmpSubObjectName))
203
+ {
204
+ // If there is already a subobject pass that to the recursive thingy
205
+ return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
206
+ }
207
+ else
208
+ {
209
+ // Create a subobject and then pass that
210
+ pObject[tmpSubObjectName] = {};
211
+ return this.checkAddressExists(pObject[tmpSubObjectName], tmpNewAddress);
212
+ }
213
+ }
214
+ }
215
+ };
216
+
217
+ module.exports = ManyfestObjectAddressResolverCheckAddressExists;