PdfAllowedTagRelations.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.tagutils;

import com.itextpdf.kernel.pdf.tagging.StandardRoles;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * This class defines the allowed parent-child relations for the PDF2.0 standard.
 */
public class PdfAllowedTagRelations {

    public static final String NUMBERED_HEADER = "Hn";
    public static final String ACTUAL_CONTENT = "CONTENT";
    private static final Pattern numberedHeaderPattern = Pattern.compile("H(\\d+)");

    protected final Map<String, Collection<String>> allowedParentChildRelations = new HashMap<>();

    /**
     * Creates a new instance of {@link PdfAllowedTagRelations}.
     */
    public PdfAllowedTagRelations() {
        allowedParentChildRelations.put("StructTreeRoot", Collections.singleton(StandardRoles.DOCUMENT));
        allowedParentChildRelations.put(StandardRoles.DOCUMENT,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.LINK, StandardRoles.ANNOT, StandardRoles.FORM,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.TABLE,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.DOCUMENTFRAGMENT,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.LINK, StandardRoles.ANNOT, StandardRoles.FORM,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.TABLE,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.PART,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC, StandardRoles.TOCI,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.FENOTE,
                        StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.DIV,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC, StandardRoles.TOCI,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.RB, StandardRoles.RT, StandardRoles.RP, StandardRoles.WARICHU, StandardRoles.WT,
                        StandardRoles.WP, StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.LI,
                        StandardRoles.LBODY, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.TR,
                        StandardRoles.TH, StandardRoles.TD, StandardRoles.THEAD, StandardRoles.TBODY,
                        StandardRoles.TFOOT, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.ART,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.DIV, StandardRoles.SECT,
                        StandardRoles.TOC, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER,
                        StandardRoles.H, StandardRoles.TITLE, StandardRoles.LBL, StandardRoles.LINK,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.FENOTE, StandardRoles.INDEX,
                        StandardRoles.L, StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE,
                        StandardRoles.FORMULA, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.SECT,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.TOC, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE,
                        StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE,
                        StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H, StandardRoles.TITLE, StandardRoles.LBL,
                        StandardRoles.LINK, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.FENOTE,
                        StandardRoles.INDEX, StandardRoles.L, StandardRoles.TABLE, StandardRoles.CAPTION,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TOC,
                Arrays.asList(StandardRoles.PART, StandardRoles.TOC, StandardRoles.TOCI, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.CAPTION, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TOCI,
                Arrays.asList(StandardRoles.DIV, StandardRoles.TOC, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.LBL, StandardRoles.REFERENCE, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.ASIDE,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC,
                        StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.P,
                        StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H, StandardRoles.LBL,
                        StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.TABLE,
                        StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.BLOCKQUOTE,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC,
                        StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.P,
                        StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H, StandardRoles.TITLE,
                        StandardRoles.LBL, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L,
                        StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.NONSTRUCT,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC, StandardRoles.TOCI,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.RB, StandardRoles.RT, StandardRoles.RP, StandardRoles.WARICHU, StandardRoles.WT,
                        StandardRoles.WP, StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.LI,
                        StandardRoles.LBODY, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.TR,
                        StandardRoles.TH, StandardRoles.TD, StandardRoles.THEAD, StandardRoles.TBODY,
                        StandardRoles.TFOOT, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.PRIVATE,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC, StandardRoles.TOCI,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.RB, StandardRoles.RT, StandardRoles.RP, StandardRoles.WARICHU, StandardRoles.WT,
                        StandardRoles.WP, StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.LI,
                        StandardRoles.LBODY, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.TR,
                        StandardRoles.TH, StandardRoles.TD, StandardRoles.THEAD, StandardRoles.TBODY,
                        StandardRoles.TFOOT, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.TITLE,
                Arrays.asList(StandardRoles.PART, StandardRoles.DIV, StandardRoles.ASIDE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.CAPTION,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.SUB,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.P,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.NOTE,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY,
                        StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.CODE,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.DIV,
                        StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(NUMBERED_HEADER,
                Arrays.asList(StandardRoles.ART, StandardRoles.SECT, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.NOTE, StandardRoles.CODE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.WARICHU, StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE,
                        StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.H,
                Arrays.asList(StandardRoles.ART, StandardRoles.SECT, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.NOTE, StandardRoles.CODE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.WARICHU, StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE,
                        StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.LBL,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.EM,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.STRONG,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.SPAN,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.QUOTE,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.LINK,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER,
                        StandardRoles.H, StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.REFERENCE,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.NOTE, StandardRoles.LBL,
                        StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.LINK,
                        StandardRoles.ANNOT, StandardRoles.FENOTE, StandardRoles.BIBENTRY, StandardRoles.FIGURE,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.ANNOT,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.WARICHU, StandardRoles.FENOTE, StandardRoles.L, StandardRoles.BIBENTRY,
                        StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.FORM,
                Arrays.asList(StandardRoles.PART, StandardRoles.DIV, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.NOTE, StandardRoles.CODE, StandardRoles.LBL, StandardRoles.REFERENCE,
                        StandardRoles.FENOTE, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.RUBY,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.RB, StandardRoles.RT,
                        StandardRoles.RP, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.RB,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.SUB, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.RT,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.SUB, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.RP,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.SUB, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.WARICHU,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.WT, StandardRoles.WP,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.WT,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.SUB, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.WP,
                Arrays.asList(ACTUAL_CONTENT, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.SUB,
                        StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE,
                        StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM,
                        StandardRoles.FIGURE, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.FENOTE,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.L, StandardRoles.TABLE, StandardRoles.CAPTION,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.INDEX,
                Arrays.asList(StandardRoles.PART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FENOTE, StandardRoles.L,
                        StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.L,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.L, StandardRoles.LI,
                        StandardRoles.CAPTION, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.LI,
                Arrays.asList(StandardRoles.DIV, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.LBL,
                        StandardRoles.LBODY, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.LBODY,
                Arrays.asList(StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.SUB, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.BIBENTRY,
                Arrays.asList(StandardRoles.PART, StandardRoles.DIV, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FENOTE, StandardRoles.FIGURE, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.TABLE,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.TR, StandardRoles.THEAD,
                        StandardRoles.TBODY, StandardRoles.TFOOT, StandardRoles.CAPTION, StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TR,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.TH, StandardRoles.TD,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TH,
                Arrays.asList(StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER,
                        StandardRoles.H, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.TD,
                Arrays.asList(StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER,
                        StandardRoles.H, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN,
                        StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT,
                        StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE,
                        StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.THEAD,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.TR,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TBODY,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.TR,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.TFOOT,
                Arrays.asList(StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.TR,
                        StandardRoles.ARTIFACT));
        allowedParentChildRelations.put(StandardRoles.CAPTION,
                Arrays.asList(StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV,
                        StandardRoles.SECT, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT,
                        StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER,
                        StandardRoles.H, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY,
                        StandardRoles.TABLE, StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT,
                        ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.FIGURE,
                Arrays.asList(StandardRoles.PART, StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM, StandardRoles.STRONG,
                        StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK, StandardRoles.REFERENCE,
                        StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY, StandardRoles.WARICHU,
                        StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.BIBENTRY,
                        StandardRoles.TABLE, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.FORMULA,
                Arrays.asList(StandardRoles.PART, StandardRoles.DIV, StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE,
                        StandardRoles.NONSTRUCT, StandardRoles.PRIVATE, StandardRoles.P, StandardRoles.NOTE,
                        StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H, StandardRoles.SUB, StandardRoles.LBL,
                        StandardRoles.EM, StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE,
                        StandardRoles.LINK, StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM,
                        StandardRoles.RUBY, StandardRoles.WARICHU, StandardRoles.FENOTE, StandardRoles.INDEX,
                        StandardRoles.L, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.CAPTION,
                        StandardRoles.FIGURE, StandardRoles.FORMULA, StandardRoles.ARTIFACT, ACTUAL_CONTENT));
        allowedParentChildRelations.put(StandardRoles.ARTIFACT,
                Arrays.asList(StandardRoles.DOCUMENT, StandardRoles.DOCUMENTFRAGMENT, StandardRoles.PART,
                        StandardRoles.ART, StandardRoles.DIV, StandardRoles.SECT, StandardRoles.TOC, StandardRoles.TOCI,
                        StandardRoles.ASIDE, StandardRoles.BLOCKQUOTE, StandardRoles.NONSTRUCT, StandardRoles.PRIVATE,
                        StandardRoles.P, StandardRoles.NOTE, StandardRoles.CODE, NUMBERED_HEADER, StandardRoles.H,
                        StandardRoles.TITLE, StandardRoles.SUB, StandardRoles.LBL, StandardRoles.EM,
                        StandardRoles.STRONG, StandardRoles.SPAN, StandardRoles.QUOTE, StandardRoles.LINK,
                        StandardRoles.REFERENCE, StandardRoles.ANNOT, StandardRoles.FORM, StandardRoles.RUBY,
                        StandardRoles.RB, StandardRoles.RT, StandardRoles.RP, StandardRoles.WARICHU, StandardRoles.WT,
                        StandardRoles.WP, StandardRoles.FENOTE, StandardRoles.INDEX, StandardRoles.L, StandardRoles.LI,
                        StandardRoles.LBODY, StandardRoles.BIBENTRY, StandardRoles.TABLE, StandardRoles.TR,
                        StandardRoles.TH, StandardRoles.TD, StandardRoles.THEAD, StandardRoles.TBODY,
                        StandardRoles.TFOOT, StandardRoles.CAPTION, StandardRoles.FIGURE, StandardRoles.FORMULA,
                        StandardRoles.ARTIFACT, ACTUAL_CONTENT));

    }

    /**
     * Checks if the given parent-child relation is allowed.
     *
     * @param parentRole The parent role.
     * @param childRole  The child role.
     * @return {@code true} if the relation is allowed, {@code false} otherwise.
     */
    public boolean isRelationAllowed(String parentRole, String childRole) {
        Collection<String> allowedChildren = allowedParentChildRelations.get(normalizeRole(parentRole));
        if (allowedChildren != null) {
            return allowedChildren.contains(normalizeRole(childRole));
        }
        throw new IllegalArgumentException("parentRole " + parentRole + " is not a valid structure tree role");
    }

    /**
     * Checks if the given parent role allows content.
     *
     * @param parentRole The parent role.
     * @return {@code true} if the parent role allows content, {@code false} otherwise.
     */
    public boolean isContentAllowedInRole(String parentRole) {
        Collection<String> allowedChildren = allowedParentChildRelations.get(normalizeRole(parentRole));
        if (allowedChildren != null) {
            return allowedChildren.contains(ACTUAL_CONTENT);
        }
        throw new IllegalArgumentException("parentRole " + parentRole + " is not a valid structure tree role");
    }

    /**
     * Normalizes the role.
     *
     * @param role The role to normalize.
     * @return The normalized role.
     */
    public String normalizeRole(String role) {
        if (role == null) {
            return null;
        }
        if (numberedHeaderPattern.matcher(role).matches()) {
            return NUMBERED_HEADER;
        }
        return role;
    }
}