2 * Core Service - egHatch
4 * Dispatches print and data storage requests to the appropriate handler.
6 * If Hatch is configured to honor the request -- current request types
7 * are 'settings', 'offline', and 'printing' -- the request will be
8 * relayed to the Hatch service. Otherwise, the request is handled
11 * Most handlers also provide direct remote and local variants to the
12 * application can decide to which to use as needed.
14 * Local storage requests are handled by $window.localStorage.
16 * Note that all top-level and remote requests return promises. All
17 * local requests return immediate values, since local requests are
20 * BEWARE: never store "fieldmapper" objects, since their structure
21 * may change over time as the IDL changes. Always flatten objects
22 * into key/value pairs before calling set*Item()
25 angular.module('egCoreMod')
28 ['$q','$window','$timeout','$interpolate','$cookies','egNet','$injector',
29 function($q , $window , $timeout , $interpolate , $cookies , egNet , $injector ) {
33 service.messages = {};
34 service.hatchAvailable = false;
35 service.auth = null; // ref to egAuth loaded on-demand to avoid circular ref.
36 service.disableServerSettings = false;
38 // key/value cache -- avoid unnecessary Hatch extension requests.
39 // Only affects *RemoteItem calls.
40 service.keyCache = {};
42 // Keep a local copy of all retrieved setting summaries, which indicate
43 // which setting types exist for each setting.
44 service.serverSettingSummaries = {};
47 * Settings with these prefixes will always live in the browser.
49 service.browserOnlyPrefixes = [
50 'eg.hatch.enable.settings', // deprecated
51 'eg.hatch.enable.offline', // deprecated
53 'current_tag_table_marc21_biblio',
58 service.keyStoredInBrowser = function(key) {
60 if (service.disableServerSettings) {
61 // When server-side storage is disabled, treat every
62 // setting like it's stored locally.
66 var browserOnly = false;
67 service.browserOnlyPrefixes.forEach(function(pfx) {
68 if (key.match(new RegExp('^' + pfx)))
75 // write a message to the Hatch port
76 service.sendToHatch = function(msg) {
79 // shallow copy and scrub msg before sending
80 angular.forEach(msg, function(val, key) {
81 if (key.match(/deferred/)) return;
85 console.debug("sending to Hatch: " + JSON.stringify(msg2));
88 $window.postMessage(msg2, $window.location.origin);
91 // Send request to Hatch or reject if Hatch is unavailable
92 service.attemptHatchDelivery = function(msg) {
93 msg.msgid = service.msgId++;
94 msg.deferred = $q.defer();
96 if (service.hatchAvailable) {
97 service.messages[msg.msgid] = msg;
98 service.sendToHatch(msg);
102 'Hatch request attempted but Hatch is not available');
103 msg.deferred.reject(msg);
106 return msg.deferred.promise;
110 // resolve the promise on the given request and remove
111 // it from our tracked requests.
112 service.resolveRequest = function(msg) {
114 if (!service.messages[msg.msgid]) {
115 console.error('no cached message for id = ' + msg.msgid);
119 // for requests sent through Hatch, only the cached
120 // request will have the original promise attached
121 msg.deferred = service.messages[msg.msgid].deferred;
122 delete service.messages[msg.msgid]; // un-cache
124 if (msg.status == 200) {
125 msg.deferred.resolve(msg.content);
127 console.warn("Hatch command failed with status="
128 + msg.status + " and message=" + msg.message);
129 msg.deferred.reject();
133 service.openHatch = function() {
135 // When the Hatch extension loads, it tacks an attribute onto
136 // the top-level documentElement to indicate it's available.
137 if (!$window.document.documentElement.getAttribute('hatch-is-open')) {
138 //console.debug("Hatch is not available");
142 $window.addEventListener("message", function(event) {
143 // We only accept messages from our own content script.
144 if (event.source != window) return;
146 // We only care about messages from the Hatch extension.
147 if (event.data && event.data.from == 'extension') {
149 // Avoid logging full Hatch responses. they can get large.
151 'Hatch responded to message ID ' + event.data.msgid);
153 service.resolveRequest(event.data);
157 service.hatchAvailable = true; // public flag
160 service.remotePrint = function(
161 context, contentType, content, withDialog) {
163 return service.getPrintConfig(context).then(
165 if (config.printer == 'hatch_file_writer') {
166 if (contentType == 'text/html') {
167 content = service.html2txt(content);
169 return service.setRemoteItem(
170 'receipt.' + context + '.txt', content, true);
172 // print configuration retrieved; print
173 return service.attemptHatchDelivery({
177 contentType : contentType,
178 showDialog : withDialog,
184 service.getPrintConfig = function(context) {
185 return service.getItem('eg.print.config.' + context);
188 service.setPrintConfig = function(context, config) {
189 return service.setItem('eg.print.config.' + context, config);
192 service.getPrinterOptions = function(name) {
193 return service.attemptHatchDelivery({
194 action : 'printer-options',
199 service.getPrinters = function() {
200 if (service.printers) // cached printers
201 return $q.when(service.printers);
203 return service.attemptHatchDelivery({action : 'printers'}).then(
205 // we have remote printers; sort by name and return
207 service.printers = printers.sort(
208 function(a,b) {return a.name < b.name ? -1 : 1});
209 return service.printers;
212 // remote call failed and there is no such thing as local
213 // printers; return empty set.
214 function() { return [] }
218 service.usePrinting = function() {
219 if (!service.hatchAvailable) {
220 return Promise.resolve(false);
222 return service.getItem('eg.hatch.enable.printing');
226 service.useSettings = function() {
227 return service.getLocalItem('eg.hatch.enable.settings');
231 service.useOffline = function() {
232 return service.getLocalItem('eg.hatch.enable.offline');
235 service.getWorkstations = function() {
236 if (service.hatchAvailable) {
237 return service.mergeWorkstations().then(
239 service.removeLocalItem('eg.workstation.all');
240 return service.getRemoteItem('eg.workstation.all');
244 return $q.when(service.getLocalItem('eg.workstation.all'));
248 // See if any workstations are stored in local storage. If so, also
249 // see if we have any stored in Hatch. If both, merged workstations
250 // from localStorage in Hatch storage, skipping any whose name
251 // collide with a workstation in Hatch. If none exist in Hatch,
252 // copy the localStorage workstations over wholesale.
253 service.mergeWorkstations = function() {
254 var existing = service.getLocalItem('eg.workstation.all');
256 if (!existing || existing.length === 0) {
260 return service.getRemoteItem('eg.workstation.all')
261 .then(function(inHatch) {
263 if (!inHatch || inHatch.length === 0) {
264 // Nothing to merge, copy the data over directly
265 console.debug('No workstations in hatch to merge');
266 return service.setRemoteItem('eg.workstation.all', existing);
270 existing.forEach(function(ws) {
271 var match = inHatch.filter(
272 function(w) {return w.name === ws.name})[0];
275 'Migrating workstation from local storage to hatch: '
281 inHatch = inHatch.concat(addMe);
282 return service.setRemoteItem('eg.workstation.all', inHatch);
286 service.getDefaultWorkstation = function() {
288 if (service.hatchAvailable) {
289 return service.getRemoteItem('eg.workstation.default')
290 .then(function(name) {
292 // We have a default in Hatch, remove any lingering
293 // value from localStorage.
294 service.removeLocalItem('eg.workstation.default');
298 name = service.getLocalItem('eg.workstation.default');
300 console.log('Migrating default workstation to Hatch ' + name);
301 return service.setRemoteItem('eg.workstation.default', name)
302 .then(function() {return name;});
308 return $q.when(service.getLocalItem('eg.workstation.default'));
312 service.setWorkstations = function(workstations, isJson) {
313 if (service.hatchAvailable) {
314 return service.setRemoteItem('eg.workstation.all', workstations);
317 service.setLocalItem('eg.workstation.all', workstations, isJson));
321 service.setDefaultWorkstation = function(name, isJson) {
322 if (service.hatchAvailable) {
323 return service.setRemoteItem('eg.workstation.default', name);
326 service.setLocalItem('eg.workstation.default', name, isJson));
330 service.removeWorkstations = function() {
331 if (service.hatchAvailable) {
332 return service.removeRemoteItem('eg.workstation.all');
335 service.removeLocalItem('eg.workstation.all'));
339 service.removeDefaultWorkstation = function() {
340 if (service.hatchAvailable) {
341 return service.removeRemoteItem('eg.workstation.default');
344 service.removeLocalItem('eg.workstation.default'));
349 // Workstation actions always use Hatch when it's available
350 service.getWorkstationItem = function(key) {
351 if (service.hatchAvailable) {
352 return service.getRemoteItem(key);
354 return $q.when(service.getLocalItem(key));
358 service.setWorkstationItem = function(key, value) {
359 if (service.hatchAvailable) {
360 return service.setRemoteItem(key, value);
362 return $q.when(service.setLocalItem(key, value));
366 service.removeWorkstationItem = function(key) {
367 if (service.hatchAvailable) {
368 return service.removeRemoteItem(key);
370 return $q.when(service.removeLocalItem(key));
374 service.keyIsWorkstation = function(key) {
375 return Boolean(key.match(/eg.workstation/));
378 // get the value for a stored item
379 service.getItem = function(key) {
381 if (service.keyIsWorkstation(key)) {
382 return service.getWorkstationItem(key);
385 if (!service.keyStoredInBrowser(key)) {
386 return service.getServerItem(key);
389 var deferred = $q.defer();
391 service.getBrowserItem(key).then(
392 function(val) { deferred.resolve(val); },
393 function() { // Hatch error
394 deferred.reject("Unable to getItem from Hatch: " + key);
398 return deferred.promise;
401 // Collect values in batch.
402 // For server-stored values espeically, this is more efficient
403 // than a series of one-off calls.
404 service.getItemBatch = function(keys) {
405 var browserKeys = [];
408 // To take full advantage of the getServerItemBatch call,
409 // we have to know in advance which keys to send to the server
410 // vs those to handle in the browser.
411 keys.forEach(function(key) {
412 if (service.keyStoredInBrowser(key)) {
413 browserKeys.push(key);
415 serverKeys.push(key);
421 var serverPromise = serverKeys.length === 0 ? $q.when() :
422 service.getServerItemBatch(serverKeys).then(function(values) {
423 angular.forEach(values, function(val, key) {
428 var browserPromises = [];
429 browserKeys.forEach(function(key) {
430 browserPromises.push(
431 service.getBrowserItem(key).then(function(val) {
437 return $q.all(browserPromises.concat(serverPromise))
438 .then(function() {return settings});
441 service.getBrowserItem = function(key) {
442 if (service.useSettings()) {
443 if (service.hatchAvailable) {
444 return service.getRemoteItem(key);
447 return $q.when(service.getLocalItem(key));
452 service.getRemoteItem = function(key) {
454 if (service.keyCache[key] != undefined)
455 return $q.when(service.keyCache[key])
457 return service.attemptHatchDelivery({
460 }).then(function(content) {
461 return service.keyCache[key] = content;
465 service.getLocalItem = function(key) {
466 var val = $window.localStorage.getItem(key);
467 if (val === null || val === undefined) return;
469 return JSON.parse(val);
472 "Deleting invalid JSON for localItem: " + key + " => " + val);
473 service.removeLocalItem(key);
478 // Force auth cookies to live under path "/" instead of "/eg/staff"
479 // so they may be shared with the Angular app.
480 // There's no way to tell under what path a cookie is stored in
481 // the browser, all we can do is migrate it regardless.
482 service.migrateAuthCookies = function() {
487 ].forEach(function(key) {
488 var val = service.getLoginSessionItem(key);
490 $cookies.remove(key, {path: '/eg/staff/'});
491 service.setLoginSessionItem(key, val);
496 service.getLoginSessionItem = function(key) {
497 var val = $cookies.get(key);
498 if (val == null) return;
499 return JSON.parse(val);
502 service.getSessionItem = function(key) {
503 var val = $window.sessionStorage.getItem(key);
504 if (val == null) return;
505 return JSON.parse(val);
509 * @param tmp bool Store the value as a session cookie only.
510 * tmp values are removed during logout or browser close.
512 service.setItem = function(key, value) {
514 if (service.keyIsWorkstation(key)) {
515 return service.setWorkstationItem(key, value);
518 if (!service.keyStoredInBrowser(key)) {
519 return service.setServerItem(key, value);
522 var deferred = $q.defer();
523 return service.setBrowserItem(key, value).then(
524 function(val) {deferred.resolve(val);},
526 function() { // Hatch error
527 deferred.reject("Unable to setItem in Hatch: " + key);
532 service.setBrowserItem = function(key, value) {
533 if (service.useSettings()) {
534 if (service.hatchAvailable) {
535 return service.setRemoteItem(key, value);
537 return $q.reject('Unable to get item from hatch');
540 return $q.when(service.setLocalItem(key, value));
544 service.setServerItem = function(key, value) {
545 if (!service.auth) service.auth = $injector.get('egAuth');
546 if (!service.auth.token()) return $q.when();
548 // If we have already attempted to retrieve a value for this
549 // setting, then we can tell up front whether applying a value
550 // at the server will be an option. If not, store locally.
551 var summary = service.serverSettingSummaries[key];
552 if (summary && !summary.has_staff_setting) {
554 if (summary.has_org_setting === 't') {
555 // When no user/ws setting types exist but an org unit
556 // setting type does, it means the value cannot be
557 // applied by an individual user. Nothing left to do.
561 // No setting types of any flavor exist.
562 // Fall back to local storage.
564 if (value === null) {
565 // a null value means clear the server setting.
566 return service.removeBrowserItem(key);
568 console.warn('No server setting type exists for ' + key);
569 return service.setBrowserItem(key, value);
574 settings[key] = value;
576 return egNet.request(
578 'open-ils.actor.settings.apply.user_or_ws',
579 service.auth.token(), settings
580 ).then(function(appliedCount) {
582 if (appliedCount == 0) {
583 console.warn('No server setting type exists for ' + key);
584 // We were unable to store the setting on the server,
585 // presumably becuase no server-side setting type exists.
586 // Add to local storage instead.
587 service.setLocalItem(key, value);
590 service.keyCache[key] = value;
595 service.getServerItem = function(key) {
596 if (key in service.keyCache) {
597 return $q.when(service.keyCache[key])
600 if (!service.auth) service.auth = $injector.get('egAuth');
601 if (!service.auth.token()) return $q.when(null);
603 return egNet.request(
605 'open-ils.actor.settings.retrieve.atomic',
606 [key], service.auth.token()
607 ).then(function(settings) {
608 return service.handleServerItemResponse(settings[0]);
612 service.handleServerItemResponse = function(summary) {
613 var key = summary.name;
614 var val = summary.value;
616 // For our purposes, we only care if a setting can be stored
617 // as an org setting or a user-or-workstation setting.
618 summary.has_staff_setting = (
619 summary.has_user_setting === 't' ||
620 summary.has_workstation_setting === 't'
623 summary.value = null; // avoid duplicate value caches
624 service.serverSettingSummaries[key] = summary;
627 // We have a server setting. Nothing left to do.
628 return $q.when(service.keyCache[key] = val);
631 if (!summary.has_staff_setting) {
633 if (summary.has_org_setting === 't') {
634 // An org unit setting type exists but no value is applied
635 // that this workstation has access to. The existence of
636 // an org unit setting type and no user/ws setting type
637 // means applying a value locally is not allowed.
638 return $q.when(service.keyCache[key] = undefined);
641 console.warn('No server setting type exists for '
642 + key + ', using local value.');
644 return service.getBrowserItem(key);
647 // A user/ws setting type exists, but no server value exists.
648 // Migrate the local setting to the server.
650 var deferred = $q.defer();
651 service.getBrowserItem(key).then(function(browserVal) {
653 if (browserVal === null || browserVal === undefined) {
654 // No local value to migrate.
655 return deferred.resolve(service.keyCache[key] = undefined);
658 // Migrate the local value to the server.
660 service.setServerItem(key, browserVal).then(
661 function(appliedCount) {
662 if (appliedCount == 1) {
663 console.info('setting ' + key + ' successfully ' +
664 'migrated to a server setting');
665 service.removeBrowserItem(key); // fire & forget
667 console.error('error migrating setting to server,'
668 + ' falling back to local value');
670 deferred.resolve(service.keyCache[key] = browserVal);
675 return deferred.promise;
678 service.getServerItemBatch = function(keys) {
679 // no cache checking for now. assumes batch mode is only
680 // called once on page load. maybe add cache checking later.
681 if (!service.auth) service.auth = $injector.get('egAuth');
682 if (!service.auth.token()) return $q.when({});
684 var foundValues = {};
685 return egNet.request(
687 'open-ils.actor.settings.retrieve.atomic',
688 keys, service.auth.token()
691 //return foundValues;
693 var deferred = $q.defer();
694 function checkOne(setting) {
696 deferred.resolve(foundValues);
699 service.handleServerItemResponse(setting)
700 .then(function(resp) {
701 if (resp !== undefined) {
702 foundValues[setting.name] = resp;
705 checkOne(settings[0]);
709 checkOne(settings[0]);
710 return deferred.promise;
716 // set the value for a stored or new item
717 // When "bare" is true, the value will not be JSON-encoded
718 // on the file system.
719 service.setRemoteItem = function(key, value, bare) {
720 service.keyCache[key] = value;
721 return service.attemptHatchDelivery({
729 // Set the value for the given key.
730 // "Local" items persist indefinitely.
731 // If the value is raw, pass it as 'value'. If it was
732 // externally JSONified, pass it via jsonified.
733 service.setLocalItem = function(key, value, jsonified) {
734 if (jsonified === undefined ) {
735 jsonified = JSON.stringify(value);
736 } else if (value === undefined) {
740 $window.localStorage.setItem(key, jsonified);
742 console.log('localStorage.setItem (overwrite) failed for '+key+': ', e);
746 service.appendItem = function(key, value) {
747 if (!service.useSettings())
748 return $q.when(service.appendLocalItem(key, value));
750 if (service.hatchAvailable)
751 return service.appendRemoteItem(key, value);
753 console.error("Unable to appendItem in Hatch: " + key);
757 // append the value to a stored or new item
758 service.appendRemoteItem = function(key, value) {
759 service.keyCache[key] = value;
760 return service.attemptHatchDelivery({
767 service.appendLocalItem = function(key, value, jsonified) {
768 if (jsonified === undefined )
769 jsonified = JSON.stringify(value);
771 var old_value = $window.localStorage.getItem(key) || '';
773 $window.localStorage.setItem( key, old_value + jsonified );
775 console.log('localStorage.setItem (append) failed for '+key+': ', e);
779 // Set the value for the given key.
780 // "LoginSession" items are removed when the user logs out or the
781 // browser is closed.
782 // If the value is raw, pass it as 'value'. If it was
783 // externally JSONified, pass it via jsonified.
784 service.setLoginSessionItem = function(key, value, jsonified) {
785 service.addLoginSessionKey(key);
786 if (jsonified === undefined )
787 jsonified = JSON.stringify(value);
788 $cookies.put(key, jsonified, {path: '/'});
791 // Set the value for the given key.
792 // "Session" items are browser tab-specific and are removed when the
794 // If the value is raw, pass it as 'value'. If it was
795 // externally JSONified, pass it via jsonified.
796 service.setSessionItem = function(key, value, jsonified) {
797 if (jsonified === undefined )
798 jsonified = JSON.stringify(value);
799 $window.sessionStorage.setItem(key, jsonified);
802 // remove a stored item
803 service.removeItem = function(key) {
805 if (service.keyIsWorkstation(key)) {
806 return service.removeWorkstationItem(key);
809 if (!service.keyStoredInBrowser(key)) {
810 return service.removeServerItem(key);
813 var deferred = $q.defer();
814 service.removeBrowserItem(key).then(
815 function(response) {deferred.resolve(response);},
816 function() { // Hatch error
817 deferred.reject("Unable to removeItem from Hatch: " + key);
821 return deferred.promise;
824 service.removeBrowserItem = function(key) {
825 if (service.useSettings()) {
826 if (service.hatchAvailable) {
827 return service.removeRemoteItem(key);
829 return $q.reject('error talking to Hatch');
832 return $q.when(service.removeLocalItem(key));
836 service.removeServerItem = function(key) {
837 return service.setServerItem(key, null);
840 service.removeRemoteItem = function(key) {
841 delete service.keyCache[key];
842 return service.attemptHatchDelivery({
848 service.removeLocalItem = function(key) {
849 $window.localStorage.removeItem(key);
852 service.removeLoginSessionItem = function(key) {
853 service.removeLoginSessionKey(key);
854 $cookies.remove(key, {path: '/'});
857 service.removeSessionItem = function(key) {
858 $window.sessionStorage.removeItem(key);
862 * Remove all "LoginSession" items.
864 service.clearLoginSessionItems = function() {
865 angular.forEach(service.getLoginSessionKeys(), function(key) {
866 service.removeLoginSessionItem(key);
869 // remove the keys cache.
870 service.removeLocalItem('eg.hatch.login_keys');
873 // if set, prefix limits the return set to keys starting with 'prefix'
874 service.getKeys = function(prefix) {
875 var promise = service.getServerKeys(prefix);
876 return service.getBrowserKeys(prefix).then(function(browserKeys) {
877 return promise.then(function(serverKeys) {
878 return serverKeys.concat(browserKeys);
883 service.getRemoteKeys = function(prefix) {
884 return service.attemptHatchDelivery({
890 service.getBrowserKeys = function(prefix) {
891 if (service.useSettings())
892 return service.getRemoteKeys(prefix);
893 return $q.when(service.getLocalKeys(prefix));
896 service.getServerKeys = function(prefix, options) {
897 if (!service.auth) service.auth = $injector.get('egAuth');
898 if (!service.auth.token()) return $q.when({});
899 return egNet.request(
901 'open-ils.actor.settings.staff.applied.names.authoritative.atomic',
902 service.auth.token(), prefix, options
906 service.getLocalKeys = function(prefix) {
909 while ( (k = $window.localStorage.key(idx++)) !== null) {
910 // key prefix match test
911 if (prefix && k.substr(0, prefix.length) != prefix) continue;
919 * Array of "LoginSession" keys.
920 * Note we have to store these as "Local" items so browser tabs can
921 * share them. We could store them as cookies, but it's more data
922 * that has to go back/forth to the server. A "LoginSession" key name is
923 * not private, though, so it's OK if they are left in localStorage
924 * until the next login.
926 service.getLoginSessionKeys = function(prefix) {
929 var login_keys = service.getLocalItem('eg.hatch.login_keys') || [];
930 angular.forEach(login_keys, function(k) {
931 // key prefix match test
932 if (prefix && k.substr(0, prefix.length) != prefix) return;
938 service.addLoginSessionKey = function(key) {
939 var keys = service.getLoginSessionKeys();
940 if (keys.indexOf(key) < 0) {
942 service.setLocalItem('eg.hatch.login_keys', keys);
946 service.removeLoginSessionKey = function(key) {
947 var keys = service.getLoginSessionKeys().filter(function(k) {
950 service.setLocalItem('eg.hatch.login_keys', keys);
953 // Copy all stored settings from localStorage to Hatch.
954 // If 'move' is true, delete the local settings once cloned.
955 service.copySettingsToHatch = function(move) {
956 var deferred = $q.defer();
957 var keys = service.getLocalKeys();
959 angular.forEach(keys, function(key) {
961 // Hatch keys are local-only
962 if (key.match(/^eg.hatch/)) return;
964 console.debug("Copying to Hatch Storage: " + key);
965 service.setRemoteItem(key, service.getLocalItem(key))
966 .then(function() { // key successfully cloned.
968 // delete the local copy if requested.
969 if (move) service.removeLocalItem(key);
971 // resolve the promise after processing the last key.
972 if (key == keys[keys.length-1])
977 return deferred.promise;
980 // Copy all stored settings from Hatch to localStorage.
981 // If 'move' is true, delete the Hatch settings once cloned.
982 service.copySettingsToLocal = function(move) {
983 var deferred = $q.defer();
985 service.getRemoteKeys().then(function(keys) {
986 angular.forEach(keys, function(key) {
987 service.getRemoteItem(key).then(function(val) {
989 console.debug("Copying to Local Storage: " + key);
990 service.setLocalItem(key, val);
992 // delete the remote copy if requested.
993 if (move) service.removeRemoteItem(key);
995 // resolve the promise after processing the last key.
996 if (key == keys[keys.length-1])
1002 return deferred.promise;
1005 service.hostname = function() {
1006 if (service.hatchAvailable) {
1007 return service.attemptHatchDelivery({action : 'hostname'})
1009 function(name) { return name; },
1010 // Gracefully handle case where Hatch has not yet been
1011 // updated to include the hostname command.
1012 function() {return null}
1015 return $q.when(null);
1018 // COPIED FROM XUL util/text.js
1019 service.reverse_preserve_string_in_html = function( text ) {
1020 text = text.replace(/&/g, '&');
1021 text = text.replace(/"/g, '"');
1022 text = text.replace(/'/g, "'");
1023 text = text.replace(/ /g, ' ');
1024 text = text.replace(/</g, '<');
1025 text = text.replace(/>/g, '>');
1029 // COPIED FROM XUL util/print.js
1030 service.html2txt = function(html) {
1031 var lines = html.split(/\n/);
1033 for (var i = 0; i < lines.length; i++) {
1034 var line = lines[i];
1036 new_lines.push(line);
1040 // This undoes the util.text.preserve_string_in_html
1041 // call that spine_label.js does
1042 line = service.reverse_preserve_string_in_html(line);
1044 // This looks for @hex attributes containing 2-digit hex
1045 // codes, and converts them into real characters
1046 line = line.replace(/(<.+?)hex=['"](.+?)['"](.*?>)/gi,
1047 function(str,p1,p2,p3,offset,s) {
1050 var hex_chars = p2.match(/[0-9,a-f,A-F][0-9,a-f,A-F]/g);
1051 for (var j = 0; j < hex_chars.length; j++) {
1052 raw_chars += String.fromCharCode( parseInt(hex_chars[j],16) );
1054 return p1 + p3 + raw_chars;
1057 line = line.replace(/<head.*?>.*?<\/head>/gi, '');
1058 line = line.replace(/<br.*?>/gi,'\r\n');
1059 line = line.replace(/<table.*?>/gi,'');
1060 line = line.replace(/<tr.*?>/gi,'');
1061 line = line.replace(/<hr.*?>/gi,'\r\n');
1062 line = line.replace(/<p.*?>/gi,'');
1063 line = line.replace(/<block.*?>/gi,'');
1064 line = line.replace(/<li.*?>/gi,' * ');
1065 line = line.replace(/<.+?>/gi,'');
1066 if (line) { new_lines.push(line); }
1069 return new_lines.join('\n');
1072 // The only requirement for opening Hatch is that the DOM be loaded.
1073 // Open the connection now so its state will be immediately available.
1074 service.openHatch();