/**
   * Returns the object representation for the given property element.
   *
   * @param xmlStream cursor must point at the <code>START_ELEMENT</code> event of the property,
   *     afterwards points at the next event after the <code>END_ELEMENT</code> of the property
   * @param propDecl property declaration
   * @param crs default SRS for all a descendant geometry properties
   * @param occurence
   * @return object representation for the given property element.
   * @throws XMLParsingException
   * @throws XMLStreamException
   * @throws UnknownCRSException
   */
  public Property<?> parseProperty(
      XMLStreamReaderWrapper xmlStream, PropertyType propDecl, CRS crs, int occurence)
      throws XMLParsingException, XMLStreamException, UnknownCRSException {

    Property<?> property = null;
    QName propName = xmlStream.getName();
    LOG.debug("- parsing property (begin): " + xmlStream.getCurrentEventInfo());
    LOG.debug("- property declaration: " + propDecl);

    CustomPropertyReader<?> parser = ptToParser.get(propDecl);

    if (parser == null) {
      if (propDecl instanceof SimplePropertyType) {
        property =
            createSimpleProperty(
                xmlStream, (SimplePropertyType) propDecl, xmlStream.getElementText().trim());
      } else if (propDecl instanceof GeometryPropertyType) {
        String href = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (href != null) {
          // TODO respect geometry type information (Point, Surface, etc.)
          GeometryReference<Geometry> refGeometry = null;
          if (specialResolver != null) {
            refGeometry =
                new GeometryReference<Geometry>(specialResolver, href, xmlStream.getSystemId());
          } else {
            refGeometry = new GeometryReference<Geometry>(idContext, href, xmlStream.getSystemId());
          }
          idContext.addReference(refGeometry);
          property = new GenericProperty<Geometry>(propDecl, propName, refGeometry);
          xmlStream.nextTag();
        } else {
          xmlStream.nextTag();
          Geometry geometry = null;
          geometry = geomReader.parse(xmlStream, crs);
          property = new GenericProperty<Geometry>(propDecl, propName, geometry);
          xmlStream.nextTag();
        }
      } else if (propDecl instanceof FeaturePropertyType) {
        String uri = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        if (uri != null) {
          FeatureReference refFeature = null;
          if (specialResolver != null) {
            refFeature = new FeatureReference(specialResolver, uri, xmlStream.getSystemId());
          } else {
            refFeature = new FeatureReference(idContext, uri, xmlStream.getSystemId());
          }
          idContext.addReference(refFeature);
          property = new GenericProperty<Feature>(propDecl, propName, refFeature);
          xmlStream.nextTag();
        } else {
          // inline feature
          if (xmlStream.nextTag() != START_ELEMENT) {
            String msg = Messages.getMessage("ERROR_INVALID_FEATURE_PROPERTY", propName);
            throw new XMLParsingException(xmlStream, msg);
          }
          // TODO make this check (no constraints on contained feature type) better
          if (((FeaturePropertyType) propDecl).getFTName() != null) {
            FeatureType expectedFt = ((FeaturePropertyType) propDecl).getValueFt();
            FeatureType presentFt = lookupFeatureType(xmlStream, xmlStream.getName());
            if (!schema.isSubType(expectedFt, presentFt)) {
              String msg =
                  Messages.getMessage(
                      "ERROR_PROPERTY_WRONG_FEATURE_TYPE",
                      expectedFt.getName(),
                      propName,
                      presentFt.getName());
              throw new XMLParsingException(xmlStream, msg);
            }
          }
          Feature subFeature = parseFeature(xmlStream, crs);
          property = new GenericProperty<Feature>(propDecl, propName, subFeature);
          xmlStream.skipElement();
        }
      } else if (propDecl instanceof CustomPropertyType) {
        Object value = new GenericCustomPropertyReader().parse(xmlStream);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof EnvelopePropertyType) {
        Envelope env = null;
        xmlStream.nextTag();
        if (xmlStream.getName().equals(new QName(gmlNs, "Null"))) {
          // TODO
          StAXParsingHelper.skipElement(xmlStream);
        } else {
          env = geomReader.parseEnvelope(xmlStream, crs);
          property = new GenericProperty<Object>(propDecl, propName, env);
        }
        xmlStream.nextTag();
      } else if (propDecl instanceof CodePropertyType) {
        String codeSpace = xmlStream.getAttributeValue(null, "codeSpace");
        String code = xmlStream.getElementText().trim();
        Object value = new CodeType(code, codeSpace);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof MeasurePropertyType) {
        String uom = xmlStream.getAttributeValue(null, "uom");
        Object value = new Measure(xmlStream.getElementText(), uom);
        property = new GenericProperty<Object>(propDecl, propName, value);
      } else if (propDecl instanceof StringOrRefPropertyType) {
        String ref = xmlStream.getAttributeValue(CommonNamespaces.XLNNS, "href");
        String string = xmlStream.getElementText().trim();
        property = new GenericProperty<Object>(propDecl, propName, new StringOrRef(string, ref));
      }
    } else {
      LOG.trace("************ Parsing property using custom parser.");
      Object value = parser.parse(xmlStream);
      property = new GenericProperty<Object>(propDecl, propName, value);
    }

    LOG.debug(" - parsing property (end): " + xmlStream.getCurrentEventInfo());
    return property;
  }