docs: private static CmsXmlContentDefinition unmarshalInternal(
          Document document,
          String schemaLocation,
          EntityResolver resolver)
      throws CmsXmlException {
  
          // analyze the document and generate the XML content type definition
          Element root = document.getRootElement();
          if (!XSD_NODE_SCHEMA.equals(root.getQName())) {
              // schema node is required
              throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_NO_SCHEMA_NODE_0));
          }
  
          List<Element> includes = CmsXmlGenericWrapper.elements(root, XSD_NODE_INCLUDE);
          if (includes.size() < 1) {
              // one include is required
              throw new CmsXmlException(Messages.get().container(Messages.ERR_CD_ONE_INCLUDE_REQUIRED_0));
          }
  
          Element include = includes.get(0);
          String target = validateAttribute(include, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
          if (!XSD_INCLUDE_OPENCMS.equals(target)) {
              // the first include must point to the default OpenCms standard schema include
              throw new CmsXmlException(
                  Messages.get().container(Messages.ERR_CD_FIRST_INCLUDE_2, XSD_INCLUDE_OPENCMS, target));
          }
  
          boolean recursive = false;
          Set<CmsXmlContentDefinition> nestedDefinitions = new HashSet<CmsXmlContentDefinition>();
          if (includes.size() > 1) {
              // resolve additional, nested include calls
              for (int i = 1; i < includes.size(); i++) {
  
                  Element inc = includes.get(i);
                  String schemaLoc = validateAttribute(inc, XSD_ATTRIBUTE_SCHEMA_LOCATION, null);
                  if (!(schemaLoc.equals(schemaLocation))) {
                      InputSource source = null;
                      try {
                          source = resolver.resolveEntity(null, schemaLoc);
                      } catch (Exception e) {
                          throw new CmsXmlException(
                              Messages.get().container(
                                  Messages.ERR_CD_BAD_INCLUDE_3,
                                  schemaLoc,
                                  schemaLocation,
                                  document.asXML()),
                              e);
                      }
                      // Couldn't resolve the entity?
                      if (null == source) {
                          throw new CmsXmlException(
                              Messages.get().container(
                                  Messages.ERR_CD_BAD_INCLUDE_3,
                                  schemaLoc,
                                  schemaLocation,
                                  document.asXML()));
                      }
                      CmsXmlContentDefinition xmlContentDefinition = unmarshal(source, schemaLoc, resolver);
                      nestedDefinitions.add(xmlContentDefinition);
                  } else {
                      // recursion
                      recursive = true;
                  }
              }
          }
  
          List<Element> elements = CmsXmlGenericWrapper.elements(root, XSD_NODE_ELEMENT);
          if (elements.size() != 1) {
              // only one root element is allowed
              throw new CmsXmlException(
                  Messages.get().container(
                      Messages.ERR_CD_ROOT_ELEMENT_COUNT_1,
                      XSD_INCLUDE_OPENCMS,
                      new Integer(elements.size())));
          }
  
          // collect the data from the root element node
          Element main = elements.get(0);
          String name = validateAttribute(main, XSD_ATTRIBUTE_NAME, null);
  
          // now process the complex types
          List<Element> complexTypes = CmsXmlGenericWrapper.elements(root, XSD_NODE_COMPLEXTYPE);
          if (complexTypes.size() != 2) {
              // exactly two complex types are required
              throw new CmsXmlException(
                  Messages.get().container(Messages.ERR_CD_COMPLEX_TYPE_COUNT_1, new Integer(complexTypes.size())));
          }
  
          // get the outer element sequence, this must be the first element
          CmsXmlComplexTypeSequence outerSequence = validateComplexTypeSequence(complexTypes.get(0), nestedDefinitions);
          CmsXmlNestedContentDefinition outer = (CmsXmlNestedContentDefinition)outerSequence.getSequence().get(0);
  
          // make sure the inner and outer element names are as required
          String outerTypeName = createTypeName(name);
          String innerTypeName = createTypeName(outer.getName());
          validateAttribute(complexTypes.get(0), XSD_ATTRIBUTE_NAME, outerTypeName);
          validateAttribute(complexTypes.get(1), XSD_ATTRIBUTE_NAME, innerTypeName);
          validateAttribute(main, XSD_ATTRIBUTE_TYPE, outerTypeName);
  
          // generate the result XML content definition
          CmsXmlContentDefinition result = new CmsXmlContentDefinition(name, null, schemaLocation);
  
          // set the nested definitions
          result.m_includes = nestedDefinitions;
          // set the schema document
          result.m_schemaDocument = document;
  
          // the inner name is the element name set in the outer sequence
          result.setInnerName(outer.getName());
          if (recursive) {
              nestedDefinitions.add(result);
          }
  
          // get the inner element sequence, this must be the second element
          CmsXmlComplexTypeSequence innerSequence = validateComplexTypeSequence(complexTypes.get(1), nestedDefinitions);
  
          // add the types from the main sequence node
          Iterator<I_CmsXmlSchemaType> it = innerSequence.getSequence().iterator();
          while (it.hasNext()) {
              result.addType(it.next());
          }
  
          // store if this content definition contains a xsd:choice sequence
          result.m_sequenceType = innerSequence.getSequenceType();
          result.m_choiceMaxOccurs = innerSequence.getChoiceMaxOccurs();
  
          // resolve the XML content handler information
          List<Element> annotations = CmsXmlGenericWrapper.elements(root, XSD_NODE_ANNOTATION);
          I_CmsXmlContentHandler contentHandler = null;
          Element appInfoElement = null;
  
          if (annotations.size() > 0) {
              List<Element> appinfos = CmsXmlGenericWrapper.elements(annotations.get(0), XSD_NODE_APPINFO);
  
              if (appinfos.size() > 0) {
                  // the first appinfo node contains the specific XML content data
                  appInfoElement = appinfos.get(0);
  
                  // check for a special content handler in the appinfo node
                  Element handlerElement = appInfoElement.element("handler");
                  if (handlerElement != null) {
                      String className = handlerElement.attributeValue("class");
                      if (className != null) {
                          contentHandler = OpenCms.getXmlContentTypeManager().getFreshContentHandler(className);
                      }
                  }
              }
          }
  
          if (contentHandler == null) {
              // if no content handler is defined, the default handler is used
              contentHandler = OpenCms.getXmlContentTypeManager().getFreshContentHandler(
                  CmsDefaultXmlContentHandler.class.getName());
          }
  
          // analyze the app info node with the selected XML content handler
          contentHandler.initialize(appInfoElement, result);
          result.m_contentHandler = contentHandler;
  
          result.freeze();
  
          if (resolver instanceof CmsXmlEntityResolver) {
              // put the generated content definition in the cache
              ((CmsXmlEntityResolver)resolver).cacheContentDefinition(schemaLocation, result);
          }
  
          return result;
      }