diff --git a/tools/editor/js/js/appdfparser.js b/tools/editor/js/js/appdfparser.js
index 26f1500f..c4936834 100644
--- a/tools/editor/js/js/appdfparser.js
+++ b/tools/editor/js/js/appdfparser.js
@@ -253,6 +253,9 @@ var appdfParser = (function() {
$curXml = getElementsByPath($xml, "application-description-file/application");
+ //Developer
+ loadText("developer");
+
//Categorization
section("categorization", "categorization", function() {
loadText("type");
diff --git a/tools/editor/js/js/appdfxmlloading.js b/tools/editor/js/js/appdfxmlloading.js
index 6bb874cb..4cf8d173 100644
--- a/tools/editor/js/js/appdfxmlloading.js
+++ b/tools/editor/js/js/appdfxmlloading.js
@@ -211,6 +211,9 @@ var appdfXMLLoader = (function() {
console.log("Description.XML is parsed");
console.log(data);
+ //set Developer name
+ $("#developer-name").val(data["developer"]);
+
//Set control values in the categorization section
$("#categorization-type").val(data["categorization"]["type"]);
appdfEditor.fillCategories();
diff --git a/tools/editor/js/js/appdfxmlsaving.js b/tools/editor/js/js/appdfxmlsaving.js
index a54baaf1..345deb5d 100644
--- a/tools/editor/js/js/appdfxmlsaving.js
+++ b/tools/editor/js/js/appdfxmlsaving.js
@@ -23,6 +23,10 @@
var appdfXMLSaver = (function() {
+ function generateDeveloperXML(xml) {
+ xml.addNonEmptyTextTag("
", $("#developer-name").val());
+ };
+
function generateCategorizationXML(xml) {
xml.addTag("", function() {
xml.addTag("", $("#categorization-type").val());
@@ -443,18 +447,19 @@ var appdfXMLSaver = (function() {
xml.addLine('');
xml.addTag('', function() {
xml.addTag('', function() {
- generateCategorizationXML(xml);
- generateDescriptionXML(xml);
- generateDescriptionLocalizationsXML(xml);
- generateContentDescriptionXML(xml);
- generateAvailabilityXML(xml);
- generatePriceXML(xml);
- generateApkFilesXML(xml);
- generateRequirementsXML(xml);
- generateTestingInstructionsXML(xml);
- generateConsentXML(xml);
- generateCustomerSupportXML(xml);
- generateStoreSpecificXML(xml);
+ generateDeveloperXML(xml);
+ generateCategorizationXML(xml);
+ generateDescriptionXML(xml);
+ generateDescriptionLocalizationsXML(xml);
+ generateContentDescriptionXML(xml);
+ generateAvailabilityXML(xml);
+ generatePriceXML(xml);
+ generateApkFilesXML(xml);
+ generateRequirementsXML(xml);
+ generateTestingInstructionsXML(xml);
+ generateConsentXML(xml);
+ generateCustomerSupportXML(xml);
+ generateStoreSpecificXML(xml);
});
});
return xml.getXmlText();
diff --git a/tools/parser-js/appdfparser.js b/tools/parser-js/appdfparser.js
index 7b3b56da..c4936834 100644
--- a/tools/parser-js/appdfparser.js
+++ b/tools/parser-js/appdfparser.js
@@ -21,6 +21,11 @@
* Depends on: jquery.js
*/
var appdfParser = (function() {
+ var checkRes = true;
+ var validErrors = true;
+ var asyncValidationCount = 0;
+
+
function isDefined(x) {
return (typeof x != "undefined");
};
@@ -29,7 +34,9 @@ var appdfParser = (function() {
return (typeof x === "undefined");
};
- function parseDescriptionXML(xmlText, onend, onerror, onprogress) {
+ function parseDescriptionXML(xmlText, onend, onerror, onprogress, checkRes) {
+ appdfParser.checkRes = checkRes;
+
//Calculate total number of actions to do
var totalProgressItems = 14;
var passedProgressItems = 0;
@@ -55,7 +62,7 @@ var appdfParser = (function() {
try {
$xml = $($.parseXML(xmlText));
} catch(e) {
- onerror([errorMessages.descriptionIsNotXML]);
+ onerror([{msg:errorMessages.descriptionIsNotXML, val:false}]);
return false;
};
progress();//1
@@ -107,7 +114,19 @@ var appdfParser = (function() {
});
};
- function loadStoreSpecificContent() {
+ //Load xml content
+ function loadXmlContent(dataPath, xmlPath) {
+ loadHelper(dataPath, xmlPath, function(d, name, $e) {
+ if ($e.length>0) {
+ var serializer = new XMLSerializer();
+ var xmlString = serializer.serializeToString($e[0]);
+ xmlString = xmlString.slice(name.length + 2, -(name.length + 3));
+ d[name] = xmlString;
+ };
+ });
+ };
+
+ function loadStoreSpecificContent() {
loadHelper('', '*', function(d, name, $e) {
for (var i = 0; i < $e.length; i++) {
d[$e[i].nodeName] = getXmlContent($($e[i]).children());
@@ -165,7 +184,7 @@ var appdfParser = (function() {
if (typeof attributeValue!=="undefined" && attributeValue!==false) {
d[name] = attributeValue;
} else {
- errors.push(errorMessages.fnWrongAttribute($e[0].tagName, name));
+ errors.push({msg:errorMessages.fnWrongAttribute($e[0].tagName, name), val:false});
};
};
});
@@ -181,7 +200,7 @@ var appdfParser = (function() {
} else if (attributeValue=="no") {
d[name] = false;
} else {
- errors.push(errorMessages.fnWrongAttrBooleanValue(attributeValue, $e[0].tagName));
+ errors.push({msg:errorMessages.fnWrongAttrBooleanValue(attributeValue, $e[0].tagName), val:false});
};
};
});
@@ -200,7 +219,7 @@ var appdfParser = (function() {
} else if (tagValue=="no") {
d[name] = false;
} else {
- errors.push(errorMessages.fnWrongBooleanValue($e[0].tagName));
+ errors.push({msg:errorMessages.fnWrongBooleanValue($e[0].tagName), val:false});
};
};
});
@@ -229,8 +248,14 @@ var appdfParser = (function() {
$curXml = $saveCurXml;
};
+ //TODO check for "application-description-file" version tag
+
+
$curXml = getElementsByPath($xml, "application-description-file/application");
+ //Developer
+ loadText("developer");
+
//Categorization
section("categorization", "categorization", function() {
loadText("type");
@@ -247,8 +272,7 @@ var appdfParser = (function() {
});
loadArray("short-description", "short-description");
- //loadXml("full-description", "full-description");
- loadText("full-description", "full-description");
+ loadXmlContent("full-description", "full-description");
loadArray("features", "features/feature");
loadText("recent-changes", "recent-changes");
@@ -259,57 +283,60 @@ var appdfParser = (function() {
loadTextAttribute("eula-link", "eula", "href")
loadText("eula", "eula");
});
- section("images/", "images", function() {
- loadHelper("app-icon", "app-icon", function(d, name, $e) {
- d[name] = [];
- $e.each(function() {
- d[name].push({
- "name" : $(this).text(),
- "width" : $(this).attr("width"),
- "height" : $(this).attr("height")
- });
- });
- });
-
- loadHelper("large-promo", "large-promo", function(d, name, $e) {
- d[name] = null;
- $e.each(function() {
- d[name] = {
- "name" : $(this).text(),
- "width" : $(this).attr("width"),
- "height" : $(this).attr("height")
- };
- });
- });
-
- loadHelper("small-promo", "small-promo", function(d, name, $e) {
- d[name] = null;
- $e.each(function() {
- d[name] = {
- "name" : $(this).text(),
- "width" : $(this).attr("width"),
- "height" : $(this).attr("height")
- };
- });
- });
-
- loadHelper("screenshots", "screenshots/screenshot", function(d, name, $e) {
- d[name] = [];
- $e.each(function() {
- d[name].push({
- "name" : $(this).text(),
- "width" : $(this).attr("width"),
- "height" : $(this).attr("height"),
- "index" : $(this).attr("index")
- });
- });
- });
- });
- section("videos/", "videos", function() {
- loadText("youtube-video", "youtube-video");
- loadArray("video-file", "video-file");
- });
+ if (appdfParser.checkRes) {
+ section("images/", "images", function() {
+ loadHelper("app-icon", "app-icon", function(d, name, $e) {
+ d[name] = [];
+ $e.each(function() {
+ d[name].push({
+ "name" : $(this).text(),
+ "width" : $(this).attr("width"),
+ "height" : $(this).attr("height")
+ });
+ });
+ });
+
+ loadHelper("large-promo", "large-promo", function(d, name, $e) {
+ d[name] = null;
+ $e.each(function() {
+ d[name] = {
+ "name" : $(this).text(),
+ "width" : $(this).attr("width"),
+ "height" : $(this).attr("height")
+ };
+ });
+ });
+
+ loadHelper("small-promo", "small-promo", function(d, name, $e) {
+ d[name] = null;
+ $e.each(function() {
+ d[name] = {
+ "name" : $(this).text(),
+ "width" : $(this).attr("width"),
+ "height" : $(this).attr("height")
+ };
+ });
+ });
+
+ loadHelper("screenshots", "screenshots/screenshot", function(d, name, $e) {
+ d[name] = [];
+ $e.each(function() {
+ d[name].push({
+ "name" : $(this).text(),
+ "width" : $(this).attr("width"),
+ "height" : $(this).attr("height"),
+ "index" : $(this).attr("index")
+ });
+ });
+ });
+ });
+
+ section("videos/", "videos", function() {
+ loadText("youtube-video", "youtube-video");
+ loadArray("video-file", "video-file");
+ });
+ };
};
//Description
@@ -385,10 +412,10 @@ var appdfParser = (function() {
"rating" : $(this).text(),
"type" : $(this).attr("type")
};
- if ($(this).attr("certificate")) {
+ if (appdfParser.checkRes && $(this).attr("certificate")) {
ratingCertificate["certificate"] = $(this).attr("certificate");
};
- if ($(this).attr("mark")) {
+ if (appdfParser.checkRes && $(this).attr("mark")) {
ratingCertificate["mark"] = $(this).attr("mark");
};
d[name].push(ratingCertificate);
@@ -453,9 +480,11 @@ var appdfParser = (function() {
});
progress();//LanguageCode*5 + 11
- section("apk-files/", "apk-files", function() {
- loadArray("apk-file", "apk-file");
- });
+ if (appdfParser.checkRes) {
+ section("apk-files/", "apk-files", function() {
+ loadArray("apk-file", "apk-file");
+ });
+ };
progress();//LanguageCode*5 + 12
section("availability", "availability", function() {
@@ -489,24 +518,55 @@ var appdfParser = (function() {
loadText("testing-instructions", "testing-instructions");
progress();//LanguageCode*5 + 14
-
- errors.append(validateDescriptionXMLData(data));
- if (errors.length==0) {
- onend(data);
- } else {
- onerror(errors);
- };
+ validErrors = true;
+
+ asyncValidationCount = 0;
+ validateDescriptionXMLData(data, function(_errors) {
+ console.log(_errors);
+ _errors = _errors.filter(function(el, index, arr) { return el.length > 0 || el.msg; });
+ if (_errors.length>0) {
+ errors.append(_errors);
+ };
+
+ if (--asyncValidationCount===0) {
+ if (errors.length===0) {
+ onend(data);
+ } else {
+ onerror(errors);
+
+ for (var i=0; i30) {
- errors.push(errorMessages.fnTitleError(languageCode));
- };
+ if (isDefined(data["title"]) && data["title"].length) {
+ if (isDefined(data["title"][0]) && data["title"][0].length>30) {
+ errors.push({msg:errorMessages.fnTitleError(languageCode), val:false});
+ };
+ } else if (languageCode==="default") {
+ errors.push({msg:errorMessages.fnTitleRequiredError(languageCode), val:false});
+ };
- if (isDefined(data["short-description"]) && data["short-description"][0].length>80) {
- errors.push(errorMessages.fnShortDescriptionError(languageCode));
+ if (isDefined(data["short-description"]) && data["short-description"].length) {
+ if (isDefined(data["short-description"][0]) && data["short-description"][0].length>80) {
+ errors.push({msg:errorMessages.fnShortDescriptionError(languageCode), val:false});
+ };
+ } else if (languageCode==="default") {
+ errors.push({msg:errorMessages.shortDescriptionRequired, val:false});
};
- //TODO check for tags
- if (isDefined(data["full-description"]) && data["full-description"].length>4000) {
- errors.push(errorMessages.fnFullDescriptionError(languageCode));
- };
-
+ if (isDefined(data["full-description"])) {
+ if (data["full-description"].length>4000) {
+ errors.push({msg:errorMessages.fnFullDescriptionError(languageCode), val:false});
+ } else {
+ //check for tags
+ var regExp = /<[^]+?>/g;
+ var tagsArr = data["full-description"].match(regExp);
+
+ var validTags = ["", "", "", "- ", ""];
+
+ if (tagsArr) {
+ regExp = /^"), val:false});
+ };
+ };
+ };
+ };
+ } else if (languageCode==="default") {
+ errors.push({msg:errorMessages.fullDescriptionRequired, val:false});
+ };
+
if ((isDefined(data["eula"]) && data["eula"].length && (isUndefined(data["eula-link"]) || data["eula-link"].length===0)) ||
(isDefined(data["eula-link"]) && data["eula-link"].length && (isUndefined(data["eula"]) || data["eula"].length===0))) {
- errors.push(errorMessages.eulaNotBothFilled);
+ errors.push({msg:errorMessages.eulaNotBothFilled, val:false});
};
if ((isDefined(data["privacy-policy"]) && data["privacy-policy"].length && (isUndefined(data["privacy-policy-link"]) || data["privacy-policy-link"].length===0)) ||
(isDefined(data["privacy-policy-link"]) && data["privacy-policy-link"].length && (isUndefined(data["privacy-policy"]) || data["privacy-policy"].length===0))) {
- errors.push(errorMessages.privacypolicyNotBothFilled);
+ errors.push({msg:errorMessages.privacypolicyNotBothFilled, val:false});
};
if (isDefined(data.features)) {
if (data.features.length>5) {
- errors.push(errorMessages.fnFeatureMaxError(languageCode));
+ errors.push({msg:errorMessages.fnFeatureMaxError(languageCode), val:false});
};
if (data.features.length<3) {
- errors.push(errorMessages.fnFeatureMinError(languageCode));
+ errors.push({msg:errorMessages.fnFeatureMinError(languageCode), val:true});
};
};
if (isDefined(data["recent-changes"]) && data["recent-changes"].length>500) {
- errors.push(errorMessages.fnRecentChangesError(languageCode));
+ errors.push({msg:errorMessages.fnRecentChangesError(languageCode), val:false});
};
return errors;
@@ -679,24 +874,29 @@ var appdfParser = (function() {
function validateConsent(data) {
var errors = [];
+ if (isUndefined(data)) {
+ //errors.push();
+ return errors;
+ };
+
if (isUndefined(data["google-android-content-guidelines"])) {
- errors.push(errorMessages.requiredGoogleAndroidTagMiss);
+ errors.push({msg:errorMessages.requiredGoogleAndroidTagMiss, val:false});
};
if (isUndefined(data["us-export-laws"])) {
- errors.push(errorMessages.requiredUSExportLawsTagMiss);
+ errors.push({msg:errorMessages.requiredUSExportLawsTagMiss, val:false});
};
if (isUndefined(data["slideme-agreement"])) {
- errors.push(errorMessages.requiredSlideMeTagMiss);
+ errors.push({msg:errorMessages.requiredSlideMeTagMiss, val:false});
};
if (isUndefined(data["free-from-third-party-copytighted-content"])) {
- errors.push(errorMessages.requiredFree3PartyTagMiss);
+ errors.push({msg:errorMessages.requiredFree3PartyTagMiss, val:false});
};
if (isUndefined(data["import-export"])) {
- errors.push(errorMessages.requiredImportExportTagMiss);
+ errors.push({msg:errorMessages.requiredImportExportTagMiss, val:false});
};
return errors;
@@ -705,7 +905,7 @@ var appdfParser = (function() {
function validateNumber(value, errorMessage) {
var patt = /^\d+\.\d+$|^\d+$/g;
if (!patt.test(value)) {
- return [errorMessage];
+ return [{msg:errorMessage, val:false}];
};
return [];
};
@@ -713,7 +913,7 @@ var appdfParser = (function() {
function validatePackageName(value, errorMessage) {
var patt = /^([a-zA-Z0-9\-]+\.)+[a-zA-Z0-9\-]+$/g;
if (!patt.test(value)) {
- return [errorMessage];
+ return [{msg:errorMessage, val:false}];
};
return [];
};
@@ -721,7 +921,7 @@ var appdfParser = (function() {
function validatePhoneNumber(value, errorMessage) {
var patt = /^\+(\(?\+?[0-9]*\)?)?[0-9_\- \(\)]*$/g;
if (!patt.test(value)) {
- return [errorMessage];
+ return [{msg:errorMessage, val:false}];
};
return [];
};
@@ -729,7 +929,7 @@ var appdfParser = (function() {
function validateEmail(value, errorMessage) {
var patt = /^.*@.*$/g;
if (!patt.test(value)) {
- return [errorMessage];
+ return [{msg:errorMessage, val:false}];
};
return [];
};
@@ -738,7 +938,7 @@ var appdfParser = (function() {
var patt = /^((http|https):\/\/)?[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:\/~\+#]*[\w\-\@?^=%&\/~\+#])?$/g;
if (!patt.test(value)) {
- return [errorMessage];
+ return [{msg:errorMessage, val:false}];
};
return [];
};
@@ -751,7 +951,7 @@ var appdfParser = (function() {
};
};
if (index<0) {
- return ["Wrong " + fieldName + " value \"" + value + "\". Must be one of " + enumArray.join(", ")];
+ return [{msg:"Wrong " + fieldName + " value \"" + value + "\". Must be one of " + enumArray.join(", "), val:false}];
} else {
return [];
};
@@ -760,18 +960,23 @@ var appdfParser = (function() {
function validatePrice(data) {
var errors = [];
- if (data["free"]) {
+ if (isUndefined(data)) {
+ //errors.push();
+ return errors;
+ };
+
+ if (data["free"]) {
if (isDefined(data["full-version"])) {
errors.append(validatePackageName(data["full-version"], "Wrong package name format \"" + data["full-version"] + "\" in full version attribute"));
};
} else {
if (isUndefined(data["base-price"])) {
- errors.push("Required base price value is missing for paid product");
+ errors.push({msg:"Required base price value is missing for paid product", val:false});
} else {
errors.append(validateNumber(data["base-price"], "Wrong price value \"" + data["base-price"] + "\". Must be a valid number like \"15.95\"."));
};
-
+ errors.append(validateCountryCode(data["local-price"]));
};
return errors;
@@ -780,16 +985,40 @@ var appdfParser = (function() {
function validateCustomerSupport(data) {
var errors = [];
- errors.append(validatePhoneNumber(data["phone"], "Wrong customer support phone number format. Only digits, brackets, spaces and dashes are allowed. Must be in international format like +1 (555) 123-45-67."));
- errors.append(validateEmail(data["email"], "Wrong customer support email format. Must be a valid email address."));
- errors.append(validateURL(data["website"], "Wrong customer support webpage format. Must be a valid URL."));
+ if (isUndefined(data)) {
+ //errors.push();
+ return errors;
+ };
+
+ if (data["phone"]) {
+ errors.append(validatePhoneNumber(data["phone"], errorMessages.wrongCustomerPhone));
+ } else {
+ errors.push({msg:errorMessages.customerPhoneRequired, val:false});
+ };
+ if (data["email"]) {
+ errors.append(validateEmail(data["email"], errorMessages.wrongCustomerEmail));
+ } else {
+ errors.push({msg:errorMessages.customerEmailRequired, val:false});
+ };
+
+ if (data["website"]) {
+ errors.append(validateURL(data["website"], errorMessages.wrongCustomerWebPage));
+ } else {
+ errors.push({msg:errorMessages.customerWebSiteRequired, val:false});
+ };
+
return errors;
};
function validateContentDescription(data) {
var errors = [];
+ if (isUndefined(data)) {
+ //errors.push();
+ return errors;
+ };
+
errors.append(validateEnum(data["content-rating"], "Content rating", [3,6,10,13,17,18]));
var yes_light_strong = ["no", "light", "strong"];
errors.append(validateEnum(data["content-descriptors"]["cartoon-violence"], "cartoon violence", yes_light_strong));
@@ -825,21 +1054,35 @@ var appdfParser = (function() {
errors.append(validateEnum(ratingCertificate["rating"], "FSK rating certificate", ["0", "6", "12", "16", "18"]));
break;
default:
- errors.push("Wrong rating certificate type value \"" + ratingCertificate["type"] + "\". Must be one of 'PEGI', 'ESRB', 'GRB', 'CERO', 'DEJUS', 'FSK'");
+ errors.push({msg:"Wrong rating certificate type value \"" + ratingCertificate["type"] + "\". Must be one of 'PEGI', 'ESRB', 'GRB', 'CERO', 'DEJUS', 'FSK'", val:false});
};
+
+ if (isUndefined(appdfXMLLoader.appdfFiles[ratingCertificate["certificate"]])) {
+ errors.push({msg:errorMessages.fnResourceNotFound(ratingCertificate["certificate"]), val:false});
+ };
};
return errors;
};
- function validateAvailabilityPeriod(data) {
+ function validateAvailability(data) {
var errors = [];
- if (data["availability"] && data["availability"]["period"]) {
+ if (!data["availability"]) {
+ return errors;
+ };
+
+ if (data["availability"]["period"]) {
var dataPeriod = data["availability"]["period"];
if (isDefined(dataPeriod["since"]) && isDefined(dataPeriod["until"]) && dataPeriod["since"].valueOf()>=dataPeriod["until"].valueOf()) {
- errors.push(errorMessages.availabilityPerionError);
+ errors.push({msg:errorMessages.availabilityPerionError, val:false});
};
};
+
+ if (data["availability"]["countries"]) {
+ var dataCountries = data["availability"]["countries"]["include"]?data["availability"]["countries"]["include"]:data["availability"]["countries"]["exclude"];
+
+ errors.append(validateCountryCode(dataCountries));
+ };
return errors;
};
@@ -847,10 +1090,10 @@ var appdfParser = (function() {
var errors = [];
if (isUndefined(data)) {
- errors.push("Required tag is missing");
+ errors.push({msg:"Required tag is missing", val:false});
} else {
if (data.length>4000) {
- errors.push("The testing instruction text must be shorter than 4000 symbols");
+ errors.push({msg:"The testing instruction text must be shorter than 4000 symbols", val:false});
};
};
diff --git a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Application.java b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Application.java
index e64aed28..4b739e12 100644
--- a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Application.java
+++ b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Application.java
@@ -128,6 +128,7 @@ public CustomerSupport getCustomerSupport() {
}
private String packageName;
+ private String developerName;
private Categorisation categorisation;
private Description mainDescription;
private List descriptionLocalisations;
@@ -149,6 +150,10 @@ public void setPackageName(String packageName) {
this.packageName = packageName;
}
+ public void setDeveloperName(String developerName) {
+ this.developerName = developerName;
+ }
+
public Categorisation getCategorisation() {
return categorisation;
}
diff --git a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/ImagesDescription.java b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/ImagesDescription.java
index ce25c470..3701f067 100644
--- a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/ImagesDescription.java
+++ b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/ImagesDescription.java
@@ -21,31 +21,31 @@
public class ImagesDescription implements ModelElement {
private List appIcons;
- private String largePromo;
- private String smallPromo;
- private List screenShots;
+ private LargePromo largePromo;
+ private SmallPromo smallPromo;
+ private List screenShots;
public List getAppIcons() {
return appIcons;
}
public void setAppIcons(List appIcons) {
this.appIcons = appIcons;
}
- public String getLargePromo() {
+ public LargePromo getLargePromo() {
return largePromo;
}
- public void setLargePromo(String largePromo) {
+ public void setLargePromo(LargePromo largePromo) {
this.largePromo = largePromo;
}
- public String getSmallPromo() {
+ public SmallPromo getSmallPromo() {
return smallPromo;
}
- public void setSmallPromo(String smallPromo) {
+ public void setSmallPromo(SmallPromo smallPromo) {
this.smallPromo = smallPromo;
}
- public List getScreenShots() {
+ public List getScreenShots() {
return screenShots;
}
- public void setScreenShots(List screenShots) {
+ public void setScreenShots(List screenShots) {
this.screenShots = screenShots;
}
@@ -56,9 +56,9 @@ public void addAppIcon(AppIcon appIcon){
appIcons.add(appIcon);
}
- public void addScreenshot(String screenshot){
+ public void addScreenshot(Screenshot screenshot){
if ( screenShots == null ){
- screenShots = new ArrayList();
+ screenShots = new ArrayList();
}
screenShots.add(screenshot);
}
diff --git a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/LargePromo.java b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/LargePromo.java
new file mode 100644
index 00000000..3e4f18af
--- /dev/null
+++ b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/LargePromo.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2012 One Platform Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package org.onepf.appdf.model;
+
+public class LargePromo {
+
+ private int width;
+ private int height;
+ private String name;
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Screenshot.java b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Screenshot.java
new file mode 100644
index 00000000..3d14843b
--- /dev/null
+++ b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/Screenshot.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright 2012 One Platform Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package org.onepf.appdf.model;
+
+public class Screenshot {
+
+ private int width;
+ private int height;
+ private int index;
+ private String name;
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public void setIndex(int index) {
+ this.index = index;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/SmallPromo.java b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/SmallPromo.java
new file mode 100644
index 00000000..5fe95399
--- /dev/null
+++ b/tools/parser-lib/model/src/main/java/org/onepf/appdf/model/SmallPromo.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright 2012 One Platform Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package org.onepf.appdf.model;
+
+public class SmallPromo {
+
+ private int width;
+ private int height;
+ private String name;
+
+ public int getHeight() {
+ return height;
+ }
+
+ public void setHeight(int height) {
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public void setWidth(int width) {
+ this.width = width;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+}
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/categories.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/categories.xml
new file mode 100644
index 00000000..097b6715
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/categories.xml
@@ -0,0 +1,238 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/countries.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/countries.xml
new file mode 100644
index 00000000..7d800ea9
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/countries.xml
@@ -0,0 +1,250 @@
+
+ Afghanistan
+ Åland Islands
+ Albania
+ Algeria
+ American Samoa
+ Andorra
+ Angola
+ Anguilla
+ Antigua and Barbuda
+ Argentina
+ Armenia
+ Aruba
+ Australia
+ Austria
+ Azerbaijan
+ Bahamas
+ Bahrain
+ Bangladesh
+ Barbados
+ Belarus
+ Belgium
+ Belize
+ Benin
+ Bermuda
+ Bhutan
+ Bolivia, Plurinational State of
+ Bonaire, Sint Eustatius and Saba
+ Bosnia and Herzegovina
+ Botswana
+ Bouvet Island
+ Brazil
+ British Indian Ocean Territory
+ Brunei Darussalam
+ Bulgaria
+ Burkina Faso
+ Burundi
+ Cambodia
+ Cameroon
+ Canada
+ Cape Verde
+ Cayman Islands
+ Central African Republic
+ Chad
+ Chile
+ China
+ Christmas Island
+ Cocos (Keeling) Islands
+ Colombia
+ Comoros
+ Congo
+ Congo, the Democratic Republic of the
+ Cook Islands
+ Costa Rica
+ Côte d'Ivoire
+ Croatia
+ Cuba
+ Curaçao
+ Cyprus
+ Czech Republic
+ Denmark
+ Djibouti
+ Dominica
+ Dominican Republic
+ Ecuador
+ Egypt
+ El Salvador
+ Equatorial Guinea
+ Eritrea
+ Estonia
+ Ethiopia
+ Falkland Islands (Malvinas)
+ Faroe Islands
+ Fiji
+ Finland
+ France
+ French Guiana
+ French Polynesia
+ French Southern Territories
+ Gabon
+ Gambia
+ Georgia
+ Germany
+ Ghana
+ Gibraltar
+ Greece
+ Greenland
+ Grenada
+ Guadeloupe
+ Guam
+ Guatemala
+ Guernsey
+ Guinea
+ Guinea-Bissau
+ Guyana
+ Haiti
+ Heard Island and McDonald Islands
+ Holy See (Vatican City State)
+ Honduras
+ Hong Kong
+ Hungary
+ Iceland
+ India
+ Indonesia
+ Iran, Islamic Republic of
+ Iraq
+ Ireland
+ Isle of Man
+ Israel
+ Italy
+ Jamaica
+ Japan
+ Jersey
+ Jordan
+ Kazakhstan
+ Kenya
+ Kiribati
+ Korea, Democratic People's Republic of
+ Korea, Republic of
+ Kuwait
+ Kyrgyzstan
+ Lao People's Democratic Republic
+ Latvia
+ Lebanon
+ Lesotho
+ Liberia
+ Libya
+ Liechtenstein
+ Lithuania
+ Luxembourg
+ Macao
+ Macedonia, The Former Yugoslav Republic of
+ Madagascar
+ Malawi
+ Malaysia
+ Maldives
+ Mali
+ Malta
+ Marshall Islands
+ Martinique
+ Mauritania
+ Mauritius
+ Mayotte
+ Mexico
+ Micronesia, Federated States of
+ Moldova, Republic of
+ Monaco
+ Mongolia
+ Montenegro
+ Montserrat
+ Morocco
+ Mozambique
+ Myanmar
+ Namibia
+ Nauru
+ Nepal
+ Netherlands
+ New Caledonia
+ New Zealand
+ Nicaragua
+ Niger
+ Nigeria
+ Niue
+ Norfolk Island
+ Northern Mariana Islands
+ Norway
+ Oman
+ Pakistan
+ Palau
+ Palestinian Territory, Occupied
+ Panama
+ Papua New Guinea
+ Paraguay
+ Peru
+ Philippines
+ Pitcairn
+ Poland
+ Portugal
+ Puerto Rico
+ Qatar
+ Réunion
+ Romania
+ Russian Federation
+ Rwanda
+ Saint Barthélemy
+ Saint Helena, Ascension and Tristan da Cunha
+ Saint Kitts and Nevis
+ Saint Lucia
+ Saint Martin (French part)
+ Saint Pierre and Miquelon
+ Saint Vincent and the Grenadines
+ Samoa
+ San Marino
+ Sao Tome and Principe
+ Saudi Arabia
+ Senegal
+ Serbia
+ Seychelles
+ Sierra Leone
+ Singapore
+ Sint Maarten (Dutch part)
+ Slovakia
+ Slovenia
+ Solomon Islands
+ Somalia
+ South Africa
+ South Georgia and the South Sandwich Islands
+ South Sudan
+ Spain
+ Sri Lanka
+ Sudan
+ Suriname
+ Svalbard and Jan Mayen
+ Swaziland
+ Sweden
+ Switzerland
+ Syrian Arab Republic
+ Taiwan, Province of China
+ Tajikistan
+ Tanzania, United Republic of
+ Thailand
+ Timor-Leste
+ Togo
+ Tokelau
+ Tonga
+ Trinidad and Tobago
+ Tunisia
+ Turkey
+ Turkmenistan
+ Turks and Caicos Islands
+ Tuvalu
+ Uganda
+ Ukraine
+ United Arab Emirates
+ United Kingdom
+ United States
+ United States Minor Outlying Islands
+ Uruguay
+ Uzbekistan
+ Vanuatu
+ Venezuela, Bolivarian Republic of
+ Viet Nam
+ Virgin Islands, British
+ Virgin Islands, U.S.
+ Wallis and Futuna
+ Western Sahara
+ Yemen
+ Zambia
+ Zimbabwe
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_currencies.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_currencies.xml
new file mode 100644
index 00000000..5bbccec0
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_currencies.xml
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_languages.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_languages.xml
new file mode 100644
index 00000000..0f6a940e
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/country_languages.xml
@@ -0,0 +1,827 @@
+
+
+ fa
+ ps
+
+
+ sv
+
+
+ sq
+
+
+ ar
+
+
+ en
+ sm
+
+
+ ca
+
+
+ pt
+
+
+ en
+
+
+ en
+
+
+ es
+
+
+ hy
+
+
+ nl
+
+
+ en
+
+
+ de
+
+
+ az
+
+
+ en
+
+
+ ar
+
+
+ bn
+
+
+ en
+
+
+ be
+ ru
+
+
+ nl
+ fr
+ de
+
+
+ en
+
+
+ fr
+
+
+ en
+
+
+ dz
+
+
+ es
+ qu
+ ay
+
+
+ nl
+ en
+
+
+ bs
+ hr
+
+
+ en
+
+
+ pt
+
+
+ en
+
+
+ ms
+
+
+ bg
+
+
+ fr
+
+
+ rn
+ fr
+
+
+ km
+
+
+ en
+ fr
+
+
+ en
+ fr
+
+
+ pt
+
+
+ en
+
+
+ fr
+
+
+ fr
+ ar
+
+
+ es
+
+
+ zh
+
+
+ en
+
+
+ ms
+ en
+
+
+ es
+
+
+ ar
+ fr
+
+
+ fr
+
+
+ fr
+
+
+ en
+
+
+ es
+
+
+ fr
+
+
+ hr
+
+
+ es
+
+
+ nl
+
+
+ el
+ tr
+
+
+ cs
+
+
+ da
+
+
+ fr
+ ar
+
+
+ en
+
+
+ es
+
+
+ es
+
+
+ ar
+
+
+ es
+
+
+ es
+ fr
+
+
+ ti
+ ar
+ en
+
+
+ et
+
+
+ am
+ en
+ ar
+ ti
+ om
+
+
+ en
+
+
+ fo
+ da
+
+
+ en
+ fj
+
+
+ fi
+ sv
+
+
+ fr
+
+
+ fr
+
+
+ fr
+ ty
+
+
+ fr
+
+
+ fr
+
+
+ en
+
+
+ ka
+
+
+ de
+
+
+ en
+
+
+ en
+
+
+ el
+
+
+ kl
+ da
+
+
+ en
+
+
+ fr
+
+
+ en
+ ch
+
+
+ es
+
+
+ en
+ fr
+
+
+ fr
+
+
+ pt
+
+
+ en
+
+
+ fr
+ ht
+
+
+ it
+ la
+ fr
+
+
+ es
+
+
+ en
+ zh
+
+
+ hu
+
+
+ is
+
+
+ hi
+ en
+
+
+ ms
+
+
+ fa
+
+
+ ar
+
+
+ en
+ ga
+ gd
+
+
+ en
+
+
+ he
+
+
+ it
+
+
+ en
+
+
+ ja
+
+
+ en
+
+
+ ar
+
+
+ ru
+ kk
+
+
+ en
+ sw
+
+
+ en
+
+
+ ko
+
+
+ ko
+
+
+ ar
+
+
+ ky
+ ru
+
+
+ lo
+
+
+ lv
+
+
+ ar
+
+
+ en
+
+
+ en
+
+
+ ar
+
+
+ de
+
+
+ lt
+
+
+ lb
+ de
+ fr
+
+
+ zh
+ pt
+
+
+ mk
+ sq
+
+
+ fr
+ mg
+
+
+ ny
+
+
+ ms
+
+
+ dv
+
+
+ fr
+
+
+ mt
+ en
+
+
+ mh
+ en
+
+
+ fr
+
+
+ ar
+
+
+ en
+
+
+ fr
+
+
+ es
+
+
+ en
+
+
+ ro
+
+
+ fr
+
+
+ mn
+
+
+ sr
+
+
+ en
+
+
+ ar
+
+
+ pt
+
+
+ my
+
+
+ en
+
+
+ na
+
+
+ ne
+
+
+ nl
+ fr
+
+
+ fr
+
+
+ en
+ mi
+
+
+ es
+
+
+ fr
+
+
+ en
+
+
+ en
+
+
+ en
+
+
+ en
+ ch
+
+
+ no
+ nn
+ nb
+
+
+ ar
+ en
+ he
+
+
+ ar
+
+
+ ur
+ en
+
+
+ en
+
+
+ es
+
+
+ en
+ ho
+
+
+ es
+ gn
+
+
+ es
+ qu
+
+
+ en
+
+
+ en
+
+
+ pl
+
+
+ pt
+
+
+ es
+ en
+
+
+ ar
+
+
+ fr
+
+
+ ro
+
+
+ ru
+
+
+ rw
+ fr
+ en
+
+
+ fr
+
+
+ no
+
+
+ en
+
+
+ en
+
+
+ en
+
+
+ en
+
+
+ fr
+
+
+ fr
+
+
+ en
+
+
+ sm
+
+
+ it
+
+
+ pt
+
+
+ ar
+
+
+ fr
+
+
+ sr
+
+
+ en
+
+
+ en
+
+
+ zh
+ en
+ ms
+
+
+ en
+ nl
+
+
+ sk
+
+
+ sl
+ it
+ hu
+
+
+ en
+
+
+ so
+
+
+ xh
+ zu
+ af
+ en
+
+
+ en
+
+
+ es
+
+
+ si
+
+
+ ar
+ en
+
+
+ nl
+
+
+ nn
+ ru
+
+
+ en
+ ss
+
+
+ sv
+
+
+ de
+ fr
+ it
+
+
+ ar
+
+
+ zh
+
+
+ tg
+
+
+ sw
+ en
+
+
+ th
+
+
+ pt
+
+
+ fr
+
+
+ en
+
+
+ to
+ en
+
+
+ en
+
+
+ ar
+
+
+ tr
+
+
+ tk
+
+
+ en
+
+
+ en
+
+
+ en
+
+
+ uk
+
+
+ ar
+
+
+ en
+ cy
+ ga
+
+
+ en
+
+
+ en
+
+
+ en
+
+
+ es
+
+
+ uz
+
+
+ en
+ fr
+
+
+ es
+
+
+ vi
+
+
+ en
+
+
+ en
+
+
+ fr
+
+
+ ar
+
+
+ ar
+
+
+ ny
+ en
+
+
+ en
+
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/currencies.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/currencies.xml
new file mode 100644
index 00000000..40bd9420
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/currencies.xml
@@ -0,0 +1,159 @@
+
+ United Arab Emirates dirham
+ Afghan afghani
+ Albanian lek
+ Armenian dram
+ Netherlands Antillean guilder
+ Angolan kwanza
+ Argentine peso
+ Australian dollar
+ Aruban florin
+ Azerbaijani manat
+ Bosnia and Herzegovina convertible mark
+ Barbados dollar
+ Bangladeshi taka
+ Bulgarian lev
+ Bahraini dinar
+ Burundian franc
+ Bermudian dollar
+ Brunei dollar
+ Boliviano
+ Brazilian real
+ Bahamian dollar
+ Bhutanese ngultrum
+ Botswana pula
+ Belarusian ruble
+ Belize dollar
+ Canadian dollar
+ Congolese franc
+ Swiss franc
+ Chilean peso
+ Chinese yuan
+ Colombian peso
+ Unidad de Valor Real
+ Costa Rican colon
+ Cuban peso
+ Cape Verde escudo
+ Czech koruna
+ Djiboutian franc
+ Danish krone
+ Dominican peso
+ Algerian dinar
+ Egyptian pound
+ Eritrean nakfa
+ Ethiopian birr
+ Euro
+ Fiji dollar
+ Falkland Islands pound
+ Pound sterling
+ Georgian lari
+ Ghanaian cedi
+ Gibraltar pound
+ Gambian dalasi
+ Guinean franc
+ Guatemalan quetzal
+ Guyanese dollar
+ Hong Kong dollar
+ Honduran lempira
+ Croatian kuna
+ Haitian gourde
+ Hungarian forint
+ Indonesian rupiah
+ Israeli new shekel
+ Indian rupee
+ Iraqi dinar
+ Iranian rial
+ Icelandic króna
+ Jamaican dollar
+ Jordanian dinar
+ Japanese yen
+ Kenyan shilling
+ Kyrgyzstani som
+ Cambodian riel
+ Comoro franc
+ North Korean won
+ South Korean won
+ Kuwaiti dinar
+ Cayman Islands dollar
+ Kazakhstani tenge
+ Lao kip
+ Lebanese pound
+ Sri Lankan rupee
+ Liberian dollar
+ Lesotho loti
+ Lithuanian litas
+ Latvian lats
+ Libyan dinar
+ Moroccan dirham
+ Moldovan leu
+ Malagasy ariary
+ Macedonian denar
+ Myanma kyat
+ Mongolian tugrik
+ Macanese pataca
+ Mauritanian ouguiya
+ Mauritian rupee
+ Maldivian rufiyaa
+ Malawian kwacha
+ Mexican peso
+ Malaysian ringgit
+ Mozambican metical
+ Namibian dollar
+ Nigerian naira
+ Nicaraguan córdoba
+ Norwegian krone
+ Nepalese rupee
+ New Zealand dollar
+ Omani rial
+ Panamanian balboa
+ Peruvian nuevo sol
+ Papua New Guinean kina
+ Philippine peso
+ Pakistani rupee
+ Polish złoty
+ Paraguayan guaraní
+ Qatari riyal
+ Romanian new leu
+ Serbian dinar
+ Russian rouble
+ Rwandan franc
+ Saudi riyal
+ Solomon Islands dollar
+ Seychelles rupee
+ Sudanese pound
+ Swedish krona/kronor
+ Singapore dollar
+ Saint Helena pound
+ Sierra Leonean leone
+ Somali shilling
+ Surinamese dollar
+ South Sudanese pound
+ São Tomé and Príncipe dobra
+ Syrian pound
+ Swazi lilangeni
+ Thai baht
+ Tajikistani somoni
+ Turkmenistani manat
+ Tunisian dinar
+ Tongan paʻanga
+ Turkish lira
+ Trinidad and Tobago dollar
+ New Taiwan dollar
+ Tanzanian shilling
+ Ukrainian hryvnia
+ Ugandan shilling
+ United States dollar
+ Uruguayan peso
+ Uzbekistan som
+ Venezuelan bolívar fuerte
+ Vietnamese dong
+ Vanuatu vatu
+ Samoan tala
+ CFA franc BEAC
+ East Caribbean dollar
+ CFA franc BCEAO
+ CFP franc
+ Yemeni rial
+ South African rand
+ Zambian kwacha
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/languages.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/languages.xml
new file mode 100644
index 00000000..be6276df
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/languages.xml
@@ -0,0 +1,214 @@
+
+ Abkhaz
+ Afar
+ Afrikaans
+ Akan
+ Albanian
+ Amharic
+ Arabic
+ Arabic (Egypt)
+ Arabic (Israel)
+ Aragonese
+ Armenian
+ Assamese
+ Avaric
+ Avestan
+ Aymara
+ Azerbaijani
+ Bambara
+ Bashkir
+ Basque
+ Belarusian
+ Bengali
+ Bihari
+ Bislama
+ Bosnian
+ Breton
+ Bulgarian
+ Burmese
+ Catalan
+ Chamorro
+ Chechen
+ Chichewa
+ Chinese
+ Chinese (PRC)
+ Chinese (Taiwan)
+ Chinese (HK)
+ Chuvash
+ Cornish
+ Corsican
+ Cree
+ Croatian
+ Czech
+ Danish
+ Divehi
+ Dutch
+ Dutch (Belgium)
+ Dutch (Netherlands)
+ Dzongkha
+ English
+ English (US)
+ English (Australia)
+ English (Britain)
+ English (Canada)
+ English (New Zealand)
+ English (Singapore)
+ English (Canada)
+ Esperanto
+ Estonian
+ Ewe
+ Faroese
+ Fijian
+ Finnish
+ French
+ French (Belgium)
+ French (Canada)
+ French (France)
+ French (Switzerland)
+ Fula
+ Galician
+ Georgian
+ German
+ German (Austria)
+ German (Germany)
+ German (Liechtenstein)
+ German (Switzerland)
+ Greek
+ GuaranГ
+ Gujarati
+ Haitian
+ Hausa
+ Hebrew
+ Herero
+ Hindi
+ Hiri Motu
+ Hungarian
+ Interlingua
+ Indonesian
+ Interlingue
+ Irish
+ Igbo
+ Inupiaq
+ Ido
+ Icelandic
+ Italian
+ Italian (Italy)
+ Italian (Switzerland)
+ Inuktitut
+ Japanese
+ Javanese
+ Kalaallisut
+ Kannada
+ Kanuri
+ Kashmiri
+ Kazakh
+ Khmer
+ Kikuyu
+ Kinyarwanda
+ Kyrgyz
+ Komi
+ Kongo
+ Korean
+ Kurdish
+ Kwanyama
+ Latin
+ Luxembourgish
+ Ganda
+ Limburgish
+ Lingala
+ Lao
+ Lithuanian
+ Luba-Katanga
+ Latvian
+ Manx
+ Macedonian
+ Malagasy
+ Malay
+ Malayalam
+ Maltese
+ Maori
+ Marathi
+ Marshallese
+ Mongolian
+ Nauru
+ Navajo
+ Norwegian Bokmal
+ North Ndebele
+ Nepali
+ Ndonga
+ Norwegian Nynorsk
+ Norwegian
+ Nuosu
+ South Ndebele
+ Occitan
+ Ojibwe
+ Oromo
+ Oriya
+ Ossetian
+ Panjabi
+ Pali
+ Persian
+ Polish
+ Pashto
+ Portuguese
+ Portuguese (Brazil)
+ Portuguese (Portugal)
+ Quechua
+ Romansh
+ Kirundi
+ Romanian
+ Russian
+ Sanskrit
+ Sardinian
+ Sindhi
+ Northern Sami
+ Samoan
+ Sango
+ Serbian
+ Gaelic
+ Shona
+ Sinhala
+ Slovak
+ Slovene
+ Somali
+ Southern Sotho
+ Spanish
+ Spanish (Spain)
+ Spanish (US)
+ Spanish (Latin America)
+ Sundanese
+ Swahili
+ Swati
+ Swedish
+ Tamil
+ Telugu
+ Tajik
+ Thai
+ Tigrinya
+ Tibetan Standard
+ Turkmen
+ Tagalog
+ Tswana
+ Tonga
+ Turkish
+ Tsonga
+ Tatar
+ Twi
+ Tahitian
+ Uighur
+ Ukrainian
+ Urdu
+ Uzbek
+ Venda
+ Vietnamese
+ Volapuk
+ Walloon
+ Welsh
+ Wolof
+ Western Frisian
+ Xhosa
+ Yiddish
+ Yoruba
+ Zhuang
+ Zulu
+
\ No newline at end of file
diff --git a/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/store_categories.xml b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/store_categories.xml
new file mode 100644
index 00000000..72de6a79
--- /dev/null
+++ b/tools/parser-lib/model/src/main/resources/org/onepf/appdf/model/store_categories.xml
@@ -0,0 +1,1732 @@
+
+
+
+
+ Tools
+ Utilities / Alarms & Clocks
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Other
+
+
+
+
+ Tools
+ Utilities / Battery Savers
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Other
+
+
+
+
+ Books & Reference
+ Books & Comic / Other
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications / E-books
+
+
+
+
+ Books & Reference
+ Books & Comic / Books & Readers
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications / E-book readers
+
+
+
+
+ Business
+ Finance / Other
+ Business & Finance
+ Business & Finance
+ Business
+ Finance / Other
+
+
+
+
+ Tools
+ Utilities / Calculators
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Other
+
+
+
+
+ Tools
+ Utilities / Calendars
+ Organizers
+ Organizers
+ Utilities
+ Tools & Utilities / Other
+
+
+
+
+ Books & Reference
+ Books & Comic / Children’s Books
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications
+
+
+
+
+ Books & Reference
+ City Info / Other
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Boston
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Chicago
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Dallas
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Los Angeles
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Miami
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / New York
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Philadelphia
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Phoenix
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / San Francisco
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+ Books & Reference
+ City Info / Seattle
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / City Guides
+
+
+
+
+ Comics
+ Books & Comic / Comic Strips
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications / Comics
+
+
+
+
+ Communication
+ Communication
+ Communication
+ Communication
+ Social Networking
+ Communication
+
+
+ Communication
+ Communication
+ Communication
+ Communication
+ Social Networking
+ Communication / E-mail
+
+
+ Communication
+ Communication
+ Communication
+ Communication
+ Social Networking
+ Communication / Instant Messaging
+
+
+ Communication
+ Communication
+ Communication
+ Communication
+ Social Networking
+ Communication / SMS
+
+
+ Communication
+ Communication
+ Communication
+ Communication
+ Social Networking
+ Communication / Other
+
+
+
+
+ Health & Fitness
+ Cooking
+ Health
+ Health
+ Health/Fitness
+ Home & Hobby / Cooking
+
+
+
+
+ Books & Reference
+ City Info / Other
+ Travel & Maps
+ Travel & Maps
+ Reference
+ Travel & Locality / Country Guides
+
+
+
+
+ Health & Fitness
+ Health & Fitness / Diet & Weight Loss
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Calorie calculators
+
+
+
+
+ Education
+ Education / Other
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education
+
+
+ Education
+ Education / Other
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Higher
+
+
+ Education
+ Education / Other
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Primary
+
+
+ Education
+ Education / Other
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Secondary
+
+
+ Education
+ Education / History
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Math
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Reading
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Science
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Test Guides
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Writing
+ Entertainment
+ Entertainment
+ Education/E-Book
+ Education / Other
+
+
+ Education
+ Education / Language
+ Languages & Translators
+ Languages & Translators
+ Education/E-Book
+ Languages
+
+
+ Education
+ Education / Language
+ Languages & Translators
+ Languages & Translators
+ Education/E-Book
+ Languages / Dictionaries
+
+
+ Education
+ Education / Language
+ Languages & Translators
+ Languages & Translators
+ Education/E-Book
+ Languages / Language learning
+
+
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment / Comedy
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment / Music
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment / Sports
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment / Theatre
+
+
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment / Other
+
+
+
+
+ Finance
+ Finance / Other
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance
+
+
+ Finance
+ Finance / Other
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance / Corporate
+
+
+ Finance
+ Finance / Other
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance / Other
+
+
+ Finance
+ Finance / Accounting
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance
+
+
+ Finance
+ Finance / Banking
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance
+
+
+ Finance
+ Finance / Investing
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance
+
+
+ Finance
+ Finance / Money & Currency
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance
+
+
+
+
+ Books & Reference
+ Books & Comic / Graphic Novels
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications
+
+
+
+
+ Health & Fitness
+ Health & Fitness / Exercise & Fitness
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Fitness
+
+
+ Health & Fitness
+ Health & Fitness / Medical
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Other
+
+
+ Health & Fitness
+ Health & Fitness / Meditation
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Other
+
+
+ Health & Fitness
+ Health & Fitness / Pregnancy
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Family Planning
+
+
+ Health & Fitness
+ Health & Fitness / Sleep Trackers
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness / Other
+
+
+
+
+ Health & Fitness
+ Health & Fitness / Other
+ Health
+ Health
+ Health/Fitness
+ Health & Fitness
+
+
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Home & Hobby
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Home & Hobby / Other
+
+
+ Finance
+ Finance / Personal Finance
+ Business & Finance
+ Business & Finance
+ Finance
+ Home & Hobby / Budgeting
+
+
+
+
+ Education
+ Kids / Other
+ Entertainment
+ Entertainment
+ Kids / All
+ Education / Early Childhood
+
+
+ Education
+ Kids / Alphabet
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Animals
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / History
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Language
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Math
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Popular Characters
+ Entertainment
+ Entertainment
+ Kids / Edutainmenttd>
+ Education / Early Childhood
+
+
+ Education
+ Kids / Reading
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Science
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+ Education
+ Kids / Writing
+ Entertainment
+ Entertainment
+ Kids / Edutainment
+ Education / Early Childhood
+
+
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle
+
+
+ Lifestyle
+ Lifestyle / Advice
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Astrology
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Celebrity
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Celebrities
+
+
+ Lifestyle
+ Lifestyle / Celebrity
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Culture
+
+
+ Lifestyle
+ Lifestyle / Celebrity
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Design
+
+
+ Lifestyle
+ Lifestyle / Celebrity
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Fashion
+
+
+ Lifestyle
+ Lifestyle / Celebrity
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Living
+
+
+ Lifestyle
+ Lifestyle / Hair & Beauty
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Home & Garden
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Parenting
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Quizzes & Games
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Relationships
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+ Lifestyle
+ Lifestyle / Self Improvement
+ Entertainment
+ Entertainment
+ Lifestyle
+ Lifestyle / Other
+
+
+
+
+ Personalization
+ Themes
+ Themes & Skins
+ Themes & Skins
+ Theme
+ Themes / Live Wallpapers
+
+
+
+
+ News & Magazines
+ Magazines
+ Entertainment
+ Entertainment
+ News/Magazine
+ Publications / Magazines
+
+
+
+
+ Music & Audio
+ Music / Other
+ Multimedia
+ Multimedia
+ Music/Video
+ Music
+
+
+ Music & Audio
+ Music / Artists
+ Multimedia
+ Multimedia
+ Music/Video
+ Music / Other
+
+
+ Music & Audio
+ Music / Instruments
+ Multimedia
+ Multimedia
+ Music/Video
+ Music / Instruments
+
+
+ Music & Audio
+ Music / Songbooks
+ Multimedia
+ Multimedia
+ Music/Video
+ Music / Other
+
+
+
+
+ Music & Audio
+ Music / Music Players
+ Multimedia
+ Multimedia
+ Music/Video
+ Music / Music players
+
+
+
+
+ Travel & Local
+ Navigation
+ Travel & Maps
+ Travel & Maps
+ Navigation
+ Travel & Locality / Navigation
+
+
+
+
+ News & Magazines
+ News & Weather / Other
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Other
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / Regional News
+
+
+ News & Magazines
+ News & Weather / Other
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / Other
+
+
+ News & Magazines
+ News & Weather / Other
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather
+
+
+ News & Magazines
+ News & Weather / Business
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Entertainment
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Health
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Politics
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Science & Tech
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / Sports
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / US
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+ News & Magazines
+ News & Weather / World
+ Entertainment
+ Entertainment
+ News/Magazine
+ News & Weather / News
+
+
+
+
+ News & Magazines
+ Newspapers
+ Entertainment
+ Entertainment
+ News/Magazine
+ Publications
+
+
+
+
+ Tools
+ Utilities / Notes
+ Organizers
+ Organizers
+ Utilities
+ Tools & Utilities / Other
+
+
+
+
+ Entertainment
+ Novelty
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+
+
+
+
+ Finance
+ Finance / Personal Finance
+ Business & Finance
+ Business & Finance
+ Finance
+ Finance / Personal
+
+
+
+
+ Personalization
+ Themes
+ Themes & Skins
+ Themes & Skins
+ Theme
+ Themes
+
+
+
+
+ Photography
+ Photography
+ Multimedia
+ Multimedia
+ Photo
+ Photography
+
+
+ Photography
+ Photography
+ Multimedia
+ Multimedia
+ Photo
+ Photography / Camera
+
+
+ Photography
+ Photography
+ Multimedia
+ Multimedia
+ Photo
+ Photography / Editing
+
+
+ Photography
+ Photography
+ Multimedia
+ Multimedia
+ Photo
+ Photography / Gallery
+
+
+ Photography
+ Photography
+ Multimedia
+ Multimedia
+ Photo
+ Photography / Sharing
+
+
+
+
+ Entertainment
+ Podcasts
+ Entertainment
+ Entertainment
+ Entertainment
+ Entertainment
+
+
+
+
+ Productivity
+ Productivity
+ Organizers
+ Organizers
+ Productivity
+ Productivity
+
+
+
+
+ Books & Reference
+ Books & Comic / Other
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications
+
+
+
+
+ Music & Audio
+ Music / Radio
+ Multimedia
+ Multimedia
+ Music/Video
+ Music / Radio
+
+
+
+
+ Business
+ Real Estate
+ Business & Finance
+ Business & Finance
+ Business
+ Finance / Other
+
+
+
+
+ Books & Reference
+ Reference
+ eBooks
+ eBooks
+ Education/E-Book
+ Publications
+
+
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Buddhism
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Chinese folk
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Christianity
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Hinduism
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Islam
+
+
+ Lifestyle
+ Lifestyle / Other
+ Entertainment
+ Entertainment
+ Lifestyle
+ Religion / Other
+
+
+
+
+ Personalization
+ Ringtones / Other
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Christian
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Classical
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Collegiate
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Comedy
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Country
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Dance & Electronic
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Jazz & Standards
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Latin
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Pop
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Rap
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Rock
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Sound Effects
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Soundtracks
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Sports
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / TV
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+ Personalization
+ Ringtones / Voicetones
+ Ringtones
+ Ringtones
+ Music/Video
+ Themes / Ringtones
+
+
+
+
+ Shopping
+ Shopping
+ Entertainment
+ Entertainment
+ Entertainment
+ Home & Hobby / Shopping
+
+
+
+
+ Social
+ Social Networking
+ Communication
+ Communication
+ Social Networking
+ Communication / Social Networking
+
+
+
+
+ Sports
+ Sports / Other
+ Entertainment
+ Entertainment
+ Sports
+ Sports
+
+
+ Sports
+ Sports / Other
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Athletic
+
+
+ Sports
+ Sports / Other
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Disabled
+
+
+ Sports
+ Sports / Other
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Extreme
+
+
+ Sports
+ Sports / Other
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Motor
+
+
+ Sports
+ Sports / Baseball
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Basketball
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Boxing
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Football
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Golf
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Hockey
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / NCAA
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Soccer
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / Tennis
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+ Sports
+ Sports / UFC
+ Entertainment
+ Entertainment
+ Sports
+ Sports / Other
+
+
+
+
+ Transportation
+ Travel / Transportation
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality / Other
+
+
+ Travel & Local
+ Travel / Other
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality
+
+
+ Travel & Local
+ Travel / Auto Rental
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality / Other
+
+
+ Travel & Local
+ Travel / Flight
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality / Other
+
+
+ Travel & Local
+ Travel / Hotel
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality / Other
+
+
+ Travel & Local
+ Travel / Trip Planner
+ Travel & Maps
+ Travel & Maps
+ Travel
+ Travel & Locality / Other
+
+
+
+
+ Libraries & Demo
+ Utilities / Other
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities
+
+
+ Libraries & Demo
+ Utilities / Other
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Developer – Programmer
+
+
+ Libraries & Demo
+ Utilities / Other
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Other
+
+
+ Libraries & Demo
+ Utilities / Other
+ Utilities
+ Utilities
+ Utilities
+ Tools & Utilities / Security
+
+
+
+
+ Media & Video
+ Entertainment
+ Entertainment
+ Entertainment
+ Music/Video
+ Entertainment / Film
+
+
+
+
+ Personalization
+ Themes
+ Themes & Skins
+ Themes & Skins
+ Theme
+ Themes / Wallpapers
+
+
+
+
+ Weather
+ News & Weather / Weather
+ Travel & Maps
+ Travel & Maps
+ Weather
+ News & Weather / Weather
+
+
+
+
+ Communication
+ Web Browsers
+ Communication
+ Communication
+ Social Networking
+ Tools & Utilities / Browsers
+
+
+
+
+
+
+ Brain & Puzzle
+ Games / Puzzles & Trivia
+ Games
+ Arcade
+ Game / Puzzle
+ Fun & Games / Puzzle
+
+
+
+
+ Brain & Puzzle
+ Games / Puzzles & Trivia
+ Games
+ Arcade
+ Game / Word/Trivia
+ Fun & Games / Other
+
+
+
+
+ Casual
+ Games / Other
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Other
+
+
+
+
+ Casual
+ Games / Other
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Drawing
+
+
+
+
+ Casual
+ Games / Casual
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Casual
+
+
+
+
+ Casual
+ Games / Educational
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Educational
+
+
+
+
+ Casual
+ Games / Kids
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Other
+
+
+
+
+ Casual
+ Games / Multiplayer
+ Games
+ Arcade
+ Game / Others
+ Fun & Games / Multiplayer
+
+
+
+
+ Casual
+ Games / Music
+ Games
+ Arcade
+ Game / Music
+ Fun & Games / Music
+
+
+
+
+ Arcade & Action
+ Games / Board
+ Games
+ Arcade
+ Game / Arcade
+ Fun & Games / Arcade
+
+
+
+
+ Arcade & Action
+ Games / Arcade
+ Games / Arcade
+ Arcade
+ Game / Board
+ Fun & Games / Board
+
+
+
+
+ Arcade & Action
+ Games / Action
+ Games / Action
+ Action
+ Game / Action
+ Fun & Games / Action
+
+
+
+
+ Arcade & Action
+ Games / Adventure
+ Games / Action
+ Action
+ Game / Adventure
+ Fun & Games / Adventure
+
+
+
+
+ Arcade & Action
+ Games / Role Playing
+ Games / Strategy
+ Strategy
+ Game / Role Playing
+ Fun & Games / Role Playing
+
+
+
+
+ Arcade & Action
+ Games / Strategy
+ Games / Strategy
+ Strategy
+ Game / Strategy
+ Fun & Games / Strategy
+
+
+
+
+ Cards & Casino
+ Games / Cards
+ Games / Cards
+ Cards
+ Game / Card/Casino
+ Fun & Games / Cards & Casino
+
+
+
+
+ Cards & Casino
+ Games / Casino
+ Games / Cards
+ Cards
+ Game / Card/Casino
+ Fun & Games / Cards & Casino
+
+
+
+
+ Racing
+ Games / Racing
+ Games / Sports
+ Sports
+ Game / Sports
+ Fun & Games / Racing
+
+
+
+
+ Sports Games
+ Games / Sports
+ Games / Sports
+ Sports
+ Game / Sports
+ Fun & Games / Sports
+
+
+
+
\ No newline at end of file
diff --git a/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/ImagesTag.java b/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/ImagesTag.java
index 92b9998a..f7bd15a5 100644
--- a/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/ImagesTag.java
+++ b/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/ImagesTag.java
@@ -16,6 +16,9 @@
package org.onepf.appdf.parser;
import org.onepf.appdf.model.AppIcon;
+import org.onepf.appdf.model.LargePromo;
+import org.onepf.appdf.model.SmallPromo;
+import org.onepf.appdf.model.Screenshot;
import org.onepf.appdf.model.Description;
import org.onepf.appdf.model.ImagesDescription;
import org.onepf.appdf.parser.util.XmlUtil;
@@ -28,7 +31,7 @@ public enum ImagesTag implements NodeParser {
private static final String WIDTH_ATTR = "width";
private static final String HEIGHT_ATTR = "height";
-
+
@Override
public void parse(Node node, Description element) {
Node widthAttr = node.getAttributes().getNamedItem(WIDTH_ATTR);
@@ -50,22 +53,73 @@ public void parse(Node node, Description element) {
}
},
+ SMALL_PROMO {
+
+ private static final String WIDTH_ATTR = "width";
+ private static final String HEIGHT_ATTR = "height";
+
+ @Override
+ public void parse(Node node, Description element)
+ throws ParsingException {
+ Node widthAttr = node.getAttributes().getNamedItem(WIDTH_ATTR);
+ Node heightAttr = node.getAttributes().getNamedItem(HEIGHT_ATTR);
+ if (widthAttr == null) {
+ throw new ParsingException("Required attribute width is missing");
+ }
+ if ( heightAttr == null ){
+ throw new ParsingException("Required attribute height is missing");
+ }
+ int width = Integer.parseInt(widthAttr.getTextContent());
+ int height = Integer.parseInt(heightAttr.getTextContent());
+ SmallPromo smallPromo = new SmallPromo();
+ smallPromo.setHeight(height);
+ smallPromo.setWidth(width);
+ smallPromo.setName(node.getTextContent());
+
+ ImagesDescription imagesDescription = getImagesDescription(element);
+ if (imagesDescription.getSmallPromo() != null) {
+ throw new ParsingException("Multiple small promos");
+ }
+ imagesDescription.setSmallPromo(smallPromo);
+ }
+ },
LARGE_PROMO {
+ private static final String WIDTH_ATTR = "width";
+ private static final String HEIGHT_ATTR = "height";
+
@Override
public void parse(Node node, Description element)
throws ParsingException {
- ImagesDescription imagesDescription = getImagesDescription(element);
+ Node widthAttr = node.getAttributes().getNamedItem(WIDTH_ATTR);
+ Node heightAttr = node.getAttributes().getNamedItem(HEIGHT_ATTR);
+ if (widthAttr == null) {
+ throw new ParsingException("Required attribute width is missing");
+ }
+ if ( heightAttr == null ){
+ throw new ParsingException("Required attribute height is missing");
+ }
+ int width = Integer.parseInt(widthAttr.getTextContent());
+ int height = Integer.parseInt(heightAttr.getTextContent());
+ LargePromo largePromo = new LargePromo();
+ largePromo.setHeight(height);
+ largePromo.setWidth(width);
+ largePromo.setName(node.getTextContent());
+
+ ImagesDescription imagesDescription = getImagesDescription(element);
if (imagesDescription.getLargePromo() != null) {
throw new ParsingException("Multiple large promos");
}
- imagesDescription.setLargePromo(node.getTextContent());
+ imagesDescription.setLargePromo(largePromo);
}
},
SCREENSHOTS {
private static final String SCREENSHOT_TAG = "screenshot";
+ private static final String WIDTH_ATTR = "width";
+ private static final String HEIGHT_ATTR = "height";
+ private static final String INDEX_ATTR = "index";
@Override
public void parse(Node node, Description element)
@@ -76,8 +130,30 @@ public void parse(Node node, Description element)
if ( !SCREENSHOT_TAG.equalsIgnoreCase(screenshotNode.getNodeName())){
throw new ParsingException("Unexpected tag inside screenshots:" + screenshotNode.getNodeName());
}
- String content = screenshotNode.getTextContent();
- imagesDescription.addScreenshot(content);
+
+ Node widthAttr = screenshotNode.getAttributes().getNamedItem(WIDTH_ATTR);
+ Node heightAttr = screenshotNode.getAttributes().getNamedItem(HEIGHT_ATTR);
+ Node indexAttr = screenshotNode.getAttributes().getNamedItem(INDEX_ATTR);
+
+ if (widthAttr == null) {
+ throw new ParsingException("Required attribute width is missing");
+ }
+ if ( heightAttr == null ){
+ throw new ParsingException("Required attribute height is missing");
+ }
+ if ( indexAttr == null ){
+ throw new ParsingException("Required attribute index is missing");
+ }
+
+ int width = Integer.parseInt(widthAttr.getTextContent());
+ int height = Integer.parseInt(heightAttr.getTextContent());
+ int index = Integer.parseInt(indexAttr.getTextContent());
+ Screenshot screenshot = new Screenshot();
+ screenshot.setHeight(height);
+ screenshot.setWidth(width);
+ screenshot.setIndex(index);
+ screenshot.setName(screenshotNode.getTextContent());
+ imagesDescription.addScreenshot(screenshot);
}
}
diff --git a/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/TopLevelTag.java b/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/TopLevelTag.java
index 0fe03422..bde25717 100644
--- a/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/TopLevelTag.java
+++ b/tools/parser-lib/parser/src/main/java/org/onepf/appdf/parser/TopLevelTag.java
@@ -28,7 +28,16 @@
public enum TopLevelTag implements NodeParser {
- CATEGORIZATION {
+ DEVELOPER {
+
+ @Override
+ public void parse(Node node, Application element)
+ throws ParsingException {
+ element.setDeveloperName(node.getTextContent());
+
+ }
+ },
+ CATEGORIZATION {
@Override
public void parse(Node node, Application application)
diff --git a/tools/parser-lib/parser/src/main/resources/org/onepf/appdf/parser/scheme.xsd b/tools/parser-lib/parser/src/main/resources/org/onepf/appdf/parser/scheme.xsd
new file mode 100644
index 00000000..4b7315e1
--- /dev/null
+++ b/tools/parser-lib/parser/src/main/resources/org/onepf/appdf/parser/scheme.xsd
@@ -0,0 +1,461 @@
+
+
+
+ AppDF description.xml schema
+ Project home: http://www.github.com/onepf/appdf
+ Author: Vassili Philippov
+ License: http://www.apache.org/licenses/LICENSE-2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/uploader/getimageinfo.py b/tools/uploader/getimageinfo.py
new file mode 100644
index 00000000..1109dbd5
--- /dev/null
+++ b/tools/uploader/getimageinfo.py
@@ -0,0 +1,61 @@
+import StringIO
+import struct
+
+def getImageInfo(data):
+ data = str(data)
+ size = len(data)
+ height = -1
+ width = -1
+ content_type = ''
+
+ # handle GIFs
+ if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
+ # Check to see if content_type is correct
+ content_type = 'image/gif'
+ w, h = struct.unpack("= 24) and data.startswith('\211PNG\r\n\032\n')
+ and (data[12:16] == 'IHDR')):
+ content_type = 'image/png'
+ w, h = struct.unpack(">LL", data[16:24])
+ width = int(w)
+ height = int(h)
+
+ # Maybe this is for an older PNG version.
+ elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
+ # Check to see if we have the right content type
+ content_type = 'image/png'
+ w, h = struct.unpack(">LL", data[8:16])
+ width = int(w)
+ height = int(h)
+
+ # handle JPEGs
+ elif (size >= 2) and data.startswith('\377\330'):
+ content_type = 'image/jpeg'
+ jpeg = StringIO.StringIO(data)
+ jpeg.read(2)
+ b = jpeg.read(1)
+ try:
+ while (b and ord(b) != 0xDA):
+ while (ord(b) != 0xFF): b = jpeg.read(1)
+ while (ord(b) == 0xFF): b = jpeg.read(1)
+ if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
+ jpeg.read(3)
+ h, w = struct.unpack(">HH", jpeg.read(4))
+ break
+ else:
+ jpeg.read(int(struct.unpack(">H", jpeg.read(2))[0])-2)
+ b = jpeg.read(1)
+ width = int(w)
+ height = int(h)
+ except struct.error:
+ pass
+ except ValueError:
+ pass
+
+ return content_type, width, height
diff --git a/tools/uploader/googleplay.py b/tools/uploader/googleplay.py
new file mode 100644
index 00000000..76430a22
--- /dev/null
+++ b/tools/uploader/googleplay.py
@@ -0,0 +1,595 @@
+import os
+import sys
+import argparse
+import zipfile
+import xml.etree.ElementTree as ET
+
+import codecs
+
+import json
+import re
+
+#from urllib import urlretrieve
+import urllib
+
+import time
+import dryscrape
+
+from getimageinfo import getImageInfo
+
+
+def parse_args():
+ argument_parser = argparse.ArgumentParser(description="AppDF publisher")
+
+ argument_parser.add_argument("file", metavar="FILE", help="AppDF file to store")
+ argument_parser.add_argument("--application", help="Application name")
+ argument_parser.add_argument("--username", help="Username")
+ #argument_parser.add_argument("--password", help="Password")
+ argument_parser.add_argument("--debug-dir",
+ help="Directory for browser screenshots")
+
+ return argument_parser.parse_args()
+
+
+def main():
+ args = parse_args()
+
+ args.password = raw_input("Password: ")
+
+ root = ET.Element("application-description-file")
+ root.set("version", "1")
+ root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
+ root.set("xsi:noNamespaceSchemaLocation", "../../../specification/appdf-description.xsd")
+
+ test = googleScrape(root, args.application, args.file, args.username, args.password)
+ root = test.scrape()
+ filelist = test.getFileList()
+
+ tree = ET.ElementTree(root)
+ tree.write("desc.xml", xml_declaration = True, method = "xml", encoding="utf-8")
+
+ zf = zipfile.PyZipFile(args.file, mode='w')
+ try:
+ zf.debug = 3
+ print 'Adding python files'
+ #zf.writepy('.')
+ zf.write("desc.xml", "description.xml")
+ for filename in filelist:
+ zf.write(filename)
+ finally:
+ zf.close()
+ #for name in zf.namelist():
+ # print name
+
+ for filename in filelist:
+ os.remove(filename)
+ os.remove("desc.xml")
+ print sys.path.insert(0, args.file)
+
+
+
+class googleScrape(object):
+ def __init__(self, xml, app, file, username, password):
+ self.xml = xml
+ self.app = app
+ self.file = file
+ self.username = username
+ self.password = password
+
+ self.filelist = []
+ self.locale = None
+
+
+ def scrape(self):
+ self.store_locale()
+ self.run()
+ self.restore_locale()
+ return self.xml
+
+
+ def run(self):
+ application = ET.SubElement(self.xml, "application")
+ application.set("platform", "android")
+
+ self.session = dryscrape.Session()
+ self.open_console()
+ self.login()
+
+ xpath = "//sidebar/nav/ul/li/a"
+ if self._ensure(xpath):
+ self._ensure(xpath).click()
+
+ self.open_app()
+ self.fillCategorization(application)
+ self.fillDescription(application)
+ self.fillContentDesctiption(application)
+ self.fillCustomerSupport(application)
+ self.fillPrices(application)
+ self.getAPK(application)
+
+ self._debug("scrape", "finish")
+
+ return self.xml
+
+ def getFileList(self):
+ return self.filelist
+
+
+ def _ensure(self, xpath):
+ return self.session.at_xpath(xpath, timeout=5)
+
+
+ def open_account(self):
+ self.session.visit("https://accounts.google.com/ServiceLogin")
+ self.session.wait()
+ self._debug("open_account", "opened")
+
+ def open_console(self):
+ self.session.visit("https://play.google.com/apps/publish/v2/")
+ self.session.wait()
+ self._debug("open_console", "opened")
+
+ def store_locale(self):
+ self.session = dryscrape.Session()
+ self.open_account()
+ self.login()
+ #store current language
+ xpath = "//body/div[4]/div[2]/div[1]/div[1]/div/div/div/div[1]/div/div[2]/div/div/div[3]/div[2]/div/div[1]/div[1]"
+ self._debug("store_local", "1")
+ if self.session.at_xpath(xpath):
+ self.locale = self.session.at_xpath(xpath).text()
+
+ xpath = "//body/div[4]/div[2]/div[1]/div[1]/div/div/div/div[1]/div/div[2]/div/div/div[3]/div[2]/div/div[1]/div[2]/a"
+ self.session.at_xpath(xpath).click()
+
+ xpath = "//div[@role=\"dialog\"]/div[2]/div/div/div/span[contains(text(), \"English (United States)\")]"
+ self.session.at_xpath(xpath).click()
+ else:
+ self.store_locale()
+
+ def restore_locale(self):
+ self.session = dryscrape.Session()
+ self.open_account()
+ self.login()
+ #restore previous language
+ xpath = "//body/div[4]/div[2]/div[1]/div[1]/div/div/div/div[1]/div/div[2]/div/div/div[3]/div[2]/div/div[1]/div[2]/a"
+ self.session.at_xpath(xpath).click()
+
+ xpath = "//div[@role=\"dialog\"]/div[2]/div/div/div[count(span[contains(text(), \"{}\")])=1]".format(self.locale)
+ self.session.at_xpath(xpath).click()
+ #if self.session.at_xpath(xpath):
+ #self.session.at_xpath(xpath).click()
+
+
+ def login(self):
+ login_url = "https://accounts.google.com/ServiceLogin"
+
+ if self.session.url().startswith(login_url):
+ email_field = self.session.at_css("#Email")
+ password_field = self.session.at_css("#Passwd")
+ email_field.set(self.username)
+ password_field.set(self.password)
+ self._debug("login", "filled")
+
+ email_field.form().submit()
+ self._debug("login", "submited")
+
+ self.session.wait()
+ xpath = "//span[@role=\"alert\" and @class=\"errormsg\"]"
+ if self.session.at_xpath(xpath):
+ print "Login error"
+ self._quit()
+ else:
+ print "Login error"
+ self._quit()
+
+
+ def open_app(self):
+ xpath = "//p[@data-column='TITLE']/span[contains(text(), '{}')]"
+ xpath = xpath.format(self.app)
+
+ if self._ensure(xpath):
+ self._ensure(xpath).click()
+ else:
+ print "Error. Application does not exist."
+ self._quit()
+
+ self._debug("open_app", "opened")
+
+
+ def fillCategorization(self, application):
+ self._debug("categorization", "start")
+
+ categorization = ET.SubElement(application, "categorization")
+
+ xpath = "//fieldset/label/div/select[@class=\"gwt-ListBox\"]"
+ type = ET.SubElement(categorization, "type")
+ type.text = self.session.at_xpath(xpath).value().lower()
+
+ xpath = "//fieldset/label/div/div/select[@class=\"gwt-ListBox\"]"
+ category = ET.SubElement(categorization, "category")
+ categoryValue = self.session.at_xpath(xpath).value().lower()
+ categoryValue = re.sub("_and_", " & ", categoryValue)
+ categoryValue = re.sub("_", " ", categoryValue)
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ categories_file = os.path.join(current_dir, "spec",
+ "store_categories.json")
+ with open(categories_file, "r") as fp:
+ categories = json.load(fp)
+ for cat in categories[type.text]:
+ for subcat in categories[type.text][cat]:
+ text = categories[type.text][cat][subcat]["google"].lower()
+ text = re.sub("(\s*/\s*)", "/", text)
+ text = re.sub("^(\s*)", "", text)
+ text = re.sub("(\s*)$", "", text)
+ if text == categoryValue:
+ category.text = cat
+ break
+
+ self._debug("categorization", "finish")
+
+
+ def fillDescription(self, application):
+ self._debug("description", "start")
+
+ description = ET.SubElement(application, "description")
+ texts = ET.SubElement(description, "texts")
+
+ xpath = "//section/div[4]/div[3]/fieldset/label/div/div/input[@class=\"gwt-TextBox\"]"
+ privacyPolicyValue = self.session.at_xpath(xpath).value()
+ if privacyPolicyValue != "":
+ privacyPolicy = ET.SubElement(texts, "privacy-policy")
+ privacyPolicy.set("href", privacyPolicyValue)
+
+ self.fillLanguage(description, texts, "en_us")
+
+ xpath = "//section/div[3]/div[2]/div[1]/div[1]/div[1]/div[1]"
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ languages_file = os.path.join(current_dir, "spec",
+ "languages.json")
+ with open(languages_file, "r") as fp:
+ languages = json.load(fp)
+
+ i = 2
+ while self.session.at_xpath(xpath+"/button[" + str(i) + "]"):
+ lang = self.session.at_xpath(xpath+"/button[" + str(i) + "]/div/span").text().lower()
+ lang_lng = re.sub("-", "_", lang[4:])
+ lang_shrt = re.sub("-", "_", lang[4:6])
+ if lang_lng in languages:
+ self._ensure(xpath+"/button[" + str(i) + "]").click()
+ description = ET.SubElement(application, "description-localization")
+ description.set("language", lang_lng)
+ texts = ET.SubElement(description, "texts")
+ self.fillLanguage(description, texts, lang_lng)
+ elif lang_shrt in languages:
+ self._ensure(xpath+"/button[" + str(i) + "]").click()
+ description = ET.SubElement(application, "description-localization")
+ description.set("language", lang_shrt)
+ texts = ET.SubElement(description, "texts")
+ self.fillLanguage(description, texts, lang_shrt)
+ i += 1
+
+ self._debug("description", "finish")
+
+ def fillLanguage(self, description, texts, lang=None):
+ #self._debug("fillLanguage", lang)
+ #texts
+ xpath = "//section/div[3]/div[2]/div[3]/div[1]/fieldset/label[1]/div/div/div/input"
+ if self.session.at_xpath(xpath).value() != "":
+ title = ET.SubElement(texts, "title")
+ value = self.session.at_xpath(xpath).value()
+ title.text = value.decode("utf-8")
+
+
+ xpath = "//section/div[3]/div[2]/div[3]/div[1]/fieldset/label[2]/div/div/div/div/textarea"
+ if self.session.at_xpath(xpath).value() != "":
+ fullDesc = ET.SubElement(texts, "full-description")
+ value = self.session.at_xpath(xpath).value()
+ fullDesc.text = value.decode("utf-8")
+
+ xpath = "//section/div[3]/div[2]/div[3]/div[1]/fieldset/label[3]/div/div/div/textarea"
+ if self.session.at_xpath(xpath).value() != "":
+ shortDesc = ET.SubElement(texts, "short-description")
+ value = self.session.at_xpath(xpath).value()
+ shortDesc.text = value.decode("utf-8")
+
+ xpath = "//section/div[3]/div[2]/div[3]/div[1]/fieldset/label[4]/div/div/div/div/textarea"
+ if self.session.at_xpath(xpath).value() != "":
+ recentChanges = ET.SubElement(texts, "recent-changes")
+ value = self.session.at_xpath(xpath).value()
+ recentChanges.text = value.decode("utf-8")
+
+ #images
+ images = None
+
+ #app-icon
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[1]/div[2]/div[1]/div[1]/div[3]/@style"
+ if self.session.at_xpath(xpath).value() == "":
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[1]/div[2]/div[1]/div[1]/div[3]/img/@src"
+ imgSrc = self.session.at_xpath(xpath).value()
+ filename = "appIcon_" + lang + "."
+ width, height, filename = self.storeRes(imgSrc, filename, True)
+
+ if images == None:
+ images = ET.SubElement(description, "images")
+
+ appIcon = ET.SubElement(images, "app-icon")
+ appIcon.text = filename
+ appIcon.set("width", str(width))
+ appIcon.set("height", str(height))
+
+ #large-promo
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[2]/div[2]/div[3]/@style"
+ if self.session.at_xpath(xpath).value() == "":
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[2]/div[2]/div[3]/img/@src"
+ imgSrc = self.session.at_xpath(xpath).value()
+ filename = "largePromo_" + lang + "."
+ width, height, filename = self.storeRes(imgSrc, filename, True)
+
+ if images == None:
+ images = ET.SubElement(description, "images")
+
+ largePromo = ET.SubElement(images, "large-promo")
+ largePromo.text = filename
+ largePromo.set("width", str(width))
+ largePromo.set("height", str(height))
+
+ #small-promo
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[3]/div[2]/div[3]/@style"
+ if self.session.at_xpath(xpath).value() == "":
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[2]/div[3]/div[2]/div[3]/img/@src"
+ imgSrc = self.session.at_xpath(xpath).value()
+ filename = "smallPromo_" + lang + "."
+ width, height, filename = self.storeRes(imgSrc, filename, True)
+
+ if images == None:
+ images = ET.SubElement(description, "images")
+
+ smallPromo = ET.SubElement(images, "small-promo")
+ smallPromo.text = filename
+ smallPromo.set("width", str(width))
+ smallPromo.set("height", str(height))
+
+
+ #screenshots
+ screenshots = None
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[2]/@style"
+ if self.session.at_xpath(xpath).value() != "":
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[1]/div[2][count(div)>1]"
+ if self.session.at_xpath(xpath):
+ if images == None:
+ images = ET.SubElement(description, "images")
+ if screenshots == None:
+ screenshots = ET.SubElement(images, "screenshots")
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[1]/div[2]"
+ self.getScreenshots(screenshots, xpath, "phone", lang)
+
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[2]/div[2][count(div)>1]"
+ if self.session.at_xpath(xpath):
+ if images == None:
+ images = ET.SubElement(description, "images")
+ if screenshots == None:
+ screenshots = ET.SubElement(images, "screenshots")
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[2]/div[2]"
+ self.getScreenshots(screenshots, xpath, "tablet7", lang)
+
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[3]/div[2][count(div)>1]"
+ if self.session.at_xpath(xpath):
+ if images == None:
+ images = ET.SubElement(description, "images")
+ if screenshots == None:
+ screenshots = ET.SubElement(images, "screenshots")
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/div[1]/div[1]/div[2]/div[1]/div[1]/div[3]/div[2]"
+ self.getScreenshots(screenshots, xpath, "tablet10", lang)
+
+
+ #videos
+ xpath = "//section/div[3]/div[2]/div[3]/div[2]/fieldset/label/div[2]/div/input"
+ if self.session.at_xpath(xpath).value() != "":
+ videos = ET.SubElement(description, "videos")
+ youtubeVideo = ET.SubElement(videos, "youtube-video")
+ value = self.session.at_xpath(xpath).value()
+ youtubeVideo.text = value.decode("utf-8")
+
+ def getScreenshots(self, screenshots, xpath, name, lang):
+ i = 1
+ while self.session.at_xpath(xpath+"/div[" + str(i) + "]/div[1][contains(@style,'display: none;')]"):
+ imageXPath = xpath + "/div[" + str(i) + "]/div[3]/img/@src"
+ imgSrc = self.session.at_xpath(imageXPath).value()
+ filename = name + "_screenshot_" + lang + "_" + str(i) + "."
+ width, height, filename = self.storeRes(imgSrc, filename, True)
+
+ screenshot = ET.SubElement(screenshots, "screenshot")
+ screenshot.text = filename
+ screenshot.set("width", str(width))
+ screenshot.set("height", str(height))
+ screenshot.set("index", str(i))
+ i+=1
+
+
+ def fillContentDesctiption(self, application):
+ self._debug("content_desctiption", "start")
+
+ content = ET.SubElement(application, "content-description")
+
+ xpath = "//fieldset/label/div/div/div/select[@class=\"gwt-ListBox\"]"
+ rateValue = self.session.at_xpath(xpath).value()
+ rating = ET.SubElement(content, "content-rating")
+ rating.text = {
+ "SUITABLE_FOR_ALL" : "3",
+ "PRE_TEEN" : "6",
+ "TEEN" : "13",
+ "MATURE" : "18"
+ }[rateValue]
+
+ self._debug("content_desctiption", "finish")
+
+
+ def fillCustomerSupport(self, application):
+ self._debug("customer_support", "start")
+
+ custom = ET.SubElement(application, "customer-support")
+
+ xpath = "//section/div[4]/div[2]/fieldset/label[1]/div/div/input"
+ website = ET.SubElement(custom, "website")
+ website.text = self.session.at_xpath(xpath).value()
+
+ xpath = "//section/div[4]/div[2]/fieldset/label[2]/div/div/input"
+ email = ET.SubElement(custom, "email")
+ email.text = self.session.at_xpath(xpath).value()
+
+ xpath = "//section/div[4]/div[2]/fieldset/label[3]/div/div/input"
+ phone = ET.SubElement(custom, "phone")
+ phone.text = self.session.at_xpath(xpath).value()
+
+ self._debug("customer_support", "finish")
+
+
+ def fillPrices(self, application):
+ xpath = "//sidebar/nav/ol[2]/li[3]/a"
+ self.session.at_xpath(xpath).click()
+
+ consent = ET.SubElement(application, "consent")
+ contentGuide = ET.SubElement(consent, "google-android-content-guidelines")
+ xpath = "//section/div[2]/div[5]/fieldset/label[2]/div[2]/div/div/span/input[@type=\"checkbox\" and @value=\"on\"]"
+ if self.session.at_xpath(xpath):
+ contentGuide.text = "yes"
+ else:
+ contentGuide.text = "no"
+
+ USExportLaws = ET.SubElement(consent, "us-export-laws")
+ xpath = "//section/div[2]/div[5]/fieldset/label[3]/div[2]/div/span/input[@type=\"checkbox\" and @value=\"on\"]"
+ if self.session.at_xpath(xpath):
+ USExportLaws.text = "yes"
+ else:
+ USExportLaws.text = "no"
+
+ countries = None
+ price = ET.SubElement(application, "price")
+ xpath = "//section/div[2]/div[2]/fieldset/label[1]/div[2]/div[1]/div[1]/div/button[1][@aria-pressed=\"true\"]"
+ if self.session.at_xpath(xpath):
+ price.set("free", "no")
+
+ xpath = "//section/div[2]/div[2]/fieldset/label[2]/div[2]/div/div[1]/div/label/input"
+ if self.session.at_xpath(xpath).value()!="":
+ basePrice = ET.SubElement(price, "base-price")
+ basePrice.text = self.session.at_xpath(xpath).value()
+
+ xpath = "//section/div[2]/div[3]/div/div[1]/div/div[3]/div/div[2]/div/div/table/tbody/tr"
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ countries_file = os.path.join(current_dir, "spec",
+ "google_countries.json")
+ with open(countries_file, "r") as fp:
+ countries_list = json.load(fp)
+ for country in countries_list:
+ xpath = "//td[div/label[@data-country-checkbox][contains(text(), \"{}\")] \
+ [count(input[@type=\"checkbox\"][@checked])>0]]/following-sibling::td/div/label/input"
+ country_name = countries_list[country].encode("utf-8")
+ if self.session.at_xpath(xpath.format(country_name)):
+ value = self.session.at_xpath(xpath.format(country_name)).value()
+ if value != "":
+ localPrice = ET.SubElement(price, "local-price")
+ localPrice.set("country", country)
+ localPrice.text = value
+ else:
+ price.set("free", "yes")
+
+
+ xpath = "//section/div[2]/div[3]/div/div[1]/div/div[3]/div/div[2]/div/div/table/tbody/tr"
+
+ current_dir = os.path.dirname(os.path.realpath(__file__))
+ countries_file = os.path.join(current_dir, "spec",
+ "google_countries.json")
+ with open(countries_file, "r") as fp:
+ countries_list = json.load(fp)
+ for country in countries_list:
+ xpath = "//label[@data-country-checkbox][contains(text(), \"{}\")][count(input[@type=\"checkbox\"][@checked])>0]"
+ country_name = countries_list[country].encode("utf-8")
+ if self.session.at_xpath(xpath.format(country_name)):
+ if countries == None:
+ availability = ET.SubElement(application, "availability")
+ countries = ET.SubElement(availability, "countries")
+ countries.set("only-listed", "yes")
+
+ include = ET.SubElement(countries, "include")
+ include.text = country
+
+
+ def getAPK(self, application):
+ xpath = "//sidebar/nav/ol[2]/li[1]/a"
+ self.session.at_xpath(xpath).click()
+
+ xpath = "//section/div[2]/div[1]/div[1]/div[2]/div[3]/table/tbody[1]/tr[1]/td"
+ if self.session.at_xpath(xpath):
+ self._ensure(xpath).click()
+
+ xpath = "//div[@class=\"gwt-PopupPanel\"]/div[@class=\"popupContent\"]/div[1]/div[1]/div[1]"
+ package = self.session.at_xpath(xpath).text()
+ application.set("package", package)
+
+ self._debug("package", "finish")
+
+ xpath = "//div[@class=\"gwt-PopupPanel\"]/div[@class=\"popupContent\"]/div/nav/button"
+ if self.session.at_xpath(xpath):
+ self.session.at_xpath(xpath).click()
+
+ xpath = "//div[@role=\"tabpanel\"][1]/div[3]/div[1]/div[3]/div/div[2]/a"
+ if self.session.at_xpath(xpath):
+ self.session.at_xpath(xpath).click()
+
+ i = 1
+ xpath = "//div[@class=\"gwt-PopupPanel\"]/div[@class=\"popupContent\"]/div/div/ol//"
+ if self.session.at_xpath(xpath+"li[@data-device-id][count(span[@aria-checked=\"true\"])][1]"):
+ requirements = ET.SubElement(application, "requirements")
+ supportedDevices = ET.SubElement(requirements, "supported-devices")
+
+ while self.session.at_xpath(xpath+"li[@data-device-id][count(span[@aria-checked=\"true\"])]["+str(i)+"]"):
+ value = self.session.at_xpath(xpath+"li[@data-device-id][count(span[@aria-checked=\"true\"])]["+ \
+ str(i)+"]/@data-device-id").value()
+ exclude = ET.SubElement(supportedDevices, "exclude")
+ exclude.text = value
+ i+=1
+
+ self._debug("supportedDevices", "finish")
+
+ xpath = "//div[@class=\"gwt-PopupPanel\"]/div[@class=\"popupContent\"]/div/footer/button[2]"
+ if self.session.at_xpath(xpath):
+ self.session.at_xpath(xpath).click()
+
+
+
+ def storeRes(self, src, filename, image=None):
+ src = re.sub("-rw$", "", src)
+ src = re.sub("\d*$", "9999", src)
+
+ data = urllib.urlopen(src).read()
+ #check image size/type
+ if image != None:
+ type, width, height = getImageInfo(data)
+ type = re.sub("image/", "", type)
+ filename += type
+ f = open(filename, "wb")
+ f.write(data)
+ f.close()
+
+ self.filelist.append(filename)
+ if image != None:
+ return (width, height, filename)
+
+ def _debug(self, action, state):
+ print action + " : " + state
+ file_name = "{}-{}-{}.png".format(time.time(), action, state)
+ self.session.render(file_name)
+
+ def _quit(self):
+ if self.locale != None:
+ self.restore_locale()
+ sys.exit(1)
+
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/tools/uploader/spec/google_countries.json b/tools/uploader/spec/google_countries.json
new file mode 100644
index 00000000..3aaa4161
--- /dev/null
+++ b/tools/uploader/spec/google_countries.json
@@ -0,0 +1,140 @@
+{
+ "AL" : "Albania",
+ "DZ" : "Algeria",
+ "AO" : "Angola",
+ "AI" : "Anguilla",
+ "AG" : "Antigua and Barbuda",
+ "AR" : "Argentina",
+ "AM" : "Armenia",
+ "AW" : "Aruba",
+ "AU" : "Australia",
+ "AT" : "Austria",
+ "AZ" : "Azerbaijan",
+ "BS" : "Bahamas",
+ "BH" : "Bahrain",
+ "BD" : "Bangladesh",
+ "BY" : "Belarus",
+ "BE" : "Belgium",
+ "BZ" : "Belize",
+ "BJ" : "Benin",
+ "BO" : "Bolivia",
+ "BA" : "Bosnia and Herzegovina",
+ "BR" : "Brazil",
+ "BG" : "Bulgaria",
+ "BF" : "Burkina Faso",
+ "KH" : "Cambodia",
+ "CM" : "Cameroon",
+ "CA" : "Canada",
+ "CV" : "Cape Verde",
+ "CL" : "Chile",
+ "CN" : "China",
+ "CO" : "Colombia",
+ "CR" : "Costa Rica",
+ "CI" : "Côte d’Ivoire",
+ "HR" : "Croatia",
+ "CY" : "Cyprus",
+ "CZ" : "Czech Republic",
+ "DK" : "Denmark",
+ "DO" : "Dominican Republic",
+ "EC" : "Ecuador",
+ "EG" : "Egypt",
+ "SV" : "El Salvador",
+ "EE" : "Estonia",
+ "FJ" : "Fiji",
+ "FI" : "Finland",
+ "FR" : "France",
+ "GA" : "Gabon",
+ "DE" : "Germany",
+ "GH" : "Ghana",
+ "GR" : "Greece",
+ "GT" : "Guatemala",
+ "GW" : "Guinea-Bissau",
+ "HT" : "Haiti",
+ "HN" : "Honduras",
+ "HK" : "Hong Kong",
+ "HU" : "Hungary",
+ "IS" : "Iceland",
+ "IN" : "India",
+ "ID" : "Indonesia",
+ "IR" : "Iran",
+ "IE" : "Ireland",
+ "IL" : "Israel",
+ "IT" : "Italy",
+ "JM" : "Jamaica",
+ "JP" : "Japan",
+ "JO" : "Jordan",
+ "KZ" : "Kazakhstan",
+ "KE" : "Kenya",
+ "KW" : "Kuwait",
+ "KG" : "Kyrgyzstan",
+ "LA" : "Laos",
+ "LV" : "Latvia",
+ "LB" : "Lebanon",
+ "LT" : "Lithuania",
+ "LU" : "Luxembourg",
+ "MK" : "Macedonia [FYROM]",
+ "MY" : "Malaysia",
+ "ML" : "Mali",
+ "MT" : "Malta",
+ "MU" : "Mauritius",
+ "MX" : "Mexico",
+ "MD" : "Moldova",
+ "MA" : "Morocco",
+ "MZ" : "Mozambique",
+ "MM" : "Myanmar [Burma]",
+ "NA" : "Namibia",
+ "NP" : "Nepal",
+ "NL" : "Netherlands",
+ "AN" : "Netherlands Antilles",
+ "NZ" : "New Zealand",
+ "NI" : "Nicaragua",
+ "NE" : "Niger",
+ "NG" : "Nigeria",
+ "NO" : "Norway",
+ "OM" : "Oman",
+ "PK" : "Pakistan",
+ "PA" : "Panama",
+ "PG" : "Papua New Guinea",
+ "PY" : "Paraguay",
+ "PE" : "Peru",
+ "PH" : "Philippines",
+ "PL" : "Poland",
+ "PT" : "Portugal",
+ "QA" : "Qatar",
+ "RO" : "Romania",
+ "RU" : "Russia",
+ "RW" : "Rwanda",
+ "SA" : "Saudi Arabia",
+ "SN" : "Senegal",
+ "RS" : "Serbia",
+ "SG" : "Singapore",
+ "SK" : "Slovakia",
+ "SI" : "Slovenia",
+ "ZA" : "South Africa",
+ "KR" : "South Korea",
+ "ES" : "Spain",
+ "LK" : "Sri Lanka",
+ "SE" : "Sweden",
+ "CH" : "Switzerland",
+ "TW" : "Taiwan",
+ "TJ" : "Tajikistan",
+ "TZ" : "Tanzania",
+ "TH" : "Thailand",
+ "TG" : "Togo",
+ "TT" : "Trinidad and Tobago",
+ "TN" : "Tunisia",
+ "TR" : "Turkey",
+ "TM" : "Turkmenistan",
+ "UG" : "Uganda",
+ "UA" : "Ukraine",
+ "AE" : "United Arab Emirates",
+ "GB" : "United Kingdom",
+ "US" : "United States",
+ "UY" : "Uruguay",
+ "UZ" : "Uzbekistan",
+ "VE" : "Venezuela",
+ "VN" : "Vietnam",
+ "YE" : "Yemen",
+ "ZM" : "Zambia",
+ "ZW" : "Zimbabwe"
+}
\ No newline at end of file
diff --git a/tools/uploader/spec/languages.json b/tools/uploader/spec/languages.json
new file mode 100644
index 00000000..1f61f3d1
--- /dev/null
+++ b/tools/uploader/spec/languages.json
@@ -0,0 +1,214 @@
+{
+ "ab" : "Abkhaz",
+ "aa" : "Afar",
+ "af" : "Afrikaans",
+ "ak" : "Akan",
+ "sq" : "Albanian",
+ "am" : "Amharic",
+ "ar" : "Arabic",
+ "ar_eg" : "Arabic (Egypt)",
+ "ar_il" : "Arabic (Israel)",
+ "an" : "Aragonese",
+ "hy" : "Armenian",
+ "as" : "Assamese",
+ "av" : "Avaric",
+ "ae" : "Avestan",
+ "ay" : "Aymara",
+ "az" : "Azerbaijani",
+ "bm" : "Bambara",
+ "ba" : "Bashkir",
+ "eu" : "Basque",
+ "be" : "Belarusian",
+ "bn" : "Bengali",
+ "bh" : "Bihari",
+ "bi" : "Bislama",
+ "bs" : "Bosnian",
+ "br" : "Breton",
+ "bg" : "Bulgarian",
+ "my" : "Burmese",
+ "ca" : "Catalan",
+ "ch" : "Chamorro",
+ "ce" : "Chechen",
+ "ny" : "Chichewa",
+ "zh" : "Chinese",
+ "zh_cn" : "Chinese (PRC)",
+ "zh_tw" : "Chinese (Taiwan)",
+ "zh_hk" : "Chinese (HK)",
+ "cv" : "Chuvash",
+ "kw" : "Cornish",
+ "co" : "Corsican",
+ "cr" : "Cree",
+ "hr" : "Croatian",
+ "cs" : "Czech",
+ "da" : "Danish",
+ "dv" : "Divehi",
+ "nl" : "Dutch",
+ "nl_be" : "Dutch (Belgium)",
+ "nl_nl" : "Dutch (Netherlands)",
+ "dz" : "Dzongkha",
+ "en" : "English",
+ "en_us" : "English (US)",
+ "en_au" : "English (Australia)",
+ "en_gb" : "English (Britain)",
+ "en_ca" : "English (Canada)",
+ "en_nz" : "English (New Zealand)",
+ "en_sg" : "English (Singapore)",
+ "en_cd" : "English (Canada)",
+ "eo" : "Esperanto",
+ "et" : "Estonian",
+ "ee" : "Ewe",
+ "fo" : "Faroese",
+ "fj" : "Fijian",
+ "fi" : "Finnish",
+ "fr" : "French",
+ "fr_be" : "French (Belgium)",
+ "fr_ca" : "French (Canada)",
+ "fr_fr" : "French (France)",
+ "fr_ch" : "French (Switzerland)",
+ "ff" : "Fula",
+ "gl" : "Galician",
+ "ka" : "Georgian",
+ "de" : "German",
+ "de_at" : "German (Austria)",
+ "de_de" : "German (Germany)",
+ "de_li" : "German (Liechtenstein)",
+ "de_ch" : "German (Switzerland)",
+ "el" : "Greek",
+ "gn" : "Guaraní",
+ "gu" : "Gujarati",
+ "ht" : "Haitian",
+ "ha" : "Hausa",
+ "he" : "Hebrew",
+ "hz" : "Herero",
+ "hi" : "Hindi",
+ "ho" : "Hiri Motu",
+ "hu" : "Hungarian",
+ "ia" : "Interlingua",
+ "id" : "Indonesian",
+ "ie" : "Interlingue",
+ "ga" : "Irish",
+ "ig" : "Igbo",
+ "ik" : "Inupiaq",
+ "io" : "Ido",
+ "is" : "Icelandic",
+ "it" : "Italian",
+ "it_it" : "Italian (Italy)",
+ "it_ch" : "Italian (Switzerland)",
+ "iu" : "Inuktitut",
+ "ja" : "Japanese",
+ "jv" : "Javanese",
+ "kl" : "Kalaallisut",
+ "kn" : "Kannada",
+ "kr" : "Kanuri",
+ "ks" : "Kashmiri",
+ "kk" : "Kazakh",
+ "km" : "Khmer",
+ "ki" : "Kikuyu",
+ "rw" : "Kinyarwanda",
+ "ky" : "Kyrgyz",
+ "kv" : "Komi",
+ "kg" : "Kongo",
+ "ko" : "Korean",
+ "ku" : "Kurdish",
+ "kj" : "Kwanyama",
+ "la" : "Latin",
+ "lb" : "Luxembourgish",
+ "lg" : "Ganda",
+ "li" : "Limburgish",
+ "ln" : "Lingala",
+ "lo" : "Lao",
+ "lt" : "Lithuanian",
+ "lu" : "Luba-Katanga",
+ "lv" : "Latvian",
+ "gv" : "Manx",
+ "mk" : "Macedonian",
+ "mg" : "Malagasy",
+ "ms" : "Malay",
+ "ml" : "Malayalam",
+ "mt" : "Maltese",
+ "mi" : "Maori",
+ "mr" : "Marathi",
+ "mh" : "Marshallese",
+ "mn" : "Mongolian",
+ "na" : "Nauru",
+ "nv" : "Navajo",
+ "nb" : "Norwegian Bokmål",
+ "nd" : "North Ndebele",
+ "ne" : "Nepali",
+ "ng" : "Ndonga",
+ "nn" : "Norwegian Nynorsk",
+ "no" : "Norwegian",
+ "ii" : "Nuosu",
+ "nr" : "South Ndebele",
+ "oc" : "Occitan",
+ "oj" : "Ojibwe",
+ "om" : "Oromo",
+ "or" : "Oriya",
+ "os" : "Ossetian",
+ "pa" : "Panjabi",
+ "pi" : "Pāli",
+ "fa" : "Persian",
+ "pl" : "Polish",
+ "ps" : "Pashto",
+ "pt" : "Portuguese",
+ "pt_br" : "Portuguese (Brazil)",
+ "pt_pt" : "Portuguese (Portugal)",
+ "qu" : "Quechua",
+ "rm" : "Romansh",
+ "rn" : "Kirundi",
+ "ro" : "Romanian",
+ "ru" : "Russian",
+ "sa" : "Sanskrit",
+ "sc" : "Sardinian",
+ "sd" : "Sindhi",
+ "se" : "Northern Sami",
+ "sm" : "Samoan",
+ "sg" : "Sango",
+ "sr" : "Serbian",
+ "gd" : "Gaelic",
+ "sn" : "Shona",
+ "si" : "Sinhala",
+ "sk" : "Slovak",
+ "sl" : "Slovene",
+ "so" : "Somali",
+ "st" : "Southern Sotho",
+ "es" : "Spanish",
+ "es_es" : "Spanish (Spain)",
+ "es_us" : "Spanish (US)",
+ "es_la" : "Spanish (Latin America)",
+ "su" : "Sundanese",
+ "sw" : "Swahili",
+ "ss" : "Swati",
+ "sv" : "Swedish",
+ "ta" : "Tamil",
+ "te" : "Telugu",
+ "tg" : "Tajik",
+ "th" : "Thai",
+ "ti" : "Tigrinya",
+ "bo" : "Tibetan Standard",
+ "tk" : "Turkmen",
+ "tl" : "Tagalog",
+ "tn" : "Tswana",
+ "to" : "Tonga",
+ "tr" : "Turkish",
+ "ts" : "Tsonga",
+ "tt" : "Tatar",
+ "tw" : "Twi",
+ "ty" : "Tahitian",
+ "ug" : "Uighur",
+ "uk" : "Ukrainian",
+ "ur" : "Urdu",
+ "uz" : "Uzbek",
+ "ve" : "Venda",
+ "vi" : "Vietnamese",
+ "vo" : "Volapük",
+ "wa" : "Walloon",
+ "cy" : "Welsh",
+ "wo" : "Wolof",
+ "fy" : "Western Frisian",
+ "xh" : "Xhosa",
+ "yi" : "Yiddish",
+ "yo" : "Yoruba",
+ "za" : "Zhuang",
+ "zu" : "Zulu"
+}
\ No newline at end of file