├── .gitignore ├── _template ├── vendor │ └── chromium │ │ └── crx │ │ └── README.txt ├── icon.png ├── _locales │ └── en │ │ └── messages.json ├── app_main.html └── manifest.json ├── lib ├── jq ├── aapt ├── package.json ├── trim_aapt.sh ├── parseApk.js └── badging_parser.js ├── main ├── misc ├── ARChonLogo.png └── ARChonLogo.svg ├── package.json ├── tools ├── crx2apk.sh └── crxmake.sh ├── README.md ├── LICENSE ├── apk2crx.sh └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules/ 3 | 4 | -------------------------------------------------------------------------------- /_template/vendor/chromium/crx/README.txt: -------------------------------------------------------------------------------- 1 | APK goes here. 2 | -------------------------------------------------------------------------------- /lib/jq: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustcltx/apk2crx/HEAD/lib/jq -------------------------------------------------------------------------------- /main: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('./main.js')(); 4 | -------------------------------------------------------------------------------- /lib/aapt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustcltx/apk2crx/HEAD/lib/aapt -------------------------------------------------------------------------------- /_template/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustcltx/apk2crx/HEAD/_template/icon.png -------------------------------------------------------------------------------- /lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "shelljs": "0.3.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /misc/ARChonLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ustcltx/apk2crx/HEAD/misc/ARChonLogo.png -------------------------------------------------------------------------------- /lib/trim_aapt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | libPath=$(dirname $0) 3 | $libPath/aapt dump badging $1 | sed "s/\(\w\)'\(\w\)/\1¿\2/g" | sed '$d' 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "adm-zip": "0.4.4", 4 | "chalk": "0.5.1", 5 | "commander": "2.6.0", 6 | "ncp": "1.0.1", 7 | "shelljs": "0.3.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/parseApk.js: -------------------------------------------------------------------------------- 1 | var $ = require('shelljs'); 2 | var parser = require('./badging_parser.js'); 3 | 4 | module.exports = function parseApk(apk, cb) { 5 | 6 | try { 7 | 8 | var manifest = parser.parse($.exec( __dirname+'/trim_aapt.sh '+apk,{silent:true}).output); 9 | 10 | cb(null, manifest); 11 | } catch (e) { 12 | cb(e); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /tools/crx2apk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for crx in $(find $1 -type f -name "*.crx");do 4 | if [ -n "$crx" ];then 5 | TEMP_DIR=`mktemp -d` 6 | unzip -o -j $crx "vendor/chromium/crx/*.apk" -d $TEMP_DIR >/dev/null 2>&1 7 | FNAME=`basename $crx` 8 | cp $TEMP_DIR/*.apk $1/${FNAME%.crx}.apk 9 | touch -r $crx $1/${FNAME%.crx}.apk 10 | rm $TEMP_DIR -r 11 | fi 12 | done -------------------------------------------------------------------------------- /_template/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appNotSupported": { 3 | "description": "Message displayed when the app is not supported.", 4 | "message": "This app is incompatible with your device. Check to make sure other Android apps work for you from the Chrome Web Store" 5 | }, 6 | "extName": { 7 | "description": "Extension name", 8 | "message": "App" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | apk2crx 2 | ======== 3 | 4 | This tool can convert Android app to Google-Chrome app. 5 | 6 | #Features 7 | 8 | + Application icon support 9 | + Multi-language support 10 | 11 | #Dependencies 12 | 13 | + [openssl](https://github.com/openssl/openssl) 14 | 15 | #Usage 16 | 17 | ``` 18 | USAGE : ./apk2crx.sh [-t] [-b] 19 | where: 20 | -h show help 21 | -t Create a tablet version 22 | -b backup apk icon 23 | ``` 24 | #LICENSE 25 | MIT 26 | 27 | #Other 28 | 29 | apk2crx online 30 | http://huodongweb.com/Crx/create 31 | 32 | -------------------------------------------------------------------------------- /_template/app_main.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vlad Filippov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/crxmake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # 3 | # Purpose: Pack a Chromium extension directory into crx format 4 | 5 | if test $# -ne 2; then 6 | echo "Usage: crxmake.sh " 7 | exit 1 8 | fi 9 | 10 | dir=$1 11 | key=$2 12 | name=$(basename "$dir") 13 | crx="$name.crx" 14 | pub="$name.pub" 15 | sig="$name.sig" 16 | zip="$name.zip" 17 | trap 'rm -f "$pub" "$sig" "$zip"' EXIT 18 | 19 | # zip up the crx dir 20 | cwd=$(pwd -P) 21 | (cd "$dir" && zip -qr -9 -X "$cwd/$zip" .) 22 | 23 | # signature 24 | openssl sha1 -sha1 -binary -sign "$key" < "$zip" > "$sig" 25 | 26 | # public key 27 | openssl rsa -pubout -outform DER < "$key" > "$pub" 2>/dev/null 28 | 29 | byte_swap () { 30 | # Take "abcdefgh" and return it as "ghefcdab" 31 | echo "${1:6:2}${1:4:2}${1:2:2}${1:0:2}" 32 | } 33 | 34 | crmagic_hex="4372 3234" # Cr24 35 | version_hex="0200 0000" # 2 36 | pub_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$pub" | awk '{print $5}'))) 37 | sig_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$sig" | awk '{print $5}'))) 38 | ( 39 | echo "$crmagic_hex $version_hex $pub_len_hex $sig_len_hex" | xxd -r -p 40 | cat "$pub" "$sig" "$zip" 41 | ) > "$crx" 42 | echo "$crx" 43 | -------------------------------------------------------------------------------- /_template/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": { 3 | "background": { 4 | "page": "app_main.html" 5 | } 6 | }, 7 | "arc_metadata": { 8 | "apkList": [ "custom-android-release-1400197.apk" ], 9 | "enableExternalDirectory": false, 10 | "formFactor": "phone", 11 | "name": "__PACKAGE__", 12 | "orientation": "portrait", 13 | "packageName": "__PACKAGE__", 14 | "useGoogleContactsSyncAdapter": false, 15 | "usePlayServices": [ "gcm" ] 16 | }, 17 | "default_locale": "en", 18 | "icons": { 19 | "128": "icon.png", 20 | "16": "icon.png" 21 | }, 22 | "import": [ { 23 | "id": "mfaihdlpglflfgpfjcifdjdjcckigekc" 24 | } ], 25 | "manifest_version": 2, 26 | "name": "__MSG_extName__", 27 | "oauth2": { 28 | "client_id": "133701689125-jj0hr4gb0ff4ulsbrn0uk2i4th946d4c.apps.googleusercontent.com", 29 | "scopes": [ ] 30 | }, 31 | "offline_enabled": true, 32 | "permissions": [ "gcm", { 33 | "socket": [ "tcp-connect", "tcp-listen", "udp-bind", "udp-send-to", "resolve-host" ] 34 | }, "unlimitedStorage", "notifications", "clipboardRead", { 35 | "fileSystem": [ "write" ] 36 | }, "https://clients2.google.com/", "videoCapture", "clipboardWrite", "identity.email", "alarms", "storage", "identity", "audioCapture" ], 37 | "requirements": { 38 | "3D": { 39 | "features": [ "webgl" ] 40 | } 41 | }, 42 | "update_url": "https://localhost", 43 | "version": "1337" 44 | } 45 | -------------------------------------------------------------------------------- /misc/ARChonLogo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /apk2crx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | function program_is_installed { 3 | if ! type $1 &>/dev/null;then 4 | echo "Error: $1 is not found" 5 | exit 6 | fi 7 | } 8 | 9 | 10 | usage(){ 11 | echo "USAGE : ./`basename $0` [-t] [-b] 12 | where: 13 | -h show help 14 | -t Create a tablet version 15 | -b backup apk icon 16 | " 17 | exit 1 18 | } 19 | 20 | program_is_installed node 21 | program_is_installed openssl 22 | 23 | 24 | [[ $# -lt 1 ]] && usage 25 | 26 | while getopts "bth" OPTION 27 | do 28 | case $OPTION in 29 | h) usage 30 | exit 31 | ;; 32 | b) BAK="-b" 33 | ;; 34 | t) ADD="-t";HD="-HD" 35 | ;; 36 | esac 37 | done 38 | 39 | myPath=$(dirname $0) 40 | libPath=$myPath/lib 41 | jq=$libPath/jq 42 | 43 | file="${@: -1}" 44 | [[ ! -f $file ]] && usage 45 | if [ ! -d "bak" ];then 46 | mkdir -p bak/ 47 | fi 48 | 49 | ### test apk ### 50 | RE=`LANG=C;jarsigner -verify $file 2>/dev/null | grep "jar verified"` 51 | if [ -z "$RE" ];then 52 | rm $file 2>/dev/null 53 | exit 54 | fi 55 | 56 | INFO=`$myPath/main $file $ADD -s $BAK` 57 | 58 | APK_PATH=`printf %s "$INFO" | $jq '.packageName' | sed s/\"//g`".android" 59 | versionName=`printf %s "$INFO" | $jq '.versionName '| sed s/\"//g` 60 | 61 | MD5=`printf %s "$INFO" | $jq '.apkHash '| sed s/\"//g` 62 | 63 | PEM_PATH="bak/$MD5"$HD".pem" 64 | 65 | if [ ! -f "$PEM_PATH" ];then 66 | openssl genrsa -out $PEM_PATH 2>/dev/null 67 | fi 68 | 69 | $myPath/tools/crxmake.sh $APK_PATH $PEM_PATH >/dev/null 2>/dev/null 70 | 71 | CRX=$APK_PATH".crx" 72 | 73 | if [ -f "$CRX" ];then 74 | MD5_TMP=`expr substr "$MD5" 1 6` 75 | 76 | if [ -n "$BAK" ] && [ ! -f "bak/$MD5.apk" ];then 77 | mv $file "bak/$MD5.apk" 78 | else 79 | rm $file 2>/dev/null 80 | fi 81 | if [ -n "$BAK" ];then 82 | mv $CRX $APK_PATH"-""$versionName""-"$MD5_TMP$HD".crx" 2>/dev/null 83 | echo $INFO 84 | fi 85 | fi 86 | if [ ! -n "$BAK" ];then 87 | rm bak -r 2>/dev/null 88 | fi 89 | ## Clean ## 90 | if [ -d "$APK_PATH" ];then 91 | rm $APK_PATH -r 2>/dev/null 92 | fi 93 | 94 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var readline = require('readline'); 4 | 5 | var program = require('commander'); 6 | var ncp = require('ncp').ncp; 7 | var chalk = require('chalk'); 8 | var rl = readline.createInterface(process.stdin, process.stdout); 9 | 10 | var parseApk = require('./lib/parseApk'); 11 | var AdmZip=require('adm-zip'); 12 | var $ = require('shelljs'); 13 | function success() { 14 | process.exit(0); 15 | } 16 | function md5(file){ 17 | return $.exec("md5sum "+file+" | awk '{print $1}'",{silent:true}).output.trim(); 18 | } 19 | 20 | module.exports = function (callback) { 21 | 22 | program 23 | .version('1.0.0') 24 | .option('-t, --tablet', 'Create a tablet version') 25 | .option('-s, --scale', 'Enable application window scaling') 26 | .option('-n, --name [value]', 'Extension display name') 27 | .option('-b --backup','Create Backup') 28 | .usage('') 29 | .parse(process.argv); 30 | 31 | var apk = program.args[0]; 32 | callback = callback || success; 33 | 34 | if (!apk) { 35 | throw new Error('Please provide a path to an APK file...'); 36 | } 37 | 38 | parseApk(apk, function (err, data) { 39 | //console.log(data); 40 | if (err) { 41 | console.log(chalk.yellow('Failed to load APK')); 42 | } 43 | 44 | var packageName = null; 45 | var realname = null; 46 | var info={}; 47 | var l={}; 48 | try { 49 | packageName = info.packageName = data.package.name; 50 | langs = data.locales; 51 | l['en']=data['application-label'].replace(/¿/, "'");//单引号冲突解决 52 | if(langs[0]!="-"){ 53 | for(var i = 1; i < langs.length;i++){ 54 | realname=data['application-label-'+langs[i]].replace(/¿/, "'"); 55 | if(realname && realname!=l['en']){ 56 | l[langs[i]]=realname; 57 | } 58 | } 59 | } 60 | 61 | info.names=l; 62 | info.versionCode=data.package.versionCode; 63 | info.versionName=data.package.versionName; 64 | info.icon=data.application['icon']; 65 | 66 | } catch (e) { 67 | console.log(e); 68 | console.log(chalk.yellow('Failed to parse package name in the APK.')); 69 | } 70 | 71 | if (!packageName) { 72 | console.log(chalk.yellow('Unknown APK package.')); 73 | console.log('Please enter the package name (i.e "com.skype.raider", if you get this wrong your app will NOT work): '); 74 | rl.prompt(); 75 | rl.on('line', function (text) { 76 | text = text.trim(); 77 | 78 | if (/\.apk$/.test(text)) { 79 | console.log(chalk.red('Package names do not end with .apk')); 80 | console.log('They usually look like com.application.developer or com.website.www'); 81 | process.exit(0); 82 | } else if (text.indexOf(' ') !== -1) { 83 | console.log(chalk.red('Package names do not contain spaces')); 84 | console.log('They usually look like com.application.developer or com.website.www'); 85 | process.exit(0); 86 | } 87 | else { 88 | info['packageName']=text; 89 | createExtension(info,apk); 90 | } 91 | }) 92 | .on('close', function () { 93 | process.exit(0); 94 | }); 95 | } else { 96 | createExtension(info,apk); 97 | //console.log(test); 98 | } 99 | 100 | function createExtension(info,apk) { 101 | var packageName = info['packageName']; 102 | var langs = info['names']; 103 | var icon_url = info['icon']; 104 | var templatePath = path.join(__dirname, '_template'); 105 | var appPath = path.join(packageName + '.android'); 106 | var bak = "bak"; 107 | var tmp=''; 108 | 109 | // TODO: refactor this if needed in the future 110 | ncp(templatePath, appPath, function (err) { 111 | if (err) { 112 | throw err; 113 | } 114 | 115 | /*set icon*/ 116 | var zip=new AdmZip(apk); 117 | 118 | zip.extractEntryTo(icon_url,appPath, /*maintainEntryPath*/false, /*overwrite*/true); 119 | 120 | 121 | fs.renameSync(path.join(appPath,path.basename(icon_url)),path.join(appPath,"icon.png")); 122 | /*备份图片和apk*/ 123 | info.apkHash = md5(apk); 124 | if(program.backup){ 125 | if(!fs.existsSync(bak)){ 126 | fs.mkdirSync(bak); 127 | } 128 | info.iconHash = md5(path.join(appPath,"icon.png")); 129 | fs.writeFileSync(path.join(bak,info.iconHash+".png"), fs.readFileSync(path.join(appPath,"icon.png"))); 130 | fs.writeFileSync(path.join(bak,info.apkHash+".apk"), fs.readFileSync(apk)); 131 | } 132 | console.log(JSON.stringify(info)); 133 | fs.writeFileSync(path.join(appPath, 'vendor', 'chromium', 'crx', 'custom-android-release-1400197.apk'), fs.readFileSync(apk)); 134 | 135 | var manifest = JSON.parse(fs.readFileSync(path.join(templatePath, 'manifest.json'))); 136 | var messages = JSON.parse(fs.readFileSync(path.join(templatePath, '_locales', 'en', 'messages.json'))); 137 | 138 | manifest.arc_metadata.name = packageName; 139 | manifest.arc_metadata.packageName = packageName; 140 | manifest.version = '1337'; 141 | 142 | if (program.tablet) { 143 | manifest.arc_metadata.formFactor = 'tablet'; 144 | manifest.arc_metadata.orientation = 'landscape'; 145 | } 146 | 147 | if (program.scale) { 148 | manifest.arc_metadata.resize = 'scale'; 149 | } 150 | 151 | fs.writeFileSync(path.join(appPath, 'manifest.json'), JSON.stringify(manifest, null, 2)); 152 | for(key in langs){ 153 | if(langs[key]){ 154 | messages.extName.message = langs[key]; 155 | }else{ 156 | messages.extName.message = langs['en']; 157 | } 158 | tmp=path.join(appPath, '_locales', key); 159 | if(!fs.existsSync(tmp)){ 160 | fs.mkdirSync(tmp); 161 | } 162 | fs.writeFileSync(path.join(appPath, '_locales', key, 'messages.json'), JSON.stringify(messages, null, 2)); 163 | } 164 | callback(appPath); 165 | 166 | }); 167 | 168 | 169 | 170 | } 171 | }); 172 | 173 | }; 174 | 175 | -------------------------------------------------------------------------------- /lib/badging_parser.js: -------------------------------------------------------------------------------- 1 | module.exports = (function() { 2 | /* 3 | * Generated by PEG.js 0.8.0. 4 | * 5 | * http://pegjs.majda.cz/ 6 | */ 7 | 8 | function peg$subclass(child, parent) { 9 | function ctor() { this.constructor = child; } 10 | ctor.prototype = parent.prototype; 11 | child.prototype = new ctor(); 12 | } 13 | 14 | function SyntaxError(message, expected, found, offset, line, column) { 15 | this.message = message; 16 | this.expected = expected; 17 | this.found = found; 18 | this.offset = offset; 19 | this.line = line; 20 | this.column = column; 21 | 22 | this.name = "SyntaxError"; 23 | } 24 | 25 | peg$subclass(SyntaxError, Error); 26 | 27 | function parse(input) { 28 | var options = arguments.length > 1 ? arguments[1] : {}, 29 | 30 | peg$FAILED = {}, 31 | 32 | peg$startRuleFunctions = { start: peg$parsestart }, 33 | peg$startRuleFunction = peg$parsestart, 34 | 35 | peg$c0 = [], 36 | peg$c1 = peg$FAILED, 37 | peg$c2 = function(value) { 38 | var result = {}; 39 | for (var i=0; i< value.length; i++) { 40 | if (value[i] !== null) { 41 | if (value[i][1].length===1) { 42 | result[value[i][0]] = value[i][1][0]; 43 | } else { 44 | result[value[i][0]] = value[i][1]; 45 | } 46 | } 47 | } 48 | return result; 49 | }, 50 | peg$c3 = /^[a-zA-Z\-]/, 51 | peg$c4 = { type: "class", value: "[a-zA-Z\\-]", description: "[a-zA-Z\\-]" }, 52 | peg$c5 = function() {return null;}, 53 | peg$c6 = /^[^:]/, 54 | peg$c7 = { type: "class", value: "[^:]", description: "[^:]" }, 55 | peg$c8 = ":", 56 | peg$c9 = { type: "literal", value: ":", description: "\":\"" }, 57 | peg$c10 = function(name, value) {return [name.join(''), value];}, 58 | peg$c11 = "''", 59 | peg$c12 = { type: "literal", value: "''", description: "\"''\"" }, 60 | peg$c13 = function() {return undefined;}, 61 | peg$c14 = "'", 62 | peg$c15 = { type: "literal", value: "'", description: "\"'\"" }, 63 | peg$c16 = /^[^']/, 64 | peg$c17 = { type: "class", value: "[^']", description: "[^']" }, 65 | peg$c18 = function(value) {return value.join('');}, 66 | peg$c19 = ",", 67 | peg$c20 = { type: "literal", value: ",", description: "\",\"" }, 68 | peg$c21 = function(head, tail) { 69 | var result = [head]; 70 | for (var i=0; i< tail.length; i++) { 71 | result.push(tail[i][2]); 72 | } 73 | return result; 74 | }, 75 | peg$c22 = function(value) {return value;}, 76 | peg$c23 = function(value) { 77 | var result = {}; 78 | for(var i=0; i pos) { 162 | peg$cachedPos = 0; 163 | peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; 164 | } 165 | advance(peg$cachedPosDetails, peg$cachedPos, pos); 166 | peg$cachedPos = pos; 167 | } 168 | 169 | return peg$cachedPosDetails; 170 | } 171 | 172 | function peg$fail(expected) { 173 | if (peg$currPos < peg$maxFailPos) { return; } 174 | 175 | if (peg$currPos > peg$maxFailPos) { 176 | peg$maxFailPos = peg$currPos; 177 | peg$maxFailExpected = []; 178 | } 179 | 180 | peg$maxFailExpected.push(expected); 181 | } 182 | 183 | function peg$buildException(message, expected, pos) { 184 | function cleanupExpected(expected) { 185 | var i = 1; 186 | 187 | expected.sort(function(a, b) { 188 | if (a.description < b.description) { 189 | return -1; 190 | } else if (a.description > b.description) { 191 | return 1; 192 | } else { 193 | return 0; 194 | } 195 | }); 196 | 197 | while (i < expected.length) { 198 | if (expected[i - 1] === expected[i]) { 199 | expected.splice(i, 1); 200 | } else { 201 | i++; 202 | } 203 | } 204 | } 205 | 206 | function buildMessage(expected, found) { 207 | function stringEscape(s) { 208 | function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } 209 | 210 | return s 211 | .replace(/\\/g, '\\\\') 212 | .replace(/"/g, '\\"') 213 | .replace(/\x08/g, '\\b') 214 | .replace(/\t/g, '\\t') 215 | .replace(/\n/g, '\\n') 216 | .replace(/\f/g, '\\f') 217 | .replace(/\r/g, '\\r') 218 | .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) 219 | .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) 220 | .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) 221 | .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); 222 | } 223 | 224 | var expectedDescs = new Array(expected.length), 225 | expectedDesc, foundDesc, i; 226 | 227 | for (i = 0; i < expected.length; i++) { 228 | expectedDescs[i] = expected[i].description; 229 | } 230 | 231 | expectedDesc = expected.length > 1 232 | ? expectedDescs.slice(0, -1).join(", ") 233 | + " or " 234 | + expectedDescs[expected.length - 1] 235 | : expectedDescs[0]; 236 | 237 | foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; 238 | 239 | return "Expected " + expectedDesc + " but " + foundDesc + " found."; 240 | } 241 | 242 | var posDetails = peg$computePosDetails(pos), 243 | found = pos < input.length ? input.charAt(pos) : null; 244 | 245 | if (expected !== null) { 246 | cleanupExpected(expected); 247 | } 248 | 249 | return new SyntaxError( 250 | message !== null ? message : buildMessage(expected, found), 251 | expected, 252 | found, 253 | pos, 254 | posDetails.line, 255 | posDetails.column 256 | ); 257 | } 258 | 259 | function peg$parsestart() { 260 | var s0, s1, s2; 261 | 262 | s0 = peg$currPos; 263 | s1 = []; 264 | s2 = peg$parseline(); 265 | if (s2 !== peg$FAILED) { 266 | while (s2 !== peg$FAILED) { 267 | s1.push(s2); 268 | s2 = peg$parseline(); 269 | } 270 | } else { 271 | s1 = peg$c1; 272 | } 273 | if (s1 !== peg$FAILED) { 274 | peg$reportedPos = s0; 275 | s1 = peg$c2(s1); 276 | } 277 | s0 = s1; 278 | 279 | return s0; 280 | } 281 | 282 | function peg$parseline() { 283 | var s0, s1, s2, s3, s4, s5, s6; 284 | 285 | s0 = peg$currPos; 286 | s1 = []; 287 | if (peg$c3.test(input.charAt(peg$currPos))) { 288 | s2 = input.charAt(peg$currPos); 289 | peg$currPos++; 290 | } else { 291 | s2 = peg$FAILED; 292 | if (peg$silentFails === 0) { peg$fail(peg$c4); } 293 | } 294 | if (s2 !== peg$FAILED) { 295 | while (s2 !== peg$FAILED) { 296 | s1.push(s2); 297 | if (peg$c3.test(input.charAt(peg$currPos))) { 298 | s2 = input.charAt(peg$currPos); 299 | peg$currPos++; 300 | } else { 301 | s2 = peg$FAILED; 302 | if (peg$silentFails === 0) { peg$fail(peg$c4); } 303 | } 304 | } 305 | } else { 306 | s1 = peg$c1; 307 | } 308 | if (s1 !== peg$FAILED) { 309 | s2 = []; 310 | s3 = peg$parse_(); 311 | while (s3 !== peg$FAILED) { 312 | s2.push(s3); 313 | s3 = peg$parse_(); 314 | } 315 | if (s2 !== peg$FAILED) { 316 | s3 = peg$parsenl(); 317 | if (s3 !== peg$FAILED) { 318 | peg$reportedPos = s0; 319 | s1 = peg$c5(); 320 | s0 = s1; 321 | } else { 322 | peg$currPos = s0; 323 | s0 = peg$c1; 324 | } 325 | } else { 326 | peg$currPos = s0; 327 | s0 = peg$c1; 328 | } 329 | } else { 330 | peg$currPos = s0; 331 | s0 = peg$c1; 332 | } 333 | if (s0 === peg$FAILED) { 334 | s0 = peg$currPos; 335 | s1 = []; 336 | if (peg$c6.test(input.charAt(peg$currPos))) { 337 | s2 = input.charAt(peg$currPos); 338 | peg$currPos++; 339 | } else { 340 | s2 = peg$FAILED; 341 | if (peg$silentFails === 0) { peg$fail(peg$c7); } 342 | } 343 | if (s2 !== peg$FAILED) { 344 | while (s2 !== peg$FAILED) { 345 | s1.push(s2); 346 | if (peg$c6.test(input.charAt(peg$currPos))) { 347 | s2 = input.charAt(peg$currPos); 348 | peg$currPos++; 349 | } else { 350 | s2 = peg$FAILED; 351 | if (peg$silentFails === 0) { peg$fail(peg$c7); } 352 | } 353 | } 354 | } else { 355 | s1 = peg$c1; 356 | } 357 | if (s1 !== peg$FAILED) { 358 | if (input.charCodeAt(peg$currPos) === 58) { 359 | s2 = peg$c8; 360 | peg$currPos++; 361 | } else { 362 | s2 = peg$FAILED; 363 | if (peg$silentFails === 0) { peg$fail(peg$c9); } 364 | } 365 | if (s2 !== peg$FAILED) { 366 | s3 = []; 367 | s4 = peg$parse_(); 368 | while (s4 !== peg$FAILED) { 369 | s3.push(s4); 370 | s4 = peg$parse_(); 371 | } 372 | if (s3 !== peg$FAILED) { 373 | s4 = peg$parsevalue(); 374 | if (s4 !== peg$FAILED) { 375 | s5 = []; 376 | s6 = peg$parsenl(); 377 | while (s6 !== peg$FAILED) { 378 | s5.push(s6); 379 | s6 = peg$parsenl(); 380 | } 381 | if (s5 !== peg$FAILED) { 382 | peg$reportedPos = s0; 383 | s1 = peg$c10(s1, s4); 384 | s0 = s1; 385 | } else { 386 | peg$currPos = s0; 387 | s0 = peg$c1; 388 | } 389 | } else { 390 | peg$currPos = s0; 391 | s0 = peg$c1; 392 | } 393 | } else { 394 | peg$currPos = s0; 395 | s0 = peg$c1; 396 | } 397 | } else { 398 | peg$currPos = s0; 399 | s0 = peg$c1; 400 | } 401 | } else { 402 | peg$currPos = s0; 403 | s0 = peg$c1; 404 | } 405 | } 406 | 407 | return s0; 408 | } 409 | 410 | function peg$parsevalue() { 411 | var s0; 412 | 413 | s0 = peg$parsestr_value(); 414 | if (s0 === peg$FAILED) { 415 | s0 = peg$parsekv_value(); 416 | } 417 | 418 | return s0; 419 | } 420 | 421 | function peg$parseone() { 422 | var s0, s1, s2, s3, s4, s5; 423 | 424 | s0 = peg$currPos; 425 | if (input.substr(peg$currPos, 2) === peg$c11) { 426 | s1 = peg$c11; 427 | peg$currPos += 2; 428 | } else { 429 | s1 = peg$FAILED; 430 | if (peg$silentFails === 0) { peg$fail(peg$c12); } 431 | } 432 | if (s1 !== peg$FAILED) { 433 | peg$reportedPos = s0; 434 | s1 = peg$c13(); 435 | } 436 | s0 = s1; 437 | if (s0 === peg$FAILED) { 438 | s0 = peg$currPos; 439 | if (input.charCodeAt(peg$currPos) === 39) { 440 | s1 = peg$c14; 441 | peg$currPos++; 442 | } else { 443 | s1 = peg$FAILED; 444 | if (peg$silentFails === 0) { peg$fail(peg$c15); } 445 | } 446 | if (s1 !== peg$FAILED) { 447 | s2 = []; 448 | if (peg$c16.test(input.charAt(peg$currPos))) { 449 | s3 = input.charAt(peg$currPos); 450 | peg$currPos++; 451 | } else { 452 | s3 = peg$FAILED; 453 | if (peg$silentFails === 0) { peg$fail(peg$c17); } 454 | } 455 | if (s3 !== peg$FAILED) { 456 | while (s3 !== peg$FAILED) { 457 | s2.push(s3); 458 | if (peg$c16.test(input.charAt(peg$currPos))) { 459 | s3 = input.charAt(peg$currPos); 460 | peg$currPos++; 461 | } else { 462 | s3 = peg$FAILED; 463 | if (peg$silentFails === 0) { peg$fail(peg$c17); } 464 | } 465 | } 466 | } else { 467 | s2 = peg$c1; 468 | } 469 | if (s2 !== peg$FAILED) { 470 | if (input.charCodeAt(peg$currPos) === 39) { 471 | s3 = peg$c14; 472 | peg$currPos++; 473 | } else { 474 | s3 = peg$FAILED; 475 | if (peg$silentFails === 0) { peg$fail(peg$c15); } 476 | } 477 | if (s3 !== peg$FAILED) { 478 | s4 = []; 479 | s5 = peg$parse_(); 480 | while (s5 !== peg$FAILED) { 481 | s4.push(s5); 482 | s5 = peg$parse_(); 483 | } 484 | if (s4 !== peg$FAILED) { 485 | peg$reportedPos = s0; 486 | s1 = peg$c18(s2); 487 | s0 = s1; 488 | } else { 489 | peg$currPos = s0; 490 | s0 = peg$c1; 491 | } 492 | } else { 493 | peg$currPos = s0; 494 | s0 = peg$c1; 495 | } 496 | } else { 497 | peg$currPos = s0; 498 | s0 = peg$c1; 499 | } 500 | } else { 501 | peg$currPos = s0; 502 | s0 = peg$c1; 503 | } 504 | } 505 | 506 | return s0; 507 | } 508 | 509 | function peg$parsestr_value() { 510 | var s0, s1, s2, s3, s4, s5, s6; 511 | 512 | s0 = peg$currPos; 513 | s1 = peg$parseone(); 514 | if (s1 !== peg$FAILED) { 515 | s2 = []; 516 | s3 = peg$currPos; 517 | if (input.charCodeAt(peg$currPos) === 44) { 518 | s4 = peg$c19; 519 | peg$currPos++; 520 | } else { 521 | s4 = peg$FAILED; 522 | if (peg$silentFails === 0) { peg$fail(peg$c20); } 523 | } 524 | if (s4 !== peg$FAILED) { 525 | s5 = []; 526 | s6 = peg$parse_(); 527 | while (s6 !== peg$FAILED) { 528 | s5.push(s6); 529 | s6 = peg$parse_(); 530 | } 531 | if (s5 !== peg$FAILED) { 532 | s6 = peg$parseone(); 533 | if (s6 !== peg$FAILED) { 534 | s4 = [s4, s5, s6]; 535 | s3 = s4; 536 | } else { 537 | peg$currPos = s3; 538 | s3 = peg$c1; 539 | } 540 | } else { 541 | peg$currPos = s3; 542 | s3 = peg$c1; 543 | } 544 | } else { 545 | peg$currPos = s3; 546 | s3 = peg$c1; 547 | } 548 | if (s3 !== peg$FAILED) { 549 | while (s3 !== peg$FAILED) { 550 | s2.push(s3); 551 | s3 = peg$currPos; 552 | if (input.charCodeAt(peg$currPos) === 44) { 553 | s4 = peg$c19; 554 | peg$currPos++; 555 | } else { 556 | s4 = peg$FAILED; 557 | if (peg$silentFails === 0) { peg$fail(peg$c20); } 558 | } 559 | if (s4 !== peg$FAILED) { 560 | s5 = []; 561 | s6 = peg$parse_(); 562 | while (s6 !== peg$FAILED) { 563 | s5.push(s6); 564 | s6 = peg$parse_(); 565 | } 566 | if (s5 !== peg$FAILED) { 567 | s6 = peg$parseone(); 568 | if (s6 !== peg$FAILED) { 569 | s4 = [s4, s5, s6]; 570 | s3 = s4; 571 | } else { 572 | peg$currPos = s3; 573 | s3 = peg$c1; 574 | } 575 | } else { 576 | peg$currPos = s3; 577 | s3 = peg$c1; 578 | } 579 | } else { 580 | peg$currPos = s3; 581 | s3 = peg$c1; 582 | } 583 | } 584 | } else { 585 | s2 = peg$c1; 586 | } 587 | if (s2 !== peg$FAILED) { 588 | peg$reportedPos = s0; 589 | s1 = peg$c21(s1, s2); 590 | s0 = s1; 591 | } else { 592 | peg$currPos = s0; 593 | s0 = peg$c1; 594 | } 595 | } else { 596 | peg$currPos = s0; 597 | s0 = peg$c1; 598 | } 599 | if (s0 === peg$FAILED) { 600 | s0 = peg$currPos; 601 | s1 = []; 602 | s2 = peg$parseone(); 603 | if (s2 !== peg$FAILED) { 604 | while (s2 !== peg$FAILED) { 605 | s1.push(s2); 606 | s2 = peg$parseone(); 607 | } 608 | } else { 609 | s1 = peg$c1; 610 | } 611 | if (s1 !== peg$FAILED) { 612 | peg$reportedPos = s0; 613 | s1 = peg$c22(s1); 614 | } 615 | s0 = s1; 616 | } 617 | 618 | return s0; 619 | } 620 | 621 | function peg$parsekv_value() { 622 | var s0, s1, s2; 623 | 624 | s0 = peg$currPos; 625 | s1 = []; 626 | s2 = peg$parsepair(); 627 | if (s2 !== peg$FAILED) { 628 | while (s2 !== peg$FAILED) { 629 | s1.push(s2); 630 | s2 = peg$parsepair(); 631 | } 632 | } else { 633 | s1 = peg$c1; 634 | } 635 | if (s1 !== peg$FAILED) { 636 | peg$reportedPos = s0; 637 | s1 = peg$c23(s1); 638 | } 639 | s0 = s1; 640 | 641 | return s0; 642 | } 643 | 644 | function peg$parsepair() { 645 | var s0, s1, s2, s3; 646 | 647 | s0 = peg$currPos; 648 | s1 = []; 649 | if (peg$c24.test(input.charAt(peg$currPos))) { 650 | s2 = input.charAt(peg$currPos); 651 | peg$currPos++; 652 | } else { 653 | s2 = peg$FAILED; 654 | if (peg$silentFails === 0) { peg$fail(peg$c25); } 655 | } 656 | if (s2 !== peg$FAILED) { 657 | while (s2 !== peg$FAILED) { 658 | s1.push(s2); 659 | if (peg$c24.test(input.charAt(peg$currPos))) { 660 | s2 = input.charAt(peg$currPos); 661 | peg$currPos++; 662 | } else { 663 | s2 = peg$FAILED; 664 | if (peg$silentFails === 0) { peg$fail(peg$c25); } 665 | } 666 | } 667 | } else { 668 | s1 = peg$c1; 669 | } 670 | if (s1 !== peg$FAILED) { 671 | if (input.charCodeAt(peg$currPos) === 61) { 672 | s2 = peg$c26; 673 | peg$currPos++; 674 | } else { 675 | s2 = peg$FAILED; 676 | if (peg$silentFails === 0) { peg$fail(peg$c27); } 677 | } 678 | if (s2 !== peg$FAILED) { 679 | s3 = peg$parseone(); 680 | if (s3 !== peg$FAILED) { 681 | peg$reportedPos = s0; 682 | s1 = peg$c28(s1, s3); 683 | s0 = s1; 684 | } else { 685 | peg$currPos = s0; 686 | s0 = peg$c1; 687 | } 688 | } else { 689 | peg$currPos = s0; 690 | s0 = peg$c1; 691 | } 692 | } else { 693 | peg$currPos = s0; 694 | s0 = peg$c1; 695 | } 696 | 697 | return s0; 698 | } 699 | 700 | function peg$parsenl() { 701 | var s0; 702 | 703 | if (peg$c29.test(input.charAt(peg$currPos))) { 704 | s0 = input.charAt(peg$currPos); 705 | peg$currPos++; 706 | } else { 707 | s0 = peg$FAILED; 708 | if (peg$silentFails === 0) { peg$fail(peg$c30); } 709 | } 710 | 711 | return s0; 712 | } 713 | 714 | function peg$parse_() { 715 | var s0; 716 | 717 | if (peg$c31.test(input.charAt(peg$currPos))) { 718 | s0 = input.charAt(peg$currPos); 719 | peg$currPos++; 720 | } else { 721 | s0 = peg$FAILED; 722 | if (peg$silentFails === 0) { peg$fail(peg$c32); } 723 | } 724 | 725 | return s0; 726 | } 727 | 728 | peg$result = peg$startRuleFunction(); 729 | 730 | if (peg$result !== peg$FAILED && peg$currPos === input.length) { 731 | return peg$result; 732 | } else { 733 | if (peg$result !== peg$FAILED && peg$currPos < input.length) { 734 | peg$fail({ type: "end", description: "end of input" }); 735 | } 736 | 737 | throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos); 738 | } 739 | } 740 | 741 | return { 742 | SyntaxError: SyntaxError, 743 | parse: parse 744 | }; 745 | })(); 746 | --------------------------------------------------------------------------------