1 package org.opentrafficsim.editor;
2
3 import java.rmi.RemoteException;
4 import java.util.ArrayList;
5 import java.util.LinkedHashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.regex.Pattern;
9
10 import org.djutils.event.Event;
11 import org.djutils.event.EventListener;
12 import org.djutils.exceptions.Throw;
13 import org.djutils.immutablecollections.Immutable;
14 import org.djutils.immutablecollections.ImmutableArrayList;
15 import org.djutils.immutablecollections.ImmutableList;
16 import org.opentrafficsim.editor.decoration.validation.XsdAllValidator;
17 import org.w3c.dom.Node;
18
19
20
21
22
23
24
25
26
27 public final class XsdTreeNodeUtil
28 {
29
30
31 private static final Pattern UPPER_PATTERN = Pattern.compile("(?=\\p{Lu})");
32
33
34 private final static Map<String, XsdAllValidator> XSD_ALL_VALIDATORS = new LinkedHashMap<>();
35
36
37
38
39 private XsdTreeNodeUtil()
40 {
41
42 }
43
44
45
46
47
48
49 public static void addXsdAllValidator(final XsdTreeNode shared, final XsdTreeNode node)
50 {
51 String path = shared.getPathString();
52 XsdAllValidator validator = XSD_ALL_VALIDATORS.computeIfAbsent(path, (p) -> new XsdAllValidator(node.getRoot()));
53 node.addNodeValidator(validator);
54 validator.addNode(node);
55 }
56
57
58
59
60
61
62
63 static int getOccurs(final Node node, final String attribute)
64 {
65 String occurs = DocumentReader.getAttribute(node, attribute);
66 if (occurs == null)
67 {
68 return 1;
69 }
70 if ("unbounded".equals(occurs))
71 {
72 return -1;
73 }
74 return Integer.valueOf(occurs);
75 }
76
77
78
79
80
81
82
83
84
85
86
87
88
89 static void addChildren(final Node node, final XsdTreeNode parentNode, final List<XsdTreeNode> children,
90 final ImmutableList<Node> hiddenNodes, final Schema schema, final boolean flattenSequence, final int skip)
91 {
92 int skipIndex = skip;
93 XsdTreeNode root = parentNode.getRoot();
94 for (int childIndex = 0; childIndex < node.getChildNodes().getLength(); childIndex++)
95 {
96 Node child = node.getChildNodes().item(childIndex);
97 switch (child.getNodeName())
98 {
99 case "xsd:element":
100 if (children.size() == skipIndex)
101 {
102 skipIndex = -1;
103 break;
104 }
105 XsdTreeNode element;
106 String ref = DocumentReader.getAttribute(child, "ref");
107 String type = DocumentReader.getAttribute(child, "type");
108 if (ref != null)
109 {
110 element = new XsdTreeNode(parentNode, XsdTreeNodeUtil.ref(child, ref, schema),
111 XsdTreeNodeUtil.append(hiddenNodes, node), child);
112 }
113 else if (type != null)
114 {
115 Node typedNode = XsdTreeNodeUtil.type(child, type, schema);
116 if (typedNode == null)
117 {
118
119 element = new XsdTreeNode(parentNode, child, XsdTreeNodeUtil.append(hiddenNodes, node));
120 }
121 else
122 {
123 element = new XsdTreeNode(parentNode, typedNode, XsdTreeNodeUtil.append(hiddenNodes, node), child);
124 }
125 }
126 else
127 {
128 element = new XsdTreeNode(parentNode, child, XsdTreeNodeUtil.append(hiddenNodes, node));
129 }
130 children.add(element);
131 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
132 new Object[] {element, parentNode, parentNode.children.indexOf(element)});
133 break;
134 case "xsd:sequence":
135 if (children.size() == skipIndex)
136 {
137 skipIndex = -1;
138 break;
139 }
140 if (flattenSequence)
141 {
142 addChildren(child, parentNode, children, XsdTreeNodeUtil.append(hiddenNodes, node), schema,
143 flattenSequence, -1);
144 }
145 else
146 {
147
148 XsdTreeNode sequence = new XsdTreeNode(parentNode, child, XsdTreeNodeUtil.append(hiddenNodes, node));
149 children.add(sequence);
150 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
151 new Object[] {sequence, parentNode, parentNode.children.indexOf(sequence)});
152 }
153 break;
154 case "xsd:choice":
155 case "xsd:all":
156 if (children.size() == skipIndex)
157 {
158 skipIndex = -1;
159 break;
160 }
161 XsdTreeNode choice = new XsdTreeNode(parentNode, child, XsdTreeNodeUtil.append(hiddenNodes, node));
162 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
163 new Object[] {choice, parentNode, parentNode.children.indexOf(choice)});
164 choice.createOptions();
165
166
167
168
169
170
171 children.add(choice);
172 choice.setOption(choice.options.get(0));
173 break;
174 case "xsd:extension":
175 if (children.size() == skipIndex)
176 {
177 skipIndex = -1;
178 break;
179 }
180 XsdTreeNode extension = new XsdTreeNode(parentNode, child, XsdTreeNodeUtil.append(hiddenNodes, node));
181 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
182 new Object[] {extension, parentNode, parentNode.children.indexOf(extension)});
183 children.add(extension);
184 break;
185 case "xsd:attribute":
186 case "xsd:annotation":
187 case "xsd:simpleType":
188 case "xsd:restriction":
189 case "xsd:simpleContent":
190 case "xsd:union":
191 case "#text":
192
193 break;
194 default:
195 System.out.println("Ignoring a " + child.getNodeName());
196 }
197 }
198 }
199
200
201
202
203
204
205
206 static ImmutableList<Node> append(final ImmutableList<Node> hiddenNodes, final Node node)
207 {
208 List<Node> list = new ArrayList<>(hiddenNodes.size() + 1);
209 list.addAll(hiddenNodes.toCollection());
210 list.add(node);
211 return new ImmutableArrayList<>(list, Immutable.WRAP);
212 }
213
214
215
216
217
218
219 static List<String> getOptionsFromRestrictions(final List<Node> restrictions)
220 {
221 List<String> options = new ArrayList<>();
222 for (Node restriction : restrictions)
223 {
224 List<Node> enumerations = DocumentReader.getChildren(restriction, "xsd:enumeration");
225 for (Node enumeration : enumerations)
226 {
227 options.add(DocumentReader.getAttribute(enumeration, "value"));
228 }
229 }
230 return options;
231 }
232
233
234
235
236
237
238
239 protected static void fireCreatedEventOnExistingNodes(final XsdTreeNode node, final EventListener listener)
240 throws RemoteException
241 {
242 List<XsdTreeNode> subNodes = node.children == null ? new ArrayList<>() : new ArrayList<>(node.children);
243
244 if (node.choice != null && node.choice.selected.equals(node))
245 {
246 subNodes.add(node.choice);
247 subNodes.addAll(node.choice.options);
248 subNodes.remove(node);
249 }
250 for (XsdTreeNode child : subNodes)
251 {
252 fireCreatedEventOnExistingNodes(child, listener);
253 }
254 Event event = new Event(XsdTreeNodeRoot.NODE_CREATED, new Object[] {node, node.getParent(), subNodes.indexOf(node)});
255 listener.notify(event);
256 }
257
258
259
260
261
262
263
264 static int resolveInsertion(final int insertIndex, final int removeIndex)
265 {
266 int tmp = insertIndex < 0 ? removeIndex : insertIndex;
267 return tmp < removeIndex ? tmp : removeIndex;
268 }
269
270
271
272
273
274
275
276
277
278
279
280
281
282 static Map<Node, ImmutableList<Node>> getRelevantNodesWithChildren(final Node node, final ImmutableList<Node> hiddenNodes,
283 final Schema schema)
284 {
285 Node complexType =
286 node.getNodeName().equals("xsd:complexType") ? node : DocumentReader.getChild(node, "xsd:complexType");
287 if (complexType != null)
288 {
289 Node sequence = DocumentReader.getChild(complexType, "xsd:sequence");
290 if (sequence != null)
291 {
292 return Map.of(sequence, append(hiddenNodes, complexType));
293 }
294 Node complexContent = DocumentReader.getChild(complexType, "xsd:complexContent");
295 if (complexContent != null)
296 {
297 Node extension = DocumentReader.getChild(complexContent, "xsd:extension");
298 if (extension != null)
299 {
300 ImmutableList<Node> hiddenExtension = append(append(hiddenNodes, complexType), complexContent);
301 LinkedHashMap<Node, ImmutableList<Node>> elements = new LinkedHashMap<>();
302 String base = DocumentReader.getAttribute(extension, "base");
303 if (base != null)
304 {
305 Node baseNode = schema.getType(base);
306 if (baseNode != null)
307 {
308 elements.putAll(getRelevantNodesWithChildren(baseNode, append(hiddenExtension, extension), schema));
309 }
310 }
311 elements.put(extension, hiddenExtension);
312 return elements;
313 }
314 }
315 return Map.of(complexType, hiddenNodes);
316 }
317 return Map.of(node, hiddenNodes);
318 }
319
320
321
322
323
324
325
326
327 static boolean haveSameType(final XsdTreeNode node1, final XsdTreeNode node2)
328 {
329 return (node1.referringXsdNode != null && node1.referringXsdNode.equals(node2.referringXsdNode))
330 || (node1.referringXsdNode == null && node1.xsdNode.equals(node2.xsdNode));
331 }
332
333
334
335
336
337
338
339
340
341 static Node ref(final Node node, final String ref, final Schema schema)
342 {
343 if (ref.equals("xi:include"))
344 {
345 return XiIncludeNode.XI_INCLUDE;
346 }
347 Node refNode = schema.getElement(ref);
348 Throw.when(refNode == null, RuntimeException.class, "Unable to load ref for %s from XSD schema.", ref);
349 return refNode;
350 }
351
352
353
354
355
356
357
358
359
360 static Node type(final Node node, final String type, final Schema schema)
361 {
362 if (type.startsWith("xsd:"))
363 {
364 return null;
365 }
366 Node typeNode = schema.getType(type);
367 Throw.when(typeNode == null, RuntimeException.class, "Unable to load type for %s from XSD schema.", type);
368 return typeNode;
369 }
370
371
372
373
374
375
376 static String separatedName(final String name)
377 {
378 String[] parts = UPPER_PATTERN.split(name);
379 if (parts.length == 1)
380 {
381 return parts[0];
382 }
383 String separator = "";
384 StringBuilder stringBuilder = new StringBuilder();
385 for (String part : parts)
386 {
387 stringBuilder.append(separator).append(part);
388 separator = " ";
389 }
390 return stringBuilder.toString();
391 }
392
393
394
395
396
397
398
399 public static boolean valuesAreEqual(final String value1, final String value2)
400 {
401 boolean value1Empty = value1 == null || value1.isEmpty();
402 boolean value2Empty = value2 == null || value2.isEmpty();
403 return (value1Empty && value2Empty) || (value1 != null && value1.equals(value2));
404 }
405
406 }