docs: protected void addCheckRule(
          CmsXmlContentDefinition contentDefinition,
          String elementName,
          String invalidate,
          String type)
      throws CmsXmlException {
  
          I_CmsXmlSchemaType schemaType = contentDefinition.getSchemaType(elementName);
          if (schemaType == null) {
              // no element with the given name
              throw new CmsXmlException(
                  Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_ELEM_1, elementName));
          }
          if (!CmsXmlVfsFileValue.TYPE_NAME.equals(schemaType.getTypeName())
              && !CmsXmlVarLinkValue.TYPE_NAME.equals(schemaType.getTypeName())) {
              // element is not a OpenCmsVfsFile
              throw new CmsXmlException(
                  Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_INVALID_TYPE_1, elementName));
          }
  
          // cache the check rule data
          Boolean invalidateParent = null;
          if ((invalidate == null)
              || invalidate.equalsIgnoreCase(Boolean.TRUE.toString())
              || invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_NODE)) {
              invalidateParent = Boolean.FALSE;
          } else if (invalidate.equalsIgnoreCase(APPINFO_ATTR_TYPE_PARENT)) {
              invalidateParent = Boolean.TRUE;
          }
          if (invalidateParent != null) {
              m_relationChecks.put(elementName, invalidateParent);
          }
          CmsRelationType relationType = (type == null ? CmsRelationType.XML_WEAK : CmsRelationType.valueOfXml(type));
          m_relations.put(elementName, relationType);
  
          if (invalidateParent != null) {
              // check the whole xpath hierarchy
              String path = elementName;
              while (CmsStringUtil.isNotEmptyOrWhitespaceOnly(path)) {
                  if (!isInvalidateParent(path)) {
                      // if invalidate type = node, then the node needs to be optional
                      if (contentDefinition.getSchemaType(path).getMinOccurs() > 0) {
                          // element is not optional
                          throw new CmsXmlException(
                              Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_OPTIONAL_1, path));
                      }
                      // no need to further check
                      break;
                  } else if (!CmsXmlUtils.isDeepXpath(path)) {
                      // if invalidate type = parent, then the node needs to be nested
                      // document root can not be invalidated
                      throw new CmsXmlException(Messages.get().container(Messages.ERR_XMLCONTENT_CHECK_NOT_EMPTY_DOC_0));
                  }
                  path = CmsXmlUtils.removeLastXpathElement(path);
              }
          }
      }