hkopendata
Version:
Access different Opendata API and data in Hong Kong
219 lines (206 loc) • 7.5 kB
JavaScript
// https://www.legco.gov.hk/datagovhk/data-dictionary-bills-db-en.pdf
const cmn = require("../../common");
const moment = require("../../moment");
const BASE_URL = "https://app.legco.gov.hk/BillsDB/odata/Vbills";
const VALID = {};
const VALID_OPT = {
$format: /^(json|xml)$/,
$top: /^[0-9]+$/,
$skip: /^[0-9]+$/,
$orderby: /^.+$/,
$filter: /^.+$/,
$inlinecount: /^(allpages)$/,
sortorder: /^(asc|desc)$/,
};
const PARAMS = {
$format: "json",
$top: 20,
}
const FIELDS = {
regex: {
"title": "name",
"bill_content_url": "bill_gazette_website",
"content_url": "website",
"hansard_url": "hansard",
"url": "website",
},
text: {
"internal_key": "id",
}
}
const SEARCH_CONFIG = {
rename: {
limit: "$top",
offset: "$skip",
sortby: "$orderby"
}
}
const KEY_TO_NAME = {
id: "internal_key",
ordinance: "ordinance_title",
bill: "bill_title",
date: "ordinance_gazette_date",
}
function validateParameters(params) {
params = cmn.ParseSearchFields(params, SEARCH_CONFIG);
let result = cmn.ValidateParameters(params, VALID, VALID_OPT),
substr = (name, val, lang) => {
return `substringof('${val}',${(KEY_TO_NAME[name] || name)}${lang == "tc" ? "_chi" : lang == "en" ? "_eng" : ""})`
};
if (params.$orderby && !/(asc|desc)/.test(params.$orderby)) {
let sortorder = params.sortorder || "asc";
let name = KEY_TO_NAME[params.$orderby] || params.$orderby;
params.$orderby = `${name} ${sortorder}`;
}
if (!params.$filter && params.filter) {
let filter = [];
for (let key in params.filter) {
if (key == "id") {
filter.push(substr(key, params.filter[key]));
} else if (/^(ordinance|bill)$/.test(key)) {
filter.push(`(${substr(key, params.filter[key], "tc")} or ${substr(key, params.filter[key], "en")})`);
} else if (/^(from|to)^/.test(key)) {
filter.push(`${KEY_TO_NAME.date} ${key == "from" ? "ge" : "le"} datetime'${params.filter[key]}'`);
}
}
if (filter.length > 0) params.$filter = filter.join(" and ");
}
["filter", "sortorder"].map(v => delete params[v]);
if (!result.error) {
result.data = {
...params
}
}
return result;
}
function search(data, opts) {
return new Promise((resolve, reject) => {
let processed = validateParameters({
...PARAMS,
...data
});
if (processed.error) {
reject(processed);
} else {
params = processed.data;
cmn.APIRequest(cmn.ReplaceURL(BASE_URL, processed.data), params)
.then((res) => {
resolve(processData(res));
})
.catch((err) => reject(err))
}
})
}
function processData(data) {
let result = [],
arr = [],
handle = (item) => {
let keys = Object.keys(item);
if (typeof item === "object" && keys.filter(v => /^\d+$/.test(v)).length > 0) {
return keys.sort().map(v => item[v]);
}
return item;
};
data.value.map(item => {
let temp = {};
item = cmn.RenameFields(item, FIELDS);
for (let key in item) {
let val = item[key],
key2 = key.replace("ordinace", "ordinance");
if (typeof val === "string") val = val.trimChar("\r\n").trim();
if (!val) continue;
if (/date/.test(key) && moment(val).isValid()) val = moment(val).format("YYYY-MM-DD");
let m = key2.match(/(.+)(_(\d+))(.*)/);
if (m) {
let k = m[1] + (m[4] || "");
if (!temp[k] || typeof temp[k] === "string") {
temp[k] = {
"1": temp[k]
}
}
temp[k][m[3]] = val;
} else {
temp[key2] = val;
}
}
for (let key in temp) {
let m;
if (m = key.match(/(.+)_(chi|eng)/)) {
let lang = m[2] == "chi" ? "tc" : "en";
if (!(m[1] in temp)) temp[m[1]] = {};
if (typeof temp[key] === "object") {
for (let k in temp[key]) {
if (!(k in temp[m[1]])) temp[m[1]][k] = {};
temp[m[1]][k][lang] = temp[key][k];
}
} else {
temp[m[1]][lang] = temp[key];
}
delete temp[key];
}
}
for (let key in temp) {
let ckey = key.toCamelCase(),
m;
if (m = key.match(/^(ordinance|bill|bills_committee|legco_brief)_/)) {
ckey = m[1].toCamelCase();
let k = key.replace("ordinace", "ordinance").replace(`${m[1]}_`, "");
if (!(ckey in temp)) temp[ckey] = {};
temp[ckey][k.toCamelCase()] = temp[key];
delete temp[key];
} else if (m = key.match(/((first|second|third)_reading)_date(_(.+))?/)) {
ckey = m[1].toCamelCase();
if (!(ckey in temp)) temp[ckey] = {};
let k = (m[4] || "date").toCamelCase();
if (typeof temp[key] === "object" && Object.keys(temp[key]).filter(v => /^\d+$/.test(v)).length > 0) {
for (let key2 in temp[key]) {
if (!(key2 in temp[ckey])) temp[ckey][key2] = {}
temp[ckey][key2][k] = temp[key][key2];
}
} else {
if (!("1" in temp[ckey])) temp[ckey]["1"] = {}
temp[ckey]["1"][k] = temp[key];
}
delete temp[key];
} else if (ckey != key) {
temp[ckey] = temp[key];
delete temp[key];
}
}
arr.push(temp);
})
arr.map(item => {
let temp = {};
for (let key in item) {
let m;
if (/^(ordinance|bill|billsCommittee)$/.test(key)) {
temp[key] = {};
let gazette;
for (let k in item[key]) {
if (m = k.match(/gazette(.+)/)) {
if (!gazette) gazette = {};
if (typeof item[key][k] === "object" && Object.keys(item[key][k]).filter(v => /^\d+$/.test(v)).length > 0) {
for (let i in item[key][k]) {
if (!(i in gazette)) gazette[i] = {};
gazette[i][m[1].toCamelCase()] = item[key][k][i];
}
} else {
if (!("1" in gazette)) gazette["1"] = {};
gazette["1"][m[1].toCamelCase()] = item[key][k];
}
delete item[key][k];
}
}
for (let k in item[key]) {
temp[key][k] = handle(item[key][k]);
}
if (gazette) temp[key].gazette = handle(gazette);
} else {
temp[key] = handle(item[key]);
}
}
result.push(temp);
})
return result;
}
module.exports = search;