rtl-css
Version:
Utility for converting CSS files using external rules
290 lines (236 loc) • 7.63 kB
JavaScript
/*!
* Copyright (C) 2015 by Yuriy Nasretdinov
*
* See license text in LICENSE file
*/
(function () {
var RTL_BEGIN = "/* @rtl begin */";
var RTL_END = "/* @rtl end */";
var LTR_BEGIN = "/* @ltr begin */";
var LTR_END = "/* @ltr end */";
var NOFLIP_BEGIN = "/* @noflip begin */";
var NOFLIP_END = "/* @noflip end */";
var SECTIONS_MARKERS = [
[NOFLIP_BEGIN, NOFLIP_END],
[RTL_BEGIN, RTL_END]
];
function trim(str) {
return str.replace(/(^\s+|\s+$)/g, '');
}
// parse rules object and form the config that is usable for processing
function processRTLConfig(config) {
var properties = config.properties;
if (!properties) {
throw new Error("Invalid config: section 'properties' is missing");
}
var values = config.values;
if (!values) {
throw new Error("Invalid config: section 'values' is missing");
}
var dynamic = {};
for (var i = 0; i < values.length; i++) {
var elem = values[i];
var elem_parts = elem.split('=', 2);
var key = elem_parts[0];
var replace = elem_parts[1];
var key_parts = key.split(':', 2);
if (key_parts.length != 2) {
throw new Error("Invalid config: incorrect 'values' rule: ':' expected in key for " + i);
}
var rule = trim(key_parts[0]);
var pattern_parts = trim(key_parts[1]).split(/\s+/g);
if (!dynamic[rule]) dynamic[rule] = {};
if (!dynamic[rule][pattern_parts.length]) dynamic[rule][pattern_parts.length] = [];
dynamic[rule][pattern_parts.length].push({pattern: pattern_parts, replace: trim(replace)});
}
return {dynamic: dynamic, options: config.options, properties: config.properties};
}
/*
* For LTR we do not need to do any automatic replaces, we just need to get rid of sections between
* RTL_BEGIN and RTL_END
*
* algorithm does not use regular expressions to archieve maximum perfomance
*/
function processLTR(contents) {
var parts = contents.split(RTL_BEGIN);
var response = [parts[0]];
for (var i = 1; i < parts.length; i++) {
var v = parts[i];
var end_pos = v.indexOf(RTL_END);
if (end_pos < 0) continue;
response.push(v.substr(end_pos + RTL_END.length));
}
return response.join('').replace(LTR_BEGIN, '').replace(LTR_END, '').replace(NOFLIP_BEGIN, '').replace(NOFLIP_END, '');
}
/*
* For RTL we need to get rid of sections between LTR_BEGIN and LTR_END and apply flip-replaces.
* These replaces must ignore sections between NOFLIP_BEGIN and NOFLIP_END as well as RTL_BEGIN and RTL_END
*/
function processCss(config, direction, contents) {
if (direction == 'ltr') {
return processLTR(contents);
}
var i, j, v, end_pos, raw_cnt = 0;
var parts = contents.split(LTR_BEGIN);
var response_arr = [parts[0]];
for (i = 1; i < parts.length; i++) {
v = parts[i];
end_pos = v.indexOf(LTR_END);
if (end_pos < 0) continue;
response_arr.push(v.substr(end_pos + LTR_END.length));
}
/* sections that do not need to be replaced */
var raw_sections = {
// key => value
};
for (i = 0; i < SECTIONS_MARKERS.length; i++) {
var m = SECTIONS_MARKERS[i];
parts = response_arr.join('').split(m[0]);
response_arr = [parts[0]];
for (j = 1; j < parts.length; j++) {
v = parts[j];
end_pos = v.indexOf(m[1]);
if (end_pos < 0) continue;
var raw_v = v.substr(0, end_pos);
var raw_k = 'raw_' + (raw_cnt++) + ';';
raw_sections[raw_k] = raw_v;
response_arr.push(raw_k);
response_arr.push(v.substr(end_pos + m[1].length));
}
}
var response = processRTL(config, response_arr.join(''));
for (var from in raw_sections) {
response = response.replace(from, raw_sections[from]);
}
return response;
}
function processSimpleRTLRule(value_raw, patterns, value_suffixes) {
var value = value_raw.replace(/\\s+$/g, '');
var k, v, i;
// cut suffix from values
for (k in value_suffixes) {
var suffix = value_suffixes[k];
if (value.indexOf(suffix, value.length - suffix.length) !== -1) {
value = value.substr(0, -suffix.length);
value = value.replace(/\\s+$/g, '');
}
}
var len = value.length - value_raw.length;
var value_suffix = len ? value_raw.substr(len) : '';
var ltrimmed = value.replace(/^\s+/g, '');
len = value.length - ltrimmed.length;
var value_prefix = len ? value.substr(0, len) : '';
var value_parts = ltrimmed.split(/\s+/g);
if (!patterns[value_parts.length]) {
return false;
}
var my_patterns = patterns[value_parts.length];
upper_loop:
for (k = 0; k < my_patterns.length; k++) {
var pat = my_patterns[k];
var m = {}; // match
for (i = 0; i < pat.pattern.length; i++) {
v = pat.pattern[i];
if (v.charAt(0) != '%') {
if (value_parts[i] !== v) {
continue upper_loop;
}
} else {
m[v] = value_parts[i];
}
}
var result = pat.replace;
for (var k_m in m) {
result = result.replace(k_m, m[k_m]);
}
return value_prefix + result.replace('--', '').replace('-0', '0') + value_suffix;
}
return false;
}
// Process RTL rules for contents
function processRTL(config, contents) {
var rules = contents.split(';');
var response_arr = [];
var dynamic = config.dynamic;
var property_rules = config.properties;
var key_prefixes = config.options.prefix;
var value_suffixes = config.options.suffix;
var last_part = rules.pop();
var rule, value, k, v, i;
var replaced_expressions = {};
var idx = 0;
var replaceFunc = function (str) {
var transform = '_replaced_value_' + (idx++) + '_';
replaced_expressions[transform] = str;
return transform;
};
for (i in rules) {
var part = rules[i];
var last_brace = part.lastIndexOf('{');
if (last_brace < 0) {
rule = part;
} else {
response_arr.push(part.substr(0, last_brace + 1));
rule = part.substr(last_brace + 1);
}
// parse "rule: value", ignore other parts
var rule_parts = rule.split(':', 2);
var key = rule_parts[0];
for (k in key_prefixes) {
key = key.replace(k, key_prefixes[k]);
}
if (key.indexOf('/*') >= 0) {
key = key.replace(/\/\*.*?\*\//g, '', key);
}
key = trim(key);
if (rule_parts.length < 2 || !dynamic[key] && !property_rules[key]) {
response_arr.push(rule);
response_arr.push(';');
continue;
}
var key_raw = rule_parts[0], value_raw = rule_parts[1];
if (property_rules[key]) {
response_arr.push(key_raw.replace(key, property_rules[key]));
response_arr.push(':');
response_arr.push(value_raw);
response_arr.push(';');
continue;
}
var patterns = dynamic[key];
if (value_raw.indexOf(',') < 0 && value_raw.indexOf("(") < 0) { // simple rule
value = processSimpleRTLRule(value_raw, patterns, value_suffixes);
if (!value) {
response_arr.push(rule);
response_arr.push(';');
continue;
}
} else { // complex rule with "(...)" and ","
replaced_expressions = {};
idx = 0;
value = value_raw.replace(
/\([^)]+\)/g,
replaceFunc
);
var value_components = value.split(',');
for (k = 0; k < value_components.length; k++) {
v = processSimpleRTLRule(value_components[k], patterns, value_suffixes);
if (v) {
value_components[k] = v;
}
}
value = value_components.join(',');
for (k in replaced_expressions) {
value = value.replace(k, replaced_expressions[k]);
}
}
response_arr.push(key_raw);
response_arr.push(':');
response_arr.push(value);
response_arr.push(';');
}
response_arr.push(last_part);
return response_arr.join('');
}
exports.processConfig = processRTLConfig;
exports.processCss = processCss;
})();