PdfNamespace.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.pdf.tagging;

import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfObjectWrapper;
import com.itextpdf.kernel.pdf.PdfString;
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A wrapper for namespace dictionaries (ISO 32000-2 section 14.7.4).
 * A namespace dictionary defines a namespace within the structure tree.
 * <p>
 * This pdf entity is meaningful only for the PDF documents of version <b>2.0 and higher</b>.
 */
public class PdfNamespace extends PdfObjectWrapper<PdfDictionary> {


    /**
     * Constructs namespace from the given {@link PdfDictionary} that represents namespace dictionary.
     * This method is useful for property reading in reading mode or modifying in stamping mode.
     * @param dictionary a {@link PdfDictionary} that represents namespace in the document.
     */
    public PdfNamespace(PdfDictionary dictionary) {
        super(dictionary);
        setForbidRelease();
    }

    /**
     * Constructs a namespace defined by the given namespace name.
     * @param namespaceName a {@link String} defining the namespace name (conventionally a uniform
     *                      resource identifier, or URI).
     */
    public PdfNamespace(String namespaceName) {
        this(new PdfString(namespaceName));
    }

    /**
     * Constructs a namespace defined by the given namespace name.
     * @param namespaceName a {@link PdfString} defining the namespace name (conventionally a uniform
     *                      resource identifier, or URI).
     */
    public PdfNamespace(PdfString namespaceName) {
        this(new PdfDictionary());
        put(PdfName.Type, PdfName.Namespace);
        put(PdfName.NS, namespaceName);
    }

    /**
     * Retrieves default namespace from provided document or adds a new one in case it's absent.
     *
     * @param pdfDocument {@link PdfDocument document} to retrieve the namespace instance
     *
     * @return default namespace instance
     */
    public static PdfNamespace getDefault(PdfDocument pdfDocument) {
        if (pdfDocument == null || pdfDocument.getStructTreeRoot() == null) {
            return new PdfNamespace(StandardNamespaces.getDefault());
        }
        for (PdfNamespace namespace : pdfDocument.getStructTreeRoot().getNamespaces()) {
            if (StandardNamespaces.getDefault().equals(namespace.getNamespaceName())) {
                return namespace;
            }
        }
        PdfNamespace defaultNamespace = new PdfNamespace(StandardNamespaces.getDefault());
        pdfDocument.getStructTreeRoot().addNamespace(defaultNamespace);
        return defaultNamespace;
    }

    /**
     * Sets the string defining the namespace name.
     * @param namespaceName a {@link String} defining the namespace name (conventionally a uniform
     *                      resource identifier, or URI).
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace setNamespaceName(String namespaceName) {
        return setNamespaceName(new PdfString(namespaceName));
    }

    /**
     * Sets the string defining the namespace name.
     * @param namespaceName a {@link PdfString} defining the namespace name (conventionally a uniform
     *                      resource identifier, or URI).
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace setNamespaceName(PdfString namespaceName) {
        return put(PdfName.NS, namespaceName);
    }

    /**
     * Gets the string defining the namespace name.
     * @return a {@link String} defining the namespace name (conventionally a uniform
     *                      resource identifier, or URI).
     */
    public String getNamespaceName() {
        PdfString ns = getPdfObject().getAsString(PdfName.NS);
        return ns != null ? ns.toUnicodeString() : null;
    }

    /**
     * Sets file specification identifying the schema file, which defines this namespace.
     * @param fileSpec a {@link PdfFileSpec} identifying the schema file.
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace setSchema(PdfFileSpec fileSpec) {
        return put(PdfName.Schema, fileSpec.getPdfObject());
    }

    /**
     * Gets file specification identifying the schema file, which defines this namespace.
     * @return a {@link PdfFileSpec} identifying the schema file.
     */
    public PdfFileSpec getSchema() {
        PdfObject schemaObject = getPdfObject().get(PdfName.Schema);
        return PdfFileSpec.wrapFileSpecObject(schemaObject);
    }

