11/*
2+ * Elemental
3+ * Copyright (C) 2024, Evolved Binary Ltd
4+ *
5+ * admin@evolvedbinary.com
6+ * https://www.evolvedbinary.com | https://www.elemental.xyz
7+ *
8+ * This library is free software; you can redistribute it and/or
9+ * modify it under the terms of the GNU Lesser General Public
10+ * License as published by the Free Software Foundation; version 2.1.
11+ *
12+ * This library is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+ * Lesser General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Lesser General Public
18+ * License along with this library; if not, write to the Free Software
19+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+ *
21+ * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+ * The original license header is included below.
23+ *
24+ * =====================================================================
25+ *
226 * eXist-db Open Source Native XML Database
327 * Copyright (C) 2001 The eXist-db Authors
428 *
2650import org .exist .util .XMLNames ;
2751import org .exist .xquery .Constants ;
2852
53+ import javax .annotation .Nullable ;
2954import javax .xml .XMLConstants ;
3055import java .util .regex .Matcher ;
3156import java .util .regex .Pattern ;
3257
3358import static org .exist .dom .QName .Validity .*;
59+ import static org .exist .util .StringUtil .isNullOrEmpty ;
3460
3561/**
3662 * Represents a QName, consisting of a local name, a namespace URI and a prefix.
3763 *
3864 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang</a>
65+ * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
3966 */
4067public class QName implements Comparable <QName > {
4168
@@ -136,7 +163,18 @@ public byte getNameType() {
136163 * @return the string representation of this qualified name.
137164 * */
138165 public String getStringValue () {
139- return getStringRepresentation (false );
166+ return getStringRepresentation (false , false );
167+ }
168+
169+ /**
170+ * Get an extended string representation of this qualified name.
171+ *
172+ * Will be of the format `local-name`, `{namespace}local-name`, or `{namespace}prefix:local-name`.
173+ *
174+ * @return the string representation of this qualified name.
175+ */
176+ public String getExtendedStringValue () {
177+ return getStringRepresentation (false , true );
140178 }
141179
142180 /**
@@ -148,23 +186,32 @@ public String getStringValue() {
148186 */
149187 @ Override
150188 public String toString () {
151- return getStringRepresentation (true );
189+ return getStringRepresentation (true , false );
152190 }
153191
154192 /**
155193 * Get a string representation of this qualified name.
156194 *
157195 * @param showNsWithoutPrefix true if the namespace should be shown even when there is no prefix, false otherwise.
158- * When shown, it will be output using Clark notation, e.g. `{http://namespace}local-name`.
196+ * When shown, it will be output using Clark notation, e.g. `{namespace}local-name`.
197+ *
198+ * @param extended true if the namespace and prefix should be shown, requires showNsWithoutPrefix == false.
159199 *
160200 * @return the string representation of this qualified name.
161201 */
162- private String getStringRepresentation (final boolean showNsWithoutPrefix ) {
202+ private String getStringRepresentation (final boolean showNsWithoutPrefix , final boolean extended ) {
163203 if (prefix != null && !prefix .isEmpty ()) {
164- return prefix + COLON + localPart ;
165- } else if (showNsWithoutPrefix && namespaceURI != null && !XMLConstants .NULL_NS_URI .equals (namespaceURI )) {
204+ if (extended ) {
205+ return LEFT_BRACE + namespaceURI + RIGHT_BRACE + prefix + COLON + localPart ;
206+ } else {
207+ return prefix + COLON + localPart ;
208+ }
209+ }
210+
211+ if (showNsWithoutPrefix && namespaceURI != null && !XMLConstants .NULL_NS_URI .equals (namespaceURI )) {
166212 return LEFT_BRACE + namespaceURI + RIGHT_BRACE + localPart ;
167213 }
214+
168215 return localPart ;
169216 }
170217
@@ -343,6 +390,52 @@ public static QName parse(final String namespaceURI, final String qname) throws
343390 private final static Pattern ptnClarkNotation = Pattern .compile ("\\ {([^&{}]*)\\ }([^&{}:]+)" );
344391 private final static Pattern ptnEqNameNotation = Pattern .compile ("Q" + ptnClarkNotation );
345392
393+ /**
394+ * Extract a QName from a namespace and qualified name string.
395+ *
396+ * @param extendedStringValue a string representation as produced by {@link #getExtendedStringValue()}, i.e.: `local-name`, `{namespace}local-name`, or `{namespace}prefix:local-name`.
397+ * @return The QName
398+ * @throws IllegalQNameException if the qname component is invalid
399+ */
400+ public static QName parse (String extendedStringValue ) throws IllegalQNameException {
401+ if (isNullOrEmpty (extendedStringValue )) {
402+ throw new IllegalQNameException (ILLEGAL_FORMAT .val , "Illegal extended string QName is empty" );
403+ }
404+
405+ final String namespaceUri ;
406+ if (extendedStringValue .charAt (0 ) == LEFT_BRACE ) {
407+ final int idxNsEnd = extendedStringValue .indexOf (RIGHT_BRACE );
408+ if (idxNsEnd == Constants .STRING_NOT_FOUND ) {
409+ throw new IllegalQNameException (ILLEGAL_FORMAT .val , "Illegal extended string QName, missing right brace: '" + extendedStringValue + "'" );
410+ }
411+ namespaceUri = extendedStringValue .substring (1 , idxNsEnd );
412+ extendedStringValue = extendedStringValue .substring (idxNsEnd + 1 );
413+ } else if (extendedStringValue .indexOf (RIGHT_BRACE ) != Constants .STRING_NOT_FOUND ) {
414+ throw new IllegalQNameException (ILLEGAL_FORMAT .val , "Illegal extended string QName, missing left brace: '" + extendedStringValue + "'" );
415+ } else {
416+ namespaceUri = XMLConstants .NULL_NS_URI ;
417+ }
418+
419+ @ Nullable final String prefix ;
420+ final int idxColon = extendedStringValue .indexOf (COLON );
421+ if (idxColon == Constants .STRING_NOT_FOUND ) {
422+ prefix = null ;
423+ } else {
424+ prefix = extendedStringValue .substring (0 , idxColon );
425+ if (!XMLNames .isNCName (prefix )) {
426+ throw new IllegalQNameException (INVALID_PREFIX .val , "Illegal extended string QName, invalid prefix: '" + extendedStringValue + "'" );
427+ }
428+ extendedStringValue = extendedStringValue .substring (idxColon + 1 );
429+ }
430+
431+ final String localPart = extendedStringValue ;
432+ if (!XMLNames .isNCName (localPart )) {
433+ throw new IllegalQNameException (INVALID_LOCAL_PART .val , "Illegal extended string QName, invalid prefix: '" + extendedStringValue + "'" );
434+ }
435+
436+ return new QName (localPart , namespaceUri , prefix );
437+ }
438+
346439 /**
347440 * Parses the given string into a QName. The method uses context to look up
348441 * a namespace URI for an existing prefix.
0 commit comments