@NbBundle.Messages({
    "# {0} - NS prefix",
    "ERR_undeclaredElementPrefix=XML namespace prefix ''{0}'' is undeclared"
  })
  @Override
  public void startElement(String uri, String localName, String qName, Attributes atts)
      throws SAXException {
    this.tagName = localName;

    FxNode newElement;

    start = contentLocator.getElementOffset();
    end = contentLocator.getEndOffset();

    addElementErrors();

    if (uri == null && !qName.equals(localName)) {
      // undeclared prefix
      int prefColon = qName.indexOf(':');
      String prefix = qName.substring(0, prefColon);
      addError("undeclared-prefix", ERR_undeclaredElementPrefix(prefix));
      newElement = accessor.createErrorElement(localName);
    } else if ("".equals(localName)) {
      newElement = accessor.createErrorElement(localName);
    } else if (FXML_FX_NAMESPACE.equals(uri)) {
      newElement = handleFxmlElement(localName, atts);
    } else {
      // non-fx namespace, should be either an instance, or a property or an event
      String eventName = FxXmlSymbols.getEventHandlerName(localName);
      if (rootComponent == null || FxXmlSymbols.isClassTagName(localName)) {
        newElement = handleClassTag(localName, atts);
      } else if (eventName != null) {
        newElement = handleEventHandlerTag(eventName);
      } else {
        newElement = handlePropertyTag(localName, atts);
      }
    }
    if (newElement == null) {
      throw new IllegalStateException();
    }
    initElement(newElement);

    FxNode newNode = newElement;

    // if not broken attempt to attach the Element to a parent
    if (!newElement.isBroken()) {
      if (newElement instanceof FxObjectBase) {
        newNode = attachInstance((FxObjectBase) newElement);
      } else if (newElement instanceof PropertyValue) {
        newNode = attachProperty((PropertyValue) newElement);
      }
    }
    attachChildNode(newNode);

    // process attributes, iff it is an instance. Attribute processing needs the node pushed
    // on the stack, so it is delayed after attachChildNode
    if (newNode.getKind() == Kind.Instance) {
      processInstanceAttributes(atts);
    }
  }
  /**
   * Processes ?include directive
   *
   * @param include
   */
  @NbBundle.Messages({
    "ERR_missingIncludeName=Missing include name",
    "# {0} - attribute name",
    "ERR_unexpectedIncludeAttribute=Unexpected attribute in fx:include: {0}"
  })
  private FxNode handleFxInclude(Attributes atts, String localName) {
    String include = null;

    for (int i = 0; i < atts.getLength(); i++) {
      String attName = atts.getLocalName(i);
      if (FX_ATTR_REFERENCE_SOURCE.equals(attName)) {
        include = atts.getValue(i);
      } else {
        String qName = atts.getQName(i);
        addAttributeError(
            qName, "unexpected-include-attribute", ERR_unexpectedIncludeAttribute(qName), qName);
      }
    }
    if (include == null) {
      // must be some text, otherwise = error
      addAttributeError(
          ContentLocator.ATTRIBUTE_TARGET, "missing-included-name", ERR_missingIncludeName());

      FxNode n = accessor.createErrorElement(localName);
      initElement(n);
      addError("invalid-fx-element", ERR_invalidFxElement(localName), localName);
      return n;
    }
    // guide: fnames starting with slash are treated relative to the classpath
    FxInclude fxInclude = accessor.createInclude(include);
    return fxInclude;
  }
  @NbBundle.Messages({
    "# {0} - tag name",
    "ERR_invalidFxElement=Unknown element in fx: namespace: {0}",
    "ERR_duplicateDefinitions=Duplicate 'definitions' element"
  })
  private FxNode handleFxmlElement(String localName, Attributes atts) {
    if (FX_DEFINITIONS.equals(localName)) {
      definitions++;

      if (definitionsFound) {
        // error, defs cannot be nested or used more than once. Ignore.
        addError("duplicate-definitions", ERR_duplicateDefinitions());
      }
      FxNode n = accessor.createElement(localName);
      definitionsNode = accessor.i(n);
      return n;
    } else if (FX_COPY.equals(localName)) {
      return handleFxReference(atts, true);
    } else if (FX_REFERENCE.equals(localName)) {
      return handleFxReference(atts, false);
    } else if (FX_INCLUDE.equals(localName)) {
      return handleFxInclude(atts, localName);
    } else {
      // error, invalid fx: element
      FxNode n = accessor.createErrorElement(localName);
      initElement(n);
      addError("invalid-fx-element", ERR_invalidFxElement(localName), localName);
      return n;
    }
  }
 @NbBundle.Messages({
   "ERR_missingLanguageName=Language name is missing",
   "ERR_duplicateLanguageDeclaration=Language is already declared"
 })
 private FxNode handleFxLanguage(String language) {
   LanguageDecl decl = accessor.createLanguage(language);
   if (language == null) {
     addAttributeError(
         ContentLocator.ATTRIBUTE_TARGET, "missing-language-name", ERR_missingLanguageName());
     accessor.makeBroken(decl);
   } else {
     if (this.language != null) {
       // error, language can be specified only once:
       addError(
           new ErrorMark(
               start,
               end - start,
               "duplicate-language",
               ERR_duplicateLanguageDeclaration(),
               fxModel.getLanguage()));
       accessor.makeBroken(decl);
     } else if (isTopLevel()) {
       this.language = decl;
     }
   }
   return decl;
 }
 private FxNode handleEventHandlerTag(String eventName) {
   FxNode node = accessor.createEventHandler(eventName);
   FxNode parent = nodeStack.peek();
   if (!(parent instanceof FxInstance)) {
     accessor.makeBroken(node);
   }
   return node;
 }
  @NbBundle.Messages({"# {0} - tag name", "ERR_invalidPropertyName=Invalid property name: {0}"})
  private FxNode handlePropertyTag(String propName, Attributes atts) {
    PropertyValue pv;

    int errorAttrs = 0;
    for (int i = 0; i < atts.getLength(); i++) {
      String uri = atts.getURI(i);
      if (uri != null) {
        String qn = atts.getQName(i);
        errorAttrs++;
        addAttributeError(
            qn, "property-namespaced-attribute", ERR_propertyElementNamespacedAttribute(qn), qn);
      }
    }

    int stProp = FxXmlSymbols.findStaticProperty(propName);
    switch (stProp) {
      case -1:
        // simple property
        if (!Utilities.isJavaIdentifier(propName)) {
          addError(
              new ErrorMark(
                  start,
                  end,
                  "invalid-property-name",
                  ERR_invalidPropertyName(propName),
                  propName));
        }
        if (errorAttrs == atts.getLength()) {
          pv = handleSimpleProperty(propName, atts);
        } else {
          pv = handleMapProperty(propName, atts);
        }
        break;

      case -2:
        // broken name, but must create a node
        pv = accessor.makeBroken(accessor.createProperty(propName, false));
        // do not add the property to the parent, it's broken beyond repair
        addError(
            new ErrorMark(
                start, end, "invalid-property-name", ERR_invalidPropertyName(propName), propName));
        break;

      default:
        // static property, just ignore for now
        pv =
            handleStaticProperty(
                propName.substring(0, stProp), propName.substring(stProp + 1), atts);
        break;
    }

    return pv;
  }
 private void attachChildNode(FxNode node) {
   FxNode top = nodeStack.peek();
   i(top).addChild(node);
   //        if (!node.isBroken() && (node.getKind() != FxNode.Kind.Element)) {
   accessor.attach(node, fxModel);
   if (!node.isBroken()) {
     accessor.addChild(top, node);
   }
   if (i(node).isElement()) {
     pushInstance(node);
   }
 }
  private FxNode processEventHandlerAttribute(String event, String content) {
    EventHandler eh;

    if (content != null && content.startsWith(EVENT_HANDLER_METHOD_PREFIX)) {
      eh = accessor.asMethodRef(accessor.createEventHandler(event));
      accessor.addContent(eh, content.substring(1));

    } else {
      eh = accessor.createEventHandler(event);
      if (content != null && content.length() > 0) {
        accessor.addContent(eh, content);
      }
    }
    return eh;
  }
 private FxNode handleEventContent(CharSequence content) {
   EventHandler eh = (EventHandler) nodeStack.peek();
   if (eh.isScript() && !eh.hasContent()) {
     if (content.length() == 0) {
       throw new UnsupportedOperationException();
     } else {
       if (content.charAt(0) == '#') {
         content = content.subSequence(1, content.length());
         eh = accessor.asMethodRef(eh);
       }
     }
   }
   accessor.addContent(eh, content);
   return eh;
 }
  @NbBundle.Messages({
    "ERR_instructionBadPlacement=Bad placement for processing instruction. Must be before all elements."
  })
  @Override
  public void processingInstruction(String target, String data) throws SAXException {
    start = contentLocator.getElementOffset();
    end = contentLocator.getEndOffset();

    addElementErrors();

    FxNode node = null;
    boolean broken = false;
    if (!isTopLevel() || rootComponent != null) {
      addError("instruction-bad-placement", ERR_instructionBadPlacement());
      broken = true;
    }

    if (FX_IMPORT.equals(target)) {
      node = handleFxImport(data);
    } else if (FX_LANGUAGE.equals(target)) {
      node = handleFxLanguage(data);
    } else if (!"xml".equals(target)) {
      handleErrorInstruction(target, data);
    }
    if (node == null) {
      return;
    }
    i(node).makePI().startAt(start).endsAt(end);
    if (broken) {
      accessor.makeBroken(node);
    }
    attachChildNode(node);
  }
  @Override
  public void startDocument() throws SAXException {
    fxModel = accessor.newModel(sourceURL, imports, instanceDefinitions);
    initElement(fxModel);

    nodeStack.push(fxModel);
  }
  @NbBundle.Messages({
    "# {0} - attribute local name",
    "ERR_unexpectedReferenceAttribute=Unexpected attribute in fx:reference or fx:copy: {0}",
    "ERR_missingReferenceSource=Missing 'source' attribute in fx:reference or fx:copy"
  })
  private FxNode handleFxReference(Attributes atts, boolean copy) {
    String refId = null;
    String id = null;

    for (int i = 0; i < atts.getLength(); i++) {
      String ns = atts.getURI(i);
      String name = atts.getLocalName(i);
      if (!FXML_FX_NAMESPACE.equals(ns)) {
        if (FX_ATTR_REFERENCE_SOURCE.equals(name) && refId == null) {
          refId = atts.getValue(i);
        } else if (!copy) {
          // error, references do not support normal attributes
          addAttributeError(
              atts.getQName(i),
              "invalid-reference-attribute",
              ERR_unexpectedReferenceAttribute(name),
              name);
        }
      } else {
        if (FX_ID.equals(name) && id == null) {
          id = atts.getValue(i);
        } else {
          // error, unexpected attribute
          addAttributeError(
              atts.getQName(i),
              "invalid-reference-attribute",
              ERR_unexpectedReferenceAttribute(name),
              name);
        }
      }
    }

    FxObjectBase ref = accessor.createCopyReference(copy, refId);
    if (refId == null || "".equals(refId)) {
      // error, no source attribute found
      addError("missing-reference-source", ERR_missingReferenceSource());
      accessor.makeBroken(ref);
    }
    return ref;
  }
 @Override
 public void endDocument() throws SAXException {
   addElementErrors();
   accessor.initModel(fxModel, controllerName, rootComponent, language);
   int end = contentLocator.getElementOffset();
   i(fxModel).endContent(end).endsAt(end, true);
   // attempt to fix up unclosed elements
   // fixNodes(i(fxModel), end);
 }
  /**
   * Processes instance (non-static) property. As per examples in Guides, instance property element
   * must NOT have any attributes; otherwise it corresponds to an readonly Map element, and the
   * property must be of the Map type.
   *
   * @param propName
   * @param atts As
   */
  @NbBundle.Messages({
    "# {0} - attribute name",
    "ERR_propertyElementNamespacedAttribute=Property elements may not contain attributes with namespace: {0}"
  })
  private PropertyValue handleSimpleProperty(String propName, Attributes atts) {
    PropertyValue p;

    // no relevant attributes to use, real simple property then
    p = accessor.createProperty(propName, false);

    return p;
  }
  private PropertyValue handleMapProperty(String propName, Attributes atts) {
    Map<String, CharSequence> contents = new HashMap<String, CharSequence>();

    for (int i = 0; i < atts.getLength(); i++) {
      String uri = atts.getURI(i);
      if (uri != null) {
        continue;
      }
      contents.put(atts.getLocalName(i), atts.getValue(i));
    }
    return accessor.createMapProperty(propName, contents);
  }
  private FxNode handleInstanceContent(CharSequence seq) {
    // find among properties as setter, which is marked as implicit. If there's none, create one.
    PropertySetter defaultSetter = null;

    for (PropertyValue p : current.getProperties()) {
      if (p instanceof PropertySetter) {
        PropertySetter ps = (PropertySetter) p;
        if (ps.isImplicit()) {
          defaultSetter = ps;
        }
      }
    }

    if (defaultSetter == null) {
      defaultSetter = accessor.createProperty(null, true);
      i(defaultSetter).startAt(contentLocator.getElementOffset());
      attachProperty(defaultSetter);
      attachChildNode(defaultSetter);
    }
    accessor.addContent(defaultSetter, seq);
    return defaultSetter;
  }
  /**
   * Processes "import" PI. Checks syntax of the identifier
   *
   * @param data
   */
  @NbBundle.Messages({
    "ERR_importNotJavaIdentifier=Imported symbol must be a class or package name.",
    "ERR_importInsideElement=Imports must be at top level, not nested in elements",
    "ERR_importFollowsRoot=Import must not follow the root element",
    "ERR_missingImportIdentifier=Identifier missing in ?import instruction"
  })
  private FxNode handleFxImport(String data) {
    if (data.endsWith("?")) {
      // recovery from unterminated ?> -- the lexer will report ? as part of PI data.
      data = data.substring(0, data.length() - 1);
    }
    if ("".equals(data)) {
      addError("missing-import-identifier", ERR_missingImportIdentifier());
      return null;
    }
    int lastDot = data.lastIndexOf('.');
    boolean starImport = false;

    if (lastDot != -1 && lastDot < data.length() - 1) {
      if (FX_IMPORT_STAR.equals(data.substring(lastDot + 1))) {
        starImport = true;
        data = data.substring(0, lastDot);
      }
    }
    ImportDecl decl = accessor.createImport(data, starImport);
    if (!FxXmlSymbols.isQualifiedIdentifier(data)) {
      addAttributeError(
          ContentLocator.ATTRIBUTE_DATA,
          "import-not-java-identifier",
          ERR_importNotJavaIdentifier(),
          data);
      accessor.makeBroken(decl);
    }

    imports.add(decl);

    return decl;
  }
 @NbBundle.Messages({
   "ERR_mixedContentNotAllowed=Mixed content is not allowed in property elements"
 })
 private FxNode handlePropertyContent(CharSequence seq) {
   FxNode node = nodeStack.peek();
   if (!(node instanceof PropertySetter)) {
     addError("unexpected-characters", ERR_unexpectedCharacters());
     return null;
   }
   // if the property has already received some bean instances, report
   // invalid content
   PropertySetter ps = (PropertySetter) node;
   if (ps.getValues() != null) {
     addError("mixed-content-not-allowed", ERR_mixedContentNotAllowed());
   }
   accessor.addContent((PropertySetter) node, seq);
   return node;
 }
 @NbBundle.Messages({
   "# {0} - parent tag local name",
   "ERR_doesNotAcceptProperty=The parent element {0} does not accept properties"
 })
 private FxNode attachProperty(PropertyValue p) {
   // FIXME - if 'current' is null,
   if (current == null) {
     FxNode node = nodeStack.peek();
     addError(
         new ErrorMark(
             start,
             end - start,
             "parent-not-accept-property",
             ERR_doesNotAcceptProperty(node.getSourceName()),
             node));
     accessor.makeBroken(p);
   }
   return p;
 }
 NodeInfo i(FxNode n) {
   return accessor.i(n);
 }
  @NbBundle.Messages({
    "# {0} - tag name",
    "ERR_tagNotJavaIdentifier=Invalid class name: {0}",
    "# {0} - tag name",
    "ERR_fxControllerPermittedOnRoot=fx:controller is not permitted on tag {0}. Can be only present on root element."
  })
  private FxNewInstance handleClassTag(String localName, Attributes atts) {
    String fxValueContent = null;
    String fxFactoryContent = null;
    String fxId = null;

    int off = contentLocator.getElementOffset() + 1; // the <

    for (int i = 0; i < atts.getLength(); i++) {
      String uri = atts.getURI(i);
      if (!FXML_FX_NAMESPACE.equals(uri)) {
        // no special attribute
        continue;
      }
      String name = atts.getLocalName(i);
      if (FX_VALUE.equals(name)) {
        fxValueContent = atts.getValue(i);
      } else if (FX_FACTORY.equals(name)) {
        fxFactoryContent = atts.getValue(i);
      } else if (FX_ID.equals(name)) {
        fxId = atts.getValue(i);
      } else if (FX_CONTROLLER.equals(name)) {
        if (nodeStack.peek().getKind() != Kind.Source) {
          addAttributeError(
              atts.getQName(i),
              "fx-controller-permitted-on-root",
              ERR_fxControllerPermittedOnRoot(localName),
              localName);
        } else {
          controllerName = atts.getValue(i);
        }
      } else {
        addAttributeError(
            atts.getQName(i),
            "invalid-property-reserved-name",
            ERR_invalidReservedPropertyName(name),
            name);
      }
    }

    // first we must check how this class tag is created.
    FxNewInstance instance =
        accessor.createInstance(localName, fxValueContent, fxFactoryContent, fxId);

    if (!FxXmlSymbols.isQualifiedIdentifier(localName)) {
      // not a java identifier, error
      addError(
          new ErrorMark(
              off,
              localName.length(),
              "invalid-class-name",
              ERR_tagNotJavaIdentifier(localName),
              localName));
      accessor.makeBroken(instance);
      return instance;
    }

    return instance;
  }
  /**
   * Checks that the instance is allowed in this context. May even create e.g. default property
   * setter etc. Will return true, if the instance can be attached to the parent.
   */
  @NbBundle.Messages({
    "# {0} - tag name",
    "ERR_moreRootElements=Duplicate root element: {0}",
    "ERR_instanceInMapProperty=Cannot add instances directly to readonly Map",
    "# {0} - parent tag name",
    "ERR_parentNotSupportInstance=Instances cannot be added to the parent {0}"
  })
  private FxNode attachInstance(FxObjectBase instance) {
    String localName = instance.getSourceName();
    int off = contentLocator.getElementOffset() + 1;

    // check the parent, whether it is appropriate to host such a node:
    FxNode parent = nodeStack.peek();

    if (parent.getKind() == Kind.Instance) {
      // pretend we have a default property
      PropertySetter s = accessor.createProperty(null, true);
      i(s).startAt(contentLocator.getElementOffset());
      attachChildNode(s);
      parent = s;
    }

    if (parent.getKind() == Kind.Source) {
      FxObjectBase old = rootComponent;
      if (old != null) {
        addError(
            new ErrorMark(
                off,
                contentLocator.getEndOffset() - off,
                "duplicate-root",
                ERR_moreRootElements(localName),
                localName));
        accessor.makeBroken(instance);
      } else if (!(instance instanceof FxInstance)) {
        // FIXME - report error that fx:reference is not accepted on root element
        throw new UnsupportedOperationException();
      } else {
        rootComponent = (FxInstance) instance;
      }
    } else if (parent.getKind() == Kind.Property) {
      if (parent instanceof MapProperty) {
        addError(
            new ErrorMark(
                off,
                contentLocator.getEndOffset() - off,
                "instance-in-map-property",
                ERR_instanceInMapProperty(),
                localName));
        accessor.makeBroken(instance);
      }
    } else if (parent.getKind() == Kind.Element
        && parent.getSourceName().equals(FxXmlSymbols.FX_DEFINITIONS)
        && (instance instanceof FxNewInstance)) {
      instanceDefinitions.add((FxNewInstance) instance);
    } else {
      if (parent.getKind() != Kind.Error) {
        addError(
            new ErrorMark(
                off,
                contentLocator.getEndOffset() - off,
                "parent-not-support-instance",
                ERR_parentNotSupportInstance(parent.getSourceName())));
        accessor.makeBroken(instance);
      }
    }
    return instance;
  }
 private PropertyValue handleStaticProperty(String className, String propName, Attributes atts) {
   // FIXME - check that attributes are empty
   StaticProperty s = accessor.createStaticProperty(propName, className);
   return s;
 }
  @NbBundle.Messages({
    "# {0} - attribute name",
    "ERR_lowercasePropertyName=Invalid property name: {0}. Property name, or the last component of a static property name must start with lowercase.",
    "# {0} - attribute name",
    "ERR_invalidReservedPropertyName=Unknown name in FXML reserved namespace: {0}",
    "# {0} - attribute qname",
    "# {1} - tag name",
    "ERR_unsupportedAttribute=Unsupported attribute {0} on {1}"
  })
  private void processInstanceAttributes(Attributes atts) {
    for (int i = 0; i < atts.getLength(); i++) {
      String uri = atts.getURI(i);
      String name = atts.getLocalName(i);
      String qname = atts.getQName(i);

      PropertySetter ps = null;

      FxNode node;

      if (qname.startsWith("xmlns")) { // NOI18N
        // FIXME - xmlns attributes will be represented as FxNodes :-/
        continue;
      }

      if (FXML_FX_NAMESPACE.equals(uri)) {
        if (!(FX_ID.equals(name)
            || FX_CONTROLLER.equals(name)
            || FX_VALUE.equals(name)
            || FX_FACTORY.contains(name))) {
          addAttributeError(
              qname,
              "error-unsupported-attribute",
              ERR_unsupportedAttribute(qname, tagName),
              qname,
              tagName);
        }
        continue;
      }

      if (current instanceof FxInstanceCopy) {
        if (FxXmlSymbols.FX_ATTR_REFERENCE_SOURCE.equals(name) && uri == null) {
          // ignore source in fx:copy
          continue;
        }
      }

      // if the name begins with "on", it's an event handler.
      if (name.startsWith(EVENT_HANDLER_PREFIX) && name.length() > EVENT_HANDLER_PREFIX_LEN) {
        String en =
            Character.toLowerCase(name.charAt(EVENT_HANDLER_PREFIX_LEN))
                + name.substring(EVENT_HANDLER_PREFIX_LEN + 1);
        node = processEventHandlerAttribute(en, atts.getValue(i));
        // special hack for fx:copy or fx:reference
      } else {
        // FIXME - error detection for static property
        int stProp = FxXmlSymbols.findStaticProperty(name);
        if (stProp == -2) {
          // report error, not a well formed property name.
          addAttributeError(qname, "invalid-property-name", ERR_lowercasePropertyName(name), name);
          node = accessor.makeBroken(accessor.createProperty(name, false));
        } else if (stProp == -1) {
          // this is a normal property
          node = ps = accessor.createProperty(name, false);
        } else {
          // it is a static property
          node =
              ps =
                  accessor.createStaticProperty(
                      name.substring(stProp + 1), name.substring(0, stProp));
        }
        if (ps != null) {
          accessor.addContent(ps, atts.getValue(i));
          node = ps;
        }
      }
      initAttribute(node, qname);
      attachProperty(ps);
      attachChildNode(node);
    }
  }