    /**
     * A dictionary that maps the names of structure types used in the namespace to their approximate equivalents in another
     * namespace.
     * @param roleMapNs a {@link PdfDictionary} which is comprised of a set of keys representing structure element types
     *                  in the namespace defined within this namespace dictionary. The corresponding value for each of these
     *                  keys shall either be a single {@link PdfName} identifying a structure element type in the default
     *                  namespace or an {@link PdfArray} where the first value shall be a structure element type name
     *                  in a target namespace with the second value being an indirect reference to the target namespace dictionary.
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace setNamespaceRoleMap(PdfDictionary roleMapNs) {
        return put(PdfName.RoleMapNS, roleMapNs);
    }

    /**
     * A dictionary that maps the names of structure types used in the namespace to their approximate equivalents in another
     * namespace.
     * @return a {@link PdfDictionary} which is comprised of a set of keys representing structure element types
     * in the namespace defined within this namespace dictionary. The corresponding value for each of these
     * keys shall either be a single {@link PdfName} identifying a structure element type in the default
     * namespace or an {@link PdfArray} where the first value shall be a structure element type name
     * in a target namespace with the second value being an indirect reference to the target namespace dictionary.
     */
    public PdfDictionary getNamespaceRoleMap() {
        return getNamespaceRoleMap(false);
    }

    /**
     * Adds to the namespace role map (see {@link #setNamespaceRoleMap(PdfDictionary)}) a single role mapping to the
     * default standard structure namespace.
     * @param thisNsRole a {@link String} identifying structure element type in this namespace.
     * @param defaultNsRole a {@link String} identifying a structure element type in the default standard structure namespace.
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace addNamespaceRoleMapping(String thisNsRole, String defaultNsRole) {
        PdfObject prevVal = getNamespaceRoleMap(true).put(PdfStructTreeRoot.convertRoleToPdfName(thisNsRole), PdfStructTreeRoot.convertRoleToPdfName(defaultNsRole));
        logOverwritingOfMappingIfNeeded(thisNsRole, prevVal);
        setModified();
        return this;
    }

    /**
     * Adds to the namespace role map (see {@link #setNamespaceRoleMap(PdfDictionary)}) a single role mapping to the
     * target namespace.
     * @param thisNsRole a {@link String} identifying structure element type in this namespace.
     * @param targetNsRole a {@link String} identifying a structure element type in the target namespace.
     * @param targetNs a {@link PdfNamespace} identifying the target namespace.
     * @return this {@link PdfNamespace} instance.
     */
    public PdfNamespace addNamespaceRoleMapping(String thisNsRole, String targetNsRole, PdfNamespace targetNs) {
        PdfArray targetMapping = new PdfArray();
        targetMapping.add(PdfStructTreeRoot.convertRoleToPdfName(targetNsRole));
        targetMapping.add(targetNs.getPdfObject());
        PdfObject prevVal = getNamespaceRoleMap(true).put(PdfStructTreeRoot.convertRoleToPdfName(thisNsRole), targetMapping);
        logOverwritingOfMappingIfNeeded(thisNsRole, prevVal);
        setModified();
        return this;
    }

    @Override
    protected boolean isWrappedObjectMustBeIndirect() {
        return true;
    }

    private PdfNamespace put(PdfName key, PdfObject value) {
        getPdfObject().put(key, value);
        setModified();
        return this;
    }

    private PdfDictionary getNamespaceRoleMap(boolean createIfNotExist) {
        PdfDictionary roleMapNs = getPdfObject().getAsDictionary(PdfName.RoleMapNS);
        if (createIfNotExist && roleMapNs == null) {
            roleMapNs = new PdfDictionary();
            put(PdfName.RoleMapNS, roleMapNs);
        }
        return roleMapNs;
    }

    private void logOverwritingOfMappingIfNeeded(String thisNsRole, PdfObject prevVal) {
        if (prevVal != null) {
            Logger logger = LoggerFactory.getLogger(PdfNamespace.class);
            String nsNameStr = getNamespaceName();
            if (nsNameStr == null) {
                nsNameStr = "this";
            }
            logger.warn(MessageFormatUtil.format(IoLogMessageConstant.MAPPING_IN_NAMESPACE_OVERWRITTEN, thisNsRole,
                    nsNameStr));
        }
    }
}