2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Ericsson
4 * Modifications Copyright (C) 2024 OpenInfra Foundation Europe
5 * ================================================================================
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 * SPDX-License-Identifier: Apache-2.0
19 * ============LICENSE_END=========================================================
21 package org.oran.smo.yangtools.parser.model.util;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.List;
28 * Utility class to help with Yang grammar.
30 * @author Mark Hollmann
32 public abstract class GrammarHelper {
35 * Returns whether the supplied string contains a white-space according to the RFC.
36 * White space characters are SPACE (0x20) and HORIZONTAL TAB (0x09).
38 public static boolean containsYangWhitespace(final String value) {
39 if (value == null || value.isEmpty()) {
43 for (int i = 0; i < value.length(); ++i) {
44 if (value.charAt(i) == 32 || value.charAt(i) == 9) {
52 * Returns whether the supplied string is a valid YANG identifier according to the RFC.
54 * A valid identifier must:
56 * Start with '_' or 'A-Z' or 'a-z'
57 * Followed by '_' or '-' or '.' or 'A-Z' or 'a-z' or '0-9'
59 * Note especially that other unicode characters are not allowed, and that an identifier MUST NOT start with a digit.
61 public static boolean isYangIdentifier(final String stringToCheck) {
63 if (stringToCheck == null || stringToCheck.isEmpty()) {
67 final char firstChar = stringToCheck.charAt(0);
68 if (!firstCharacterOkForYangIdentifier(firstChar)) {
72 for (int i = 1; i < stringToCheck.length(); ++i) {
73 final char c = stringToCheck.charAt(i);
74 if (!otherCharacterOkForYangIdentifier(c)) {
82 private static boolean firstCharacterOkForYangIdentifier(final char c) {
83 return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c == 95; // A-Z a-z _
86 private static boolean otherCharacterOkForYangIdentifier(final char c) {
87 return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || (c >= 48 && c <= 57) || c == '_' || c == '-' || c == '.';
91 * Returns whether the supplied string is an identifier reference according to the RFC.
93 * An identifier is either prefix:identifier or just an identifier.
95 * The rules for prefix are the same as for identifier.
97 public static boolean isYangIdentifierReference(final String stringToCheck) {
99 if (stringToCheck == null || stringToCheck.isEmpty()) {
103 if (stringToCheck.charAt(0) == ':' || stringToCheck.charAt(stringToCheck.length() - 1) == ':') {
107 if (stringToCheck.contains(":")) {
108 final String[] split = stringToCheck.split(":");
109 if (split.length != 2) {
112 return isYangIdentifier(split[0]) && isYangIdentifier(split[1]);
114 return isYangIdentifier(stringToCheck);
119 * Given a string containing white-space separated strings, extracts those. Typically used where an
120 * argument allows for a "space"-separated list of data node names, such as the 'key' or 'unique'
123 * The RFC is contradictory: In chapter 7.8.2 it says that leaf names are separated by "space character",
124 * but the official grammar definition in chapter 14 allows any whitespace (and line break). We err
125 * on the side of caution and align with the official grammar.
127 public static List<String> parseToStringList(final String value) {
129 if (value == null || value.trim().isEmpty()) {
130 return Collections.emptyList();
133 final char[] asChars = value.toCharArray();
135 if (!containsWhitespace(asChars)) {
136 return Collections.singletonList(value);
139 final List<String> result = new ArrayList<>();
141 StringBuilder sb = null;
143 for (final char c : asChars) {
145 if (isWhitespace(c)) { // end of a single string reached
146 result.add(sb.toString());
149 sb.append(c); // add to the single string
152 if (!isWhitespace(c)) { // new string starts
153 sb = new StringBuilder(20);
159 if (sb != null) { // value did not end with whitespace (it rarely does), so add remainder
160 result.add(sb.toString());
166 private static boolean containsWhitespace(final char[] chars) {
167 for (final char c : chars) {
168 if (isWhitespace(c)) {
175 private static boolean isWhitespace(final char c) {
176 return (c == ' ' || c == '\t' || c == '\n');
180 * Returns whether the supplied string can be legally used in YANG unquoted.
182 * In general, any string can be expressed quoted, but for readability this is
183 * omitted where possible.
187 * An unquoted string is any sequence of characters that does not contain any space,
188 * tab, carriage return, or line feed characters, a single or double quote character,
189 * a semicolon (";"), braces ("{" or "}"), or comment sequences (double-slash,
190 * slash-star, star-slash)
192 * Note that any keyword can legally appear as an unquoted string. Within an unquoted
193 * string, every character is preserved. Note that this means that the backslash
194 * character does not have any special meaning in an unquoted string.
196 public static boolean isUnquotableString(final String valueToCheck) {
198 if (valueToCheck.isEmpty()) {
199 return false; // correct - empty string must always be represented as "" in YANG.
202 if (valueToCheck.contains("//") || valueToCheck.contains("/*") || valueToCheck.contains("*/")) {
206 for (final char c : valueToCheck.toCharArray()) {
207 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\'' || c == '"' || c == ';' || c == '{' || c == '}' || c == '+') {