├── package.json ├── LICENSE ├── cleanpdf.pl ├── README.md ├── owa2pdf.js └── jquery-2.1.0.min.js /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "owa2pdf", 3 | "version": "0.2.4", 4 | "title": "owa2pdf", 5 | "description": "Convert MS-Office (Word/Excel/PowerPoint) documents to PDF files via Office Online (and OneDrive).", 6 | "keywords": [ 7 | "pdf", 8 | "ms-office", 9 | "convert", 10 | "office-online", 11 | "onedrive" 12 | ], 13 | "dependencies": { 14 | "phantomjs": "~1.9" 15 | }, 16 | "homepage": "https://github.com/anseki/owa2pdf", 17 | "repository": { 18 | "type": "git", 19 | "url": "git://github.com/anseki/owa2pdf.git" 20 | }, 21 | "bugs": "https://github.com/anseki/owa2pdf/issues", 22 | "license": "MIT", 23 | "author": { 24 | "name": "anseki", 25 | "url": "https://github.com/anseki" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 anseki 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. -------------------------------------------------------------------------------- /cleanpdf.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # cleanpdf.pl 4 | # owa2pdf 5 | # https://github.com/anseki/owa2pdf 6 | # 7 | # Copyright (c) 2014 anseki 8 | # Licensed under the MIT license. 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use PDF::API2; 14 | use CAM::PDF; 15 | 16 | my $path_src = $ARGV[0]; 17 | _error("Specify PDF file. $path_src") unless -f $path_src; 18 | my $path_dest = "$path_src.dst.pdf"; 19 | my $path_temp = "$path_src.tmp.pdf"; 20 | 21 | my $pdf_src = eval { PDF::API2->open($path_src); }; 22 | unless ($pdf_src) { # Try to convert version via CAM::PDF 23 | my $campdf = eval { CAM::PDF->new($path_src); } or _error(" Can't open. $path_src"); 24 | # toPDF() and openScalar() can't pass to PDF::API2. 25 | $campdf->cleanoutput($path_temp); 26 | $pdf_src = eval { PDF::API2->open($path_temp); } or _error(" Can't open. $path_temp"); 27 | } 28 | 29 | my $pages_count = $pdf_src->pages() or _error(' No page.'); 30 | my $pdf_dest = PDF::API2->new(-onecolumn => 1); 31 | delete $pdf_dest->{pdf}->{Info}; 32 | delete $pdf_dest->{catalog}->{PageLayout}; 33 | foreach my $page_num (1 .. $pages_count) { 34 | $pdf_dest->importpage($pdf_src, $page_num); 35 | } 36 | 37 | $pdf_dest->saveas($path_dest); 38 | $pdf_src->end(); 39 | $pdf_dest->end(); 40 | 41 | unlink $path_src; 42 | rename $path_dest, $path_src or _error("rename ERROR: $!"); 43 | 44 | exit; 45 | 46 | sub _error { 47 | my $msg = shift; 48 | print STDERR "$msg\n"; 49 | unlink grep $_ && -f $_, ($path_dest, $path_temp); 50 | exit 1; 51 | } 52 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # owa2pdf 2 | 3 | Convert MS-Office (Word/Excel/PowerPoint) documents to PDF files via [Office Online](https://office.com/) (and [OneDrive](https://onedrive.live.com/)). 4 | 5 | In some cases, the application on non-Windows machine (e.g. Web Application) uses something (e.g. [Apache OpenOffice](http://www.openoffice.org/)) to convert files. But, it is difficult to parse the layout of the MS-Office document. 6 | owa2pdf uses the Office Online that is provided by Microsoft, that working is high quality. 7 | 8 | ## Usage 9 | 10 | Install [PhantomJS](http://phantomjs.org/) 1.9+ first. 11 | `owa2pdf.js` and `jquery-2.0.3.min.js` must be put on same directory. And, Microsoft account is needed. (see https://signup.live.com/) 12 | 13 | ```shell 14 | phantomjs owa2pdf.js -u "user@example.com" -p "password" -i /path/source.docx -o /path/dest.pdf 15 | ``` 16 | 17 | The `--ignore-ssl-errors=true` option may be needed. 18 | 19 | ```shell 20 | phantomjs --ignore-ssl-errors=true owa2pdf.js -u "user@example.com" -p "password" -i /path/source.docx -o /path/dest.pdf 21 | ``` 22 | 23 | ## cleanpdf.pl 24 | 25 | When the PDF file which is made by Office Online is opened by Adobe Reader, "Print" dialog-box is displayed. It's done by script which was embedded by Office Online. And some other things are embedded by Office Online. 26 | `cleanpdf.pl` removes some things which embedded by Office Online. This is Perl script which needs [`PDF::API2`](http://search.cpan.org/perldoc?PDF%3A%3AAPI2) and [`CAM::PDF`](http://search.cpan.org/perldoc?CAM%3A%3APDF) modules. (e.g. `cpanm PDF::API2`, `cpanm CAM::PDF`) 27 | 28 | At first, install 2 modules by your favorite installer. 29 | 30 | ```shell 31 | cpanm PDF::API2 CAM::PDF 32 | ``` 33 | 34 | Then, clean files by `cleanpdf.pl`. 35 | 36 | ```shell 37 | ./cleanpdf.pl /path/dest.pdf 38 | ``` 39 | 40 | ## Notes 41 | 42 | + owa2pdf is slow. 43 | + If you have Windows machine and MS-Office, using them is better. 44 | + If Microsoft releases Office Online API someday, using that is better. (owa2pdf is Web scraping.) 45 | + Your application might have to retry calling the script. The converting sometimes fails by various causes (e.g. network, MS server, etc.). 46 | + Your application might have to use the plural accounts, if it converts many files successively. 47 | -------------------------------------------------------------------------------- /owa2pdf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * owa2pdf 3 | * https://github.com/anseki/owa2pdf 4 | * 5 | * Copyright (c) 2014 anseki 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | ;(function(undefined) { 10 | 'use strict'; 11 | 12 | var page = require('webpage').create(), 13 | args = require('system').args, 14 | fs = require('fs'), 15 | arg, msUser, msPw, srcFile, destFile, 16 | jqLoad = true, ignoreFail = false, pdfSaved = false, 17 | uriFiles, uriAppFrame, uriApp, appClass, 18 | actionLoopInterval = 1000, countStep = 1, onReadyCallback, timerId, i, iLen, 19 | debugScrnFile, debugScrnCnt, 20 | 21 | TIMEOUT = 30, 22 | JQ_PATH = './jquery-2.1.0.min.js', 23 | defaultReqHeaders = {'Accept-Language': 'en-US,en;q=0.8'}, 24 | DEBUG = 0, // >=1: Message, bit2: Screenshot, bit3: Resource I/O 25 | 26 | // Temporary file Utility (path, baseName, baseNameRe) 27 | // OneDrive is a stickler for file name. 28 | tmpFile = { 29 | init: function(dirPath, ext) { 30 | this.dirPath = dirPath; 31 | this.ext = ext; 32 | this.baseNum = (new Date()).getTime(); 33 | return this.reNew(); 34 | }, 35 | reNew: function() { 36 | var newBaseName, newPath, extStr = this.ext ? '.' + this.ext : '', i; 37 | for (i = 0; i < 1000; i++) { 38 | newBaseName = '_' + (this.baseNum++).toString(16); 39 | newPath = this.dirPath + fs.separator + newBaseName + extStr; 40 | if (!fs.exists(newPath)) { 41 | this.path = newPath; 42 | this.baseName = newBaseName + extStr; 43 | this.baseNameRe = new RegExp('^' + 44 | newBaseName.replace(/(\W)/g, '\\$1') + 45 | (extStr ? '(?:' + extStr.replace(/(\W)/g, '\\$1') + ')?' : '') + 46 | '$', 'i'); 47 | return true; 48 | } 49 | } 50 | return false; 51 | } 52 | }, 53 | 54 | selector = { 55 | loginUser: 'i0116', 56 | loginPw: 'i0118', 57 | loginPersistent: 'idChkBx_PWD_KMSI0Pwd', 58 | loginBtn: 'idSIButton9', 59 | logoutBtn: 'c_signout', 60 | uploadElm: '.cb_uploadInput input[type="file"]', 61 | fileItem: '.c-ListView .tileContent.officeDoc .namePlate .title,' + 62 | '.c-ListView .c-SetItemRow a.file .cellText', 63 | cmdView: '.c_mcp .uxfa_m li a', 64 | frameApp: 'sdx_ow_iframe', 65 | cmdFile: '#jewelcontainer .cui-jewel-jewelMenuLauncher', 66 | cmdPrint: { 67 | word: '#faPrint-Menu32', 68 | excel: '#m_excelWebRenderer_ewaCtl_faPrint-Menu32', 69 | ppoint: '#PptJewel\\.Print-Menu32' 70 | }, 71 | cmdPrintSub: { 72 | word: '#jbtnDirectPrint-Menu48', 73 | excel: '#m_excelWebRenderer_ewaCtl_Jewel\\.Print-Menu48', 74 | ppoint: '#PptJewel\\.Print\\.PrintToPdf-Menu48' 75 | }, 76 | cmdExcel: { 77 | dialog: '#ewaDialogInner', // visibility: visible; 78 | optEntire: '#printEntireItem', 79 | btnPrint: 'button[type="submit"].ewa-dlg-button', 80 | ctrlArea: '#print_bar,.spacer.noprint', 81 | loading: 'print_loading_div', 82 | }, 83 | linkPdf: '#PrintPDFLink', 84 | cmdRemove: '.c_mcp .uxfa_m li a' 85 | }; 86 | 87 | for (i = 1, iLen = args.length; i < iLen; i++) { 88 | arg = args[i].trim(); 89 | if (arg === '-u') { msUser = args[++i].trim(); } 90 | else if (arg === '-p') { msPw = args[++i].trim(); } 91 | else if (arg === '-i') { srcFile = args[++i].trim(); } 92 | else if (arg === '-o') { destFile = args[++i].trim(); } 93 | // else if (arg === '-s') { silent = true; } 94 | else if (arg === '-debug') { DEBUG = parseInt(args[++i].trim(), 10); } 95 | } 96 | if (!msUser || !msPw || !srcFile || !destFile) { 97 | console.error('Usage: phantomjs owa2pdf.js' + 98 | ' -u "user@example.com" -p "password" -i "/path/source.docx" -o "/path/dest.pdf"'); 99 | phantom.exit(1); 100 | } 101 | 102 | // I/O files. 103 | if (!fs.exists(srcFile) || !fs.isFile(srcFile)) { 104 | console.error('-i not found: "' + srcFile + '"'); 105 | phantom.exit(1); 106 | } 107 | (function() { 108 | var pos = destFile.lastIndexOf(fs.separator), 109 | destDirPath = pos > -1 ? destFile.substr(0, pos) : '.', // '/file' => '' 110 | srcBaseName, ext; 111 | if (!(pos > -1 ? destFile.substr(pos + 1) : destFile)) { // Check destBaseName 112 | console.error('-o invalid: "' + destFile + '"'); 113 | phantom.exit(1); 114 | } 115 | if (!fs.exists(destFile)) { 116 | // Root directory exists always. 117 | // destDirPath may be root directory even if it isn't ''. 118 | // But check destDirPath for syntax. 119 | if (destDirPath) { 120 | if (!fs.exists(destDirPath)) { 121 | try { 122 | if (!fs.makeTree(destDirPath)) { 123 | console.error('Can\'t make directory: "' + destDirPath + '"'); 124 | phantom.exit(1); 125 | } 126 | } catch(e) { 127 | console.error('Error(' + e.message + ') ' + e.description + ' - ' + e); 128 | phantom.exit(1); 129 | } 130 | } else if (!fs.isDirectory(destDirPath)) { 131 | console.error('Already exists that isn\'t directory: "' + destDirPath + '"'); 132 | phantom.exit(1); 133 | } 134 | } 135 | } else if (!fs.isFile(destFile)) { 136 | console.error('-o is not file: "' + destFile + '"'); 137 | phantom.exit(1); 138 | } else if (!fs.isWritable(destFile)) { 139 | console.error('-o not writable: "' + destFile + '"'); 140 | phantom.exit(1); 141 | } 142 | // For temporary file. 143 | if (!fs.isWritable(destDirPath || '/')) { 144 | console.error('Directory in -o not writable: "' + destDirPath + '"'); 145 | phantom.exit(1); 146 | } 147 | // Get ext from srcFile. 148 | pos = srcFile.lastIndexOf(fs.separator); 149 | srcBaseName = pos > -1 ? srcFile.substr(pos + 1) : srcFile; 150 | pos = srcBaseName.lastIndexOf('.'); 151 | ext = pos > -1 ? srcBaseName.substr(pos + 1) : ''; 152 | tmpFile.init(destDirPath, ext); 153 | })(); 154 | 155 | // For debug mode. 156 | if (DEBUG) { 157 | if (DEBUG & 2) { 158 | page.viewportSize = { width: 800, height: 600 }; // For screenshot 159 | debugScrnFile = Math.floor((new Date()).getTime() / 1000); 160 | debugScrnCnt = 0; 161 | } 162 | page.onConsoleMessage = function(msg) { 163 | // This error occurs when page.settings.webSecurityEnabled isn't set to false 164 | if (msg.indexOf('Unsafe JavaScript attempt to access frame with URL') > -1) 165 | { return; } 166 | debugLog('(onConsoleMessage) ' + msg); 167 | }; 168 | page.onError = function(msg, trace) { 169 | var msgs = [msg]; 170 | if (trace && trace.length) { 171 | trace.forEach(function(t) { 172 | msgs.push(' -> ' + t.file + ': ' + t.line + 173 | (t.function ? ' (in function "' + t.function + '")' : '')); 174 | }); 175 | } 176 | debugLog('======== onError'); 177 | debugLog(msgs.join('\n')); 178 | debugLog('======== /onError'); 179 | }; 180 | if (DEBUG & 4) { 181 | page.onResourceReceived = function(response) { 182 | var head; 183 | // if ((response.url || '').indexOf('storage.live.com') < 0) { return; } 184 | response.url = response.url.replace(/([;:]base64,[^;]{3})[^;]+/i, '$1...'); 185 | head = 'onResourceReceived (#' + response.id + 186 | ', stage "' + response.stage + '", url: ' + response.url + ')'; 187 | debugLog('======== ' + head); 188 | debugLog(JSON.stringify(response, null, ' ')); 189 | debugLog('======== /' + head); 190 | }; 191 | } 192 | } 193 | // Regardless debug mode. (customHeaders) 194 | page.onResourceRequested = function(requestData) { 195 | var head; 196 | page.customHeaders = defaultReqHeaders; // For XMLHttpRequest.setRequestHeader 197 | if (DEBUG & 4) { 198 | // if ((requestData.url || '').indexOf('storage.live.com') < 0) { return; } 199 | requestData.url = requestData.url.replace(/([;:]base64,[^;]{3})[^;]+/i, '$1...'); 200 | head = 'onResourceRequested (#' + requestData.id + 201 | ', url: ' + requestData.url + ')'; 202 | debugLog('======== ' + head); 203 | debugLog(JSON.stringify(requestData, null, ' ')); 204 | debugLog('======== /' + head); 205 | } 206 | }; 207 | // Regardless debug mode. (SSL handshake failed) 208 | page.onResourceError = function(resourceError) { 209 | var head; 210 | if (DEBUG & 4) { 211 | // if ((resourceError.url || '').indexOf('storage.live.com') < 0) { return; } 212 | resourceError.url = resourceError.url.replace(/([;:]base64,[^;]{3})[^;]+/i, '$1...'); 213 | head = 'onResourceError (#' + resourceError.id + 214 | ', url: ' + resourceError.url + ')'; 215 | debugLog('======== ' + head); 216 | debugLog(JSON.stringify(resourceError, null, ' ')); 217 | debugLog('======== /' + head); 218 | } 219 | if (resourceError.errorCode === 6) { // SSL handshake failed 220 | console.error(resourceError.errorString + ': ' + resourceError.url); 221 | console.error('"--ignore-ssl-errors=true" for phantomjs may be needed.'); 222 | phantom.exit(1); 223 | } 224 | }; 225 | function debugLog(msg, getScrn) { 226 | var filename, date, timeStamp; 227 | if (!DEBUG) { return; } 228 | date = new Date(); 229 | timeStamp = ('00' + date.getHours()).slice(-2) + 230 | ':' + ('00' + date.getMinutes()).slice(-2) + 231 | ':' + ('00' + date.getSeconds()).slice(-2); 232 | if ((DEBUG & 2) && getScrn) { 233 | filename = debugScrnFile + '_' + 234 | ('000' + (debugScrnCnt++)).slice(-3) + '.png'; 235 | page.render(filename); 236 | } 237 | console.log('[DEBUG ' + timeStamp + '] ' + msg + 238 | (filename ? ' : ' + filename : '')); 239 | } 240 | 241 | function pageInit() { 242 | // Don't use DOMContentLoaded event because script in the page is first. 243 | page.onLoadFinished = function(status) { 244 | debugLog('onLoadFinished() called. Status: ' + status + ' URL: ' + page.url); 245 | if (status === 'success') { 246 | pageInit(); // For next loading. Now, onLoadFinished was reset. 247 | if (jqLoad) { // Inject jQuery to new page. 248 | if (!page.evaluate( 249 | function() { return window.jQuery ? true : false; })) { 250 | debugLog('Inject jQuery.'); 251 | if (!page.injectJs(JQ_PATH)) { 252 | console.error('Can\'t load "' + JQ_PATH + '"'); 253 | phantom.exit(1); 254 | } 255 | } else { debugLog('jQuery already exists.'); } 256 | } 257 | if (onReadyCallback) { 258 | setTimeout(function() { 259 | onReadyCallback(); 260 | // onLoadFinished may be called again at same page. 261 | onReadyCallback = null; 262 | }, 0); 263 | } 264 | } else if (!ignoreFail) { 265 | console.error('Unable to load the address! : ' + page.url); 266 | phantom.exit(1); 267 | } 268 | }; 269 | } 270 | 271 | // pageOpen() instead of page.open(). 272 | function pageOpen(url, onReady) { 273 | onReadyCallback = onReady; 274 | page.open(url); 275 | } 276 | 277 | // action: URI or Function that returns boolean, 278 | // or [URI, jqLoad, ignoreFail] or [Function, jqLoad, ignoreFail]. 279 | // examine: Function that returns *true* to next step or String to exit. 280 | function dfdAction(name, action, examine, redoInterval) { 281 | var dfd = $.Deferred(), timeCount = TIMEOUT, 282 | actionDone = false, redoCount = redoInterval; 283 | clearTimeout(timerId); // No Passing 284 | function docReady() { 285 | return page.evaluate(function(jqLoad) { 286 | return document.readyState === 'complete' && (!jqLoad || window.jQuery); 287 | }, jqLoad); 288 | } 289 | function actionLoop() { 290 | var examineRes = 0; 291 | debugLog(name + ' ---- actionLoop() Try: ' + (TIMEOUT - timeCount + 1), true); 292 | // The page may be loaded by examine(). 293 | if (docReady() && (examineRes = examine()) === true) { 294 | debugLog(name + ' : OK'); 295 | debugLog(name + ' ======== /dfdAction()'); 296 | dfd.resolve(); 297 | } else if (typeof examineRes === 'string') { 298 | debugLog(name + ' '); 299 | debugLog(name + ' ======== /dfdAction()'); 300 | dfd.reject(name + ' ' + examineRes); 301 | } else if ((timeCount -= countStep) <= 0) { 302 | debugLog(name + ' '); 303 | debugLog(name + ' ======== /dfdAction()'); 304 | dfd.reject(name + ' Can\'t parse page'); 305 | } else if (docReady() && !actionDone) { 306 | if (Array.isArray(action)) { 307 | if (typeof action[1] === 'boolean') { jqLoad = action[1]; } 308 | if (typeof action[2] === 'boolean') { ignoreFail = action[2]; } 309 | // Not array in next loop 310 | action = action[0]; 311 | } 312 | if (typeof action === 'string') { 313 | debugLog(name + ' : ' + action); 314 | actionDone = true; 315 | pageOpen(action, actionLoop); 316 | } else if (typeof action === 'function') { 317 | debugLog(name + ' '); 318 | actionDone = action(); 319 | clearTimeout(timerId); // No Passing 320 | timerId = setTimeout(actionLoop, actionDone ? 1 : actionLoopInterval); 321 | } 322 | } else { 323 | if (actionDone && redoInterval && --redoCount <= 0) { 324 | debugLog(name + ' '); 325 | actionDone = false; 326 | redoCount = redoInterval; 327 | } 328 | clearTimeout(timerId); // No Passing 329 | timerId = setTimeout(actionLoop, actionLoopInterval); 330 | } 331 | } 332 | 333 | debugLog(name + ' ======== dfdAction()'); 334 | actionLoop(); 335 | return dfd.promise(); 336 | } 337 | 338 | // page.settings.loadImages = false; 339 | // If page.settings.webSecurityEnabled isn't set to false, 340 | // XMLHttpRequest can't do cross-domain. But, it seem to no problem? 341 | // Or, Office Online need it? 342 | page.settings.webSecurityEnabled = false; 343 | // page.settings.localToRemoteUrlAccessEnabled = true; 344 | // MS may kick PhantomJS someday. 345 | page.settings.userAgent = 346 | (page.settings.userAgent + '').replace(/\s*PhantomJS(?:\/[\d\.]+)?/i, ''); 347 | page.customHeaders = defaultReqHeaders; 348 | // Inject jQuery to this space and initial page. 349 | if (!phantom.injectJs(JQ_PATH) || !page.injectJs(JQ_PATH)) { 350 | console.error('Can\'t load "' + JQ_PATH + '"'); 351 | phantom.exit(1); 352 | } 353 | 354 | pageInit(); 355 | // "jQuery" must be used instead of "$" in the page. $ is undefined. 356 | // Don't use jQuery in navigation. 357 | // ============================ Step 00: Open OneDrive login page 358 | dfdAction('Step 00', 359 | 'https://onedrive.live.com/?gologin=1', 360 | // Current PhantomJS can't parse cookie jar and persistent login. 361 | // Therefore, do login every time. 362 | function() { 363 | return page.evaluate(function(selector) { 364 | return document.getElementById(selector.loginUser) && 365 | document.getElementById(selector.loginPw) && 366 | document.getElementById(selector.loginBtn) ? true : false; 367 | }, selector); 368 | } 369 | ) 370 | // ============================ Step 01: Login 371 | .then(function() { return dfdAction('Step 01', 372 | function() { 373 | return page.evaluate(function(selector, msUser, msPw) { 374 | console.log('Step 01: Submit login form'); 375 | document.getElementById(selector.loginUser).value = msUser; 376 | document.getElementById(selector.loginPw).value = msPw; 377 | // // Persistent login 378 | // document.getElementById(selector.loginPersistent).checked = true; 379 | jQuery('#' + selector.loginBtn).trigger('click'); 380 | return true; 381 | }, selector, msUser, msPw); 382 | }, 383 | function() { 384 | return page.evaluate(function(selector/*, msUser*/) { 385 | // if (!document.getElementById(selector.logoutBtn) || 386 | // !window.$Config) { return false; } 387 | // // The user which already logged in (persistent login) may be another user. 388 | // if (window.$Config.email === msUser) { return true; } 389 | return document.getElementById(selector.logoutBtn) ? true : false; 390 | }, selector/*, msUser*/); 391 | } 392 | ); }) 393 | // ============================ Step 02: Open "File" page 394 | .then(function() { return dfdAction('Step 02', 395 | function() { 396 | return page.evaluate(function() { 397 | // var elmTarget = document.querySelector('.c-LeftNavBar'), 398 | // navCtrl = elmTarget.control; 399 | // if (elmTarget && navCtrl && navCtrl.controlName === 'LeftNavBar' && 400 | // navCtrl.dataContext && 401 | // navCtrl.dataContext.item && 402 | // navCtrl.dataContext.item.urls && 403 | // typeof navCtrl.dataContext.item.urls.viewInBrowser === 'string') { 404 | // document.location = navCtrl.dataContext.item.urls.viewInBrowser; 405 | // console.log('Step 02: ' + navCtrl.dataContext.item.urls.viewInBrowser); 406 | // return true; 407 | // } else { return false; } 408 | var jqTarget = 409 | jQuery('.c-LeftNavBar>.quickview:first a.quickview_header_text'); 410 | if (jqTarget.length) { 411 | console.log('Step 02: Click the navigation to root directory'); 412 | jqTarget.trigger('click'); 413 | return true; 414 | } else { 415 | console.log('Step 02: The navigation to root directory is not found'); 416 | return false; 417 | } 418 | }); 419 | }, 420 | function() { 421 | return page.evaluate(function(selector) { 422 | return document.querySelector(selector.uploadElm) ? true : false; 423 | }, selector); 424 | } 425 | ); }) 426 | // ============================ Step 03: Get upload file name 427 | .then(function() { return dfdAction('Step 03', 428 | function() { 429 | if (page.evaluate(function() 430 | { return document.getElementsByClassName('c-ListView'); })) { 431 | debugLog('Step 03: Upload file "' + tmpFile.baseName + '" already exists'); 432 | tmpFile.reNew(); 433 | return true; 434 | } else { 435 | debugLog('Step 03: The file list not ready'); 436 | return false; 437 | } 438 | }, 439 | function() { 440 | // RegExp is not primitive type, but it's converted to literal code. 441 | // https://github.com/ariya/phantomjs/blob/269154071100332c201fc619b658b07d9fdd6cd6/src/modules/webpage.js#L375 442 | return page.evaluate(function(selector, re) { 443 | return document.getElementsByClassName('c-ListView') && 444 | !jQuery(selector.fileItem).filter(function() { 445 | return re.test(jQuery(this).text()); 446 | }).length ? true : false; 447 | }, selector, tmpFile.baseNameRe); 448 | } 449 | ); }) 450 | // ============================ Step 04: File upload 451 | .then(function() { return dfdAction('Step 04', 452 | function() { 453 | if (page.evaluate(function() { 454 | return window.wLive && 455 | window.wLive.Core && 456 | window.wLive.Core.Html5FileUpload && 457 | window.$Network && window.$Network.fetchXML; 458 | })) { 459 | try { 460 | // fs.copy returns false always? 461 | fs.copy(srcFile, tmpFile.path); 462 | // if (!fs.copy(srcFile, tmpFile.path)) { 463 | if (!fs.exists(tmpFile.path) || !fs.isFile(tmpFile.path)) { 464 | console.error('Can\'t copy file: "' + srcFile + 465 | '" to "' + tmpFile.path + '"'); 466 | phantom.exit(1); 467 | } 468 | } catch(e) { 469 | console.error('Error(' + e.message + ') ' + e.description + ' - ' + e); 470 | phantom.exit(1); 471 | } 472 | debugLog('Step 04: File upload: "' + tmpFile.path + '"'); 473 | page.evaluate(function() { 474 | /* 475 | The Blob is disabled at XMLHttpRequest.send in PhantomJS. 476 | https://github.com/ariya/phantomjs/blob/269154071100332c201fc619b658b07d9fdd6cd6/src/qt/src/3rdparty/webkit/Source/WebCore/xml/XMLHttpRequest.cpp#L550 477 | Calling Order 478 | 1. wLive.Core.Html5FileUpload 479 | 2. wLive.Core.SingleFileUpload 480 | 3. wLive.Core.Html5FileUpload.post 481 | 4. $Network.fetchXML 482 | 5. Some frames are loaded... 483 | 6.