@Override
 public void startElement(String uri, String localName, String qName, Attributes attributes)
     throws SAXException {
   super.startElement(uri, localName, qName, attributes);
   if (localName.equals("repo")) {
     String pk = attributes.getValue("", "pubkey");
     if (pk != null) pubkey = pk;
   } else if (localName.equals("application") && curapp == null) {
     curapp = new DB.App();
     curapp.detail_Populated = true;
     Bundle progressData = createProgressData(repo.address);
     progressCounter++;
     progressListener.onProgress(
         new ProgressListener.Event(
             RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML,
             progressCounter,
             totalAppCount,
             progressData));
   } else if (localName.equals("package") && curapp != null && curapk == null) {
     curapk = new DB.Apk();
     curapk.id = curapp.id;
     curapk.repo = repo.id;
     hashType = null;
   } else if (localName.equals("hash") && curapk != null) {
     hashType = attributes.getValue("", "type");
   }
   curchars.setLength(0);
 }
  @Override
  public void endElement(String uri, String localName, String qName) throws SAXException {

    super.endElement(uri, localName, qName);
    String curel = localName;
    String str = curchars.toString();
    if (str != null) {
      str = str.trim();
    }

    if (curel.equals("application") && curapp != null) {

      // If we already have this application (must be from scanning a
      // different repo) then just merge in the apks.
      // TODO: Scanning the whole app list like this every time is
      // going to be stupid if the list gets very big!
      boolean merged = false;
      for (DB.App app : apps) {
        if (app.id.equals(curapp.id)) {
          app.apks.addAll(curapp.apks);
          merged = true;
          break;
        }
      }
      if (!merged) apps.add(curapp);

      curapp = null;

    } else if (curel.equals("package") && curapk != null && curapp != null) {
      curapp.apks.add(curapk);
      curapk = null;
    } else if (curapk != null && str != null) {
      if (curel.equals("version")) {
        curapk.version = str;
      } else if (curel.equals("versioncode")) {
        try {
          curapk.vercode = Integer.parseInt(str);
        } catch (NumberFormatException ex) {
          curapk.vercode = 0;
        }
      } else if (curel.equals("size")) {
        try {
          curapk.detail_size = Integer.parseInt(str);
        } catch (NumberFormatException ex) {
          curapk.detail_size = 0;
        }
      } else if (curel.equals("hash")) {
        if (hashType == null || hashType.equals("md5")) {
          if (curapk.detail_hash == null) {
            curapk.detail_hash = str;
            curapk.detail_hashType = "MD5";
          }
        } else if (hashType.equals("sha256")) {
          curapk.detail_hash = str;
          curapk.detail_hashType = "SHA-256";
        }
      } else if (curel.equals("sig")) {
        curapk.sig = str;
      } else if (curel.equals("srcname")) {
        curapk.srcname = str;
      } else if (curel.equals("apkname")) {
        curapk.apkName = str;
      } else if (curel.equals("sdkver")) {
        try {
          curapk.minSdkVersion = Integer.parseInt(str);
        } catch (NumberFormatException ex) {
          curapk.minSdkVersion = 0;
        }
      } else if (curel.equals("added")) {
        try {
          curapk.added = str.length() == 0 ? null : mXMLDateFormat.parse(str);
        } catch (ParseException e) {
          curapk.added = null;
        }
      } else if (curel.equals("permissions")) {
        curapk.detail_permissions = DB.CommaSeparatedList.make(str);
      } else if (curel.equals("features")) {
        curapk.features = DB.CommaSeparatedList.make(str);
      }
    } else if (curapp != null && str != null) {
      if (curel.equals("id")) {
        curapp.id = str;
      } else if (curel.equals("name")) {
        curapp.name = str;
      } else if (curel.equals("icon")) {
        curapp.icon = str;
      } else if (curel.equals("description")) {
        // This is the old-style description. We'll read it
        // if present, to support old repos, but in newer
        // repos it will get overwritten straight away!
        curapp.detail_description = "<p>" + str + "</p>";
      } else if (curel.equals("desc")) {
        // New-style description.
        curapp.detail_description = str;
      } else if (curel.equals("summary")) {
        curapp.summary = str;
      } else if (curel.equals("license")) {
        curapp.license = str;
      } else if (curel.equals("category")) {
        curapp.category = str;
      } else if (curel.equals("source")) {
        curapp.detail_sourceURL = str;
      } else if (curel.equals("donate")) {
        curapp.detail_donateURL = str;
      } else if (curel.equals("web")) {
        curapp.detail_webURL = str;
      } else if (curel.equals("tracker")) {
        curapp.detail_trackerURL = str;
      } else if (curel.equals("added")) {
        try {
          curapp.added = str.length() == 0 ? null : mXMLDateFormat.parse(str);
        } catch (ParseException e) {
          curapp.added = null;
        }
      } else if (curel.equals("lastupdated")) {
        try {
          curapp.lastUpdated = str.length() == 0 ? null : mXMLDateFormat.parse(str);
        } catch (ParseException e) {
          curapp.lastUpdated = null;
        }
      } else if (curel.equals("marketversion")) {
        curapp.curVersion = str;
      } else if (curel.equals("marketvercode")) {
        try {
          curapp.curVercode = Integer.parseInt(str);
        } catch (NumberFormatException ex) {
          curapp.curVercode = 0;
        }
      } else if (curel.equals("antifeatures")) {
        curapp.antiFeatures = DB.CommaSeparatedList.make(str);
      } else if (curel.equals("requirements")) {
        curapp.requirements = DB.CommaSeparatedList.make(str);
      }
    }
  }