/*
 * Copyright (c) 2022-2025 XMLmind Software. All rights reserved.
 *
 * Author: Hussein Shafie
 *
 * This file is part of several XMLmind projects.
 * For conditions of distribution and use, see the accompanying legal.txt file.
 */
package com.xmlmind.util;

import java.net.MalformedURLException;
import java.net.URL;
import org.xml.sax.InputSource;
import org.xml.sax.EntityResolver;
import org.xml.sax.XMLReader;
import javax.xml.transform.TransformerException;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.URIResolver;

/**
 * Specifies the catalog-based entity resolution services needed by 
 * most XMLmind software components.
 * <p>Implementations are expected to be thread-safe.
 */
public interface XMLResolver extends EntityResolver, URIResolver {
    /**
     * Adds specified &lt;rewriteURI&gt; entry to the XML catalogs 
     * being used by this resolver.
     *
     * @param uriStartString prefix of an URI
     * @param rewritePrefix new prefix for URIs starting with 
     * <code>uriStartString</code>.
     * Must be the start of an <em>absolute</em> URL
     * in order to be able to use this entry to resolve URIs.
     */
    void addRewriteURIEntry(String uriStartString, String rewritePrefix);

    /**
     * Resolves specified the public identifier/system identifier of an entity.
     * If a mapping is found in the XML catalogs, this  mapping is returned. 
     * Otherwise returns <code>null</code>.
     *
     * @param publicId the public identifier of the entity. 
     * May be <code>null</code>.
     * @param systemId the system identifier of the entity. 
     * <em>Not</em> <code>null</code>.
     * @return found mapping (a URI reference) or <code>null</code>
     */
    String getResolvedEntity(String publicId, String systemId);
    
    /**
     * Resolves specified URI. If a mapping is found in the XML catalogs, this
     * mapping is returned. Otherwise returns <code>null</code>.
     * 
     * @param uri URI for which a mapping is to be found.
     * This URI may have a fragment.
     * @return found mapping (a URI reference) or <code>null</code>
     */
    String resolveURI(String uri);
    
    /**
     * Resolves specified URI. If a mapping is found in the XML catalogs, this
     * mapping is returned. Otherwise specified URI is parsed as an URL (if
     * relative, it is resolved against specified base) and this URL is
     * returned.
     * 
     * @param uri URI for which a mapping is to be found.
     * This URI may have a fragment.
     * @param baseURL base URL used if <code>uri</code> 
     * needs to be parsed as an
     * URL. May be <code>null</code>.
     * @return resolved URL
     * @exception MalformedURLException if specified URI needs to be
     * parsed as an URL but is malformed
     */
    default URL resolveURI(String uri, URL baseURL)
        throws MalformedURLException {
        String resolved = resolveURI(uri);
        if (resolved != null) {
            return URLUtil.createURL(resolved);
        } else {
            return URLUtil.createURL(baseURL, uri);
        }
    }
    
    default InputSource resolveEntity(String publicId, String systemId) {
        String resolved = getResolvedEntity(publicId, systemId);
        if (resolved == null)  {
            return null;
        }

        // Like in URLUtil.createURL().
        resolved = URIComponent.encode(resolved);
        InputSource source = new InputSource(resolved); 
        source.setPublicId(publicId);
        
        return source;
    }
    
    default Source resolve(String href, String base)
        throws TransformerException {
        String resolved = resolveURI(href);
        if (resolved != null) {
            // Like in URLUtil.createURL().
            resolved = URIComponent.encode(resolved);
        } else {
            href = URIComponent.encode(href);

            // Absolute URL?
            URL url = null;
            try {
                url = URLUtil.newURL(href);
            } catch (MalformedURLException ignored) {}

            if (url == null && base != null) {
                // Relative URL?
                base = URIComponent.encode(base);

                URL baseURL;
                try {
                    baseURL = URLUtil.newURL(base);
                } catch (MalformedURLException e) {
                    throw new TransformerException(
                        "'" + base + "' malformed URL");
                }

                if (href.length() == 0) {
                    url = baseURL;
                } else {
                    try {
                        url = URLUtil.newURL(baseURL, href);
                    } catch (MalformedURLException ignored) {}
                }
            }

            if (url == null) {
                String msg = "'" + href + "' malformed URL";
                if (base != null) {
                    msg += " (with base '" + base + "')";
                }
                throw new TransformerException(msg);
            }
            
            resolved = url.toExternalForm();
        }

        SAXSource source = new SAXSource();
        source.setInputSource(new InputSource(resolved));

        // Needed to resolve the public id of DOCTYPE in files loaded by
        // the "document()" XSLT function.
        XMLReader xmlReader = null;
        try {
            xmlReader = XMLUtil.newSAXParser().getXMLReader();
            xmlReader.setEntityResolver(this);
        } catch (Throwable ignored) {}
        if (xmlReader != null) {
            source.setXMLReader(xmlReader);
        }

        return source;
    }
}

