content: /*
   * This library is part of OpenCms -
   * the Open Source Content Management System
   *
   * Copyright (c) Alkacon Software GmbH & Co. KG (https://www.alkacon.com)
   *
   * This library is free software; you can redistribute it and/or
   * modify it under the terms of the GNU Lesser General Public
   * License as published by the Free Software Foundation; either
   * version 2.1 of the License, or (at your option) any later version.
   *
   * This library 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
   * Lesser General Public License for more details.
   *
   * For further information about Alkacon Software GmbH & Co. KG, please see the
   * company website: https://www.alkacon.com
   *
   * For further information about OpenCms, please see the
   * project website: https://www.opencms.org
   *
   * You should have received a copy of the GNU Lesser General Public
   * License along with this library; if not, write to the Free Software
   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   */
  
  package org.opencms.workplace.editors;
  
  import org.opencms.file.CmsFile;
  import org.opencms.file.CmsObject;
  import org.opencms.file.CmsRequestContext;
  import org.opencms.file.CmsResource;
  import org.opencms.file.CmsResourceFilter;
  import org.opencms.file.collectors.A_CmsResourceCollector;
  import org.opencms.i18n.CmsEncoder;
  import org.opencms.i18n.CmsLocaleManager;
  import org.opencms.i18n.CmsMultiMessages;
  import org.opencms.json.JSONArray;
  import org.opencms.json.JSONException;
  import org.opencms.json.JSONObject;
  import org.opencms.jsp.CmsJspActionElement;
  import org.opencms.lock.CmsLockType;
  import org.opencms.main.CmsException;
  import org.opencms.main.CmsLog;
  import org.opencms.main.OpenCms;
  import org.opencms.util.CmsRequestUtil;
  import org.opencms.util.CmsStringUtil;
  import org.opencms.util.CmsUUID;
  import org.opencms.widgets.A_CmsWidget;
  import org.opencms.widgets.I_CmsWidget;
  import org.opencms.widgets.I_CmsWidgetDialog;
  import org.opencms.widgets.I_CmsWidgetParameter;
  import org.opencms.workplace.CmsWorkplace;
  import org.opencms.workplace.CmsWorkplaceSettings;
  import org.opencms.workplace.editors.directedit.CmsDirectEditButtonSelection;
  import org.opencms.xml.CmsXmlContentDefinition;
  import org.opencms.xml.CmsXmlEntityResolver;
  import org.opencms.xml.CmsXmlException;
  import org.opencms.xml.CmsXmlUtils;
  import org.opencms.xml.content.CmsXmlContent;
  import org.opencms.xml.content.CmsXmlContentErrorHandler;
  import org.opencms.xml.content.CmsXmlContentFactory;
  import org.opencms.xml.content.CmsXmlContentTab;
  import org.opencms.xml.content.CmsXmlContentValueSequence;
  import org.opencms.xml.types.CmsXmlNestedContentDefinition;
  import org.opencms.xml.types.I_CmsXmlContentValue;
  import org.opencms.xml.types.I_CmsXmlSchemaType;
  
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.util.ArrayList;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.Set;
  
  import javax.servlet.ServletException;
  import javax.servlet.http.HttpServletRequest;
  import javax.servlet.jsp.JspException;
  
  import org.apache.commons.logging.Log;
  
  /**
   * Creates the editor for XML content definitions.<p>
   *
   * @since 6.0.0
   */
  public class CmsXmlContentEditor extends CmsEditor implements I_CmsWidgetDialog {
  
      /** Action for checking content before executing the direct edit action. */
      public static final int ACTION_CHECK = 151;
  
      /** Action for confirming the XML content structure correction. */
      public static final int ACTION_CONFIRMCORRECTION = 152;
  
      /** Value for the action: copy the current locale. */
      public static final int ACTION_COPYLOCALE = 141;
  
      /** Action for correction of the XML content structure confirmed. */
      public static final int ACTION_CORRECTIONCONFIRMED = 153;
  
      /** Action for optional element creation. */
      public static final int ACTION_ELEMENT_ADD = 154;
  
      /** Action for element move down operation. */
      public static final int ACTION_ELEMENT_MOVE_DOWN = 155;
  
      /** Action for element move up operation. */
      public static final int ACTION_ELEMENT_MOVE_UP = 156;
  
      /** Action for optional element removal. */
      public static final int ACTION_ELEMENT_REMOVE = 157;
  
      /** Action for new file creation. */
      public static final int ACTION_NEW = 158;
  
      /** Action that sub choices should be determined. */
      public static final int ACTION_SUBCHOICES = 159;
  
      /** Request context attribute for the page from which the editor was opened. */
      public static final String ATTRIBUTE_EDITCONTEXT = CmsXmlContentEditor.class.getName() + ".ATTRIBUTE_EDITCONTEXT";
  
      /** Indicates that the content should be checked before executing the direct edit action. */
      public static final String EDITOR_ACTION_CHECK = "check";
  
      /** Indicates that the correction of the XML content structure should be confirmed. */
      public static final String EDITOR_ACTION_CONFIRMCORRECTION = "confirmcorrect";
  
      /** Indicates an optional element should be created. */
      public static final String EDITOR_ACTION_ELEMENT_ADD = "addelement";
  
      /** Indicates an element should be moved down. */
      public static final String EDITOR_ACTION_ELEMENT_MOVE_DOWN = "elementdown";
  
      /** Indicates an element should be moved up. */
      public static final String EDITOR_ACTION_ELEMENT_MOVE_UP = "elementup";
  
      /** Indicates an optional element should be removed. */
      public static final String EDITOR_ACTION_ELEMENT_REMOVE = "removeelement";
  
      /** Indicates a new file should be created. */
      public static final String EDITOR_ACTION_NEW = CmsDirectEditButtonSelection.VALUE_NEW;
  
      /** Indicates that sub choices should be determined. */
      public static final String EDITOR_ACTION_SUBCHOICES = "subchoices";
  
      /** Indicates that the contents of the current locale should be copied to other locales. */
      public static final String EDITOR_COPYLOCALE = "copylocale";
  
      /** Indicates that the correction of the XML content structure was confirmed by the user. */
      public static final String EDITOR_CORRECTIONCONFIRMED = "correctconfirmed";
  
      /** Parameter name for the request parameter "choiceelement". */
      public static final String PARAM_CHOICEELEMENT = "choiceelement";
  
      /** Parameter name for the request parameter "choicetype". */
      public static final String PARAM_CHOICETYPE = "choicetype";
  
      /** Parameter name for the request parameter "editcontext". */
      public static final String PARAM_EDITCONTEXT = "editcontext";
  
      /** Parameter name for the request parameter "elementindex". */
      public static final String PARAM_ELEMENTINDEX = "elementindex";
  
      /** Parameter name for the request parameter "elementname". */
      public static final String PARAM_ELEMENTNAME = "elementname";
  
      /** Parameter name for the request parameter "newlink". */
      public static final String PARAM_NEWLINK = "newlink";
  
      /** Constant for the editor type, must be the same as the editors subfolder name in the VFS. */
      private static final String EDITOR_TYPE = "xmlcontent";
  
      /** The log object for this class. */
      private static final Log LOG = CmsLog.getLog(CmsXmlContentEditor.class);
  
      /** The content object to edit. */
      private CmsXmlContent m_content;
  
      /** The currently active tab during form generation. */
      private CmsXmlContentTab m_currentTab;
  
      /** The currently active tab index during form generation. */
      private int m_currentTabIndex;
  
      /** The element locale. */
      private Locale m_elementLocale;
  
      /** The list of tabs that have an element with an error. */
      private List<CmsXmlContentTab> m_errorTabs;
  
      /** File object used to read and write contents. */
      private CmsFile m_file;
  
      /** The set of help message IDs that have already been used. */
      private Set<String> m_helpMessageIds;
  
      /** The content creation mode. */
      private String m_mode;
  
      /** Indicates if an optional element is included in the form. */
      private boolean m_optionalElementPresent;
  
      /** Parameter stores the name of the choice element to add. */
      private String m_paramChoiceElement;
  
      /** Parameter stores the flag if the element to add is a choice type. */
      private String m_paramChoiceType;
  
      /** The page in whose context the content is being edited. */
      private String m_paramEditContext;
  
      /** Parameter stores the index of the element to add or remove. */
      private String m_paramElementIndex;
  
      /** Parameter stores the name of the element to add or remove. */
      private String m_paramElementName;
  
      /** The selected model file for the new resource. */
      private String m_paramModelFile;
  
      /** Parameter to indicate if a new XML content resource should be created. */
      private String m_paramNewLink;
  
      /** The post-create handler class. */
      private String m_postCreateHandler;
  
      /** The error handler for the xml content. */
      private CmsXmlContentErrorHandler m_validationHandler;
  
      /** The list of tabs that have an element with a warning. */
      private List<CmsXmlContentTab> m_warningTabs;
  
      /** Visitor implementation that stored the widgets for the content.  */
      private CmsXmlContentWidgetVisitor m_widgetCollector;
  
      /**
       * Public constructor.<p>
       *
       * @param jsp an initialized JSP action element
       */
      public CmsXmlContentEditor(CmsJspActionElement jsp) {
  
          super(jsp);
      }
  
      /**
       * Performs the change element language action of the editor.<p>
       */
      public void actionChangeElementLanguage() {
  
          // save eventually changed content of the editor
          Locale oldLocale = CmsLocaleManager.getLocale(getParamOldelementlanguage());
          Locale newLocale = getElementLocale();
          try {
              setEditorValues(oldLocale);
              if (!m_content.validate(getCms()).hasErrors(oldLocale)) {
                  // no errors found in content
                  if (!m_content.hasLocale(newLocale)) {
                      // check if we should copy the content from a default locale
                      boolean addNew = true;
                      List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
                      if (locales.size() > 1) {
                          // default locales have been set, try to find a match
                          try {
                              m_content.copyLocale(locales, newLocale);
                              addNew = false;
                          } catch (CmsXmlException e) {
                              // no matching default locale was available, we will create a new one later
                          }
                      }
                      if (addNew) {
                          // create new element if selected language element is not present
                          try {
                              m_content.addLocale(getCms(), newLocale);
                          } catch (CmsXmlException e) {
                              if (LOG.isErrorEnabled()) {
                                  LOG.error(e.getLocalizedMessage(), e);
                              }
                          }
                      }
                  }
                  //save to temporary file
                  writeContent();
                  // set default action to suppress error messages
                  setAction(ACTION_DEFAULT);
              } else {
                  // errors found, switch back to old language to show errors
                  setParamElementlanguage(getParamOldelementlanguage());
                  // set stored locale to null to reinitialize it
                  m_elementLocale = null;
              }
          } catch (Exception e) {
              // should usually never happen
              if (LOG.isInfoEnabled()) {
                  LOG.info(e.getLocalizedMessage(), e);
              }
          }
      }
  
      /**
       * Deletes the temporary file and unlocks the edited resource when in direct edit mode.<p>
       *
       * @param forceUnlock if true, the resource will be unlocked anyway
       */
      @Override
      public void actionClear(boolean forceUnlock) {
  
          // delete the temporary file
          deleteTempFile();
          boolean directEditMode = Boolean.valueOf(getParamDirectedit()).booleanValue();
          boolean modified = Boolean.valueOf(getParamModified()).booleanValue();
          if (directEditMode || forceUnlock || !modified) {
              // unlock the resource when in direct edit mode, force unlock is true or resource was not modified
              try {
                  getCms().unlockResource(getParamResource());
              } catch (CmsException e) {
                  // should usually never happen
                  if (LOG.isInfoEnabled()) {
                      LOG.info(e.getLocalizedMessage(), e);
                  }
              }
          }
      }
  
      /**
       * Performs the copy locale action.<p>
       *
       * @throws JspException if something goes wrong
       */
      public void actionCopyElementLocale() throws JspException {
  
          try {
              setEditorValues(getElementLocale());
              if (!hasValidationErrors()) {
                  // save content of the editor only to the temporary file
                  writeContent();
                  CmsObject cloneCms = getCloneCms();
                  CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
                  cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
                  // remove eventual release & expiration date from temporary file to make preview work
                  cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false);
                  cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false);
              }
          } catch (CmsException e) {
              // show error page
              showErrorPage(this, e);
          }
      }
  
      /**
       * Performs the delete locale action.<p>
       *
       * @throws JspException if something goes wrong
       */
      public void actionDeleteElementLocale() throws JspException {
  
          try {
              Locale loc = getElementLocale();
              m_content.removeLocale(loc);
              //write the modified xml content
              writeContent();
              List<Locale> locales = m_content.getLocales();
              if (locales.size() > 0) {
                  // set first locale as new display locale
                  Locale newLoc = locales.get(0);
                  setParamElementlanguage(newLoc.toString());
                  m_elementLocale = newLoc;
              } else {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(Messages.get().getBundle().key(Messages.LOG_GET_LOCALES_1, getParamResource()));
                  }
              }
  
          } catch (CmsXmlException e) {
              // an error occurred while trying to delete the locale, stop action
              showErrorPage(e);
          } catch (CmsException e) {
              // should usually never happen
              if (LOG.isInfoEnabled()) {
                  LOG.info(e.getLocalizedMessage(), e);
              }
          }
      }
  
      /**
       * Performs a configurable action performed by the editor.<p>
       *
       * The default action is: save resource, clear temporary files and publish the resource directly.<p>
       *
       * @throws IOException if a forward fails
       * @throws ServletException of a forward fails
       * @throws JspException if including a JSP fails
       */
      public void actionDirectEdit() throws IOException, JspException, ServletException {
  
          // get the action class from the OpenCms runtime property
          I_CmsEditorActionHandler actionClass = OpenCms.getWorkplaceManager().getEditorActionHandler();
          if (actionClass == null) {
              // error getting the action class, save content and exit the editor
              actionSave();
              actionExit();
          } else {
              actionClass.editorAction(this, getJsp());
          }
      }
  
      /**
       * Performs the exit editor action.<p>
       *
       * @see org.opencms.workplace.editors.CmsEditor#actionExit()
       */
      @Override
      public void actionExit() throws IOException, JspException, ServletException {
  
          if (getAction() == ACTION_CANCEL) {
              // save and exit was canceled
              return;
          }
          // unlock resource if we are in direct edit mode
          actionClear(false);
          // close the editor
          actionClose();
      }
  
      /**
       * Moves an element in the xml content either up or down.<p>
       *
       * Depends on the given action value.<p>
       *
       * @throws JspException if including the error page fails
       */
      public void actionMoveElement() throws JspException {
  
          // set editor values from request
          try {
              setEditorValues(getElementLocale());
          } catch (CmsXmlException e) {
              // an error occurred while trying to set the values, stop action
              showErrorPage(e);
              return;
          }
          // get the necessary parameters to move the element
          int index = 0;
          try {
              index = Integer.parseInt(getParamElementIndex());
          } catch (Exception e) {
              // ignore, should not happen
          }
  
          // get the value to move
          I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index);
  
          if (getAction() == ACTION_ELEMENT_MOVE_DOWN) {
              // move down the value
              value.moveDown();
          } else {
              // move up the value
              value.moveUp();
          }
          if (getValidationHandler().hasWarnings(getElementLocale())) {
              // there were warnings for the edited content, reset validation handler to avoid display issues
              resetErrorHandler();
          }
          try {
              // write the modified content to the temporary file
              writeContent();
          } catch (CmsException e) {
              // an error occurred while trying to save
              showErrorPage(e);
          }
      }
  
      /**
       * Creates a new XML content item for editing.<p>
       *
       * @throws JspException in case something goes wrong
       */
      public void actionNew() throws JspException {
  
          String newFileName = "";
          try {
              newFileName = A_CmsResourceCollector.createResourceForCollector(
                  getCms(),
                  m_paramNewLink,
                  getElementLocale(),
                  getParamResource(),
                  getParamModelFile(),
                  getParamMode(),
                  getParamPostCreateHandler());
              // wipe out parameters for the editor to ensure proper operation
              setParamNewLink(null);
              setParamAction(null);
              setParamResource(newFileName);
              setAction(ACTION_DEFAULT);
  
              // create the temporary file to work with
              setParamTempfile(createTempFile());
  
              // set the member variables for the content
              m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
              if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(getParamModelFile())) {
                  m_content = CmsXmlContentFactory.unmarshal(
                      getCms(),
                      getCms().readFile(newFileName, CmsResourceFilter.ALL));
              } else {
                  m_content = CmsXmlContentFactory.unmarshal(getCms(), m_file);
              }
  
          } catch (CmsException e) {
              if (LOG.isErrorEnabled()) {
                  LOG.error(Messages.get().getBundle().key(Messages.LOG_CREATE_XML_CONTENT_ITEM_1, m_paramNewLink), e);
              }
              throw new JspException(e);
          } finally {
              try {
                  // delete the new file
                  getCms().deleteResource(newFileName, CmsResource.DELETE_REMOVE_SIBLINGS);
              } catch (CmsException e) {
                  // ignore
              }
          }
      }
  
      /**
       * Performs the preview XML content action in a new browser window.<p>
       *
       * @throws IOException if redirect fails
       * @throws JspException if inclusion of error page fails
       */
      public void actionPreview() throws IOException, JspException {
  
          try {
              // save content of the editor only to the temporary file
              setEditorValues(getElementLocale());
              writeContent();
              CmsObject cloneCms = getCloneCms();
              CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
              cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
              // remove eventual release & expiration date from temporary file to make preview work
              cloneCms.setDateReleased(getParamTempfile(), CmsResource.DATE_RELEASED_DEFAULT, false);
              cloneCms.setDateExpired(getParamTempfile(), CmsResource.DATE_EXPIRED_DEFAULT, false);
  
          } catch (CmsException e) {
              // show error page
              showErrorPage(this, e);
          }
  
          // get preview uri from content handler
          String previewUri = m_content.getHandler().getPreview(getCms(), m_content, getParamTempfile());
  
          // create locale request parameter
          StringBuffer param = new StringBuffer(8);
          if (previewUri.indexOf('?') != -1) {
              param.append("&");
          } else {
              param.append("?");
          }
          param.append(CmsLocaleManager.PARAMETER_LOCALE);
          param.append("=");
          param.append(getParamElementlanguage());
  
          // redirect to the temporary file with currently active element language or to the specified preview uri
          sendCmsRedirect(previewUri + param);
      }
  
      /**
       * Performs the save content action.<p>
       *
       * @see org.opencms.workplace.editors.CmsEditor#actionSave()
       */
      @Override
      public void actionSave() throws JspException {
  
          actionSave(getElementLocale());
          if (getAction() != ACTION_CANCEL) {
              // save successful, set save action
              setAction(ACTION_SAVE);
          }
      }
  
      /**
       * Performs the save content action.<p>
       *
       * This is also used when changing the element language.<p>
       *
       * @param locale the locale to save the content
       * @throws JspException if including the error page fails
       */
      public void actionSave(Locale locale) throws JspException {
  
          try {
              setEditorValues(locale);
              // check if content has errors
              if (!hasValidationErrors()) {
                  // no errors found, write content and copy temp file contents
                  writeContent();
                  commitTempFile();
                  // set the modified parameter
                  setParamModified(Boolean.TRUE.toString());
                  // update the offline search indices
                  OpenCms.getSearchManager().updateOfflineIndexes();
              }
          } catch (CmsException e) {
              showErrorPage(e);
          }
  
      }
  
      /**
       * Adds an optional element to the XML content or removes an optional element from the XML content.<p>
       *
       * Depends on the given action value.<p>
       *
       * @throws JspException if including the error page fails
       */
      public void actionToggleElement() throws JspException {
  
          // set editor values from request
          try {
              setEditorValues(getElementLocale());
          } catch (CmsXmlException e) {
              // an error occurred while trying to set the values, stop action
              showErrorPage(e);
              return;
          }
  
          // get the necessary parameters to add/remove the element
          int index = 0;
          try {
              index = Integer.parseInt(getParamElementIndex());
          } catch (Exception e) {
              // ignore, should not happen
          }
  
          if (getAction() == ACTION_ELEMENT_REMOVE) {
              // remove the value , first get the value to remove
              I_CmsXmlContentValue value = m_content.getValue(getParamElementName(), getElementLocale(), index);
              m_content.removeValue(getParamElementName(), getElementLocale(), index);
              // check if the value was a choice option and the last one
              if (value.isChoiceOption()
                  && (m_content.getSubValues(
                      CmsXmlUtils.removeLastXpathElement(getParamElementName()),
                      getElementLocale()).size() == 0)) {
                  // also remove the parent choice type value
                  String xpath = CmsXmlUtils.removeLastXpathElement(getParamElementName());
                  m_content.removeValue(xpath, getElementLocale(), CmsXmlUtils.getXpathIndexInt(xpath) - 1);
              }
          } else {
              // add the new value after the clicked element
              if (m_content.hasValue(getParamElementName(), getElementLocale())) {
                  // when other values are present, increase index to use right position
                  index += 1;
              }
              String elementPath = getParamElementName();
              if (CmsStringUtil.isNotEmpty(getParamChoiceElement())) {
                  // we have to add a choice element, first check if the element to add itself is part of a choice or not
                  boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue();
                  I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath);
                  if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) {
                      // this is a choice option or a nested choice type to add, remove last element name from xpath
                      elementPath = CmsXmlUtils.removeLastXpathElement(elementPath);
                  } else {
                      // this is a choice type to add, first create type element
                      m_content.addValue(getCms(), elementPath, getElementLocale(), index);
                      elementPath = CmsXmlUtils.createXpathElement(elementPath, index + 1);
                      // all eventual following elements to create have to be at first position
                      index = 0;
                  }
                  // check if there are nested choice elements to add
                  if (CmsXmlUtils.isDeepXpath(getParamChoiceElement())) {
                      // create all missing elements except the last one
                      String pathToChoice = CmsXmlUtils.removeLastXpathElement(getParamChoiceElement());
                      String newPath = elementPath;
                      while (CmsStringUtil.isNotEmpty(pathToChoice)) {
                          String createElement = CmsXmlUtils.getFirstXpathElement(pathToChoice);
                          newPath = CmsXmlUtils.concatXpath(newPath, createElement);
                          pathToChoice = CmsXmlUtils.isDeepXpath(pathToChoice)
                          ? CmsXmlUtils.removeFirstXpathElement(pathToChoice)
                          : null;
                          I_CmsXmlContentValue newVal = m_content.addValue(getCms(), newPath, getElementLocale(), index);
                          newPath = newVal.getPath();
                          // all eventual following elements to create have to be at first position
                          index = 0;
                      }
                      // create the path to the last choice element
                      elementPath = CmsXmlUtils.concatXpath(
                          newPath,
                          CmsXmlUtils.getLastXpathElement(getParamChoiceElement()));
                  } else {
                      // create the path to the choice element
                      elementPath += "/" + getParamChoiceElement();
                  }
              }
              // add the value
              m_content.addValue(getCms(), elementPath, getElementLocale(), index);
          }
  
          if (getValidationHandler().hasWarnings(getElementLocale())) {
              // there were warnings for the edited content, reset validation handler to avoid display issues
              resetErrorHandler();
          }
  
          try {
              // write the modified content to the temporary file
              writeContent();
          } catch (CmsException e) {
              // an error occurred while trying to save
              showErrorPage(e);
          }
  
      }
  
      /**
       * Returns the JSON array with information about the choices of a given element.<p>
       *
       * The returned array is only filled if the given element has choice options, otherwise an empty array is returned.<br/>
       * Note: the first array element is an object containing information if the element itself is a choice type,
       * the following elements are the choice option items.<p>
       *
       * @param elementName the element name to check (complete xpath)
       * @param choiceType flag indicating if the given element name represents a choice type or not
       * @param checkChoice flag indicating if the element name should be checked if it is a choice option and choice type
       *
       * @return the JSON array with information about the choices of a given element
       */
      public JSONArray buildElementChoices(String elementName, boolean choiceType, boolean checkChoice) {
  
          JSONArray choiceElements = new JSONArray();
          String choiceName = elementName;
          I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementName);
          if (checkChoice && elemType.isChoiceOption() && elemType.isChoiceType()) {
              // the element itself is a choice option and again a choice type, remove the last element to get correct choices
              choiceName = CmsXmlUtils.removeLastXpathElement(elementName);
          }
          // use xpath to get choice information
          if (m_content.hasChoiceOptions(choiceName, getElementLocale())) {
              // we have choice options, first add information about type to create
              JSONObject info = new JSONObject();
              try {
                  // put information if element is a choice type
                  info.put("choicetype", choiceType);
                  choiceElements.put(info);
                  // get the available choice options for the choice element
                  List<I_CmsXmlSchemaType> options = m_content.getChoiceOptions(choiceName, getElementLocale());
                  for (Iterator<I_CmsXmlSchemaType> i = options.iterator(); i.hasNext();) {
                      // add the available element options
                      I_CmsXmlSchemaType type = i.next();
                      JSONObject option = new JSONObject();
                      String key = A_CmsWidget.LABEL_PREFIX
                          + type.getContentDefinition().getInnerName()
                          + "."
                          + type.getName();
                      // add element name, label and help info
                      option.put("name", type.getName());
                      option.put("label", keyDefault(key, type.getName()));
                      option.put("help", keyDefault(key + A_CmsWidget.HELP_POSTFIX, ""));
                      // add info if the choice itself is a (sub) choice type
                      option.put("subchoice", type.isChoiceType());
                      choiceElements.put(option);
                  }
              } catch (JSONException e) {
                  // ignore, should not happen
              }
          }
          return choiceElements;
      }
  
      /**
       * Builds the HTML String for the element language selector.<p>
       *
       * This method has to use the resource request parameter because the temporary file is
       * not available in the upper button frame.<p>
       *
       * @param attributes optional attributes for the &lt;select&gt; tag
       * @return the HTML for the element language select box
       */
      public String buildSelectElementLanguage(String attributes) {
  
          return buildSelectElementLanguage(attributes, getParamResource(), getElementLocale());
      }
  
      /**
       * Returns the available sub choices for a nested choice element.<p>
       *
       * @return the available sub choices for a nested choice element as JSON array string
       */
      public String buildSubChoices() {
  
          String elementPath = getParamElementName();
  
          // we have to add a choice element, first check if the element to add itself is part of a choice or not
          boolean choiceType = Boolean.valueOf(getParamChoiceType()).booleanValue();
          I_CmsXmlSchemaType elemType = m_content.getContentDefinition().getSchemaType(elementPath);
          if (!choiceType || (elemType.isChoiceOption() && elemType.isChoiceType())) {
              // this is a choice option or a nested choice type to add, remove last element name from xpath
              elementPath = CmsXmlUtils.removeLastXpathElement(elementPath);
          }
          elementPath = CmsXmlUtils.concatXpath(elementPath, getParamChoiceElement());
          return buildElementChoices(elementPath, choiceType, false).toString();
      }
  
      /**
       * @see org.opencms.widgets.I_CmsWidgetDialog#getButtonStyle()
       */
      public int getButtonStyle() {
  
          return getSettings().getUserSettings().getEditorButtonStyle();
      }
  
      /**
       * @see org.opencms.workplace.editors.CmsEditor#getEditorResourceUri()
       */
      @Override
      public String getEditorResourceUri() {
  
          return getSkinUri() + "editors/" + EDITOR_TYPE + "/";
      }
  
      /**
       * Returns the current element locale.<p>
       *
       * @return the current element locale
       */
      public Locale getElementLocale() {
  
          if (m_elementLocale == null) {
              if (CmsStringUtil.isNotEmpty(getParamElementlanguage()) && !"null".equals(getParamElementlanguage())) {
                  m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage());
              } else {
                  initElementLanguage();
                  m_elementLocale = CmsLocaleManager.getLocale(getParamElementlanguage());
              }
          }
          return m_elementLocale;
      }
  
      /**
       * @see org.opencms.widgets.I_CmsWidgetDialog#getHelpMessageIds()
       */
      public Set<String> getHelpMessageIds() {
  
          if (m_helpMessageIds == null) {
              m_helpMessageIds = new HashSet<String>();
          }
          return m_helpMessageIds;
      }
  
      /**
       * Returns the name of the choice element to add.<p>
       *
       * @return the name of the choice element to add
       */
      public String getParamChoiceElement() {
  
          return m_paramChoiceElement;
      }
  
      /**
       * Returns the flag if the element to add is a choice type.<p>
       *
       * @return the flag if the element to add is a choice type
       */
      public String getParamChoiceType() {
  
          return m_paramChoiceType;
      }
  
      /**
       * Gets the editor context path (usually either a container page path or null).<p>
       *
       * @return the editor context path
       */
      public String getParamEditContext() {
  
          return m_paramEditContext;
      }
  
      /**
       * Returns the index of the element to add or remove.<p>
       *
       * @return the index of the element to add or remove
       */
      public String getParamElementIndex() {
  
          return m_paramElementIndex;
      }
  
      /**
       * Returns the name of the element to add or remove.<p>
       *
       * @return the name of the element to add or remove
       */
      public String getParamElementName() {
  
          return m_paramElementName;
      }
  
      /**
       * Returns the mode.<p>
       *
       * @return the mode
       */
      public String getParamMode() {
  
          return m_mode;
      }
  
      /**
       * Returns the parameter that specifies the model file name.<p>
       *
       * @return the parameter that specifies the model file name
       */
      public String getParamModelFile() {
  
          return m_paramModelFile;
      }
  
      /**
       * Returns the "new link" parameter.<p>
       *
       * @return the "new link" parameter
       */
      public String getParamNewLink() {
  
          return m_paramNewLink;
      }
  
      /**
       * Returns the postCreateHandler.<p>
       *
       * @return the postCreateHandler
       */
      public String getParamPostCreateHandler() {
  
          return m_postCreateHandler;
      }
  
      /**
       * @see org.opencms.widgets.I_CmsWidgetDialog#getUserAgent()
       */
      public String getUserAgent() {
  
          return getJsp().getRequest().getHeader(CmsRequestUtil.HEADER_USER_AGENT);
      }
  
      /**
       * Returns the different xml editor widgets used in the form to display.<p>
       *
       * @return the different xml editor widgets used in the form to display
       */
      public CmsXmlContentWidgetVisitor getWidgetCollector() {
  
          if (m_widgetCollector == null) {
              // create an instance of the widget collector
              m_widgetCollector = new CmsXmlContentWidgetVisitor(getCms(), getElementLocale());
              m_content.visitAllValuesWith(m_widgetCollector);
          }
          return m_widgetCollector;
      }
  
      /**
       * Generates the HTML form for the XML content editor.<p>
       *
       * @return the HTML that generates the form for the XML editor
       */
      public String getXmlEditorForm() {
  
          // set "editor mode" attribute (required for link replacement in the root site)
          getCms().getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_EDITOR, Boolean.TRUE);
  
          // add customized message bundle eventually specified in XSD of XML content
          addMessages(m_content.getHandler().getMessages(getLocale()));
          ((CmsMultiMessages)getMessages()).setFallbackHandler(m_content.getHandler().getMessageKeyHandler());
          // initialize tab lists for error handling before generating the editor form
          m_errorTabs = new ArrayList<CmsXmlContentTab>();
          m_warningTabs = new ArrayList<CmsXmlContentTab>();
  
          return getXmlEditorForm(m_content.getContentDefinition(), "", true, false).toString();
      }
  
      /**
       * Generates the HTML for the end of the HTML editor form page.<p>
       *
       * @return the HTML for the end of the HTML editor form page
       * @throws JspException if including the error page fails
       */
      public String getXmlEditorHtmlEnd() throws JspException {
  
          StringBuffer result = new StringBuffer(16384);
          if (m_optionalElementPresent) {
              // disabled optional element(s) present, reset widgets to show help bubbles on optional form entries
              resetWidgetCollector();
          }
          try {
              // get all widgets from collector
              Iterator<String> i = getWidgetCollector().getWidgets().keySet().iterator();
              while (i.hasNext()) {
                  // get the value of the widget
                  String key = i.next();
                  I_CmsXmlContentValue value = getWidgetCollector().getValues().get(key);
                  I_CmsWidget widget = getWidgetCollector().getWidgets().get(key);
                  result.append(widget.getDialogHtmlEnd(getCms(), this, (I_CmsWidgetParameter)value));
  
              }
  
              // add empty help text layer
              result.append("<div class=\"help\" id=\"helpText\" ");
              result.append("onmouseover=\"showHelpText();\" onmouseout=\"hideHelpText();\"></div>\n");
  
              // add empty element button layer
              result.append("<div class=\"xmlButtons\" id=\"xmlElementButtons\" ");
              result.append(
                  "onmouseover=\"checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\"></div>\n");
  
              // return the HTML
              return result.toString();
          } catch (Exception e) {
              showErrorPage(e);
              return "";
          }
      }
  
      /**
       * Generates the JavaScript includes for the used widgets in the editor form.<p>
       *
       * @return the JavaScript includes for the used widgets
       * @throws JspException if including the error page fails
       */
      public String getXmlEditorIncludes() throws JspException {
  
          StringBuffer result = new StringBuffer(1024);
  
          // first include general JQuery JS and UI components
          result.append("<script  src=\"");
          result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.js");
          result.append("\"></script>\n");
          result.append("<script  src=\"");
          result.append(CmsWorkplace.getSkinUri()).append("jquery/packed/jquery.ui.js");
          result.append("\"></script>\n");
  
          // including dialog-helper.js to be used by ADE gallery widgets
          result.append("<script  src=\"");
          result.append(CmsWorkplace.getSkinUri()).append("components/widgets/dialog-helper.js");
          result.append("\"></script>\n");
  
          // import the JavaScript for JSON helper functions
          result.append("<script  src=\"");
          result.append(CmsWorkplace.getSkinUri()).append("commons/json2.js");
          result.append("\"></script>\n");
          result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
          result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.css");
          result.append("\">\n");
          result.append("<link rel=\"stylesheet\" type=\"text/css\" href=\"");
          result.append(CmsWorkplace.getSkinUri()).append("jquery/css/ui-ocms/jquery.ui.ocms.css");
          result.append("\">\n");
  
          try {
              // iterate over unique widgets from collector
              Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
              while (i.hasNext()) {
                  I_CmsWidget widget = i.next();
                  result.append(widget.getDialogIncludes(getCms(), this));
                  result.append("\n");
              }
          } catch (Exception e) {
              showErrorPage(e);
          }
          return result.toString();
      }
  
      /**
       * Generates the JavaScript initialization calls for the used widgets in the editor form.<p>
       *
       * @return the JavaScript initialization calls for the used widgets
       * @throws JspException if including the error page fails
       */
      public String getXmlEditorInitCalls() throws JspException {
  
          StringBuffer result = new StringBuffer(512);
          try {
              // iterate over unique widgets from collector
              Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
              while (i.hasNext()) {
                  I_CmsWidget widget = i.next();
                  result.append(widget.getDialogInitCall(getCms(), this));
              }
          } catch (Exception e) {
              showErrorPage(e);
          }
          return result.toString();
      }
  
      /**
       * Generates the JavaScript initialization methods for the used widgets.<p>
       *
       * @return the JavaScript initialization methods for the used widgets
       *
       * @throws JspException if an error occurs during JavaScript generation
       */
      public String getXmlEditorInitMethods() throws JspException {
  
          StringBuffer result = new StringBuffer(1024);
  
          // first create JS for eventual tabs
          StringBuffer tabs = new StringBuffer(512);
          if (m_content.getHandler().getTabs().size() > 0) {
              // we have some tabs defined, initialize them using JQuery
              result.append("var xmlSelectedTab = 0;\n");
              result.append("var xmlErrorTabs = new Array();\n");
              result.append("var xmlWarningTabs = new Array();\n");
              tabs.append("\t$xmltabs = $(\"#xmltabs\").tabs({});\n");
              tabs.append("\t$xmltabs.tabs(\"select\", xmlSelectedTab);\n");
              tabs.append("\tfor (var i=0; i<xmlErrorTabs.length; i++) {\n");
              tabs.append("\t\t$(\"#OcmsTabTab\" + xmlErrorTabs[i]).addClass(\"ui-state-error\");\n");
              tabs.append("\t}\n");
              tabs.append("\tfor (var i=0; i<xmlWarningTabs.length; i++) {\n");
              tabs.append("\t\t$(\"#OcmsTabTab\" + xmlWarningTabs[i]).addClass(\"ui-state-warning\");\n");
              tabs.append("\t}\n");
          }
  
          // create JS for UI dialog
          result.append("var dialogTitleAddChoice = \"");
          result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0));
          result.append("\";\n");
          result.append("var dialogTitleAddSubChoice = \"");
          result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_SUB_ADD_HL_0));
          result.append("\";\n");
          result.append("var vfsPathEditorForm = \"");
          result.append(getJsp().link(CmsEditor.PATH_EDITORS + "xmlcontent/editor_form.jsp"));
          result.append("\";\n");
          result.append("if (jQuery) {\n");
          result.append("$(document).ready(function(){\n");
          result.append(tabs);
          result.append("\t$(\"#xmladdelementdialog\").dialog({\n");
          result.append("\t\ttitle: \"");
          result.append(key(Messages.GUI_EDITOR_XMLCONTENT_CHOICE_ADD_HL_0));
          result.append("\",\n");
          result.append("\t\tautoOpen: false,\n");
          result.append("\t\tbgiframe: true,\n");
          result.append("\t\tminHeight: 150,\n");
          result.append("\t\tminWidth: 300,\n");
          result.append("\t\twidth: 360,\n");
          result.append("\t\tmodal: true,\n");
          result.append("\t\tbuttons: {  \"");
          result.append(key(org.opencms.workplace.Messages.GUI_DIALOG_BUTTON_CANCEL_0));
          result.append("\": function() { $(this).dialog(\"close\"); } }\n");
          result.append("\t});\n");
          result.append("});\n");
          result.append("}\n");
  
          try {
              // iterate over unique widgets from collector
              Iterator<I_CmsWidget> i = getWidgetCollector().getUniqueWidgets().iterator();
              while (i.hasNext()) {
                  I_CmsWidget widget = i.next();
                  result.append(widget.getDialogInitMethod(getCms(), this));
                  result.append("\n");
              }
          } catch (Exception e) {
              showErrorPage(e);
          }
          return result.toString();
      }
  
      /**
       * Returns true if the edited content contains validation errors, otherwise false.<p>
       *
       * @return true if the edited content contains validation errors, otherwise false
       */
      public boolean hasValidationErrors() {
  
          return getValidationHandler().hasErrors();
      }
  
      /**
       * Returns true if the preview is available for the edited xml content.<p>
       *
       * This method has to use the resource request parameter and read the file from vfs because the temporary file is
       * not available in the upper button frame.<p>
       *
       * @return true if the preview is enabled, otherwise false
       */
      public boolean isPreviewEnabled() {
  
          try {
              // read the original file because temporary file is not created when opening button frame
              CmsFile file = getCms().readFile(getParamResource(), CmsResourceFilter.ALL);
              CmsXmlContent content = CmsXmlContentFactory.unmarshal(getCloneCms(), file);
              return content.getHandler().getPreview(getCms(), m_content, getParamResource()) != null;
          } catch (Exception e) {
              // error reading or unmarshalling, no preview available
              return false;
          }
      }
  
      /**
       * Sets the editor values for the locale with the parameters from the request.<p>
       *
       * Called before saving the xml content, redisplaying the input form,
       * changing the language and adding or removing elements.<p>
       *
       * @param locale the locale of the content to save
       * @throws CmsXmlException if something goes wrong
       */
      public void setEditorValues(Locale locale) throws CmsXmlException {
  
          List<String> names = m_content.getNames(locale);
          Iterator<String> i = names.iterator();
          while (i.hasNext()) {
              String path = i.next();
              I_CmsXmlContentValue value = m_content.getValue(path, locale);
              if (value.isSimpleType()) {
                  // We don't care about the old editor for the 'inheritable' widget configuration,
                  // so we're using the old getWidget method here
                  I_CmsWidget widget = value.getContentDefinition().getContentHandler().getWidget(value);
                  widget.setEditorValue(
                      getCms(),
                      getJsp().getRequest().getParameterMap(),
                      this,
                      (I_CmsWidgetParameter)value);
              }
          }
      }
  
      /**
       * Sets the name of the choice element to add.<p>
       *
       * @param choiceElement the name of the choice element to add
       */
      public void setParamChoiceElement(String choiceElement) {
  
          m_paramChoiceElement = choiceElement;
      }
  
      /**
       * Sets the flag if the element to add is a choice type.<p>
       *
       * @param paramChoiceType the flag if the element to add is a choice type
       */
      public void setParamChoiceType(String paramChoiceType) {
  
          m_paramChoiceType = paramChoiceType;
      }
  
      /**
       * Sets the edit context URI.<p>
       *
       * @param editContext the edit context URI.
       */
      public void setParamEditContext(String editContext) {
  
          m_paramEditContext = editContext;
          CmsObject cms = getCms();
          if ((cms != null) && (editContext != null)) {
              cms.getRequestContext().setAttribute(ATTRIBUTE_EDITCONTEXT, editContext);
          }
      }
  
      /**
       * Sets the index of the element to add or remove.<p>
       *
       * @param elementIndex the index of the element to add or remove
       */
      public void setParamElementIndex(String elementIndex) {
  
          m_paramElementIndex = elementIndex;
      }
  
      /**
       * Sets the name of the element to add or remove.<p>
       *
       * @param elementName the name of the element to add or remove
       */
      public void setParamElementName(String elementName) {
  
          m_paramElementName = elementName;
      }
  
      /**
       * Sets the content creation mode.<p>
       *
       * @param mode the content creation mode
       */
      public void setParamMode(String mode) {
  
          m_mode = mode;
      }
  
      /**
       * Sets the parameter that specifies the model file name.<p>
       *
       * @param paramMasterFile the parameter that specifies the model file name
       */
      public void setParamModelFile(String paramMasterFile) {
  
          m_paramModelFile = paramMasterFile;
      }
  
      /**
       * Sets the "new link" parameter.<p>
       *
       * @param paramNewLink the "new link" parameter to set
       */
      public void setParamNewLink(String paramNewLink) {
  
          m_paramNewLink = CmsEncoder.decode(paramNewLink);
      }
  
      /**
       * Sets the post-create handler class name.<p>
       *
       * @param handler the post-create handler class name
       */
      public void setParamPostCreateHandler(String handler) {
  
          m_postCreateHandler = handler;
      }
  
      /**
       * Determines if the element language selector is shown dependent on the available Locales.<p>
       *
       * @return true, if more than one Locale is available, otherwise false
       */
      public boolean showElementLanguageSelector() {
  
          List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales(getCms(), getParamResource());
          if ((locales == null) || (locales.size() < 2)) {
              // for less than two available locales, do not create language selector
              return false;
          }
          return true;
      }
  
      /**
       * @see org.opencms.workplace.tools.CmsToolDialog#useNewStyle()
       */
      @Override
      public boolean useNewStyle() {
  
          return false;
      }
  
      /**
       * @see org.opencms.workplace.editors.CmsEditor#commitTempFile()
       */
      @Override
      protected void commitTempFile() throws CmsException {
  
          super.commitTempFile();
          m_file = getCloneCms().readFile(getParamResource());
          m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
      }
  
      /**
       * Makes sure the requested locale node is present in the content document
       * by either copying an existing locale node or creating an empty one.<p>
       *
       * @param locale the requested locale
       *
       * @return the locale
       */
      protected Locale ensureLocale(Locale locale) {
  
          // get the default locale for the resource
          List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
          if (m_content != null) {
              if (!m_content.hasLocale(locale)) {
                  try {
  
                      // to copy anything we need at least one locale
                      if ((m_content.getLocales().size() > 0)) {
                          // required locale not available, check if an existing default locale should be copied as "template"
                          try {
                              // a list of possible default locales has been set as property, try to find a match
                              m_content.copyLocale(locales, locale);
  
                          } catch (CmsException e) {
                              m_content.addLocale(getCms(), locale);
                          }
  
                      } else {
                          m_content.addLocale(getCms(), locale);
                      }
  
                      writeContent();
                  } catch (CmsException e) {
                      LOG.error(e.getMessageContainer(), e);
                  }
              }
              if (!m_content.hasLocale(locale)) {
                  // value may have changed because of the copy operation
                  locale = m_content.getLocales().get(0);
              }
          }
          return locale;
      }
  
      /**
       * Initializes the editor content when opening the editor for the first time.<p>
       *
       * Not necessary for the xmlcontent editor.<p>
       */
      @Override
      protected void initContent() {
  
          // nothing to be done for the xmlcontent editor form
      }
  
      /**
       * Initializes the element language for the first call of the editor.<p>
       */
      protected void initElementLanguage() {
  
          // get the default locale for the resource
          List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales(getCms(), getParamResource());
          Locale locale = locales.get(0);
          locale = ensureLocale(locale);
          setParamElementlanguage(locale.toString());
      }
  
      /**
       * @see org.opencms.workplace.CmsWorkplace#initWorkplaceRequestValues(org.opencms.workplace.CmsWorkplaceSettings, javax.servlet.http.HttpServletRequest)
       */
      @Override
      protected void initWorkplaceRequestValues(CmsWorkplaceSettings settings, HttpServletRequest request) {
  
          // fill the parameter values in the get/set methods
          fillParamValues(request);
          // set the dialog type
          setParamDialogtype(EDITOR_TYPE);
  
          if (getParamNewLink() != null) {
              setParamAction(EDITOR_ACTION_NEW);
          } else {
              // initialize a content object from the temporary file
              if ((getParamTempfile() != null) && !"null".equals(getParamTempfile())) {
                  try {
                      m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
                      m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
                  } catch (CmsException e) {
                      // error during initialization, show error page
                      try {
                          showErrorPage(this, e);
                      } catch (JspException exc) {
                          // should usually never happen
                          if (LOG.isInfoEnabled()) {
                              LOG.info(exc);
                          }
                      }
                  }
              }
          }
  
          // set the action for the JSP switch
          if (EDITOR_SAVE.equals(getParamAction())) {
              setAction(ACTION_SAVE);
          } else if (EDITOR_SAVEEXIT.equals(getParamAction())) {
              setAction(ACTION_SAVEEXIT);
          } else if (EDITOR_EXIT.equals(getParamAction())) {
              setAction(ACTION_EXIT);
          } else if (EDITOR_ACTION_SUBCHOICES.equals(getParamAction())) {
              setAction(ACTION_SUBCHOICES);
          } else if (EDITOR_CLOSEBROWSER.equals(getParamAction())) {
              // closed browser window accidentally, unlock resource and delete temporary file
              actionClear(true);
              return;
          } else if (EDITOR_ACTION_CHECK.equals(getParamAction())) {
              setAction(ACTION_CHECK);
          } else if (EDITOR_SAVEACTION.equals(getParamAction())) {
              setAction(ACTION_SAVEACTION);
              try {
                  actionDirectEdit();
              } catch (Exception e) {
                  // should usually never happen
                  if (LOG.isInfoEnabled()) {
                      LOG.info(e.getLocalizedMessage(), e);
                  }
              }
              setAction(ACTION_EXIT);
          } else if (EDITOR_COPYLOCALE.equals(getParamAction())) {
              setAction(ACTION_COPYLOCALE);
          } else if (EDITOR_DELETELOCALE.equals(getParamAction())) {
              setAction(ACTION_DELETELOCALE);
          } else if (EDITOR_SHOW.equals(getParamAction())) {
              setAction(ACTION_SHOW);
          } else if (EDITOR_SHOW_ERRORMESSAGE.equals(getParamAction())) {
              setAction(ACTION_SHOW_ERRORMESSAGE);
          } else if (EDITOR_CHANGE_ELEMENT.equals(getParamAction())) {
              setAction(ACTION_SHOW);
              actionChangeElementLanguage();
          } else if (EDITOR_ACTION_ELEMENT_ADD.equals(getParamAction())) {
              setAction(ACTION_ELEMENT_ADD);
              try {
                  actionToggleElement();
              } catch (JspException e) {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(
                          org.opencms.workplace.Messages.get().getBundle().key(
                              org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
                  }
              }
          } else if (EDITOR_ACTION_ELEMENT_REMOVE.equals(getParamAction())) {
              setAction(ACTION_ELEMENT_REMOVE);
              try {
                  actionToggleElement();
              } catch (JspException e) {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(
                          org.opencms.workplace.Messages.get().getBundle().key(
                              org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
                  }
              }
          } else if (EDITOR_ACTION_ELEMENT_MOVE_DOWN.equals(getParamAction())) {
              setAction(ACTION_ELEMENT_MOVE_DOWN);
              try {
                  actionMoveElement();
              } catch (JspException e) {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(
                          org.opencms.workplace.Messages.get().getBundle().key(
                              org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
                  }
              }
          } else if (EDITOR_ACTION_ELEMENT_MOVE_UP.equals(getParamAction())) {
              setAction(ACTION_ELEMENT_MOVE_UP);
              try {
                  actionMoveElement();
              } catch (JspException e) {
                  if (LOG.isErrorEnabled()) {
                      LOG.error(
                          org.opencms.workplace.Messages.get().getBundle().key(
                              org.opencms.workplace.Messages.LOG_INCLUDE_ERRORPAGE_FAILED_0));
                  }
              }
          } else if (EDITOR_ACTION_NEW.equals(getParamAction())) {
              setAction(ACTION_NEW);
              return;
          } else if (EDITOR_PREVIEW.equals(getParamAction())) {
              setAction(ACTION_PREVIEW);
          } else if (EDITOR_CORRECTIONCONFIRMED.equals(getParamAction())) {
              setAction(ACTION_SHOW);
              try {
                  // correct the XML structure before showing the form
                  correctXmlStructure();
              } catch (CmsException e) {
                  // error during correction
                  try {
                      showErrorPage(this, e);
                  } catch (JspException exc) {
                      // should usually never happen
                      if (LOG.isInfoEnabled()) {
                          LOG.info(exc);
                      }
                  }
              }
          } else {
              // initial call of editor
              setAction(ACTION_DEFAULT);
              try {
                  // lock resource if autolock is enabled in configuration
                  if (Boolean.valueOf(getParamDirectedit()).booleanValue()) {
                      // set a temporary lock in direct edit mode
                      checkLock(getParamResource(), CmsLockType.TEMPORARY);
                  } else {
                      // set common lock
                      checkLock(getParamResource());
                  }
                  // create the temporary file
                  setParamTempfile(createTempFile());
                  // initialize a content object from the created temporary file
                  m_file = getCms().readFile(getParamTempfile(), CmsResourceFilter.ALL);
                  m_content = CmsXmlContentFactory.unmarshal(getCloneCms(), m_file);
                  // check the XML content against the given XSD
                  try {
                      m_content.validateXmlStructure(new CmsXmlEntityResolver(getCms()));
                  } catch (CmsXmlException eXml) {
                      // validation failed, check the settings for handling the correction
                      if (OpenCms.getWorkplaceManager().isXmlContentAutoCorrect()) {
                          // correct the XML structure automatically according to the XSD
                          correctXmlStructure();
                      } else {
                          // show correction confirmation dialog
                          setAction(ACTION_CONFIRMCORRECTION);
                      }
                  }
  
              } catch (CmsException e) {
                  // error during initialization
                  try {
                      showErrorPage(this, e);
                  } catch (JspException exc) {
                      // should usually never happen
                      if (LOG.isInfoEnabled()) {
                          LOG.info(exc);
                      }
                  }
              }
              // set the initial element language if not given in request parameters
              if (getParamElementlanguage() == null) {
                  initElementLanguage();
              } else {
                  Locale locale = CmsLocaleManager.getLocale(getParamElementlanguage());
                  ensureLocale(locale);
              }
          }
      }
  
      /**
       * Returns the HTML for the element operation buttons add, move, remove.<p>
       *
       * @param value the value for which the buttons are generated
       * @param addElement if true, the button to add an element is shown
       * @param removeElement if true, the button to remove an element is shown
       * @return the HTML for the element operation buttons
       */
      private String buildElementButtons(I_CmsXmlContentValue value, boolean addElement, boolean removeElement) {
  
          StringBuffer jsCall = new StringBuffer(512);
          String elementName = CmsXmlUtils.removeXpathIndex(value.getPath());
  
          // indicates if at least one button is active
          boolean buttonPresent = false;
  
          int index = value.getIndex();
  
          jsCall.append("showElementButtons('");
          jsCall.append(elementName);
          jsCall.append("', ");
          jsCall.append(index);
          jsCall.append(", ");
  
          // build the remove element button if required
          if (removeElement) {
              jsCall.append(Boolean.TRUE);
              buttonPresent = true;
          } else {
              jsCall.append(Boolean.FALSE);
          }
          jsCall.append(", ");
  
          // build the move down button (move down in API is move up for content editor)
          if (index > 0) {
              // build active move down button
              jsCall.append(Boolean.TRUE);
              buttonPresent = true;
          } else {
              jsCall.append(Boolean.FALSE);
          }
          jsCall.append(", ");
  
          // build the move up button (move up in API is move down for content editor)
          int indexCount = m_content.getIndexCount(elementName, getElementLocale());
          if (index < (indexCount - 1)) {
              // build active move up button
              jsCall.append(Boolean.TRUE);
              buttonPresent = true;
          } else {
              jsCall.append(Boolean.FALSE);
          }
          jsCall.append(", ");
  
          // build the add element button if required
          if (addElement) {
              jsCall.append(Boolean.TRUE);
              buttonPresent = true;
          } else {
              jsCall.append(Boolean.FALSE);
          }
          jsCall.append(", ");
  
          JSONArray newElements = buildElementChoices(elementName, value.isChoiceType(), true);
          jsCall.append("'").append(CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'");
          jsCall.append(");");
  
          String result;
          if (buttonPresent) {
              // at least one button active, create mouseover button
              String btIcon = "xmledit.png";
              String btAction = jsCall.toString();
              // determine icon to use and if a direct click action is possible
              if (addElement && removeElement) {
                  btIcon = "xmledit_del_add.png";
              } else if (addElement) {
                  btIcon = "xmledit_add.png";
                  // create button action to add element on button click
                  StringBuffer action = new StringBuffer(128);
                  action.append("addElement('");
                  action.append(elementName);
                  action.append("', ");
                  action.append(index);
                  action.append(", '").append(
                      CmsEncoder.escape(newElements.toString(), CmsEncoder.ENCODING_UTF_8)).append("'");
                  action.append(");");
                  btAction = action.toString();
              } else if (removeElement) {
                  btIcon = "xmledit_del.png";
                  // create button action to remove element on button click
                  StringBuffer action = new StringBuffer(128);
                  action.append("removeElement('");
                  action.append(elementName);
                  action.append("', ");
                  action.append(index);
                  action.append(");");
                  btAction = action.toString();
              }
              StringBuffer href = new StringBuffer(512);
              href.append("javascript:");
              href.append(btAction);
              href.append("\" onmouseover=\"");
              href.append(jsCall);
              href.append("checkElementButtons(true);\" onmouseout=\"checkElementButtons(false);\" id=\"btimg.");
              href.append(elementName).append(".").append(index);
              result = button(href.toString(), null, btIcon, Messages.GUI_EDITOR_XMLCONTENT_ELEMENT_BUTTONS_0, 0);
          } else {
              // no active button, create a spacer
              result = buttonBarSpacer(1);
          }
  
          return result;
      }
  
      /**
       * Corrects the XML structure of the edited content according to the XSD.<p>
       *
       * @throws CmsException if the correction fails
       */
      private void correctXmlStructure() throws CmsException {
  
          m_content.setAutoCorrectionEnabled(true);
          m_content.correctXmlStructure(getCms());
          // write the corrected temporary file
          writeContent();
      }
  
      /**
       * Returns the error handler for error handling of the edited xml content.<p>
       *
       * @return the error handler
       */
      private CmsXmlContentErrorHandler getValidationHandler() {
  
          if (m_validationHandler == null) {
              // errors were not yet checked, do this now and store result in member
              m_validationHandler = m_content.validate(getCms());
          }
          return m_validationHandler;
      }
  
      /**
       * Generates the HTML form for the XML content editor.<p>
       *
       * This is a recursive method because nested schemas are possible,
       * do not call this method directly.<p>
       *
       * @param contentDefinition the content definition to start with
       * @param pathPrefix for nested xml content
       * @param showHelpBubble if the code for a help bubble should be generated
       * @param superTabOpened if the super tab is opened
       *
       * @return the HTML that generates the form for the XML editor
       */
      private StringBuffer getXmlEditorForm(
          CmsXmlContentDefinition contentDefinition,
          String pathPrefix,
          boolean showHelpBubble,
          boolean superTabOpened) {
  
          StringBuffer result = new StringBuffer(1024);
          // only show errors if editor is not opened initially
          boolean showErrors = (getAction() != ACTION_NEW)
              && (getAction() != ACTION_DEFAULT)
              && (getAction() != ACTION_ELEMENT_ADD)
              && (getAction() != ACTION_ELEMENT_REMOVE)
              && (getAction() != ACTION_ELEMENT_MOVE_DOWN)
              && (getAction() != ACTION_ELEMENT_MOVE_UP);
  
          try {
              // check if we are in a nested content definition
              boolean nested = CmsStringUtil.isNotEmpty(pathPrefix);
              boolean useTabs = false;
              boolean tabOpened = false;
              StringBuffer selectedTabScript = new StringBuffer(64);
  
              boolean collapseLabel = false;
              boolean firstElement = true;
  
              // show error header once if there were validation errors
              if (!nested && showErrors && (getValidationHandler().hasErrors())) {
  
                  result.append("<div class=\"ui-widget\">");
                  result.append(
                      "<div class=\"ui-state-error ui-corner-all\" style=\"padding: 0pt 0.7em;\"><div style=\"padding: 3px 0;\">");
                  result.append(
                      "<span class=\"ui-icon ui-icon-alert\" style=\"float: left; margin-right: 0.3em;\"></span>");
                  boolean differentLocaleErrors = false;
                  if ((getValidationHandler().getErrors(getElementLocale()) == null)
                      || (getValidationHandler().getErrors().size() > getValidationHandler().getErrors(
                          getElementLocale()).size())) {
  
                      differentLocaleErrors = true;
                      result.append(
                          "<span id=\"xmlerrordialogbutton\" class=\"ui-icon ui-icon-newwin\" style=\"float: left; margin-right: 0.3em;\"></span>");
                  }
                  result.append(key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0));
                  result.append("</div>");
  
                  // show errors in different locales
                  if (differentLocaleErrors) {
  
                      result.append("<div id=\"xmlerrordialog\" style=\"display: none;\">");
                      // iterate through all found errors
                      Map<Locale, Map<String, String>> locErrors = getValidationHandler().getErrors();
                      Iterator<Map.Entry<Locale, Map<String, String>>> locErrorsIter = locErrors.entrySet().iterator();
                      while (locErrorsIter.hasNext()) {
                          Map.Entry<Locale, Map<String, String>> locEntry = locErrorsIter.next();
                          Locale locale = locEntry.getKey();
  
                          // skip errors in the actual locale
                          if (getElementLocale().equals(locale)) {
                              continue;
                          }
  
                          result.append("<div style=\"padding: 3px;\"><strong>");
                          result.append(
                              key(
                                  Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_LANG_1,
                                  new Object[] {locale.getLanguage()}));
                          result.append("</strong></div>\n");
                          result.append("<ul>");
  
                          // iterate through the found errors in a different locale
                          Map<String, String> elErrors = locEntry.getValue();
                          Iterator<Map.Entry<String, String>> elErrorsIter = elErrors.entrySet().iterator();
                          while (elErrorsIter.hasNext()) {
                              Map.Entry<String, String> elEntry = elErrorsIter.next();
                              String nodeName = elEntry.getKey();
                              String errorMsg = elEntry.getValue();
                              // output the error message
                              result.append("<li>");
                              result.append(nodeName);
                              result.append(": ");
                              result.append(errorMsg);
                              result.append("</li>\n");
                          }
  
                          result.append("</ul>");
                      }
  
                      result.append("</div>\n");
                      result.append("<script >\n");
                      result.append("$(\"#xmlerrordialog\").dialog({\n");
                      result.append("\tautoOpen: true,\n");
                      result.append("\tbgiframe: true,\n");
                      result.append("\twidth: 500,\n");
                      result.append("\tposition: 'center',\n");
                      result.append("\tdialogClass: 'ui-state-error',\n");
                      result.append("\ttitle: '").append(
                          key(Messages.ERR_EDITOR_XMLCONTENT_VALIDATION_ERROR_TITLE_0)).append("',\n");
                      result.append("\tmaxHeight: 600\n");
                      result.append("});\n");
  
                      result.append(
                          "$(\"#xmlerrordialogbutton\").bind(\"click\", function(e) {$(\"#xmlerrordialog\").dialog(\"open\");});\n");
                      result.append("</script>");
                  }
                  result.append("</div></div>");
              }
  
              if (!nested) {
                  // check if tabs should be shown
                  useTabs = contentDefinition.getContentHandler().getTabs().size() > 0;
                  if (useTabs) {
                      // we have some tabs available, generate them on first level
                      result.append("<div id=\"xmltabs\" class=\"ui-tabs\">\n<ul>\n");
                      for (Iterator<CmsXmlContentTab> i = contentDefinition.getContentHandler().getTabs().iterator(); i.hasNext();) {
                          CmsXmlContentTab tab = i.next();
                          result.append("\t<li id=\"OcmsTabTab").append(tab.getIdName()).append("\"><a href=\"#OcmsTab");
                          result.append(tab.getIdName());
                          result.append("\"><span>");
                          result.append(
                              keyDefault(
                                  A_CmsWidget.LABEL_PREFIX + contentDefinition.getInnerName() + "." + tab.getTabName(),
                                  tab.getTabName()));
                          result.append("</span></a></li>\n");
                      }
                      result.append("</ul>\n");
                  }
              }
  
              // if xsd:choice then we just need to get one element of the sequence
              Iterator<I_CmsXmlSchemaType> it = contentDefinition.getChoiceMaxOccurs() > 0
              ? contentDefinition.getTypeSequence().subList(0, 1).iterator()
              : contentDefinition.getTypeSequence().iterator();
  
              // iterate the type sequence
              while (it.hasNext()) {
                  // get the type
                  I_CmsXmlSchemaType type = it.next();
  
                  boolean tabCurrentlyOpened = false;
  
                  if (useTabs) {
                      // check if a tab is starting with this element
                      for (int tabIndex = 0; tabIndex < contentDefinition.getContentHandler().getTabs().size(); tabIndex++) {
                          CmsXmlContentTab checkTab = contentDefinition.getContentHandler().getTabs().get(tabIndex);
                          if (checkTab.getStartName().equals(type.getName())) {
                              // a tab is starting, add block element
                              if (tabOpened) {
                                  // close a previously opened tab
                                  result.append("</table>\n</div>\n");
                              }
                              result.append("<div id=\"OcmsTab");
                              result.append(checkTab.getIdName());
                              result.append("\" class=\"ui-tabs-hide\">\n");
                              // set necessary values
                              tabOpened = true;
                              tabCurrentlyOpened = true;
                              collapseLabel = checkTab.isCollapsed();
                              m_currentTab = checkTab;
                              m_currentTabIndex = tabIndex;
                              // leave loop
                              break;
                          }
                      }
                  }
  
                  if (firstElement || tabCurrentlyOpened) {
                      // create table before first element or if a tab has been opened before
                      result.append("<table class=\"xmlTable");
                      if (nested && !superTabOpened) {
                          // use other style for nested content definition table if tab was not opened on upper level
                          result.append("Nested");
                      }
                      result.append("\">\n");
                      firstElement = false;
                  }
  
                  // get the element sequence of the current type
                  CmsXmlContentValueSequence elementSequence = m_content.getValueSequence(
                      pathPrefix + type.getName(),
                      getElementLocale());
                  int elementCount = elementSequence.getElementCount();
  
                  // check if value is optional or multiple
                  boolean addValue = elementCount < elementSequence.getMaxOccurs();
                  boolean removeValue = elementCount > elementSequence.getMinOccurs();
  
                  // assure that at least one element is present in sequence
                  boolean disabledElement = false;
  
                  if ((contentDefinition.getChoiceMaxOccurs() == 0) && (elementCount < 1)) {
                      // current element is disabled, create dummy element if it is a nested type or no choice option
                      elementCount = 1;
                      elementSequence.addValue(getCms(), 0);
                      disabledElement = true;
                      m_optionalElementPresent = true;
                  }
  
                  // loop through multiple elements
                  for (int j = 0; j < elementCount; j++) {
                      // get value and corresponding widget
                      I_CmsXmlContentValue value = elementSequence.getValue(j);
  
                      String key = value.getPath();
  
                      // check if a tab should be preselected for an added, removed or moved up/down element
                      if ((m_currentTab != null) && CmsStringUtil.isNotEmpty(getParamElementName())) {
                          // an element was modified, add JS to preselect tab
                          if (key.startsWith(getParamElementName()) && (selectedTabScript.length() == 0)) {
                              selectedTabScript.append("<script >\n\txmlSelectedTab = ").append(m_currentTabIndex).append(
                                  ";\n</script>\n");
                          }
                      }
  
                      // show errors and/or warnings
                      if (showErrors
                          && getValidationHandler().hasErrors(getElementLocale())
                          && getValidationHandler().getErrors(getElementLocale()).containsKey(key)) {
                          // show error message
                          if (collapseLabel) {
                              result.append("<tr><td class=\"xmlTdError\"><img src=\"");
                              result.append(getEditorResourceUri());
                              result.append("error.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">");
                              result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key)));
                              result.append("</td><td></td></tr>\n");
                          } else {
                              result.append("<tr><td></td><td><img src=\"");
                              result.append(getEditorResourceUri());
                              result.append("error.png");
                              result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdError\">");
                              result.append(resolveMacros(getValidationHandler().getErrors(getElementLocale()).get(key)));
                              result.append("</td><td></td></tr>\n");
                          }
  
                          // mark tab as error tab if tab is present
                          String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath());
                          if (((m_currentTab != null) && !m_errorTabs.contains(m_currentTab))
                              && (elemName.equals(m_currentTab.getStartName())
                                  || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) {
                              m_errorTabs.add(m_currentTab);
                          }
  
                      }
                      // warnings can be additional to errors
                      if (showErrors
                          && getValidationHandler().hasWarnings(getElementLocale())
                          && getValidationHandler().getWarnings(getElementLocale()).containsKey(key)) {
                          // show warning message
                          if (collapseLabel) {
                              result.append("<tr><td class=\"xmlTdError\"><img src=\"");
                              result.append(getEditorResourceUri());
                              result.append("warning.png\" border=\"0\" alt=\"\" align=\"left\" hspace=\"5\">");
                              result.append(
                                  resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key)));
                              result.append("</td><td></td></tr>\n");
                          } else {
                              result.append("<tr><td></td><td><img src=\"");
                              result.append(getEditorResourceUri());
                              result.append("warning.png");
                              result.append("\" border=\"0\" alt=\"\"></td><td class=\"xmlTdWarning\">");
                              result.append(
                                  resolveMacros(getValidationHandler().getWarnings(getElementLocale()).get(key)));
                              result.append("</td><td></td></tr>\n");
                          }
  
                          // mark tab as warning tab if tab is present
                          String elemName = CmsXmlUtils.getFirstXpathElement(value.getPath());
                          if (((m_currentTab != null) && !m_warningTabs.contains(m_currentTab))
                              && (elemName.equals(m_currentTab.getStartName())
                                  || (!CmsXmlUtils.isDeepXpath(value.getPath()) && value.getName().equals(elemName)))) {
                              m_warningTabs.add(m_currentTab);
                          }
                      }
  
                      // We don't care about the old editor for the 'inheritable' widget configuration,
                      // so we're using the old getWidget method here
                      I_CmsWidget widget = value.isSimpleType()
                      ? contentDefinition.getContentHandler().getWidget(value)
                      : null;
  
                      int index = value.getIndex();
                      // create label and help bubble cells
                      result.append("<tr>");
                      if (!collapseLabel) {
                          result.append("<td class=\"xmlLabel");
                          if (disabledElement) {
                              // element is disabled, mark it with CSS
                              result.append("Disabled");
                          }
                          result.append("\">");
                          result.append(
                              keyDefault(A_CmsWidget.getLabelKey((I_CmsWidgetParameter)value), value.getName()));
                          if (elementCount > 1) {
                              result.append(" [").append(index + 1).append("]");
                          }
                          result.append(": </td>");
                          if (showHelpBubble
                              && (widget != null)
                              && (CmsXmlUtils.getXpathIndexInt(value.getPath()) == 1)) {
                              // show help bubble only on first element of each content definition
                              result.append(widget.getHelpBubble(getCms(), this, (I_CmsWidgetParameter)value));
                          } else {
                              // create empty cell for all following elements
                              result.append(buttonBarSpacer(16));
                          }
                      }
  
                      // append individual widget HTML cell if element is enabled
                      if (disabledElement) {
                          // disabled element, show message for optional element
                          result.append("<td class=\"xmlTdDisabled maxwidth\">");
                          result.append(key(Messages.GUI_EDITOR_XMLCONTENT_OPTIONALELEMENT_0));
                          result.append("</td>");
  
                      } else {
                          // element is enabled, show it
                          if ((widget != null) && value.isSimpleType()) {
                              // this is a simple type, display widget
                              result.append(widget.getDialogWidget(getCms(), this, (I_CmsWidgetParameter)value));
                          } else {
                              // recurse into nested type sequence
                              result.append("<td class=\"maxwidth\">");
                              boolean showHelp = (j == 0);
                              superTabOpened = !nested && tabOpened && collapseLabel;
                              result.append(
                                  getXmlEditorForm(
                                      ((CmsXmlNestedContentDefinition)value).getNestedContentDefinition(),
                                      value.getPath() + "/",
                                      showHelp,
                                      superTabOpened));
                              result.append("</td>");
                          }
                      }
  
                      // append element operation (add, remove, move) buttons if required
                      result.append(buildElementButtons(value, addValue, removeValue));
  
                      // close row
                      result.append("</tr>\n");
  
                      // remove disabled element to avoid eventual side effects, e.g. in widget configurations
                      if (disabledElement) {
                          elementSequence.removeValue(0);
                      }
  
                  }
              }
              // close table
              result.append("</table>\n");
              if (tabOpened) {
                  // close last open tab
                  result.append("</div>\n");
              }
              if (!nested && useTabs) {
                  // close block element around tabs
                  result.append("</div>\n");
                  // mark eventual warning and error tabs
                  result.append("<script >\n");
                  for (Iterator<CmsXmlContentTab> i = m_warningTabs.iterator(); i.hasNext();) {
                      CmsXmlContentTab checkTab = i.next();
                      if (!m_errorTabs.contains(checkTab)) {
                          result.append("\txmlWarningTabs[xmlWarningTabs.length] = \"").append(
                              checkTab.getIdName()).append("\";\n");
                      }
                  }
                  for (Iterator<CmsXmlContentTab> i = m_errorTabs.iterator(); i.hasNext();) {
                      CmsXmlContentTab checkTab = i.next();
                      result.append("\txmlErrorTabs[xmlErrorTabs.length] = \"").append(checkTab.getIdName()).append(
                          "\";\n");
                  }
                  result.append("</script>\n");
              }
              if (selectedTabScript.length() > 0) {
                  result.append(selectedTabScript);
              }
          } catch (Throwable t) {
              LOG.error(Messages.get().getBundle().key(Messages.ERR_XML_EDITOR_0), t);
          }
          return result;
      }
  
      /**
       * Resets the error handler member variable to reinitialize the error messages.<p>
       */
      private void resetErrorHandler() {
  
          m_validationHandler = null;
      }
  
      /**
       * Resets the widget collector member variable to reinitialize the widgets.<p>
       *
       * This is needed to display the help messages of optional elements before building the html end of the form.<p>
       */
      private void resetWidgetCollector() {
  
          m_widgetCollector = null;
      }
  
      /**
       * Writes the xml content to the vfs and re-initializes the member variables.<p>
       *
       * @throws CmsException if writing the file fails
       */
      private void writeContent() throws CmsException {
  
          String decodedContent = m_content.toString();
          try {
              m_file.setContents(decodedContent.getBytes(getFileEncoding()));
          } catch (UnsupportedEncodingException e) {
              throw new CmsException(Messages.get().container(Messages.ERR_INVALID_CONTENT_ENC_1, getParamResource()), e);
          }
          // the file content might have been modified during the write operation
          CmsObject cloneCms = getCloneCms();
          CmsUUID tempProjectId = OpenCms.getWorkplaceManager().getTempFileProjectId();
          cloneCms.getRequestContext().setCurrentProject(getCms().readProject(tempProjectId));
          m_file = cloneCms.writeFile(m_file);
          m_content = CmsXmlContentFactory.unmarshal(cloneCms, m_file);
  
      }
  }
  