001/*
002 * ============================================================================
003 * Copyright © 2002-2024 by Thomas Thrien.
004 * All Rights Reserved.
005 * ============================================================================
006 * Licensed to the public under the agreements of the GNU Lesser General Public
007 * License, version 3.0 (the "License"). You may obtain a copy of the License at
008 *
009 *      http://www.gnu.org/licenses/lgpl.html
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014 * License for the specific language governing permissions and limitations
015 * under the License.
016 */
017
018package org.tquadrat.foundation.xml.builder;
019
020import static java.util.Locale.ROOT;
021import static java.util.regex.Pattern.compile;
022import static org.apiguardian.api.API.Status.STABLE;
023import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
024import static org.tquadrat.foundation.lang.Objects.isNull;
025import static org.tquadrat.foundation.lang.Objects.nonNull;
026import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
027import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
028import static org.tquadrat.foundation.util.StringUtils.isNotEmptyOrBlank;
029import static org.tquadrat.foundation.util.StringUtils.splitString;
030import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.Validator.VALIDATOR_AttributeName;
031import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.Validator.VALIDATOR_ElementName;
032import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.Validator.VALIDATOR_NMToken;
033import static org.tquadrat.foundation.xml.builder.XMLBuilderUtils.Validator.VALIDATOR_Prefix;
034
035import java.io.IOException;
036import java.io.Serial;
037import java.lang.ref.WeakReference;
038import java.net.URI;
039import java.nio.charset.Charset;
040import java.util.ArrayList;
041import java.util.Collection;
042import java.util.EventObject;
043import java.util.concurrent.atomic.AtomicReference;
044import java.util.function.Predicate;
045import java.util.function.Supplier;
046import java.util.regex.Pattern;
047import java.util.regex.PatternSyntaxException;
048
049import org.apiguardian.api.API;
050import org.tquadrat.foundation.annotation.ClassVersion;
051import org.tquadrat.foundation.annotation.UtilityClass;
052import org.tquadrat.foundation.exception.NullArgumentException;
053import org.tquadrat.foundation.exception.PrivateConstructorForStaticClassCalledError;
054import org.tquadrat.foundation.util.StringUtils;
055import org.tquadrat.foundation.xml.builder.internal.ProcessingInstructionImpl;
056import org.tquadrat.foundation.xml.builder.internal.XMLDocumentImpl;
057import org.tquadrat.foundation.xml.builder.internal.XMLElementImpl;
058
059/**
060 *  A collection of XML related utility methods and factory methods for XML
061 *  elements.
062 *
063 *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
064 *  @version $Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $
065 *  @since 0.0.5
066 *
067 *  @UMLGraph.link
068 */
069@SuppressWarnings( "ClassWithTooManyMethods" )
070@UtilityClass
071@ClassVersion( sourceVersion = "$Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $" )
072@API( status = STABLE, since = "0.0.5" )
073public final class XMLBuilderUtils
074{
075        /*---------------*\
076    ====** Inner Classes **====================================================
077        \*---------------*/
078    /**
079     *  The (default) validators.
080     *
081     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
082     *  @version $Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $
083     *  @since 0.0.5
084     *
085     *  @UMLGraph.link
086     */
087    @ClassVersion( sourceVersion = "$Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $" )
088    @API( status = STABLE, since = "0.0.5" )
089    public enum Validator
090    {
091            /*------------------*\
092        ====** Enum Declaration **=============================================
093            \*------------------*/
094        /**
095         *  The attribute name validator.
096         */
097        @SuppressWarnings( "synthetic-access" )
098        VALIDATOR_AttributeName( XMLBuilderUtils::isValidAttributeName, XMLBuilderUtils::getAttributeNameValidator ),
099
100        /**
101         *  The element name validator.
102         */
103        @SuppressWarnings( "synthetic-access" )
104        VALIDATOR_ElementName( XMLBuilderUtils::isValidElementName, XMLBuilderUtils::getElementNameValidator ),
105
106        /**
107         *  The nmtoken validator.
108         */
109        @SuppressWarnings( "synthetic-access" )
110        VALIDATOR_NMToken( XMLBuilderUtils::isValidNMToken, XMLBuilderUtils::getNMTokenValidator ),
111
112        /**
113         *  The namespace prefix validator.
114         */
115        @SuppressWarnings( "synthetic-access" )
116        VALIDATOR_Prefix( XMLBuilderUtils::isValidPrefix, XMLBuilderUtils::getPrefixValidator );
117
118            /*------------*\
119        ====** Attributes **===================================================
120            \*------------*/
121        /**
122         *  The method that retrieves the current validator.
123         */
124        @SuppressWarnings( {"NonFinalFieldInEnum", "FieldMayBeFinal"} )
125        private Supplier<? extends Predicate<CharSequence>> m_CurrentValidatorSupplier;
126
127        /**
128         *  The default validator.
129         */
130        @SuppressWarnings( {"FieldMayBeFinal", "NonFinalFieldInEnum"} )
131        private Predicate<CharSequence> m_DefaultValidator;
132
133            /*--------------*\
134        ====** Constructors **=================================================
135            \*--------------*/
136        /**
137         *  Creates a new {@code Validator} instance.
138         *
139         *  @param  defaultValidator    The default validator.
140         *  @param  currentValidatorSupplier    The method that retrieves the
141         *      current validator.
142         */
143        private Validator( final Predicate<CharSequence> defaultValidator, final Supplier<? extends Predicate<CharSequence>> currentValidatorSupplier )
144        {
145            m_CurrentValidatorSupplier = currentValidatorSupplier;
146            m_DefaultValidator = defaultValidator;
147        }   //  Validator()
148
149            /*---------*\
150        ====** Methods **======================================================
151            \*---------*/
152        /**
153         *  Returns the current validator.
154         *
155         *  @return The current validator.
156         */
157        public final Predicate<CharSequence> getCurrent() { return m_CurrentValidatorSupplier.get(); }
158
159        /**
160         *  Returns the default validator.
161         *
162         *  @return The default validator.
163         */
164        @SuppressWarnings( "SuspiciousGetterSetter" )
165        public final Predicate<CharSequence> getDefault() { return m_DefaultValidator; }
166    }
167    //  enum Validator
168
169    /**
170     *  The
171     *  {@link EventObject}
172     *  for changes to the validator configuration.
173     *
174     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
175     *  @version $Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $
176     *  @since 0.0.5
177     *
178     *  @UMLGraph.link
179     */
180    @SuppressWarnings( "PublicInnerClass" )
181    @ClassVersion( sourceVersion = "$Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $" )
182    @API( status = STABLE, since = "0.0.5" )
183    public static class ValidatorChangeEvent extends EventObject
184    {
185            /*------------------------*\
186        ====** Static Initialisations **=======================================
187            \*------------------------*/
188        /**
189         *  The serial version UID for objects of this class: {@value}.
190         *
191         *  @hidden
192         */
193        @Serial
194        private static final long serialVersionUID = 1L;
195
196            /*------------*\
197        ====** Attributes **===================================================
198            \*------------*/
199        /**
200         *  The new validator.
201         *
202         *  @serial
203         */
204        private final Predicate<CharSequence> m_NewValidator;
205
206        /**
207         *  The previous validator.
208         *
209         *  @serial
210         */
211        private final Predicate<CharSequence> m_OldValidator;
212
213        /**
214         *  The validator that changed.
215         *
216         *  @serial
217         */
218        private final Validator m_Validator;
219
220            /*--------------*\
221        ====** Constructors **=================================================
222            \*--------------*/
223        /**
224         *  Creates a new {@code ValidatorChangeEvent} instance.
225         *
226         *  @param  validator   The validator that changed.
227         *  @param  oldValidator    The previous validator.
228         *  @param  newValidator    The new validator.
229         */
230        ValidatorChangeEvent( final Validator validator, final Predicate<CharSequence> oldValidator, final Predicate<CharSequence> newValidator )
231        {
232            super( requireNonNullArgument( validator, "validator" ) );
233            m_Validator = validator;
234            m_OldValidator = oldValidator;
235            m_NewValidator = newValidator;
236        }   //  ValidatorChangeEvent()
237
238            /*---------*\
239        ====** Methods **======================================================
240            \*---------*/
241        /**
242         *  Returns the new validator.
243         *
244         *  @return The new validator.
245         */
246        public final Predicate<CharSequence> getNewValidator() { return m_NewValidator; }
247
248        /**
249         *  Returns the previous validator.
250         *
251         *  @return The old validator.
252         */
253        public final Predicate<CharSequence> getOldValidator() { return m_OldValidator; }
254
255        /**
256         *  Gets the validator that was changed.
257         *
258         *  @return The changed validator.
259         */
260        public final Validator getValidator() { return m_Validator; }
261    }
262    //  class ValidatorChangeEvent
263
264    /**
265     *  The interface for listeners to
266     *  {@link ValidatorChangeEvent}s
267     *
268     *  @extauthor Thomas Thrien - thomas.thrien@tquadrat.org
269     *  @version $Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $
270     *  @since 0.0.5
271     *
272     *  @UMLGraph.link
273     */
274    @FunctionalInterface
275    @ClassVersion( sourceVersion = "$Id: XMLBuilderUtils.java 1101 2024-02-18 00:18:48Z tquadrat $" )
276    @API( status = STABLE, since = "0.0.5" )
277    public static interface ValidatorChangeListener
278    {
279            /*---------*\
280        ====** Methods **======================================================
281            \*---------*/
282        /**
283         *  This method gets called each time a validator changes.
284         *
285         *  @param  event   The change event.
286         */
287        @SuppressWarnings( "UseOfConcreteClass" )
288        public void validatorChanged( final ValidatorChangeEvent event );
289    }
290    //  interface ValidatorChangeListener
291
292        /*-----------*\
293    ====** Constants **========================================================
294        \*-----------*/
295    /**
296     *  The regular expression for a valid start character of an XML name.
297     *  Usually, the colon (':') is also allowed, but for namespace
298     *  aware parsers, this is used as the separator between the namespace
299     *  prefix and the name itself.
300     */
301    private static final String XML_NAME_FirstChar = """
302        A-Z_a-z\
303        \\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\
304        \\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\
305        \\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD\\x{10000}-\\x{EFFFF}""";
306
307    /**
308     *  The regular expression for a character that is valid for an XML after
309     *  the first character. Usually, the colon (':') is also allowed, but for
310     *  namespace aware parsers, this is used as the separator between the
311     *  namespace prefix and the name itself.
312     */
313    @SuppressWarnings( "ConstantExpression" )
314    private static final String XML_NAME_OtherChar = "-"
315        + XML_NAME_FirstChar
316        + ".0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
317
318        /*------------*\
319    ====** Attributes **=======================================================
320        \*------------*/
321    /**
322     *  The validator change listeners.
323     */
324    @SuppressWarnings( "StaticCollection" )
325    private static final Collection<WeakReference<ValidatorChangeListener>> m_ValidatorChangeListeners = new ArrayList<>();
326
327        /*------------------------*\
328    ====** Static Initialisations **===========================================
329        \*------------------------*/
330    /**
331     *  The method that validates an XML attribute name.
332     */
333    private static final AtomicReference<Predicate<CharSequence>> m_AttributeNameValidator = new AtomicReference<>( XMLBuilderUtils::isValidAttributeName );
334
335    /**
336     *  The method that validates an XML element name.
337     */
338    private static final AtomicReference<Predicate<CharSequence>> m_ElementNameValidator = new AtomicReference<>( XMLBuilderUtils::isValidElementName );
339
340    /**
341     *  The pattern that is used to validate a nmtoken.
342     */
343    private static final Pattern m_NMTokenPattern;
344
345    /**
346     *  The method that validates an XML nmtoken.
347     */
348    private static final AtomicReference<Predicate<CharSequence>> m_NMTokenValidator =  new AtomicReference<>( XMLBuilderUtils::isValidNMToken );
349
350    /**
351     *  The method that validates an XML namespace prefix.
352     */
353    private static final AtomicReference<Predicate<CharSequence>> m_PrefixValidator =  new AtomicReference<>( XMLBuilderUtils::isValidPrefix );
354
355    /**
356     *  The pattern that is used to validate an XML name.
357     */
358    private static final Pattern m_XMLNamePattern;
359
360    static
361    {
362        //---* Defines the patterns for the validation *-----------------------
363        try
364        {
365            //noinspection RegExpUnnecessaryNonCapturingGroup,ConstantExpression
366            m_NMTokenPattern = compile( "(?:[" + XML_NAME_FirstChar + "])(?:[" + XML_NAME_OtherChar + ":])*" );
367            //noinspection RegExpUnnecessaryNonCapturingGroup,ConstantExpression
368            m_XMLNamePattern = compile( "(?:[" + XML_NAME_FirstChar + "])(?:[" + XML_NAME_OtherChar + "])*" );
369        }
370        catch( final PatternSyntaxException e )
371        {
372            throw new ExceptionInInitializerError( e );
373        }
374    }
375
376        /*--------------*\
377    ====** Constructors **=====================================================
378        \*--------------*/
379    /**
380     *  No instance allowed for this class.
381     */
382    private XMLBuilderUtils() { throw new PrivateConstructorForStaticClassCalledError( XMLBuilderUtils.class ); }
383
384        /*---------*\
385    ====** Methods **==========================================================
386        \*---------*/
387    /**
388     *  Adds a validator change listener.
389     *
390     *  @param  listener    The listener.
391     */
392    @API( status = STABLE, since = "0.0.5" )
393    public static final void addValidatorChangeListener( final ValidatorChangeListener listener )
394    {
395        final var reference = new WeakReference<>( requireNonNullArgument( listener, "listener" ) );
396        synchronized( m_ValidatorChangeListeners )
397        {
398            m_ValidatorChangeListeners.add( reference );
399        }
400    }   //  addValidatorChangeListener()
401
402    /**
403     *  Composes the
404     *  {@link ProcessingInstruction}
405     *  for the XML file header.
406     *
407     *  @param  encoding    The encoding for the resulting document.
408     *  @param  standalone  {@code true} if the XML document is standalone,
409     *      {@code false} if not.
410     *  @return The new processing instruction.
411     */
412    @API( status = STABLE, since = "0.0.5" )
413    public static final ProcessingInstruction composeXMLHeader( final Charset encoding, final boolean standalone )
414    {
415        final var retValue = new ProcessingInstructionImpl();
416        retValue.setAttribute( "version", "1.0" );
417        retValue.setAttribute( "encoding", requireNonNullArgument( encoding, "encoding" ).name() );
418        retValue.setAttribute( "standalone", standalone ? "yes" : "no" );
419
420        //---* Done *----------------------------------------------------------
421        return retValue;
422    }   //  composeXMLHeader()
423
424    /**
425     *  Creates a
426     *  {@link ProcessingInstruction}.
427     *
428     *  @param  elementName The name for the processing instruction.
429     *  @return The new processing instruction.
430     */
431    @API( status = STABLE, since = "0.0.5" )
432    public static final ProcessingInstruction createProcessingInstruction( final String elementName )
433    {
434        final var retValue = new ProcessingInstructionImpl( elementName );
435
436        //---* Done *----------------------------------------------------------
437        return retValue;
438    }   //  createProcessingInstruction()
439
440    /**
441     *  Creates a
442     *  {@link ProcessingInstruction}.
443     *
444     *  @param  parent  The document that owns the new procession instruction.
445     *  @param  elementName The name for the processing instruction.
446     *  @return The new processing instruction.
447     */
448    @API( status = STABLE, since = "0.0.5" )
449    public static final ProcessingInstruction createProcessingInstruction( final XMLDocument parent, final String elementName )
450    {
451        final var retValue = createProcessingInstruction( elementName );
452        requireNonNullArgument( parent, "parent" ).addProcessingInstruction( retValue );
453
454        //---* Done *----------------------------------------------------------
455        return retValue;
456    }   //  createProcessingInstruction()
457
458    /**
459     *  Creates a
460     *  {@link ProcessingInstruction}.
461     *
462     *  @param  elementName The name for the processing instruction.
463     *  @param  data    The data for the processing instruction.
464     *  @return The new processing instruction.
465     */
466    @API( status = STABLE, since = "0.0.5" )
467    public static final ProcessingInstruction createProcessingInstruction( final String elementName, final CharSequence data )
468    {
469        final var retValue = new ProcessingInstructionImpl( elementName, data );
470
471        //---* Done *----------------------------------------------------------
472        return retValue;
473    }   //  createProcessingInstruction()
474
475    /**
476     *  Creates a
477     *  {@link ProcessingInstruction}.
478     *
479     *  @param  parent  The document that owns the new procession instruction.
480     *  @param  elementName The name for the processing instruction.
481     *  @param  data    The data for the processing instruction.
482     *  @return The new processing instruction.
483     */
484    @API( status = STABLE, since = "0.0.5" )
485    public static final ProcessingInstruction createProcessingInstruction( final XMLDocument parent, final String elementName, final CharSequence data )
486    {
487        final var retValue = createProcessingInstruction( elementName, data );
488        requireNonNullArgument( parent, "parent" ).addProcessingInstruction( retValue );
489
490        //---* Done *----------------------------------------------------------
491        return retValue;
492    }   //  createProcessingInstruction()
493
494    /**
495     *  <p>{@summary Creates an XML document that will not have an explicit doc
496     *  type, the root element will be {@code <root>}}. The encoding is defined
497     *  as UTF-8.</p>
498     *  <p>Basically, this document would have the DTD</p>
499     *  <pre><code>&lt;!ELEMENT root ANY&gt;</code></pre>.
500     *  <p>The root element allows attributes and children, but will not
501     *  validate them. It also allows text.</p>
502     *
503     *  @return The new XML document.
504     */
505    @API( status = STABLE, since = "0.0.5" )
506    public static final XMLDocument createXMLDocument() { return new XMLDocumentImpl(); }
507
508    /**
509     *  <p>{@summary Creates an XML document that uses the given element name for the root
510      *  element.}</p>
511     *  <p>The given element name is validated using the method that is
512     *  provided by
513     *  {@link #getElementNameValidator()}.</p>
514     *  <p>The created root element allows attributes and children, but will
515     *  not validate them. It also allows text.</p>
516     *
517     *  @param  elementName The element name.
518     *  @return The new XML document.
519     */
520    @API( status = STABLE, since = "0.0.5" )
521    public static final XMLDocument createXMLDocument( final String elementName )
522    {
523        return new XMLDocumentImpl( requireNotEmptyArgument( elementName, "elementName" ) );
524    }   //  createXMLDocument()
525
526    /**
527     *  Creates an XML document that uses the given element for the root
528     *  element.
529     *
530     *  @param  rootElement The root element.
531     *  @return The new XML document.
532     */
533    @API( status = STABLE, since = "0.0.5" )
534    public static final XMLDocument createXMLDocument( final XMLElement rootElement )
535    {
536        return createXMLDocument( requireNonNullArgument( rootElement, "rootElement" ), true );
537    }   //  createXMLDocument()
538
539    /**
540     *  Creates an XML document that uses the given element for the root
541     *  element.
542     *
543     *  @param  rootElement The root element.
544     *  @param  standalone  {@code true} for a standalone document,
545     *      {@code false} otherwise.
546     *  @return The new XML document.
547     */
548    @API( status = STABLE, since = "0.0.5" )
549    public static final XMLDocument createXMLDocument( final XMLElement rootElement, final boolean standalone )
550    {
551        return new XMLDocumentImpl( requireNonNullArgument( rootElement, "rootElement" ), standalone );
552    }   //  createXMLDocument()
553
554    /**
555     *  Creates an XML document that uses the given element for the root
556     *  element with the given encoding and DTD.
557     *
558     *  @param  rootElement The root element for this document.
559     *  @param  encoding    The encoding for the new XML document.
560     *  @param  name    The name for the DTD.
561     *  @param  uri The URI for the DTD.
562     *  @return The new XML document.
563     */
564    @API( status = STABLE, since = "0.0.5" )
565    public static final XMLDocument createXMLDocument( final XMLElement rootElement, final Charset encoding, final String name, final URI uri )
566    {
567        return new XMLDocumentImpl( requireNonNullArgument( rootElement, "rootElement" ), requireNonNullArgument( encoding, "encoding" ), requireNotEmptyArgument( name, "name" ), requireNonNullArgument( uri, "uri" ) );
568    }   //  createXMLDocument()
569
570    /**
571     *  Creates an XML document that uses the given element for the root
572     *  element with the given encoding and DTD.
573     *
574     *  @param  rootElement The root element for this document.
575     *  @param  encoding    The encoding for the new XML document.
576     *  @param  uri The URI for the DTD.
577     *  @return The new XML document.
578     */
579    @API( status = STABLE, since = "0.0.5" )
580    public static final XMLDocument createXMLDocument( final XMLElement rootElement, final Charset encoding, final URI uri )
581    {
582        return new XMLDocumentImpl( requireNonNullArgument( rootElement, "rootElement" ), requireNonNullArgument( encoding, "encoding" ), requireNonNullArgument( uri, "uri" ) );
583    }   //  createXMLDocument()
584
585    /**
586     *  Creates an XML element for the given element name that supports
587     *  attributes, namespaces, children, text, {@code CDATA} and comments.<br>
588     *  <br>The given element name is validated using the method that is
589     *  provided by
590     *  {@link #getElementNameValidator()}.<br>
591     *  <br>The new element allows attributes and children, but will not
592     *  validate them. It also allows text.
593     *
594     *  @param  elementName The element name.
595     *  @return The new XML element.
596     */
597    @API( status = STABLE, since = "0.0.5" )
598    public static final XMLElement createXMLElement( final String elementName )
599    {
600        return new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
601    }   //  createXMLElement()
602
603    /**
604     *  <p>{@summary Creates an XML element for the given element name that
605     *  supports attributes, namespaces, children, text, {@code CDATA} and
606     *  comments, and add the given text.}</p>
607     *  <p>The given element name is validated using the method that is
608     *  provided by
609     *  {@link #getElementNameValidator()}.</p>
610     *  <p>The new element allows attributes and children, but will not
611     *  validate them. It also allows text (obviously).</p>
612     *
613     *  @param  elementName The element name.
614     *  @param  text    The text for the new element.
615     *  @return The new XML element.
616     */
617    @API( status = STABLE, since = "0.0.5" )
618    public static final XMLElement createXMLElement( final String elementName, final CharSequence text )
619    {
620        final XMLElement retValue = new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
621        retValue.addText( requireNonNullArgument( text, "text" ) );
622
623        //---* Done *----------------------------------------------------------
624        return retValue;
625    }   //  createXMLElement()
626
627    /**
628     *  Creates an XML element for the given element name that supports
629     *  attributes, namespaces, children, text, {@code CDATA} and comments, and
630     *  adds it as child to the given parent.<br>
631     *  <br>The given element name is validated using the method that is
632     *  provided by
633     *  {@link #getElementNameValidator()}.<br>
634     *  <br>The new element allows attributes and children, but will not
635     *  validate them. It also allows text.
636     *
637     *  @param  elementName The element name.
638     *  @param  parent  The parent element.
639     *  @return The new XML element.
640     */
641    @API( status = STABLE, since = "0.0.5" )
642    public static final XMLElement createXMLElement( final String elementName, final XMLElement parent )
643    {
644        final XMLElement retValue = new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
645        requireNonNullArgument( parent, "parent" ).addChild( retValue );
646
647        //---* Done *----------------------------------------------------------
648        return retValue;
649    }   //  createXMLElement()
650
651    /**
652     *  Creates an XML element for the given element name that supports
653     *  attributes, namespaces, children, text, {@code CDATA} and comments,
654     *  adds the given text, and adds it as child to the given parent.<br>
655     *  <br>The given element name is validated using the method that is
656     *  provided by
657     *  {@link #getElementNameValidator()}.<br>
658     *  <br>The new element allows attributes and children, but will not
659     *  validate them. It also allows text (obviously).
660     *
661     *  @param  elementName The element name.
662     *  @param  parent  The parent element.
663     *  @param  text    The text for the new element.
664     *  @return The new XML element.
665     */
666    @API( status = STABLE, since = "0.0.5" )
667    public static final XMLElement createXMLElement( final String elementName, final XMLElement parent, final CharSequence text )
668    {
669        final XMLElement retValue = new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
670        retValue.addText( requireNonNullArgument( text, "text" ) );
671        requireNonNullArgument( parent, "parent" ).addChild( retValue );
672
673        //---* Done *----------------------------------------------------------
674        return retValue;
675    }   //  createXMLElement()
676
677    /**
678     *  Creates an XML element for the given tag and adds it as child to the
679     *  given document.<br>
680     *  <br>The given element name is validated using the method that is
681     *  provided by
682     *  {@link #getElementNameValidator()}.<br>
683     *  <br>The new element allows attributes and children, but will not
684     *  validate them. It also allows text.
685     *
686     *  @param  elementName The element name.
687     *  @param  parent  The document.
688     *  @return The new XML element.
689     */
690    @API( status = STABLE, since = "0.0.5" )
691    public static final XMLElement createXMLElement( final String elementName, final XMLDocument parent )
692    {
693        final XMLElement retValue = new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
694        requireNonNullArgument( parent, "parent" ).addChild( retValue );
695
696        //---* Done *----------------------------------------------------------
697        return retValue;
698    }   //  createXMLElement()
699
700    /**
701     *  Creates an XML element for the given tag and with the given text, and
702     *  adds it as child to the given document.<br>
703     *  <br>The given element name is validated using the method that is
704     *  provided by
705     *  {@link #getElementNameValidator()}.<br>
706     *  <br>The new element allows attributes and children, but will not
707     *  validate them. It also allows text (obviously).
708     *
709     *  @param  elementName The element name.
710     *  @param  parent  The document.
711     *  @param  text    The text for the new element.
712     *  @return The new XML element.
713     */
714    @API( status = STABLE, since = "0.0.5" )
715    public static final XMLElement createXMLElement( final String elementName, final XMLDocument parent, final CharSequence text )
716    {
717        final XMLElement retValue = new XMLElementImpl( requireNotEmptyArgument( elementName, "elementName" ) );
718        retValue.addText( requireNonNullArgument( text, "text" ) );
719        requireNonNullArgument( parent, "parent" ).addChild( retValue );
720
721        //---* Done *----------------------------------------------------------
722        return retValue;
723    }   //  createXMLElement()
724
725    /**
726     *  Escapes the characters in a {@code String} using XML entities.<br>
727     *  <br>For example:<br>
728     *  <br>{@code "bread" & "butter"}<br>
729     *  <br>becomes:<br>
730     *  <br><code>&amp;quot;bread&amp;quot; &amp;amp;
731     *  &amp;quot;butter&amp;quot;</code>.<br>
732     *  <br>Delegates to
733     *  {@link StringUtils#escapeXML(CharSequence)}.
734     *
735     *  @param  input   The {@code String} to escape, may be null.
736     *  @return A new escaped {@code String}, or {@code null} if the
737     *      argument was already {@code null}.
738     *
739     *  @see #unescapeXML(CharSequence)
740     */
741    @API( status = STABLE, since = "0.0.5" )
742    public static String escapeXML( final CharSequence input ) { return StringUtils.escapeXML( input ); }
743
744    /**
745     *  Escapes the characters in a {@code String} using XML entities and
746     *  writes them to an
747     *  {@link Appendable}.<br>
748     *  <br>For example:<br>
749     *  <br>{@code "bread" & "butter"}<br>
750     *  <br>becomes:<br>
751     *  <br><code>&amp;quot;bread&amp;quot; &amp;amp;
752     *  &amp;quot;butter&amp;quot;</code>.<br>
753     *  <br>Delegates to
754     *  {@link StringUtils#escapeXML(Appendable,CharSequence)}.
755     *
756     *  @param  appendable  The appendable object receiving the escaped string.
757     *  @param  input   The {@code String} to escape, may be {@code null}.
758     *  @throws NullArgumentException   The appendable is {@code null}.
759     *  @throws IOException when {@code Appendable} passed throws the exception
760     *      from calls to the
761     *      {@link Appendable#append(char)}
762     *      method.
763     *
764     *  @see #escapeXML(CharSequence)
765     *  @see #unescapeXML(CharSequence)
766     */
767    @API( status = STABLE, since = "0.0.5" )
768    public static void escapeXML( final Appendable appendable, final CharSequence input ) throws IOException
769    {
770        StringUtils.escapeXML( appendable, input );
771    }   //  escapeXML()
772
773    /**
774     *  Returns the method to validate attribute names.
775     *
776     *  @return The validator method.
777     */
778    @API( status = STABLE, since = "0.0.5" )
779    public static final Predicate<CharSequence> getAttributeNameValidator() { return m_AttributeNameValidator.get(); }
780
781    /**
782     *  Returns the method to validate element names.
783     *
784     *  @return The validator method.
785     */
786    @API( status = STABLE, since = "0.0.5" )
787    public static final Predicate<CharSequence> getElementNameValidator() { return m_ElementNameValidator.get(); }
788
789    /**
790     *  Returns the method to validate {@code nmtoken}s.
791     *
792     *  @return The validator method.
793     */
794    @API( status = STABLE, since = "0.0.5" )
795    public static final Predicate<CharSequence> getNMTokenValidator() { return m_NMTokenValidator.get(); }
796
797    /**
798     *  Returns the method to validate prefixes.
799     *
800     *  @return The validator method.
801     */
802    @API( status = STABLE, since = "0.0.5" )
803    public static final Predicate<CharSequence> getPrefixValidator() { return m_PrefixValidator.get(); }
804
805    /**
806     *  The default implementation for an attribute name validator.
807     *
808     *  @param  attributeName   The name to test.
809     *  @return {@code true} if the given name is valid, {@code false}
810     *  otherwise.
811     *
812     *  @see #getAttributeNameValidator()
813     */
814    private static final boolean isValidAttributeName( final CharSequence attributeName )
815    {
816        var retValue = isNotEmptyOrBlank( attributeName );
817        if( retValue )
818        {
819            var attribute = EMPTY_STRING;
820            final var parts = splitString( attributeName, ':' );
821            switch( parts.length )
822            {
823                //  No namespace prefix.
824                case 1 -> attribute = parts[0];
825                //  Namespace prefix.
826                case 2 ->
827                {
828                    attribute = parts[1];
829                    retValue = getPrefixValidator().test( parts[0] );
830                }
831                // More than one colon
832                default -> retValue = false;
833            }
834            if( retValue && (attribute.length() >= 3) ) retValue = !attribute.toLowerCase( ROOT ).startsWith( "xml" );
835            if( retValue ) retValue = m_XMLNamePattern.matcher( attribute ).matches();
836        }
837
838        //---* Done *----------------------------------------------------------
839        return retValue;
840    }   //  isValidAttributeName()
841
842    /**
843     *  The default implementation for an element name validator.
844     *
845     *  @param  elementName   The name to test.
846     *  @return {@code true} if the given name is valid, {@code false}
847     *  otherwise.
848     *
849     *  @see #getElementNameValidator()
850     */
851    private static final boolean isValidElementName( final CharSequence elementName )
852    {
853        var retValue = isNotEmptyOrBlank( elementName );
854        if( retValue )
855        {
856            var element = EMPTY_STRING;
857            final var parts = splitString( elementName, ':' );
858            switch( parts.length )
859            {
860                //  No namespace prefix.
861                case 1 -> element = parts[0];
862                //  Namespace prefix.
863                case 2 ->
864                {
865                    element = parts[1];
866                    retValue = getPrefixValidator().test( parts[0] );
867                }
868                // More than one colon
869                default -> retValue = false;
870            }
871            if( retValue && (element.length() >= 3) ) retValue = !element.toLowerCase( ROOT ).startsWith( "xml" );
872            if( retValue ) retValue = m_XMLNamePattern.matcher( element ).matches();
873        }
874
875        //---* Done *----------------------------------------------------------
876        return retValue;
877    }   //  isValidElementName()
878
879    /**
880     *  The default implementation for an NMToken validator.
881     *
882     *  @param  nmtoken   The NMToken to test.
883     *  @return {@code true} if the given NMToke is valid, {@code false}
884     *  otherwise.
885     *
886     *  @see #getNMTokenValidator()
887     */
888    private static final boolean isValidNMToken( final CharSequence nmtoken )
889    {
890        var retValue = isNotEmptyOrBlank( nmtoken );
891        if( retValue ) retValue = m_NMTokenPattern.matcher( nmtoken ).matches();
892
893        //---* Done *----------------------------------------------------------
894        return retValue;
895    }   //  isValidNMToken()
896
897    /**
898     *  The default implementation for a prefix validator.
899     *
900     *  @param  prefix  The prefix to test.
901     *  @return {@code true} if the given prefix is valid, {@code false}
902     *  otherwise.
903     *
904     *  @see #getPrefixValidator()
905     */
906    private static final boolean isValidPrefix( final CharSequence prefix )
907    {
908        var retValue = isNotEmptyOrBlank( prefix );
909        if( retValue ) retValue = m_XMLNamePattern.matcher( prefix ).matches();
910
911        //---* Done *----------------------------------------------------------
912        return retValue;
913    }   //  isValidPrefix()
914
915    /**
916     *  Removes the given validator change listener.
917     *
918     *  @param  listener    The listener to remove.
919     */
920    @API( status = STABLE, since = "0.0.5" )
921    public static final void removeValidatorChangeListener( final ValidatorChangeListener listener )
922    {
923        if( nonNull( listener ) )
924        {
925            synchronized( m_ValidatorChangeListeners )
926            {
927                var removed = false;
928                for( final var reference : m_ValidatorChangeListeners )
929                {
930                    final var current = reference.get();
931                    if( isNull( current ) )
932                    {
933                        removed = true;
934                    }
935                    else if( current == listener )
936                    {
937                        reference.clear();
938                        removed = true;
939                    }
940                }
941
942                //---* Housekeeping: get rid of the dead references *----------
943                if( removed ) m_ValidatorChangeListeners.removeIf( r -> isNull( r.get() ) );
944            }
945        }
946    }   //  removeValidatorChangeListener()
947
948    /**
949     *  Sets the validators back to the default configuration. The current
950     *  validators are abandoned.
951     *
952     *  @see #getAttributeNameValidator()
953     *  @see #getElementNameValidator()
954     *  @see #getNMTokenValidator()
955     *  @see #getPrefixValidator()
956     *  @see #setAttributeNameValidator(Predicate)
957     *  @see #setElementNameValidator(Predicate)
958     *  @see #setNMTokenValidator(Predicate)
959     *  @see #setPrefixValidator(Predicate)
960     */
961    @API( status = STABLE, since = "0.0.5" )
962    public static final void restoreDefaultValidators()
963    {
964        //---* Set the validator methods *-------------------------------------
965        setAttributeNameValidator( XMLBuilderUtils::isValidAttributeName );
966        setElementNameValidator( XMLBuilderUtils::isValidElementName );
967        setNMTokenValidator( XMLBuilderUtils::isValidNMToken );
968        setPrefixValidator( XMLBuilderUtils::isValidPrefix );
969    }   //  restoreDefaultValidators()
970
971    /**
972     *  Sets the method to validate attribute names.
973     *
974     *  @param  validator The validator method.
975     */
976    @API( status = STABLE, since = "0.0.5" )
977    public static final void setAttributeNameValidator( final Predicate<CharSequence> validator )
978    {
979        final var oldValidator = m_AttributeNameValidator.getAndSet( requireNonNullArgument( validator, "validator" ) );
980        synchronized( m_ValidatorChangeListeners )
981        {
982            if( !m_ValidatorChangeListeners.isEmpty() )
983            {
984                final var event = new ValidatorChangeEvent( VALIDATOR_AttributeName, oldValidator, validator );
985                for( final var reference : m_ValidatorChangeListeners )
986                {
987                    final var listener = reference.get();
988                    if( nonNull( listener ) ) listener.validatorChanged( event );
989                }
990            }
991        }
992    }   //  setAttributeNameValidator()
993
994    /**
995     *  Sets the method to validate element names.
996     *
997     *  @param  validator The validator method.
998     */
999    @API( status = STABLE, since = "0.0.5" )
1000    public static final void setElementNameValidator( final Predicate<CharSequence> validator )
1001    {
1002        final var oldValidator = m_ElementNameValidator.getAndSet( requireNonNullArgument( validator, "validator" ) );
1003        synchronized( m_ValidatorChangeListeners )
1004        {
1005            if( !m_ValidatorChangeListeners.isEmpty() )
1006            {
1007                final var event = new ValidatorChangeEvent( VALIDATOR_ElementName, oldValidator, validator );
1008                for( final var reference : m_ValidatorChangeListeners )
1009                {
1010                    final var listener = reference.get();
1011                    if( nonNull( listener ) ) listener.validatorChanged( event );
1012                }
1013            }
1014        }
1015    }   //  setElementNameValidator()
1016
1017    /**
1018     *  Sets the method to validate {@code nmtoken}s.
1019     *
1020     *  @param  validator The validator method.
1021     */
1022    @API( status = STABLE, since = "0.0.5" )
1023    public static final void setNMTokenValidator( final Predicate<CharSequence> validator )
1024    {
1025        final var oldValidator = m_NMTokenValidator.getAndSet( requireNonNullArgument( validator, "validator" ) );
1026        synchronized( m_ValidatorChangeListeners )
1027        {
1028            if( !m_ValidatorChangeListeners.isEmpty() )
1029            {
1030                final var event = new ValidatorChangeEvent( VALIDATOR_NMToken, oldValidator, validator );
1031                for( final var reference : m_ValidatorChangeListeners )
1032                {
1033                    final var listener = reference.get();
1034                    if( nonNull( listener ) ) listener.validatorChanged( event );
1035                }
1036            }
1037        }
1038    }   //  setNMTokenValidator()
1039
1040    /**
1041     *  Sets the method to validate prefixes.
1042     *
1043     *  @param  validator The validator method.
1044     */
1045    @API( status = STABLE, since = "0.0.5" )
1046    public static final void setPrefixValidator( final Predicate<CharSequence> validator )
1047    {
1048        final var oldValidator = m_PrefixValidator.getAndSet( requireNonNullArgument( validator, "validator" ) );
1049        synchronized( m_ValidatorChangeListeners )
1050        {
1051            if( !m_ValidatorChangeListeners.isEmpty() )
1052            {
1053                final var event = new ValidatorChangeEvent( VALIDATOR_Prefix, oldValidator, validator );
1054                for( final var reference : m_ValidatorChangeListeners )
1055                {
1056                    final var listener = reference.get();
1057                    if( nonNull( listener ) ) listener.validatorChanged( event );
1058                }
1059            }
1060        }
1061    }   //  setPrefixValidator()
1062
1063    /**
1064     *  <p>{@summary Strips HTML or XML comments from the given String.}</p>
1065     *  <p>Delegates to
1066     *  {@link StringUtils#stripXMLComments(CharSequence)}.</p>
1067     *
1068     *  @param  input   The HTML/XML string.
1069     *  @return The string without the comments.
1070     */
1071    @API( status = STABLE, since = "0.0.5" )
1072    public static final String stripXMLComments( final CharSequence input ) { return StringUtils.stripXMLComments( input ); }
1073
1074    /**
1075     *  Unescapes an XML string containing XML entity escapes to a string
1076     *  containing the actual Unicode characters corresponding to the
1077     *  escapes.<br>
1078     *  <br>If an entity is unrecognised, it is left alone, and inserted
1079     *  verbatim into the result string. e.g. &quot;&amp;gt;&amp;zzzz;x&quot;
1080     *  will become &quot;&gt;&amp;zzzz;x&quot;.<br>
1081     *  <br>Delegates to
1082     *  {@link StringUtils#unescapeXML(CharSequence)}.
1083     *
1084     *  @param  input The {@code String} to unescape, may be {@code null}.
1085     *  @return A new unescaped {@code String}, {@code null} if the given
1086     *      string was already {@code null}.
1087     *
1088     *  @see #escapeXML(CharSequence)
1089     *  @see #escapeXML(Appendable,CharSequence)
1090     */
1091    @API( status = STABLE, since = "0.0.5" )
1092    public static final String unescapeXML( final CharSequence input ) { return StringUtils.unescapeXML( input ); }
1093
1094    /**
1095     *  Unescapes an XML String containing XML entity escapes to a String
1096     *  containing the actual Unicode characters corresponding to the escapes
1097     *  and writes it to the given
1098     *  {@link Appendable}.<br>
1099     *  <br>If an entity is unrecognised, it is left alone, and inserted
1100     *  verbatim into the result string. e.g. &quot;&amp;gt;&amp;zzzz;x&quot;
1101     *  will become &quot;&gt;&amp;zzzz;x&quot;.<br>
1102     *  <br>Delegates to
1103     *  {@link StringUtils#unescapeXML(Appendable,CharSequence)}.
1104     *
1105     *  @param  appendable  The appendable receiving the unescaped string.
1106     *  @param  input The {@code String} to unescape, may be {@code null}.
1107     *  @throws NullArgumentException   The writer is {@code null}.
1108     *  @throws IOException An IOException occurred.
1109     *
1110     *  @see #escapeXML(CharSequence)
1111     */
1112    @API( status = STABLE, since = "0.0.5" )
1113    public static final void unescapeXML( final Appendable appendable, final CharSequence input ) throws IOException
1114    {
1115        StringUtils.unescapeXML( appendable, input );
1116    }   //  unescapeXML()
1117}
1118//  class XMLBuilderUtils
1119
1120/*
1121 *  End of File
1122 */