private void indentHeading(AuthorElement heading) throws Exception {
   Matcher matcher = HEADING_PATTERN.matcher(heading.getLocalName());
   matcher.matches();
   int lvl = Integer.parseInt(matcher.group(1));
   if (lvl == 6) throw new AuthorOperationException("Operation not permitted on h6.");
   AuthorElement level = (AuthorElement) heading.getParent();
   assert (isDtbElement("level" + lvl, level));
   controller.renameElement(heading, "h" + (lvl + 1));
   AuthorElement nextSubLevel =
       xpath.getElement("following-sibling::level" + (lvl + 1), heading, controller);
   controller.surroundInFragment(
       dtbElement("level" + (lvl + 1)),
       heading.getStartOffset(),
       (nextSubLevel != null) ? nextSubLevel.getStartOffset() - 1 : level.getEndOffset() - 1);
   AuthorElement previousLevel =
       xpath.getElement(
           "preceding-sibling::*[not(normalize-space(string(.))='')][1][self::level" + lvl + "]",
           level,
           controller);
   if (previousLevel != null) {
     int insertAt = previousLevel.getEndOffset();
     controller.insertFragment(
         insertAt,
         controller.createDocumentFragment(level.getStartOffset() + 1, level.getEndOffset() - 1));
     controller.deleteNode(level);
     level = getElementAtOffset(insertAt + 1, controller);
   }
   editor.setCaretPosition(
       xpath.getElement("descendant::h" + (lvl + 1), level, controller).getStartOffset() + 1);
 }
 protected void doOperation() throws Exception {
   AuthorElement element = getElementAtOffset(getSelectionStart(editor), controller);
   Matcher matcher = HEADING_PATTERN.matcher(element.getLocalName());
   if (!isDtbElement(element) || !matcher.matches())
     throw new AuthorOperationException("Operation only permitted on heading elements.");
   indentHeading(element);
 }